Base Constraint Generation: private API

PortfolioOptimisers.ComparisonOperatorType
const ComparisonOperator = Union{typeof(==), typeof(<=), typeof(>=)}

Union type representing supported comparison operators for constraint generation.

This type is used to specify which comparison operators are valid for defining constraints. It includes equality and both directions of inequality.

The group exists to be a type bound rather than a check. A field annotated with it refuses a fourth operator where the value is stored, so CentralityConstraint(; comp = <) raises a TypeError from the keyword constructor, and no constraint generator carries a branch for an operator it can never receive.

Related

source
PortfolioOptimisers.comparison_sign_ineq_flagFunction
comparison_sign_ineq_flag(op::ComparisonOperator)
comparison_sign_ineq_flag(op::AbstractString)

Return the multiplicative sign and inequality flag for a given comparison operator.

This is the one table mapping a comparison operator to the pair every constraint generator needs: the sign that files a >= row as a <= row, and the flag that sorts the row into the inequality block or the equality block. A parsed constraint carries its operator as a String, and a constraint estimator carries it as a function, so the table dispatches on both spellings and neither caller writes its own copy.

Arguments

  • op::ComparisonOperator: The comparison operator, as a function.
  • op::AbstractString: The comparison operator, as the string a ParsingResult carries.

Validation

  • A string op is one of "==", "<=" or ">=". Anything else raises an ArgumentError naming the three.
  • A function op outside ComparisonOperator matches no method and raises a MethodError. A field bounded by that alias refuses such an operator earlier, at construction.

Returns

  • sign::Int: The multiplicative sign for the constraint.
  • is_inequality::Bool: true if the operator is an inequality, false for equality.

Examples

julia> PortfolioOptimisers.comparison_sign_ineq_flag(==)(1, false)julia> PortfolioOptimisers.comparison_sign_ineq_flag(<=)(1, true)julia> PortfolioOptimisers.comparison_sign_ineq_flag(>=)(-1, true)julia> PortfolioOptimisers.comparison_sign_ineq_flag(">=")(-1, true)

Related

source
PortfolioOptimisers.resolve_axis_nameFunction
resolve_axis_name(name, nx::AbstractVector, sdict::AbstractDict) -> Option{Vector}

Resolve one name against an axis: an axis entry names itself, a sdict key expands to its members, and an unknown name gives nothing.

This is the precedence every name-taking constraint generator uses — asset first, then group — written once. The caller diagnoses the nothing, because the suggestion pool differs by caller. The member vector is a copy: sdict is the caller's UniverseSets dictionary, which is configuration reused across folds and optimisers, so de-duplication must never edit it.

Algorithm

  1. When name is on nx, return [name]. The axis is read before the dictionary, so an asset and a group of one name resolve to the asset, and a group of that name is unreachable.
  2. Otherwise read members, the entry sdict holds for name, or nothing when it holds none.
  3. Return unique(members), so a group naming one asset twice contributes one member.

Arguments

  • name: The name to resolve.
  • nx: The axis, usually the asset universe.
  • sdict: Dictionary mapping group names to vectors of member names.

Returns

  • [name] if name is on the axis.
  • unique(members) if name is a key of sdict.
  • nothing if name is neither.

Examples

An asset name wins over a group of the same name, and a repeated member collapses.

julia> nx = ["A", "B", "C"];julia> sdict = Dict("A" => ["B", "C"], "G" => ["A", "A", "B"]);julia> PortfolioOptimisers.resolve_axis_name("A", nx, sdict)1-element Vector{String}: "A"julia> PortfolioOptimisers.resolve_axis_name("G", nx, sdict)2-element Vector{String}: "A" "B"julia> isnothing(PortfolioOptimisers.resolve_axis_name("Q", nx, sdict))true

Related

source
PortfolioOptimisers.axis_name_indicesFunction
axis_name_indices(members, nx::AbstractVector, on_missing) -> Vector

Map resolved member names to axis indices, drop the members that miss the axis, and report them once through on_missing.

on_missing takes the vector of missing members and decides the policy, so a caller can throw, warn, or stay silent without a second copy of the mapping. on_missing is not called when every member is on the axis.

Algorithm

  1. Map each member to idx, its first index on nx, or to nothing when it is on no index. A repeated axis name is therefore reachable only at its first index.
  2. Collect missing_members, the members whose index is nothing.
  3. Drop the nothings from idx.
  4. When missing_members is not empty, call on_missing on it. The call comes after step 3, so a callback that throws throws with the surviving indices already discarded, and a callback that warns leaves the caller holding them.

Arguments

  • members: Member names, as resolve_axis_name returns them.
  • nx: The axis, usually the asset universe.
  • on_missing: Callable applied to the vector of members that miss the axis.

Returns

  • idx: Axis indices of the members that are on the axis, in the order of members. The element type is Int when no member missed the axis, and Union{Nothing, Int} when one did, because step 3 removes the nothings from the vector and not from its type.

Examples

julia> nx = ["A", "B", "C"];julia> PortfolioOptimisers.axis_name_indices(["C", "A"], nx, m -> nothing)2-element Vector{Int64}: 3 1julia> missed = String[];julia> PortfolioOptimisers.axis_name_indices(["A", "Z", "C"], nx, m -> append!(missed, m))2-element Vector{Union{Nothing, Int64}}: 1 3julia> missed1-element Vector{String}: "Z"

Related

source