Phylogeny Constraints

PortfolioOptimisers.SemiDefinitePhylogenyEstimatorType
struct SemiDefinitePhylogenyEstimator{__T_pl, __T_p} <: AbstractPhylogenyConstraintEstimator

Forbids co-movement between related assets through a semidefinite relaxation, refitting the structure from returns.

The estimator holds the source that builds the relatedness matrix and the penalty factor p. phylogeny_constraints refits the source against a returns matrix and returns a SemiDefinitePhylogeny, which carries the equations this constraint solves.

Which pairs a network source relates — and therefore how strong the constraint is — comes from its AbstractSeparationAlgorithm, not from anything set here. The constraint is weight-inert: A ⊙ W == 0 is the same constraint at any magnitude, so the separation changes the cardinality of the forbidden set and nothing else.

Warning

NetworkEstimator(; sep = PathLength()) relates every reachable pair. A bare PathLength leaves dmax = nothing, which resolves to the observed diameter, so nothing is outside the budget — measured, 190 of 190 pairs — and this estimator then forbids all pairwise co-movement. It is the opposite end of the dial from HopCount's default n = 1. State a numeric dmax to select anything narrower.

Fields

  • pl: Network estimator or clustering estimator. A precomputed PhylogenyResult or Clusters is not accepted: this slot says how to build the phylogeny for whatever universe the estimator is given, and a precomputed one answers for a fixed universe instead. Pass the constraint result if you already have the structure.
  • p: Non-negative penalty factor on the trace of the semidefinite matrix variable. It is read only when the model does not already minimise a variance: a variance objective is itself a trace against that variable, so it pulls the relaxation down on its own and no second term is added. What p buys is how closely the matrix variable tracks the outer product it stands for, and nothing else: the gap is small under a variance objective, which closes it itself, and wider under a tail measure such as conditional value at risk. The relatedness rows hold either way.

Constructors

SemiDefinitePhylogenyEstimator(;    pl::NwE_ClE = NetworkEstimator(),    p::Number = 0.05) -> SemiDefinitePhylogenyEstimator

Keywords correspond to the struct's fields. The default p = 0.05 is the value the source's own worked example uses.

Validation

  • p >= 0.
  • pl is bounded by NwE_ClE: a precomputed PhylogenyResult or Clusters is rejected by the type, not by a check, so the keyword constructor raises TypeError rather than deferring the problem to a solve. Build SemiDefinitePhylogeny instead, which is what phylogeny_constraints(estimator, X) returns.

Examples

julia> SemiDefinitePhylogenyEstimator()SemiDefinitePhylogenyEstimator  pl ┼ NetworkEstimator     │    ce ┼ PortfolioOptimisersCovariance     │       │   ce ┼ Covariance     │       │      │    me ┼ SimpleExpectedReturns     │       │      │       │   w ┴ nothing     │       │      │    ce ┼ GeneralCovariance     │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)     │       │      │       │    w ┴ nothing     │       │      │   alg ┼ FullMoment()     │       │      │     w ┴ nothing     │       │   mp ┼ MatrixProcessing     │       │      │     pdm ┼ Posdef     │       │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton     │       │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()     │       │      │      dn ┼ nothing     │       │      │      dt ┼ nothing     │       │      │     alg ┼ nothing     │       │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)     │    de ┼ Distance     │       │   power ┼ nothing     │       │     alg ┴ CanonicalDistance()     │   alg ┼ KruskalTree     │       │     args ┼ Tuple{}: ()     │       │   kwargs ┴ @NamedTuple{}: NamedTuple()     │   sep ┼ HopCount     │       │   n ┴ Int64: 1   p ┴ Float64: 0.05

Related

References

  • [70] D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
  • [71] D. Cajas. A Graph Theory Approach to Portfolio Optimization Part II. Available at SSRN 4667426 (2023).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Sections 13.1.7.2 and 13.2.4.2.
source
PortfolioOptimisers.SemiDefinitePhylogenyType
struct SemiDefinitePhylogeny{__T_A, __T_p} <: AbstractPhylogenyConstraintResult

Drives the product of weights of every related pair of assets to zero through a semidefinite relaxation.

Relatedness is whatever the source that built A calls related: a neighbourhood over a network, or membership of one cluster.

p sets the size of the relaxation gap and not the strength of the constraint. A ⊙ W == 0 is written whether or not p is read, so a larger p forbids no more pairs. It only makes W track the outer product it stands for more closely. A wider gap lets a pair of related assets both hold weight while their entry of W absorbs the product.

Mathematical definition

The relaxation replaces the outer product of the weights with a symmetric matrix variable and bounds it below through a Schur complement. Where the objective is already a trace against that variable — a variance — the constraint form applies:

\[\begin{align} \underset{\boldsymbol{w},\, \mathbf{W}}{\min}\quad & \mathrm{tr}(\mathbf{\Sigma} \mathbf{W})\\ \textrm{s.t.}\quad & \begin{bmatrix} \mathbf{W} & \boldsymbol{w} \\ \boldsymbol{w}^\intercal & k \end{bmatrix} \succeq 0\,,\\ & \mathbf{A} \odot \mathbf{W} = 0\,,\\ & \mathbf{W} \in \mathbb{S}^{N}\,,\quad \boldsymbol{w} \in \mathcal{W}\,. \end{align}\]

For every other risk measure nothing in the objective pulls the relaxation down, so a penalty term does it instead:

\[\begin{align} \underset{\boldsymbol{w},\, \mathbf{W}}{\min}\quad & \phi(\boldsymbol{w}) + p\, \mathrm{tr}(\mathbf{W})\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\mathbf{W}$: Symmetric $N \times N$ matrix variable that relaxes $\boldsymbol{w}\boldsymbol{w}^\intercal / k$.
  • $\mathbf{A}$: Relatedness matrix, the A field.
  • $p$: Penalty factor, the p field.
  • $k$: Budget scaling / homogenisation variable.
  • $\mathbf{\Sigma}$: Covariance matrix.
  • $\phi$: Risk measure of the optimiser.
  • $\odot$: Hadamard product.
  • $\mathbb{S}^{N}$: Set of real symmetric $N \times N$ matrices.
  • $\mathcal{W}$: Rest of the feasible set.

Fields

  • A: Symmetric relatedness matrix with a zero diagonal. A network source gives the range connection matrix, a clustering source the adjacency label matrix. Stored as given.
  • p: Non-negative penalty factor on the trace of the semidefinite matrix variable. It is read only when the model does not already minimise a variance: a variance objective is itself a trace against that variable, so it pulls the relaxation down on its own and no second term is added. What p buys is how closely the matrix variable tracks the outer product it stands for, and nothing else: the gap is small under a variance objective, which closes it itself, and wider under a tail measure such as conditional value at risk. The relatedness rows hold either way.

Constructors

SemiDefinitePhylogeny(    A::MatNum_PhRMatNum,    p::Number) -> SemiDefinitePhylogenySemiDefinitePhylogeny(;    A::MatNum_PhRMatNum,    p::Number = 0.05) -> SemiDefinitePhylogeny

Keywords correspond to the struct's fields. The default p = 0.05 is the value the source's own worked example uses.

Validation

  • LinearAlgebra.issymmetric(A) and all(iszero, LinearAlgebra.diag(A)).
  • p >= 0.

Examples

julia> SemiDefinitePhylogeny([0.0 1.0; 1.0 0.0], 0.05)SemiDefinitePhylogeny  A ┼ 2×2 Matrix{Float64}  p ┴ Float64: 0.05

Related

References

  • [70] D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
  • [71] D. Cajas. A Graph Theory Approach to Portfolio Optimization Part II. Available at SSRN 4667426 (2023).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Sections 13.1.7.2 and 13.2.4.2.
source
PortfolioOptimisers.IntegerPhylogenyEstimatorType
struct IntegerPhylogenyEstimator{__T_pl, __T_B} <: AbstractPhylogenyConstraintEstimator

Caps how many related assets may be held at once, refitting the structure from returns.

The estimator holds the source that builds the relatedness matrix and the cap B. phylogeny_constraints refits the source against a returns matrix and returns an IntegerPhylogeny, which carries the equations this constraint solves.

Which pairs a network source relates comes from its AbstractSeparationAlgorithm, and B is an integer cardinality counted over them. The relatedness itself stays binary under either separation: PhylogenyResult's matrix is Int, and a graded one would not be countable here.

Warning

NetworkEstimator(; sep = PathLength()) relates every reachable pair. A bare PathLength leaves dmax = nothing, which resolves to the observed diameter, so nothing is outside the budget — measured, 190 of 190 pairs. It is the opposite end of the dial from HopCount's default n = 1. State a numeric dmax to select anything narrower.

Fields

  • pl: Network estimator or clustering estimator. A precomputed PhylogenyResult or Clusters is not accepted: this slot says how to build the phylogeny for whatever universe the estimator is given, and a precomputed one answers for a fixed universe instead. Pass the constraint result if you already have the structure.
  • B: Right-hand side of A * z <= B, where z is the held indicator: the largest number of assets that may be held out of each row of A. A scalar applies to every row. A vector states one bound per row, so its length must match the row count of the stored A and not the number of assets. On an estimator the rows do not exist yet, so a vector is only checked against the largest number of clusters the clustering estimator can return.

Constructors

IntegerPhylogenyEstimator(;    pl::NwE_ClE = NetworkEstimator(),    B::Int_VecInt = 1) -> IntegerPhylogenyEstimator

Keywords correspond to the struct's fields.

Validation

Examples

julia> IntegerPhylogenyEstimator()IntegerPhylogenyEstimator  pl ┼ NetworkEstimator     │    ce ┼ PortfolioOptimisersCovariance     │       │   ce ┼ Covariance     │       │      │    me ┼ SimpleExpectedReturns     │       │      │       │   w ┴ nothing     │       │      │    ce ┼ GeneralCovariance     │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)     │       │      │       │    w ┴ nothing     │       │      │   alg ┼ FullMoment()     │       │      │     w ┴ nothing     │       │   mp ┼ MatrixProcessing     │       │      │     pdm ┼ Posdef     │       │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton     │       │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()     │       │      │      dn ┼ nothing     │       │      │      dt ┼ nothing     │       │      │     alg ┼ nothing     │       │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)     │    de ┼ Distance     │       │   power ┼ nothing     │       │     alg ┴ CanonicalDistance()     │   alg ┼ KruskalTree     │       │     args ┼ Tuple{}: ()     │       │   kwargs ┴ @NamedTuple{}: NamedTuple()     │   sep ┼ HopCount     │       │   n ┴ Int64: 1   B ┴ Int64: 1

Related

References

  • [72] F. Ricca and A. Scozzari. Portfolio optimization through a network approach: network assortative mixing and portfolio diversification. European Journal of Operational Research 312, 700–717 (2024).
  • [70] D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
  • [71] D. Cajas. A Graph Theory Approach to Portfolio Optimization Part II. Available at SSRN 4667426 (2023).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Sections 13.1.7.1 and 13.2.4.1.
source
PortfolioOptimisers.IntegerPhylogenyType
struct IntegerPhylogeny{__T_A, __T_B} <: AbstractPhylogenyConstraintResult

Caps at B the number of related assets a mixed-integer model may hold at once.

Relatedness is whatever the source that built A calls related: a neighbourhood over a network, or membership of one cluster. A network source with B = 1 forbids holding two assets that are neighbours. A clustering source with B = 1 holds at most one asset per cluster.

Mathematical definition

The cap is a mutually exclusive investment constraint on the held binary:

\[\begin{align} \underset{\boldsymbol{w}}{\mathrm{opt}}\quad & \phi(\boldsymbol{w})\\ \textrm{s.t.}\quad & \mathbf{A} \boldsymbol{z} \leq \boldsymbol{B}\,,\\ & \boldsymbol{\ell} \odot \boldsymbol{z} \leq \boldsymbol{w} \leq \boldsymbol{u} \odot \boldsymbol{z}\,,\\ & \boldsymbol{z} \in \{0, 1\}^{N}\,,\quad \boldsymbol{w} \in \mathcal{W}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{z}$: Held binary, one entry per asset.
  • $\mathbf{A}$: Stored relatedness rows, the A field.
  • $\boldsymbol{B}$: Cap, the B field.
  • $\boldsymbol{\ell}$, $\boldsymbol{u}$: Lower and upper weight bounds.
  • $\phi$: Objective function of the optimiser.
  • $\odot$: Hadamard product.
  • $\mathcal{W}$: Rest of the feasible set.

Algorithm

The constructor rewrites the matrix it is given, so the stored A is a derived quantity:

  1. Check that the diagonal of A is zero and that A is symmetric.
  2. Add the identity to A, which puts each asset in its own row.
  3. Drop the repeated rows of the sum with unique(...; dims = 1), giving the stored A. One row survives per distinct neighbourhood or cluster, so the stored matrix is usually shorter than it is wide.
  4. Check that B is non-empty, non-negative and finite.
  5. Check size(A, 1) == length(B) when B is a vector, against the row count of step 3 and not against the number of assets.

Fields

  • A: Row set of the relatedness matrix, stored as unique(A + I; dims = 1) and not as the matrix passed in. The identity puts each asset in its own row, and the deduplication drops rows that repeat. One row per distinct neighbourhood or cluster survives, which is why the stored matrix is usually shorter than it is wide.
  • B: Right-hand side of A * z <= B, where z is the held indicator: the largest number of assets that may be held out of each row of A. A scalar applies to every row. A vector states one bound per row, so its length must match the row count of the stored A and not the number of assets. On an estimator the rows do not exist yet, so a vector is only checked against the largest number of clusters the clustering estimator can return.

Constructors

IntegerPhylogeny(    A::MatNum_PhRMatNum,    B::Int_VecInt) -> IntegerPhylogenyIntegerPhylogeny(;    A::MatNum_PhRMatNum,    B::Int_VecInt = 1) -> IntegerPhylogeny

Keywords correspond to the struct's fields.

Validation

  • LinearAlgebra.issymmetric(A) and all(iszero, LinearAlgebra.diag(A)).

  • B is validated with assert_nonempty_nonneg_finite_val.

    • AbstractVector: size(unique(A + LinearAlgebra.I; dims = 1), 1) == length(B).

Examples

julia> IntegerPhylogeny(; A = [0.0 1.0; 1.0 0.0], B = 2)IntegerPhylogeny  A ┼ 1×2 Matrix{Float64}  B ┴ Int64: 2

Related

References

  • [72] F. Ricca and A. Scozzari. Portfolio optimization through a network approach: network assortative mixing and portfolio diversification. European Journal of Operational Research 312, 700–717 (2024).
  • [70] D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
  • [71] D. Cajas. A Graph Theory Approach to Portfolio Optimization Part II. Available at SSRN 4667426 (2023).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Sections 13.1.7.1 and 13.2.4.1.
source
PortfolioOptimisers.CentralityConstraintType
struct CentralityConstraint{__T_A, __T_B, __T_comp} <: AbstractCentralityConstraint

Bounds the average centrality of the portfolio against a threshold read off the centrality vector itself.

The constraint diversifies by the influence an asset has in the network, rather than by its weight. centrality_constraints turns it into one row of a LinearConstraint.

Mathematical definition

\[\begin{align} \underset{\boldsymbol{w}}{\mathrm{opt}}\quad & \phi(\boldsymbol{w})\\ \textrm{s.t.}\quad & \boldsymbol{c}^\intercal \boldsymbol{w} \mathbin{\square} \bar{c}\,,\\ & \boldsymbol{w} \in \mathcal{W}\,. \end{align}\]

Where:

  • $\boldsymbol{w}$: Portfolio weights vector $N \times 1$.
  • $\boldsymbol{c}$: Centrality vector the estimator in A computes.
  • $\bar{c}$: Threshold, the B field.
  • $\square$: Comparison operator, the comp field.
  • $\phi$: Objective function of the optimiser.
  • $\mathcal{W}$: Rest of the feasible set.

The source states this constraint as an equality against a desired average centrality. Either sense of the inequality is admitted here as well.

When B is a VectorToScalarMeasure, $\bar{c}$ is that measure of $\boldsymbol{c}$, so the threshold moves with the graph and the constraint always has a feasible point.

Fields

  • A: Centrality estimator. Its centrality vector is the row of the generated linear constraint.
  • B: Right-hand side of the constraint. A number is the threshold itself. A VectorToScalarMeasure derives the threshold from the centrality vector A produces, so the constraint always has a feasible point. The measure reads that vector, never the row after comp has flipped its sign, so MinValue() gives the smallest entry under <= and under >= alike.
  • comp: Comparison operator for the centrality constraint. == builds an equality row, every other operator an inequality row.

Constructors

CentralityConstraint(;    A::CentralityEstimator = CentralityEstimator(),    B::Num_VecToScaM = MinValue(),    comp::ComparisonOperator = <=) -> CentralityConstraint

Keywords correspond to the struct's fields.

Validation

  • Every argument is bounded by its type and by nothing else: A by CentralityEstimator, B by Num_VecToScaM, and comp by ComparisonOperator. A value outside a bound raises a TypeError from the keyword constructor.
  • No value is checked. A non-finite B reaches the generated row unchanged: centrality_constraints stores a NaN or an Inf right-hand side and the model carries it to the solver.

Examples

julia> CentralityConstraint()CentralityConstraint     A ┼ CentralityEstimator       │   pl ┼ NetworkEstimator       │      │    ce ┼ PortfolioOptimisersCovariance       │      │       │   ce ┼ Covariance       │      │       │      │    me ┼ SimpleExpectedReturns       │      │       │      │       │   w ┴ nothing       │      │       │      │    ce ┼ GeneralCovariance       │      │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)       │      │       │      │       │    w ┴ nothing       │      │       │      │   alg ┼ FullMoment()       │      │       │      │     w ┴ nothing       │      │       │   mp ┼ MatrixProcessing       │      │       │      │     pdm ┼ Posdef       │      │       │      │         │      alg ┼ UnionAll: NearestCorrelationMatrix.Newton       │      │       │      │         │   kwargs ┴ @NamedTuple{}: NamedTuple()       │      │       │      │      dn ┼ nothing       │      │       │      │      dt ┼ nothing       │      │       │      │     alg ┼ nothing       │      │       │      │   order ┴ NTuple{4, Symbol}: (:pdm, :dn, :dt, :alg)       │      │    de ┼ Distance       │      │       │   power ┼ nothing       │      │       │     alg ┴ CanonicalDistance()       │      │   alg ┼ KruskalTree       │      │       │     args ┼ Tuple{}: ()       │      │       │   kwargs ┴ @NamedTuple{}: NamedTuple()       │      │   sep ┼ HopCount       │      │       │   n ┴ Int64: 1       │   ct ┼ DegreeCentrality       │      │     kind ┼ Int64: 0       │      │   kwargs ┴ @NamedTuple{}: NamedTuple()     B ┼ MinValue()  comp ┴ typeof(<=): <=

Related

References

  • [70] D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
  • [5] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.6.
source
PortfolioOptimisers.phylogeny_constraintsFunction
phylogeny_constraints(plc::Option{<:PlCE_PlC}, X::MatNum; dims::Int = 1, kwargs...)
phylogeny_constraints(plcs::VecPlCE_PlC, args...; kwargs...)

Generate phylogeny-based portfolio constraints from an estimator or result.

phylogeny_constraints constructs constraint objects based on phylogenetic, res, or network structures among assets. It supports both semi-definite and integer constraint forms, accepting either an estimator (which wraps a phylogeny or clustering model and penalty parameters) or a precomputed result. If plc is nothing, returns nothing.

If plcs is a vector, this method broadcasts over each element, returning a vector of constraint results.

Arguments

  • plc: A phylogeny constraint estimator, result, or nothing.
  • X: Data matrix (observations × assets) (ignored when plc is not an estimator).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments passed to the underlying phylogeny matrix routine (ignored when est is not an estimator).

Returns

  • res: Constraint result.

    • SemiDefinitePhylogeny: For semi-definite constraint estimators/results.
    • IntegerPhylogeny: For integer constraint estimators/results.
    • nothing: If est is nothing.

Related

source
phylogeny_constraints(
    plc::AbstractPhylogenyConstraintEstimator,
    pr::Union{AbstractPriorResult, ReturnsResult};
    rd,
    x_src,
    kwargs...
) -> Union{IntegerPhylogeny, SemiDefinitePhylogeny{<:AbstractMatrix{var"#s137"}, <:Number} where var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}

Compute phylogeny constraints from asset returns in a prior result using a phylogeny constraint estimator.

phylogeny_constraints delegates to the asset-returns variant by extracting X from pr (or rd if provided and x_src is :data).

Algorithm

  1. Pick the asset returns matrix X from the carrier that x_src names, with returns_matrix_picker.
  2. Call the asset-returns method of phylogeny_constraints with X, passing both carriers on as pr and rd, and return the constraint result it produces.

Arguments

  • plc: Phylogeny constraint estimator.
  • pr: Prior result or returns result. Both carry the asset returns matrix X and the feature matrix Z, so either can supply them.
  • rd: The returns result to use. Read for X only when x_src is :data, and passed on to the estimator tree.
  • x_src: Which returns matrix the clustering, phylogeny and centrality estimators read: :prior takes the prior result's X, :data takes the raw returns result's X. Ignored when no returns result is available, in which case the prior result's X is used.
  • kwargs...: Additional keyword arguments passed to the estimator.

Returns

  • Phylogeny constraint result.

Related

source
PortfolioOptimisers.centrality_constraintsFunction
centrality_constraints(ccs::CC_VecCC, X::MatNum; dims::Int = 1, strict::Bool = false,
                       kwargs...)

Reduce one or more CentralityConstraint estimators to a LinearConstraint.

Each estimator contributes one row. The coefficients of the row are the centrality vector of the asset network, and the right-hand side is read off that vector or given as a number. A constraint whose centrality vector is empty or all zero contributes no row, because the row it would build holds for every set of weights: two assets under BetweennessCentrality give the zero vector, and a lone such constraint makes this function return nothing. The drop is reported through strict_diagnostic, so strict decides whether it raises or warns.

Algorithm

  1. Check that ccs is not empty when it is a vector.
  2. Check dims with assert_dims, and read the asset count N from the axis of X that dims does not name. N is the width of every row the loop builds.
  3. For each cc in ccs, compute the centrality vector A with the estimator in cc.A, along dims.
  4. Report cc through strict_diagnostic with zero_centrality_msg and skip it when A is empty or all zero.
  5. Read the sign d and the inequality flag flag_ineq of cc.comp from comparison_sign_ineq_flag.
  6. Derive the right-hand side B from cc.B against the unscaled A with vec_to_real_measure, and scale it by d.
  7. Scale A by d, which negates a >= row so that every inequality is stored in the A w <= B sense.
  8. Append A and B to the inequality pair or to the equality pair, as flag_ineq selects.
  9. Reshape each flat accumulator into N columns, giving one row per surviving constraint, and build the LinearConstraint from the halves that hold a row. Return nothing when neither half does.

Step 6 runs before step 7 by design. Deriving the threshold from the scaled row negates it a second time, which cancels the flip and turns a MinValue() into a MaxValue() with the wrong sign.

Arguments

  • ccs: A single CentralityConstraint or a vector of them.
  • X: Data matrix.
  • dims: Dimension along which to perform the computation.
  • strict: If true, a constraint whose centrality vector is empty or all zero raises an ArgumentError; if false, it issues a warning. The constraint is dropped either way.
  • kwargs...: Additional keyword arguments passed to the centrality estimator.

Validation

  • !isempty(ccs) when ccs is a vector. A breach raises an IsEmptyError.
  • dims in (1, 2). The check is made with assert_dims, which raises the DomainError.
  • A centrality vector that is empty or all zero raises an ArgumentError when strict is true, and issues a warning otherwise. The constraint is dropped either way.
  • No value of cc.B is checked. A NaN or an Inf threshold reaches the returned right-hand side unchanged.

Returns

  • lc::Option{<:LinearConstraint}: The assembled inequality and equality rows, or nothing when every constraint was skipped.

Related

source
centrality_constraints(ccs::Option{<:LinearConstraint}, args...; kwargs...)

No-op fallback for centrality-based constraint propagation.

This method returns the input LinearConstraint object or nothing unchanged. It is used to pass through an already constructed centrality-based constraint object, enabling composability and uniform interface handling in constraint generation workflows.

Arguments

  • ccs: An existing LinearConstraint object or nothing.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • ccs::Option{<:LinearConstraint}: The input constraint object or nothing, unchanged.

Related

source
centrality_constraints(
    ccs::Union{CentralityConstraint, AbstractVector{<:CentralityConstraint}},
    pr::Union{AbstractPriorResult, ReturnsResult};
    rd,
    x_src,
    kwargs...
) -> Union{Nothing, LinearConstraint}

Compute centrality constraints from asset returns in a prior result using a centrality constraint estimator.

centrality_constraints delegates to the asset-returns variant by extracting X from pr (or rd if provided and x_src is :data).

Algorithm

  1. Pick the asset returns matrix X from the carrier that x_src names, with returns_matrix_picker.
  2. Call the asset-returns method of centrality_constraints with X, passing both carriers on as pr and rd, and return the constraint result it produces.

Arguments

  • ccs: Centrality constraint estimator or vector thereof.
  • pr: Prior result or returns result. Both carry the asset returns matrix X and the feature matrix Z, so either can supply them.
  • rd: The returns result to use. Read for X only when x_src is :data, and passed on to the estimator tree.
  • x_src: Which returns matrix the clustering, phylogeny and centrality estimators read: :prior takes the prior result's X, :data takes the raw returns result's X. Ignored when no returns result is available, in which case the prior result's X is used.
  • kwargs...: Additional keyword arguments passed to the estimator. strict is read by the asset-returns variant, which reports a dropped zero centrality vector through it.

Returns

  • Centrality constraint result.

Related

source

References

[5]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[70]
D. Cajas. A Graph Theory Approach to Portfolio Optimization. Available at SSRN 4602019 (2023).
[71]
D. Cajas. A Graph Theory Approach to Portfolio Optimization Part II. Available at SSRN 4667426 (2023).
[72]
F. Ricca and A. Scozzari. Portfolio optimization through a network approach: network assortative mixing and portfolio diversification. European Journal of Operational Research 312, 700–717 (2024).