Linear Constraints (a)

PortfolioOptimisers.PartialLinearConstraint — Type
struct PartialLinearConstraint{__T_A, __T_B} <: AbstractConstraintResult

Holds 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) -> PartialLinearConstraint

Keywords correspond to the struct's fields.

Validation

  • !isempty(A).
  • !isempty(B).
  • size(A, 1) == length(B), one row of A per entry of 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

References

  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 9.1, Equation 9.1.
source
PortfolioOptimisers.LinearConstraint — Type
struct LinearConstraint{__T_ineq, __T_eq} <: AbstractConstraintResult

Holds 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) -> LinearConstraint

Keywords correspond to the struct's fields.

Validation

  • Both eq and ineq cannot be nothing at 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 A along the asset axis.
  • Each row runs over the whole universe that it was assembled against, so a slice of A changes what the row states. port_opt_view states 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.
source
PortfolioOptimisers.ParsingResult — Type
struct ParsingResult{__T_vars, __T_coef, __T_op, __T_rhs, __T_eqn} <: AbstractParsingResult

Holds 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) -> ParsingResult

Positional 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

source
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.

Warning

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

  1. Take val as the fill value, or zero(datatype) when val is nothing.
  2. Take key as the universe key nxkey, or sets.xkey when key is nothing, and read the universe nx from sets.dict under it.
  3. Allocate arr, one entry per name of nx, filled with the value of step 1.
  4. Read the counterpart axis with counterpart_axis_names, the other of the two asset axes UniverseSets declares.
  5. For each (key, val) pair of dict, in the order dict iterates in, write val into arr through name_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 the strict flag.
  6. Return arr.

Arguments

  • dict: A dictionary, vector of pairs, or single pair mapping asset or group names to values.
  • sets: The UniverseSets containing the asset universe and group definitions.
  • val: The value assigned to every asset before dict is applied. nothing means zero(datatype).
  • key: The key of the universe in sets.dict, or nothing. When it is not nothing, it replaces sets.xkey.
  • datatype: Element type of the value the array is filled with when val is nothing.
  • strict: If true, a key of dict that sets does not hold throws. If false, it warns.

Validation

  • A key of dict that names neither an asset, nor a group, nor an entry of the counterpart axis raises an ArgumentError when strict is true, and warns otherwise.

Returns

  • arr::VecNum: Value array, one entry per name of the universe.

Related

source
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

  1. Return val. The method reads none of its other arguments and none of its keywords.

Arguments

  • val: nothing or a number.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • val::Option{<:Number}: The input val, unchanged.

Related

source
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

  1. Take key as the universe key, or sets.xkey when key is nothing, and read the universe from sets.dict under it.
  2. Check val against the length of that universe.
  3. Return val.

Arguments

  • val: One value per asset of the universe.
  • sets: The UniverseSets that holds the universe.
  • ::Any: The fill value of the mapping methods, ignored here.
  • key: The key of the universe in sets.dict, or nothing. When it is not nothing, it replaces sets.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

source
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

  1. Take key as the universe key, or sets.xkey when key is nothing, and read the universe from sets.dict under it.
  2. Check the size of val along dims against the length of that universe.
  3. Return val.

Arguments

  • val: A matrix with one row or one column per asset, as dims selects.
  • sets: The UniverseSets that holds the universe.
  • ::Any: The fill value of the mapping methods, ignored here.
  • key: The key of the universe in sets.dict, or nothing. When it is not nothing, it replaces sets.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

source
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

  1. Take key as the universe key, or sets.xkey when key is nothing, and read the universe from sets.dict under it, giving its length N.
  2. Convert N to datatype and take its reciprocal, giving iN. The division runs in datatype, so a Rational{Int} gives 1//N exactly.
  3. Return the range of length N whose start and stop are both iN.

Arguments

  • ::UniformValues: The algorithm that selects this method.
  • sets: The UniverseSets whose universe gives N.
  • ::Any: Fill value for API consistency (ignored).
  • key: The key of the universe in sets.dict, or nothing. When it is not nothing, it replaces sets.xkey.
  • datatype: Element type of the returned range.
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • val::AbstractRange: A range of length N, each entry the reciprocal of N. A float datatype gives a StepRangeLen, and a Rational one gives a LinRange.

Related

source
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

  1. Read xkey and uxkey from sets, and open an empty dictionary dict of the type sets.dict has.
  2. For an entry of sets.dict whose key starts with xkey, take view(v, i), the group restricted to the selected assets.
  3. For an entry whose key starts with uxkey, take the unique entries of the xkey-prefixed partition it names, restricted to i. The unique-entry group is therefore derived from the sliced partition and never from the original one.
  4. 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".
  5. Copy every other entry unchanged into the same dict. The tfkey-, utfkey-, cfkey- and ucfkey-prefixed entries and every plain group come back bit-identical.
  6. Return the UniverseSets built from dict and the seven unchanged key prefixes, which revalidates the prefix grammar over the viewed universe.

Arguments

  • sets: The UniverseSets to view.
  • i: The asset index selection.
  • args...: Additional positional arguments (ignored).

Returns

  • sets::UniverseSets: A new UniverseSets over the selected assets, declaring the same seven key prefixes as the original.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).