Linear Constraints (a)
PortfolioOptimisers.PartialLinearConstraint — Type
struct PartialLinearConstraint{__T_A, __T_B} <: AbstractConstraintResultHolds the coefficient matrix A and the right-hand side vector B of one half of a linear constraint block.
The field of LinearConstraint that holds the half, ineq or eq, makes it an inequality or an equality, and LinearConstraint states the form of each half. One row of A and the entry of B beside it are one constraint, so the constructor refuses a pair whose count of rows and count of entries differ.
Fields
A: Linear constraint coefficient matrix.
B: Linear constraint response vector.
Constructors
PartialLinearConstraint(; A::MatNum, B::VecNum) -> PartialLinearConstraintKeywords correspond to the struct's fields.
Validation
!isempty(A).!isempty(B).size(A, 1) == length(B), one row ofAper entry ofB.
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
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.1, Equation 9.1.
PortfolioOptimisers.LinearConstraint — Type
struct LinearConstraint{__T_ineq, __T_eq} <: AbstractConstraintResultHolds the inequality half and the equality half of a linear constraint block.
Each half is a PartialLinearConstraint, and either one may be absent. The optimiser writes every row scaled and homogenised, as sc * (A * w - k * B) <= 0 for the inequality half and == 0 for the equality half, where sc is the constraint scale and k is the homogenisation scalar of a ratio objective. The returned solution is de-homogenised, so it satisfies the form below whatever the objective is.
Mathematical definition
\[\begin{align} \mathbf{A}_\text{ineq} \boldsymbol{x} &\leq \boldsymbol{B}_\text{ineq} \\ \mathbf{A}_\text{eq} \boldsymbol{x} &= \boldsymbol{B}_\text{eq}\,. \end{align}\]
Where:
- $\mathbf{A}$: Constraint coefficient matrix.
- $\boldsymbol{B}$: Constraint response vector.
- $\text{ineq}$: Subscript for inequality constraints.
- $\text{eq}$: Subscript for equality constraints.
- $\boldsymbol{x}$: Constrained variable.
- $\boldsymbol{a}^\intercal$: One row of a coefficient matrix.
- $b$: The entry of a response vector beside that row.
One row and the entry beside it are one constraint. The row runs over the entries of $\boldsymbol{x}$, in the order of the universe the constraint is written against.
The inequality half is defined in the $\leq$ sense, so the sense a row is written in fixes the half that holds it. The row $\boldsymbol{a}^\intercal \boldsymbol{x} = b$ is an equality and belongs to the $\text{eq}$ half. The row $\boldsymbol{a}^\intercal \boldsymbol{x} \leq b$ belongs to the $\text{ineq}$ half as it stands. The row $\boldsymbol{a}^\intercal \boldsymbol{x} \geq b$ is the same constraint as $-\boldsymbol{a}^\intercal \boldsymbol{x} \leq -b$, so it belongs to the $\text{ineq}$ half with both sides negated.
Fields
ineq: Optional inequality constraints.
eq: Optional equality constraints.
Constructors
LinearConstraint(; ineq::Option{<:PartialLinearConstraint} = nothing, eq::Option{<:PartialLinearConstraint} = nothing) -> LinearConstraintKeywords correspond to the struct's fields.
Validation
- Both
eqandineqcannot benothingat the same time,!(isnothing(ineq) && isnothing(eq)).
View parameters
LinearConstraint defines its own port_opt_view method rather than deriving one from field tags.
- The method reads the index and ignores it. It returns both halves unchanged, and it never slices
Aalong the asset axis. - Each row runs over the whole universe that it was assembled against, so a slice of
Achanges what the row states.port_opt_viewstates why this slot needs the identity.
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
References
- [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.1, Equation 9.1.
PortfolioOptimisers.ParsingResult — Type
struct ParsingResult{__T_vars, __T_coef, __T_op, __T_rhs, __T_eqn} <: AbstractParsingResultHolds one linear constraint equation in canonical form.
parse_equation returns it, and it holds what get_linear_constraints needs to assemble a row. That is the variable names, their coefficients, the comparison operator, the right-hand side and the equation as a formatted string.
Fields
vars: Variable names in the parsed constraint expression.
coef: Coefficients corresponding to the constraint variables.
op: Comparison operator (==,<=, or>=).
rhs: Right-hand side value of the constraint.
eqn: Formatted string representation of the constraint equation.
Constructors
ParsingResult( vars::VecStr, coef::VecNum, op::AbstractString, rhs::Number, eqn::AbstractString) -> ParsingResultPositional arguments correspond to the struct's fields. There is no keyword constructor, because parse_equation is the producer of this type.
Validation
length(vars) == length(coef).
Examples
julia> PortfolioOptimisers.ParsingResult(["w_A", "w_B"], [1.0, 2.0], "<=", 1.0, "w_A + 2.0*w_B <= 1.0")ParsingResult vars ┼ Vector{String}: ["w_A", "w_B"] coef ┼ Vector{Float64}: [1.0, 2.0] op ┼ String: "<=" rhs ┼ Float64: 1.0 eqn ┴ String: "w_A + 2.0*w_B <= 1.0"Related
PortfolioOptimisers.estimator_to_val — Function
estimator_to_val(dict::MultiEstValType, sets::UniverseSets,
val::Option{<:Number} = nothing,
key::Option{<:AbstractString} = nothing;
datatype::DataType = Float64, strict::Bool = false)
estimator_to_val(dict::PairStrNum, sets::UniverseSets,
val::Option{<:Number} = nothing,
key::Option{<:AbstractString} = nothing;
datatype::DataType = Float64, strict::Bool = false)Return one value per asset of the universe, from a mapping of asset names and group names to values.
The function fills a vector with val and writes the values of dict into it, through the universe and the groups of sets. A key of dict that sets does not hold throws or warns, as strict selects.
A later key that writes the same asset overwrites the earlier value. A Dict has no fixed order, so use an OrderedDict or a vector of pairs to fix which value is kept.
Algorithm
- Take
valas the fill value, orzero(datatype)whenvalisnothing. - Take
keyas the universe keynxkey, orsets.xkeywhenkeyisnothing, and read the universenxfromsets.dictunder it. - Allocate
arr, one entry per name ofnx, filled with the value of step 1. - Read the counterpart axis with
counterpart_axis_names, the other of the two asset axesUniverseSetsdeclares. - For each
(key, val)pair ofdict, in the orderdictiterates in, writevalintoarrthroughname_to_val!. A key that names an asset writes one entry, a key that names a group writes one entry per member, a key that names the counterpart axis is skipped in silence, and a key that names none of them is reported through thestrictflag. - Return
arr.
Arguments
dict: A dictionary, vector of pairs, or single pair mapping asset or group names to values.sets: TheUniverseSetscontaining the asset universe and group definitions.val: The value assigned to every asset beforedictis applied.nothingmeanszero(datatype).key: The key of the universe insets.dict, ornothing. When it is notnothing, it replacessets.xkey.datatype: Element type of the value the array is filled with whenvalisnothing.strict: Iftrue, a key ofdictthatsetsdoes not hold throws. Iffalse, it warns.
Validation
- A key of
dictthat names neither an asset, nor a group, nor an entry of the counterpart axis raises anArgumentErrorwhenstrictistrue, and warns otherwise.
Returns
arr::VecNum: Value array, one entry per name of the universe.
Related
estimator_to_val(val::Option{<:Number}, args...; kwargs...)Return a number or nothing unchanged.
This method is the fallback for an input that is already a number or nothing, which needs no mapping. A vector of numbers goes to the vector method, which checks its length.
Algorithm
- Return
val. The method reads none of its other arguments and none of its keywords.
Arguments
val:nothingor a number.args...: Additional positional arguments (ignored).kwargs...: Additional keyword arguments (ignored).
Returns
val::Option{<:Number}: The inputval, unchanged.
Related
estimator_to_val(val::VecNum, sets::UniverseSets, ::Any = nothing,
key::Option{<:AbstractString} = nothing; kwargs...)Check a value vector against the length of the universe, and return it unchanged.
A caller that already holds one value per asset needs no mapping, so this method checks the length and nothing else.
Algorithm
- Take
keyas the universe key, orsets.xkeywhenkeyisnothing, and read the universe fromsets.dictunder it. - Check
valagainst the length of that universe. - Return
val.
Arguments
val: One value per asset of the universe.sets: TheUniverseSetsthat holds the universe.::Any: The fill value of the mapping methods, ignored here.key: The key of the universe insets.dict, ornothing. When it is notnothing, it replacessets.xkey.kwargs...: Additional keyword arguments (ignored).
Validation
length(val) == length(sets.dict[ifelse(isnothing(key), sets.xkey, key)]).
Returns
val::VecNum: The input vector, unchanged.
Related
estimator_to_val(val::MatNum, sets::UniverseSets, ::Any = nothing,
key::Option{<:AbstractString} = nothing; dims::Int = 2, kwargs...)Check the size of a value matrix along dims against the length of the universe, and return it unchanged.
A caller that already holds one row or column per asset needs no mapping, so this method checks the size and nothing else.
Algorithm
- Take
keyas the universe key, orsets.xkeywhenkeyisnothing, and read the universe fromsets.dictunder it. - Check the size of
valalongdimsagainst the length of that universe. - Return
val.
Arguments
val: A matrix with one row or one column per asset, asdimsselects.sets: TheUniverseSetsthat holds the universe.::Any: The fill value of the mapping methods, ignored here.key: The key of the universe insets.dict, ornothing. When it is notnothing, it replacessets.xkey.dims: The dimension whose size must equal the length of the universe.kwargs...: Additional keyword arguments (ignored).
Validation
size(val, dims) == length(sets.dict[ifelse(isnothing(key), sets.xkey, key)]).
Returns
val::MatNum: The input matrix, unchanged.
Related
estimator_to_val(::UniformValues, sets::UniverseSets, ::Any = nothing,
key::Option{<:AbstractString} = nothing;
datatype::DataType = Float64, kwargs...)Return a uniform value vector for all assets in the universe defined by sets.
UniformValues states the closed form the entries take. The value is a range rather than a vector, so no array is allocated.
Algorithm
- Take
keyas the universe key, orsets.xkeywhenkeyisnothing, and read the universe fromsets.dictunder it, giving its lengthN. - Convert
Ntodatatypeand take its reciprocal, givingiN. The division runs indatatype, so aRational{Int}gives1//Nexactly. - Return the range of length
Nwhose start and stop are bothiN.
Arguments
::UniformValues: The algorithm that selects this method.sets: TheUniverseSetswhose universe givesN.::Any: Fill value for API consistency (ignored).key: The key of the universe insets.dict, ornothing. When it is notnothing, it replacessets.xkey.datatype: Element type of the returned range.kwargs...: Additional keyword arguments (ignored).
Returns
val::AbstractRange: A range of lengthN, each entry the reciprocal ofN. A floatdatatypegives aStepRangeLen, and aRationalone gives aLinRange.
Related
PortfolioOptimisers.port_opt_view — Method
port_opt_view(
sets::UniverseSets,
i,
args...
) -> UniverseSets{var"#s185", var"#s1851", var"#s1852", var"#s1853", var"#s1854", var"#s1855", var"#s1856", <:AbstractDict{var"#s1800", var"#s1799"}} where {var"#s185"<:AbstractString, var"#s1851"<:AbstractString, var"#s1852"<:AbstractString, var"#s1853"<:AbstractString, var"#s1854"<:AbstractString, var"#s1855"<:AbstractString, var"#s1856"<:AbstractString, var"#s1800"<:AbstractString, var"#s1799"}
Return a view of a UniverseSets restricted to the assets at index i.
The view slices the asset axis and no other. An asset index has no meaning on a factor axis, so the view returns a tfkey-prefixed entry and a cfkey-prefixed entry bit-identical. The view drops the Non-Investable Axis, as step 4 states. The sets declares the axis of each entry by its key, so a sets in a @vprop field is safe to view. The method takes no factor index. port_opt_view(rd, i, j, k) can slice rd.nf, but no internal caller passes a k that is not a colon, so a user who slices factors must update the sets.
Algorithm
- Read
xkeyanduxkeyfromsets, and open an empty dictionarydictof the typesets.dicthas. - For an entry of
sets.dictwhose key starts withxkey, takeview(v, i), the group restricted to the selected assets. - For an entry whose key starts with
uxkey, take the unique entries of thexkey-prefixed partition it names, restricted toi. The unique-entry group is therefore derived from the sliced partition and never from the original one. - Skip the entry whose key equals
nikey. Only a door writes the Non-Investable Axis, so a view never carries one. Otherwise a cluster of a nested optimisation inherits the departures of its parent and charges each of them again, once per cluster. The match is exact and not by prefix, so the view keeps a plain group such as"nikkei225", whose name starts with the default"ni". - Copy every other entry unchanged into the same
dict. Thetfkey-,utfkey-,cfkey- anducfkey-prefixed entries and every plain group come back bit-identical. - Return the
UniverseSetsbuilt fromdictand the seven unchanged key prefixes, which revalidates the prefix grammar over the viewed universe.
Arguments
sets: TheUniverseSetsto view.i: The asset index selection.args...: Additional positional arguments (ignored).
Returns
sets::UniverseSets: A newUniverseSetsover the selected assets, declaring the same seven key prefixes as the original.
Related
References
- [5]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).