Linear Constraints
PortfolioOptimisers.PartialLinearConstraint Type
struct PartialLinearConstraint{T1, T2} <: AbstractConstraintResult
A::T1
B::T2
endContainer for a set of linear constraints (either equality or inequality) in the form A * x = B or A * x ≤ B.
PartialLinearConstraint stores the coefficient matrix A and right-hand side vector B for a group of linear constraints. This type is used internally by LinearConstraint to represent either the equality or inequality constraints in a portfolio optimisation problem.
Fields
A: Coefficient matrix of the linear constraints.B: Right-hand side vector of the linear constraints.
Constructor
PartialLinearConstraint(; A::MatNum, B::VecNum)Keyword arguments correspond to the fields above.
Validation
!isempty(A).!isempty(B).
Examples
julia> PartialLinearConstraint(; A = [1.0 2.0; 3.0 4.0], B = [5.0, 6.0])
PartialLinearConstraint
A ┼ 2×2 Matrix{Float64}
B ┴ Vector{Float64}: [5.0, 6.0]Related
sourcePortfolioOptimisers.LinearConstraint Type
struct LinearConstraint{T1, T2} <: AbstractConstraintResult
ineq::T1
eq::T2
endContainer for a set of linear constraints, separating inequality and equality constraints.
LinearConstraint holds both the inequality and equality constraints for a portfolio optimisation problem, each represented as a PartialLinearConstraint. This type is used to encapsulate all linear constraints in a unified structure, enabling composable and modular constraint handling.
Fields
ineq: Inequality constraints, as aPartialLinearConstraintornothing.eq: Equality constraints, as aPartialLinearConstraintornothing.
Constructor
LinearConstraint(; ineq::Option{<:PartialLinearConstraint} = nothing,
eq::Option{<:PartialLinearConstraint} = nothing)Keyword arguments correspond to the fields above.
Validation
!(isnothing(ineq) && isnothing(eq)), i.e. they cannot both benothingat the same time.
Examples
julia> ineq = PartialLinearConstraint(; A = [1.0 2.0; 3.0 4.0], B = [5.0, 6.0]);
julia> eq = PartialLinearConstraint(; A = [7.0 8.0; 9.0 10.0], B = [11.0, 12.0]);
julia> LinearConstraint(; ineq = ineq, eq = eq)
LinearConstraint
ineq ┼ PartialLinearConstraint
│ A ┼ 2×2 Matrix{Float64}
│ B ┴ Vector{Float64}: [5.0, 6.0]
eq ┼ PartialLinearConstraint
│ A ┼ 2×2 Matrix{Float64}
│ B ┴ Vector{Float64}: [11.0, 12.0]Related
sourcePortfolioOptimisers.LinearConstraintEstimator Type
struct LinearConstraintEstimator{T1, T2} <: AbstractConstraintEstimator
val::T1
key::T2
endContainer for one or more linear constraint equations to be parsed and converted into constraint matrices.
Fields
val: A single equation as anAbstractStringorExpr, or a vector of such equations.key: (Optional) Key in theAssetSetsto specify the asset universe for constraint generation. When provided, takes precedence overkeyfield ofAssetSets.
Constructor
LinearConstraintEstimator(; val::EqnType, key::Option{<:AbstractString} = nothing)Keyword arguments correspond to the fields above.
Validation
!isempty(val).
Examples
julia> lce = LinearConstraintEstimator(; val = ["w_A + w_B == 1", "w_A >= 0.1"]);
julia> sets = AssetSets(; key = "nx", dict = Dict("nx" => ["w_A", "w_B"]));
julia> linear_constraints(lce, sets)
LinearConstraint
ineq ┼ PartialLinearConstraint
│ A ┼ 1×2 LinearAlgebra.Transpose{Float64, Matrix{Float64}}
│ B ┴ Vector{Float64}: [-0.1]
eq ┼ PartialLinearConstraint
│ A ┼ 1×2 LinearAlgebra.Transpose{Float64, Matrix{Float64}}
│ B ┴ Vector{Float64}: [1.0]Related
sourcePortfolioOptimisers.ParsingResult Type
struct ParsingResult{T1, T2, T3, T4, T5} <: AbstractParsingResult
vars::T1
coef::T2
op::T3
rhs::T4
eqn::T5
endStructured result for standard linear constraint equation parsing.
ParsingResult is the canonical output of parse_equation for standard linear constraints. It stores all information needed to construct constraint matrices for portfolio optimisation, including variable names, coefficients, the comparison operator, right-hand side value, and a formatted equation string.
Fields
vars: Vector of variable names as strings.coef: Vector of coefficients.op: The comparison operator as a string.rhs: The right-hand side value.eqn: The formatted equation string.
Related
sourcePortfolioOptimisers.replace_group_by_assets Function
replace_group_by_assets(res::PR_VecPR,
sets::AssetSets; bl_flag::Bool = false, ep_flag::Bool = false,
rho_flag::Bool = false)If res is a vector of ParsingResult objects, this function will be applied to each element of the vector.
Expand group or special variable references in a ParsingResult to their corresponding asset names.
This function takes a ParsingResult containing variable names (which may include group names, prior(...) expressions, or correlation views like (A, B)), and replaces these with the actual asset names from the provided AssetSets. It supports Black-Litterman-style group expansion, entropy pooling prior views, and correlation view parsing for advanced constraint generation.
Arguments
res: AParsingResultobject containing variables and coefficients to be expanded.sets: AnAssetSetsobject specifying the asset universe and groupings.bl_flag: Iftrue, enables Black-Litterman-style group expansion.ep_flag: Iftrue, enables expansion ofprior(...)expressions for entropy pooling.rho_flag: Iftrue, enables expansion of correlation views(A, B)for entropy pooling.
Validation
- `bl_flag` can only be `true` if both `ep_flag` and `rho_flag` are `false`.
- `rho_flag` can only be `true` if `ep_flag` is also `true`.Details
Group names in
res.varsare replaced by the corresponding asset names fromsets.dict.If
bl_flagistrue, coefficients for group references are divided equally among the assets in the group.If
ep_flagistrue, expandsprior(asset)orprior(group)expressions for entropy pooling.If
rho_flagistrue, expands correlation view expressions(A, B)orprior(A, B)for entropy pooling, mapping them to asset pairs.If a variable or group is not found in
sets.dict, it is skipped.
Returns
res::ParsingResult: A newParsingResultwith all group and special variable references expanded to asset names.
Examples
julia> sets = AssetSets(; key = "nx", dict = Dict("nx" => ["A", "B", "C"], "group1" => ["A", "B"]));
julia> res = parse_equation("group1 + 2C == 1")
ParsingResult
vars ┼ Vector{String}: ["C", "group1"]
coef ┼ Vector{Float64}: [2.0, 1.0]
op ┼ String: "=="
rhs ┼ Float64: 1.0
eqn ┴ SubString{String}: "2.0*C + group1 == 1.0"
julia> replace_group_by_assets(res, sets)
ParsingResult
vars ┼ Vector{String}: ["C", "A", "B"]
coef ┼ Vector{Float64}: [2.0, 1.0, 1.0]
op ┼ String: "=="
rhs ┼ Float64: 1.0
eqn ┴ String: "2.0*C + 1.0*A + 1.0*B == 1.0"Related
sourcePortfolioOptimisers.estimator_to_val Function
estimator_to_val(dict::EstValType, sets::AssetSets, val::Option{<:Number} = nothing, key::Option{<:AbstractString} = nothing; strict::Bool = false)Return value for assets or groups, based on a mapping and asset sets.
The function creates the vector and sets the values for assets or groups as specified by dict, using the asset universe and groupings in sets. If a key in dict is not found in the asset sets, the function either throws an error or issues a warning, depending on the strict flag.
Arguments
arr: The array to be modified in-place.dict: A dictionary, vector of pairs, or single pair mapping asset or group names to values.sets: TheAssetSetscontaining the asset universe and group definitions.val: The default value to assign to assets not specified indict.key: (Optional) Key in theAssetSetsto specify the asset universe for constraint generation. When provided, takes precedence overkeyfield ofAssetSets.strict: Iftrue, throws an error if a key indictis not found in the asset sets; iffalse, issues a warning.
Details
- Iterates over the (key, value) pairs in
dict.
Warning
If the same asset is found in subsequent iterations, its value will be overwritten in favour of the most recent one. To ensure determinism, use an OrderedDict or a vector of pairs.
If a key in
dictmatches an asset in the universe, the corresponding entry inarris set to the specified value.If a key matches a group in
sets, all assets in the group are set to the specified value usinggroup_to_val!.If a key is not found and
strictistrue, anArgumentErroris thrown; otherwise, a warning is issued.The operation is performed in-place on
arr.
Returns
arr::VecNum: Value array.
Related
sourceestimator_to_val(val::Option{<:Number}, args...; kwargs...)Fallback no-op for value mapping in asset/group estimators.
This method returns the input value val as-is, without modification or mapping. It serves as a fallback for cases where the input is already a numeric value, a vector of numeric values, or nothing, and no further processing is required.
Arguments
val: A value of typeNothingor a single numeric value.args...: Additional positional arguments (ignored).kwargs...: Additional keyword arguments (ignored).
Returns
val::Option{<:Number}: The inputval, unchanged.
Related
sourceestimator_to_val(val::VecNum, sets::AssetSets, ::Any = nothing,
key::Option{<:AbstractString} = nothing; kwargs...)Return a numeric vector for asset/group estimators, validating length against asset universe.
This method checks that the input vector val matches the length of the asset universe in sets, and returns it unchanged if valid. It is used as a fast path for workflows where the value vector is already constructed and requires only defensive validation.
Arguments
val: Numeric vector to be mapped to assets/groups.sets:AssetSetscontaining the asset universe and group definitions.::Any: Fill value for API consistency (ignored).key: (Optional) Key in theAssetSetsto specify the asset universe for constraint generation. When provided, takes precedence overkeyfield ofAssetSets.kwargs...: Additional keyword arguments (ignored).
Returns
val::VecNum: The input vector, unchanged.
Validation
length(val) == length(sets.dict[ifelse(isnothing(key), sets.key, key)].
Related
sourcePortfolioOptimisers.parse_equation Function
parse_equation(eqn::EqnType;
ops1::Tuple = ("==", "<=", ">="), ops2::Tuple = (:call, :(==), :(<=), :(>=)),
datatype::DataType = Float64, kwargs...)Parse a linear constraint equation from a string into a structured ParsingResult.
Arguments
eqn: The equation string to parse.eqn::AbstractVector: Each element needs to meet the criteria below.eqn::AbstractString: Must contain exactly one comparison operator fromops1.ops1: Tuple of valid comparison operators as strings.
eqn::Expr: Must contain exactly one comparison operator fromops1.ops2: Tuple of valid comparison operator expressions.
datatype: The numeric type to use for coefficients and right-hand side.kwargs...: Additional keyword arguments, ignored.
Validation
The equation must contain exactly one valid comparison operator from
ops1.Both sides of the equation must be valid Julia expressions.
Details
If
eqn::AbstractVector, the function is applied element-wise.The function first checks for invalid operator patterns (e.g.,
"++").It searches for the first occurrence of a valid comparison operator from
ops1in the equation string. Errors if there are more than one or none.The equation is split into left- and right-hand sides using the detected operator.
If
eqn::AbstractString:- Both sides are parsed into Julia expressions using
Meta.parse.
- Both sides are parsed into Julia expressions using
If
eqn::Expr:- Expression is ready as is.
Numeric functions and constants (e.g.,
Inf) are recursively evaluated.All terms are moved to the left-hand side and collected, separating coefficients and variables.
The constant term is moved to the right-hand side, and the equation is formatted for display.
The result is returned as a
ParsingResultcontaining the collected information.
Returns
If
eqn::Str_Expr:res::ParsingResult: Structured parsing result.
If
eqn::AbstractVector:res::Vector{ParsingResult}: Vector of structured parsing results.
Examples
julia> parse_equation("w_A + 2w_B <= 1")
ParsingResult
vars ┼ Vector{String}: ["w_A", "w_B"]
coef ┼ Vector{Float64}: [1.0, 2.0]
op ┼ String: "<="
rhs ┼ Float64: 1.0
eqn ┴ SubString{String}: "w_A + 2.0*w_B <= 1.0"Related
sourcePortfolioOptimisers.linear_constraints Function
linear_constraints(lcs::Option{<:LinearConstraint}, args...; kwargs...)No-op fallback for returning an existing LinearConstraint object or nothing.
This method is used to pass through an already constructed LinearConstraint object or nothing without modification. It enables composability and uniform interface handling in constraint generation workflows, allowing functions to accept either raw equations or pre-built constraint objects.
Arguments
lcs: An existingLinearConstraintobject ornothing.args...: Additional positional arguments (ignored).kwargs...: Additional keyword arguments (ignored).
Returns
lcs::Option{<:LinearConstraint}: The input, unchanged.
Related
sourcelinear_constraints(eqn::EqnType,
sets::AssetSets; ops1::Tuple = ("==", "<=", ">="),
key::Option{<:AbstractString} = nothing;
ops2::Tuple = (:call, :(==), :(<=), :(>=)), datatype::DataType = Float64,
strict::Bool = false, bl_flag::Bool = false)Parse and convert one or more linear constraint equations into a LinearConstraint object.
This function parses one or more constraint equations (as strings, expressions, or vectors thereof), replaces group or asset references using the provided AssetSets, and constructs the corresponding constraint matrices. The result is a LinearConstraint object containing both equality and inequality constraints, suitable for use in portfolio optimisation routines.
Arguments
eqn: A single constraint equation (asAbstractStringorExpr), or a vector of such equations.sets: AnAssetSetsobject specifying the asset universe and groupings.ops1: Tuple of valid comparison operators as strings.ops2: Tuple of valid comparison operators as expression heads.datatype: Numeric type for coefficients and right-hand side.strict: Iftrue, throws an error if a variable or group is not found insets; iffalse, issues a warning.bl_flag: Iftrue, enables Black-Litterman-style group expansion.
Details
Each equation is parsed using
parse_equation, supporting both string and expression input.Asset and group references in the equations are expanded using
replace_group_by_assetsand the providedsets.The function separates equality and inequality constraints, assembling the corresponding matrices and right-hand side vectors.
Input validation is performed using
@argcheckto ensure non-empty and consistent constraints.Returns
nothingif no valid constraints are found after parsing and expansion.
Returns
lcs::LinearConstraint: An object containing the assembled equality and inequality constraints, ornothingif no constraints are present.
Examples
julia> sets = AssetSets(; key = "nx", dict = Dict("nx" => ["w_A", "w_B", "w_C"]));
julia> linear_constraints(["w_A + w_B == 1", "w_A >= 0.1"], sets)
LinearConstraint
ineq ┼ PartialLinearConstraint
│ A ┼ 1×3 LinearAlgebra.Transpose{Float64, Matrix{Float64}}
│ B ┴ Vector{Float64}: [-0.1]
eq ┼ PartialLinearConstraint
│ A ┼ 1×3 LinearAlgebra.Transpose{Float64, Matrix{Float64}}
│ B ┴ Vector{Float64}: [1.0]Related
linear_constraints(lcs::LcE_VecLcE,
sets::AssetSets; datatype::DataType = Float64, strict::Bool = false,
bl_flag::Bool = false)If lcs is a vector of LinearConstraintEstimator objects, this function is broadcast over the vector.
This method is a wrapper calling:
linear_constraints(lcs.val, sets, lcs.key; datatype = datatype, strict = strict, bl_flag = bl_flag)It is used for type stability and to provide a uniform interface for processing constraint estimators, as well as simplifying the use of multiple estimators simultaneously.
Related
sourcePortfolioOptimisers.AbstractParsingResult Type
abstract type AbstractParsingResult <: AbstractConstraintResult endAbstract supertype for all equation parsing result types in PortfolioOptimisers.jl.
All concrete and/or abstract types representing parsing results should be subtypes of AbstractParsingResult.
Related
sourcePortfolioOptimisers.group_to_val! Function
group_to_val!(nx::VecStr, sdict::AbstractDict, key::Any, val::Number,
dict::EstValType, arr::VecNum, strict::Bool)Set values in a vector for all assets belonging to a specified group.
group_to_val! maps the assets in group key to their corresponding indices in the asset universe nx, and sets the corresponding entries in the vector arr to the value val. If the group is not found, the function either throws an error or issues a warning, depending on the strict flag.
Arguments
nx: Vector of asset names.sdict: Dictionary mapping group names to vectors of asset names.key: Name of the group of assets to set values for.val: The value to assign to the assets in the group.dict: The original dictionary, vector of pairs, or pair being processed (used for logging messages).arr: The array to be modified in-place.strict: Iftrue, throws an error ifkeyis not found insdict; iffalse, issues a warning.
Details
If
keyis found insdict, all assets in the group are mapped to their indices innx, and the corresponding entries inarrare set toval.If
keyis not found andstrictistrue, anArgumentErroris thrown; otherwise, a warning is issued.
Returns
nothing. The operation is performed in-place onarr.
Related
sourcePortfolioOptimisers._parse_equation Function
_parse_equation(lhs, opstr::AbstractString, rhs; datatype::DataType = Float64)Parse and canonicalise a linear constraint equation from Julia expressions.
_parse_equation takes the left-hand side (lhs) and right-hand side (rhs) of a constraint equation, both as Julia expressions, and a comparison operator string (opstr). It evaluates numeric functions, moves all terms to the left-hand side, collects coefficients and variables, and returns a ParsingResult with the canonicalised equation.
Arguments
lhs: Left-hand side of the equation as a Julia expression.opstr: Comparison operator as a string.rhs: Right-hand side of the equation as a Julia expression.datatype: Numeric type for coefficients and right-hand side.
Details
Recursively evaluates numeric functions and constants (e.g.,
Inf) on both sides.Moves all terms to the left-hand side (
lhs - rhs == 0).Collects and sums like terms, separating variables and constants.
Moves the constant term to the right-hand side, variables to the left.
Formats the simplified equation as a string.
Returns a
ParsingResultcontaining variable names, coefficients, operator, right-hand side value, and formatted equation.
Returns
res::ParsingResult: Structured result with canonicalised variables, coefficients, operator, right-hand side, and formatted equation.
Related
sourcePortfolioOptimisers._rethrow_parse_error Function
_rethrow_parse_error(expr; side = :lhs)Internal utility for error handling during equation parsing.
_rethrow_parse_error is used to detect and handle incomplete or invalid expressions encountered while parsing constraint equations. It is called on both sides of an equation during parsing to ensure that the expressions are valid and complete. If an incomplete expression is detected, a Meta.ParseError is thrown; otherwise, the function returns nothing.
Arguments
expr: The parsed Julia expression to check. Can be anExpr,Nothing, or any other type.side: Symbol indicating which side of the equation is being checked (:lhsor:rhs). Used for error messages.
Details
If
exprisNothing, a warning is issued indicating that the side is empty and zero is assumed.If
expris an incomplete expression (expr.head == :incomplete), aMeta.ParseErroris thrown with a descriptive message.For all other cases, the function returns
nothingand does not modify the input.
Validation
- Throws a
Meta.ParseErrorif the expression is incomplete.
Returns
nothing.
Related
sourcePortfolioOptimisers._format_term Function
_format_term(coeff, var)Format a single term in a linear constraint equation as a string.
_format_term takes a coefficient and a variable name and returns a string representation suitable for display in a canonicalised linear constraint equation. Handles special cases for coefficients of 1 and -1 to avoid redundant notation.
Arguments
coeff: Numeric coefficient for the variable.var: Variable name as a string.
Details
- If `coeff == 1`, returns `"$var"` (no explicit coefficient).
- If `coeff == -1`, returns `"-$(var)"` (no explicit coefficient).
- Otherwise, returns `"$(coeff)*$(var)"`.Returns
term_str::String: The formatted term as a string.
Related
sourcePortfolioOptimisers._collect_terms! Function
_collect_terms!(expr, coeff, terms)Recursively collect and expand terms from a Julia expression for linear constraint parsing.
_collect_terms! traverses a Julia expression tree representing a linear equation, expanding and collecting all terms into a vector of (coefficient, variable) pairs. It handles numeric constants, variables, and arithmetic operations (+, -, *, /), supporting canonicalisation of linear constraint equations for further processing.
Arguments
expr: The Julia expression to traverse.coeff: The current numeric coefficient to apply.terms: A vector to which(coefficient, variable)pairs are appended in-place. Each pair is of the form(Float64, Option{<:String}), whereNothingindicates a constant term.
Details
expr:Number: Appends(coeff * oftype(coeff, expr), nothing)toterms.Symbol: Appends(coeff, string(expr))toterms.Expr:For multiplication (
*), distributes the coefficient to the numeric part.For division (
/), divides the coefficient by the numeric denominator.For addition (
+), recursively collects terms from all arguments.For subtraction (
-), recursively collects terms from all arguments except the last, which is negated.For all other expressions, treats as a variable and appends as
(coeff, string(expr)).
Returns
nothing. The function modifiestermsin-place.
Related
sourcePortfolioOptimisers._collect_terms Function
_collect_terms(expr::Union{Symbol, Expr, <:Number})Expand and collect all terms from a Julia expression representing a linear constraint equation.
_collect_terms takes a Julia expression (such as the left-hand side of a constraint equation), recursively traverses its structure, and returns a vector of (coefficient, variable) pairs. It supports numeric constants, variables, and arithmetic operations (+, -, *, /), and is used to canonicalise linear constraint equations for further processing.
Arguments
expr: The Julia expression to expand.
Details
- Calls [`_collect_terms!`](@ref) internally with an initial coefficient of `1.0` and an empty vector.
- Numeric constants are collected as `(coefficient, nothing)`.
- Variables are collected as `(coefficient, variable_name)`.
- Arithmetic expressions are recursively expanded and collected.Returns
terms::Vector{Tuple{Float64, Option{<:String}}}: A vector of(coefficient, variable)pairs, wherevariableis a string for variable terms ornothingfor constant terms.
Related
sourcePortfolioOptimisers._eval_numeric_functions Function
_eval_numeric_functions(expr)Recursively evaluate numeric functions and constants in a Julia expression.
_eval_numeric_functions traverses a Julia expression tree and evaluates any sub-expressions that are purely numeric, including standard mathematical functions and constants (such as Inf). This is used to simplify constraint equations before further parsing and canonicalisation.
Arguments
expr: The Julia expression to evaluate. Can be aNumber,Symbol, orExpr.
Details
expr:Number: It is returned as-is.:Inf: ReturnsInf.Expr: Representing a function call, and all arguments are numeric, the function is evaluated and replaced with its result.Otherwise, the function recurses into sub-expressions, returning a new expression with numeric parts evaluated.
Returns
- The evaluated expression, with all numeric sub-expressions replaced by their computed values. Non-numeric or symbolic expressions are returned in their original or partially simplified form.
Related
sourcePortfolioOptimisers.nothing_asset_sets_view Function
nothing_asset_sets_view(::Nothing, ::Any)No-op fallback for indexing nothing asset sets.
Returns
nothing.