Phylogeny

PortfolioOptimisers.PhylogenyResultType
struct PhylogenyResult{__T_X} <: AbstractPhylogenyResult

Carries a validated phylogeny matrix or a centrality vector.

PhylogenyResult stores the output of phylogeny-based estimation routines, such as network or clustering-based phylogeny matrices, or centrality vectors. It is used throughout the package to represent validated phylogeny structures for constraint generation, centrality analysis, and related workflows.

Fields

  • X: Phylogeny matrix or vector.

Constructors

PhylogenyResult(;    X::ArrNum) -> PhylogenyResult

Keywords correspond to the struct's fields.

Validation

  • !isempty(X)..
  • IfXis aMatNum`:
    • Must be symmetric, LinearAlgebra.issymmetric(X)
    • Must have zero diagonal, all(iszero, LinearAlgebra.diag(X)).

Examples

julia> PhylogenyResult(; X = [0 1 0; 1 0 1; 0 1 0])PhylogenyResult  X ┴ 3×3 Matrix{Int64}julia> PhylogenyResult(; X = [0.2, 0.5, 0.3])PhylogenyResult  X ┴ Vector{Float64}: [0.2, 0.5, 0.3]

Related

source
PortfolioOptimisers.BetweennessCentralityType
struct BetweennessCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithm

Scores each asset by the share of the network's shortest paths that run through it.

BetweennessCentrality computes the betweenness centrality of nodes in a graph, measuring the extent to which a node lies on shortest paths between other nodes.

Declares DistancePolarity, unless ov overrides it: it is defined over shortest paths, so its weights must be distances. On a tree the weighted answer equals the unweighted one — a tree has exactly one path between any two vertices, so no weighting can change the shortest-path set — which is a theorem about the graph rather than a limitation, and it does not hold on the similarity branch. Set ov to TopologyOnly to withdraw the declaration and read the topology alone.

Fields

  • args: Positional arguments for the centrality function.
  • kwargs: Keyword arguments for the centrality function.
  • ov: Polarity override. TopologyOnly asks for the centrality over the network's topology alone, so centrality_polarity answers nothing and centrality_graph builds the plain graph. nothing leaves the algorithm's declared polarity in force.

Constructors

BetweennessCentrality(;    args::Tuple = (),    kwargs::NamedTuple = (;),    ov::Option{TopologyOnly} = nothing) -> BetweennessCentrality

Keywords correspond to the struct's fields.

Examples

julia> BetweennessCentrality()BetweennessCentrality    args ┼ Tuple{}: ()  kwargs ┼ @NamedTuple{}: NamedTuple()      ov ┴ nothing

Related

References

  • [59] L. C. Freeman. A set of measures of centrality based on betweenness. Sociometry 40, 35–41 (1977).
  • [60] U. Brandes. A faster algorithm for betweenness centrality. The Journal of Mathematical Sociology 25, 163–177 (2001).
source
PortfolioOptimisers.ClosenessCentralityType
struct ClosenessCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithm

Scores each asset by the reciprocal of its mean shortest-path distance to the others.

ClosenessCentrality computes the closeness centrality of nodes in a graph, measuring how close a node is to all other nodes.

Declares DistancePolarity, unless ov overrides it: it sums shortest-path lengths, so its weights must be distances. It reads them on both branches, so its answer on a NetworkEstimator source differs from the unweighted one — measured over twenty assets, a maximum absolute change of 0.713 on a triangulated maximally filtered graph and 0.538 on a tree. Set ov to TopologyOnly to withdraw the declaration and read the topology alone.

Fields

  • args: Positional arguments for the centrality function.
  • kwargs: Keyword arguments for the centrality function.
  • ov: Polarity override. TopologyOnly asks for the centrality over the network's topology alone, so centrality_polarity answers nothing and centrality_graph builds the plain graph. nothing leaves the algorithm's declared polarity in force.

Constructors

ClosenessCentrality(;    args::Tuple = (),    kwargs::NamedTuple = (;),    ov::Option{TopologyOnly} = nothing) -> ClosenessCentrality

Keywords correspond to the struct's fields.

Examples

julia> ClosenessCentrality()ClosenessCentrality    args ┼ Tuple{}: ()  kwargs ┼ @NamedTuple{}: NamedTuple()      ov ┴ nothing

Related

References

  • [61] L. C. Freeman. Centrality in social networks conceptual clarification. Social Networks 1, 215–239 (1979).
source
PortfolioOptimisers.DegreeCentralityType
struct DegreeCentrality{__T_kind, __T_kwargs} <: AbstractCentralityAlgorithm

Counts the network edges that touch each asset, divided by the number of other assets.

DegreeCentrality computes the degree centrality of nodes in a graph. It is the simplest score of the family, and the shipped default of CentralityEstimator's ct.

Mathematical definition

The degree vector of an adjacency matrix $\mathbf{A}$ over $n$ assets is

\[\begin{align} \mathbf{D}_n &= \mathbf{A}\,\mathbf{1}_n\,, \end{align}\]

Where:

  • $\mathbf{A}$: Binary adjacency matrix of the network.
  • $\mathbf{1}_n$: Column vector of ones of length $n$.

Graphs.jl normalises that vector by default, so what this type returns is $\mathbf{D}_n / (n - 1)$ and not $\mathbf{D}_n$. Measured over a 20-asset minimum spanning tree, the first six entries of $\mathbf{D}_n$ are [3, 1, 2, 4, 3, 1] and the returned scores are [0.1579, 0.0526, 0.1053, 0.2105, 0.1579, 0.0526], a maximum absolute difference of 3.7894736842105265. kwargs = (; normalize = false) recovers $\mathbf{D}_n$ exactly.

The factor is the whole difference, and it re-ranks nothing. average_centrality is linear in the score vector, so a constant scale moves the average by that same constant.

The three kind values coincide on these structures

kind selects the total, the in- or the out-degree. Every graph this library builds is undirected, where the three are one number: measured over the same tree, kind = 0, 1 and 2 agree exactly. The field is kept because Graphs.jl takes it, not because it selects anything here.

Declares no polarity and runs on the plain graph: Graphs.degree_centrality counts edges and ignores what they weigh. It is therefore one of the algorithms for which the estimator's sep stays live — the unweighted route reads the separation closure phylogeny_matrix builds, so HopCount(; n = 2) does change this answer.

It carries no ov field, and TopologyOnly is not applicable to it: the topology alone is what it already reads, so there is no declaration to withdraw. DegreeCentrality(; ov = TopologyOnly()) is a MethodError.

Fields

  • kind: Degree type (0: total, 1: in-degree, 2: out-degree).
  • kwargs: Keyword arguments for the centrality function.

Constructors

DegreeCentrality(;    kind::Integer = 0,    kwargs::NamedTuple = (;)) -> DegreeCentrality

Keywords correspond to the struct's fields.

Validation

  • 0 <= kind <= 2.

Examples

julia> DegreeCentrality(; kind = 1)DegreeCentrality    kind ┼ Int64: 1  kwargs ┴ @NamedTuple{}: NamedTuple()

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.3.1, Equation 13.3.
  • [61] L. C. Freeman. Centrality in social networks conceptual clarification. Social Networks 1, 215–239 (1979).
source
PortfolioOptimisers.EigenvectorCentralityType
struct EigenvectorCentrality{__T_ov} <: AbstractCentralityAlgorithm

Scores each asset by the leading eigenvector of the network's adjacency matrix.

EigenvectorCentrality computes the eigenvector centrality of nodes in a graph, measuring the influence of a node based on the centrality of its neighbours.

Mathematical definition

\[\begin{align} \mathbf{EC}_n &= \dfrac{1}{\lambda_{\mathrm{max}}}\,\mathbf{A}\,\mathbf{q}_{\mathrm{max}}\,, \end{align}\]

Where:

  • $\mathbf{A}$: Adjacency matrix of the network, weighted on the similarity branch.
  • $\lambda_{\mathrm{max}}$: Largest eigenvalue of $\mathbf{A}$.
  • $\mathbf{q}_{\mathrm{max}}$: Eigenvector of $\lambda_{\mathrm{max}}$.

The right-hand side is $\mathbf{q}_{\mathrm{max}}$ itself, so the score is the leading eigenvector under whatever normalisation the eigensolver applies. Graphs.jl returns it with unit 2-norm, and takes the absolute value of every entry — the leading eigenvector of a non-negative matrix shares one sign, by the Perron-Frobenius theorem, so that changes no ordering. Measured over a 20-asset triangulated maximally filtered graph, the returned vector matches the formula above to 4.163336342344337e-16, has 2-norm 1.0 and runs from 0.0715 to 0.4576.

Declares SimilarityPolarity — the only member that declares it — unless ov overrides it: it is the leading eigenvector of the adjacency matrix itself, so a stronger link must contribute a larger entry. It therefore reads weights on the similarity branch alone. A tree is selected by minimising a distance and carries no similarity, so this algorithm runs unweighted there rather than being handed the wrong quantity. Set ov to TopologyOnly to withdraw the declaration and read the topology alone.

The weights change the answer by less than the shortest-path algorithms do, and they do change it: over the same triangulated maximally filtered graph the weighted and unweighted vectors differ by a maximum absolute 0.009892049284000948 and correlate 0.9985, on entries of size about 0.2.

Fields

  • ov: Polarity override. TopologyOnly asks for the centrality over the network's topology alone, so centrality_polarity answers nothing and centrality_graph builds the plain graph. nothing leaves the algorithm's declared polarity in force.

Constructors

EigenvectorCentrality(;    ov::Option{TopologyOnly} = nothing) -> EigenvectorCentrality

Keywords correspond to the struct's fields.

Examples

julia> EigenvectorCentrality()EigenvectorCentrality  ov ┴ nothing

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.3.2, Equation 13.4.
  • [62] P. Bonacich. Power and centrality: a family of measures. American Journal of Sociology 92, 1170–1182 (1987).
source
PortfolioOptimisers.KatzCentralityType
struct KatzCentrality{__T_alpha} <: AbstractCentralityAlgorithm

Scores each asset by every walk that reaches it, discounted geometrically by the walk's length.

KatzCentrality computes the Katz centrality of nodes in a graph, measuring the influence of a node based on the number and length of walks between nodes, controlled by the attenuation factor alpha.

Declares no polarity and runs on the plain graph: Graphs.katz_centrality binarises its input through adjacency_matrix(g, Bool), and throws an InexactError when the graph is weighted. The unweighted route is real code here rather than an absent check.

It carries no ov field, and TopologyOnly is not applicable to it: the topology alone is what it already reads, so there is no declaration to withdraw. KatzCentrality(; ov = TopologyOnly()) is a MethodError.

alpha must be below the reciprocal of the largest eigenvalue

The Katz score sums $\sum_{k \geq 1} \alpha^{k}\mathbf{A}^{k}$, which converges only for $\alpha < 1 / \lambda_{\mathrm{max}}$, where $\lambda_{\mathrm{max}}$ is the largest eigenvalue of the adjacency matrix. Above that bound the linear solve still returns a vector, and the vector is not a centrality: measured over a 20-asset minimum spanning tree, $\lambda_{\mathrm{max}} = 2.3585443300773266$ and the bound is 0.42399033473634745. At alpha = 0.3 every score is positive, between 0.13148 and 0.36762. At alpha = 0.5 the scores run -0.45187 to 0.45187, and a negative centrality has no reading.

The constructor cannot check this. $\lambda_{\mathrm{max}}$ is a property of the graph, and the graph is built later by centrality_graph, so the validation is alpha > 0 and the bound is the caller's to respect. A dense network raises $\lambda_{\mathrm{max}}$ and lowers the bound, so a value that held on a tree can fail on a triangulated maximally filtered graph over the same assets.

Fields

  • alpha: Attenuation factor for Katz centrality.

Constructors

KatzCentrality(;    alpha::Number = 0.3) -> KatzCentrality

Keywords correspond to the struct's fields.

Validation

  • alpha > 0.

Examples

julia> KatzCentrality(; alpha = 0.1)KatzCentrality  alpha ┴ Float64: 0.1

Related

References

  • [63] L. Katz. A new status index derived from sociometric analysis. Psychometrika 18, 39–43 (1953).
source
PortfolioOptimisers.PagerankType
struct Pagerank{__T_n, __T_alpha, __T_epsilon} <: AbstractCentralityAlgorithm

Scores each asset by the stationary distribution of a damped random walk over the network.

Pagerank computes the PageRank of nodes in a graph, measuring the importance of nodes based on the structure of incoming links. The algorithm is controlled by the damping factor alpha, number of iterations n, and convergence tolerance epsilon.

Declares no polarity and runs on the plain graph: Graphs.pagerank walks outdegree and inneighbors alone and never reads an edge weight. Measured over a 20-asset triangulated maximally filtered graph, the weighted and the plain graph give the identical vector, to 0.0. Like DegreeCentrality it therefore keeps the estimator's sep live, reading the separation closure rather than the structure.

It carries no ov field, and TopologyOnly is not applicable to it: the topology alone is what it already reads, so there is no declaration to withdraw. Pagerank(; ov = TopologyOnly()) is a MethodError.

Fields

  • n: Number of iterations.
  • alpha: Damping factor.
  • epsilon: Convergence threshold.

Constructors

Pagerank(;    n::Integer = 100,    alpha::Number = 0.85,    epsilon::Number = 1e-6) -> Pagerank

Keywords correspond to the struct's fields.

Validation

  • n > 0.
  • 0 < alpha < 1.
  • epsilon > 0.

Examples

julia> Pagerank(; alpha = 0.9, n = 200, epsilon = 1e-8)Pagerank        n ┼ Int64: 200    alpha ┼ Float64: 0.9  epsilon ┴ Float64: 1.0e-8

Related

References

  • [64] S. Brin and L. Page. The anatomy of a large-scale hypertextual Web search engine. Computer Networks and ISDN Systems 30, 107–117 (1998).
source
PortfolioOptimisers.RadialityCentralityType
struct RadialityCentrality{__T_ov} <: AbstractCentralityAlgorithm

Scores each asset by its mean shortest-path distance, measured against the network's diameter.

RadialityCentrality computes the radiality centrality of nodes in a graph, measuring how close a node is to all other nodes, adjusted for the maximum possible distance.

Declares DistancePolarity, unless ov overrides it: it reads shortest-path lengths against the graph's diameter, so its weights must be distances. It reads them on both branches, and its answer moves when they arrive — measured over twenty assets, a maximum absolute change of 0.248 on a triangulated maximally filtered graph and 0.234 on a tree. Set ov to TopologyOnly to withdraw the declaration and read the topology alone.

Fields

  • ov: Polarity override. TopologyOnly asks for the centrality over the network's topology alone, so centrality_polarity answers nothing and centrality_graph builds the plain graph. nothing leaves the algorithm's declared polarity in force.

Constructors

RadialityCentrality(;    ov::Option{TopologyOnly} = nothing) -> RadialityCentrality

Keywords correspond to the struct's fields.

Examples

julia> RadialityCentrality()RadialityCentrality  ov ┴ nothing

Related

References

  • [65] T. W. Valente and R. K. Foreman. Integration and radiality: measuring the extent of an individual's connectedness and reachability in a network. Social Networks 20, 89–105 (1998).
source
PortfolioOptimisers.StressCentralityType
struct StressCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithm

Counts the shortest paths of the network that pass through each asset.

StressCentrality computes the stress centrality of nodes in a graph, measuring the number of shortest paths passing through each node.

Declares DistancePolarity, unless ov overrides it: it counts shortest paths, so its weights must be distances. Like BetweennessCentrality it is unchanged by them on a tree, where the shortest-path set is fixed by the structure alone, and does move on the similarity branch. Set ov to TopologyOnly to withdraw the declaration and read the topology alone.

Fields

  • args: Positional arguments for the centrality function.
  • kwargs: Keyword arguments for the centrality function.
  • ov: Polarity override. TopologyOnly asks for the centrality over the network's topology alone, so centrality_polarity answers nothing and centrality_graph builds the plain graph. nothing leaves the algorithm's declared polarity in force.

Constructors

StressCentrality(;    args::Tuple = (),    kwargs::NamedTuple = (;),    ov::Option{TopologyOnly} = nothing) -> StressCentrality

Keywords correspond to the struct's fields.

Examples

julia> StressCentrality()StressCentrality    args ┼ Tuple{}: ()  kwargs ┼ @NamedTuple{}: NamedTuple()      ov ┴ nothing

Related

References

  • [66] A. Shimbel. Structural parameters of communication networks. The Bulletin of Mathematical Biophysics 15, 501–507 (1953).
source
PortfolioOptimisers.KruskalTreeType
struct KruskalTree{__T_args, __T_kwargs} <: AbstractTreeType

Grows the minimum spanning tree by taking the lightest edge that joins two components.

KruskalTree specifies the use of Kruskal's algorithm for constructing a minimum spanning tree from a graph.

Fields

  • args: Positional arguments for the spanning tree function. Every positional slot those functions declare is a weight channel, so assert_tree_args refuses a matrix or a vector here: the weights arrive with the graph.
  • kwargs: Keyword arguments for the spanning tree function. assert_tree_args refuses minimize, which would invert the minimisation the tree branch is defined by.

Constructors

KruskalTree(;    args::Tuple = (),    kwargs::NamedTuple = (;)) -> KruskalTree

Keywords correspond to the struct's fields.

Examples

julia> KruskalTree()KruskalTree    args ┼ Tuple{}: ()  kwargs ┴ @NamedTuple{}: NamedTuple()

Related

References

  • [67] J. B. Kruskal. On the shortest spanning subtree of a graph and the traveling salesman problem. Proceedings of the American Mathematical Society 7, 48–50 (1956).
source
PortfolioOptimisers.BoruvkaTreeType
struct BoruvkaTree{__T_args, __T_kwargs} <: AbstractTreeType

Grows the minimum spanning tree by joining every component to its own lightest neighbour at once.

BoruvkaTree specifies the use of Boruvka's algorithm for constructing a minimum spanning tree from a graph.

Fields

  • args: Positional arguments for the spanning tree function. Every positional slot those functions declare is a weight channel, so assert_tree_args refuses a matrix or a vector here: the weights arrive with the graph.
  • kwargs: Keyword arguments for the spanning tree function. assert_tree_args refuses minimize, which would invert the minimisation the tree branch is defined by.

Constructors

BoruvkaTree(;    args::Tuple = (),    kwargs::NamedTuple = (;)) -> BoruvkaTree

Keywords correspond to the struct's fields.

Examples

julia> BoruvkaTree()BoruvkaTree    args ┼ Tuple{}: ()  kwargs ┴ @NamedTuple{}: NamedTuple()

Related

References

  • [68] O. Borůvka. O jistém problému minimálním. Práce Moravské Přírodovědecké Společnosti 3, 37–58 (1926).
source
PortfolioOptimisers.PrimTreeType
struct PrimTree{__T_args, __T_kwargs} <: AbstractTreeType

Grows the minimum spanning tree outward from a single starting vertex.

PrimTree specifies the use of Prim's algorithm for constructing a minimum spanning tree from a graph.

Fields

  • args: Positional arguments for the spanning tree function. Every positional slot those functions declare is a weight channel, so assert_tree_args refuses a matrix or a vector here: the weights arrive with the graph.
  • kwargs: Keyword arguments for the spanning tree function. assert_tree_args refuses minimize, which would invert the minimisation the tree branch is defined by.

Constructors

PrimTree(;    args::Tuple = (),    kwargs::NamedTuple = (;)) -> PrimTree

Keywords correspond to the struct's fields.

Examples

julia> PrimTree()PrimTree    args ┼ Tuple{}: ()  kwargs ┴ @NamedTuple{}: NamedTuple()

Related

References

  • [69] R. C. Prim. Shortest connection networks and some generalizations. The Bell System Technical Journal 36, 1389–1401 (1957).
source
PortfolioOptimisers.NetworkEstimatorType
struct NetworkEstimator{__T_ce, __T_de, __T_alg<:(Union{var"#s936", var"#s935"} where {var"#s936"<:AbstractNonNegativeSimilarityMatrixAlgorithm, var"#s935"<:AbstractTreeType}), __T_sep} <: AbstractNetworkEstimator

Builds an asset network from a covariance estimate, and says which pairs of assets it relates.

NetworkEstimator encapsulates the configuration for constructing a network from asset data, including the covariance estimator, distance estimator, tree or similarity algorithm, and the separation algorithm that says how far apart two assets sit in the resulting graph.

Fields

  • ce: Covariance estimator.
  • de: Distance matrix estimator.
  • alg: Tree or similarity matrix algorithm. A similarity here selects the network by building a PMFG, so the family is the non-negative one and AngularSimilarity is refused.
  • sep: Separation algorithm, the rule measuring how far apart two assets sit in the network and the budget beyond which they count as unrelated.

Constructors

NetworkEstimator(;    ce::StatsBase.CovarianceEstimator = PortfolioOptimisersCovariance(),    de::AbstractDistanceEstimator = Distance(; alg = CanonicalDistance()),    alg::Tree_SimMat = KruskalTree(),    sep::AbstractSeparationAlgorithm = HopCount()) -> NetworkEstimator

Keywords correspond to the struct's fields.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • ce: Recursively updated via factory.
  • de: Recursively updated via factory.

The separation lives here, not on the consumer

sep says which pairs the network relates, and every consumer that reads a closure of this graph needs that answer: phylogeny_matrix and the phylogeny constraint families, both clusterise methods, and Proximity. It therefore sits on the estimator that builds the graph rather than on any one of them — a rule visible only to the feature producer would be structurally invisible to the constraint path, which receives nothing but this estimator.

The one exception is a consumer that reads the structure rather than a closure of it, and sep is inert there: the weighted routes of centrality_graph take the weighted graph itself, because a closure is a sum of matrix powers and a power of a weighted matrix sums products of distances. So a HopCount of n = 2 moves a DegreeCentrality and leaves a ClosenessCentrality where it was. At the default HopCount(; n = 1) nothing is visible, since the closure of a graph at one hop is the graph.

The budget rides on the member: HopCount(; n = 2) rather than a bare n = 2 beside sep. A budget stated apart from the rule that measures it has no statable unit, and becomes a dead field the moment a member measures something other than hops — which PathLength does, budgeting in the distance estimator's units instead.

Only HopCount is admitted by every consumer, and the split falls on whether the consumer walks a matrix power. Both clusterise methods accumulate $\sum_{i=0}^{n}(\mathbf{D}^i - \mathbf{A}^i)$, so they read sep.n as a power count and refuse PathLength at dispatch: a radius has no analogue of a matrix power. phylogeny_matrix and Proximity take either, each through a method of its own — a hop ball is a clamped power sum, a radius ball is a threshold on separation_matrix.

Examples

julia> NetworkEstimator()NetworkEstimator   ce ┼ PortfolioOptimisersCovariance      │   ce ┼ Covariance      │      │    me ┼ SimpleExpectedReturns      │      │       │   w ┴ nothing      │      │    ce ┼ GeneralCovariance      │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)      │      │       │    w ┴ nothing      │      │   alg ┴ FullMoment()      │   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

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.4.
  • [70] R. N. Mantegna. Hierarchical structure in financial markets. The European Physical Journal B 11, 193–197 (1999).
  • [57] M. Tumminello, T. Aste, T. Di Matteo and R. N. Mantegna. A tool for filtering information in complex systems. Proceedings of the National Academy of Sciences 102, 10421–10426 (2005).
  • [56] G. P. Massara, T. Di Matteo and T. Aste. Network Filtering for Big Data: Triangulated Maximally Filtered Graph. Journal of Complex Networks 5, 161–178 (2016).
source
PortfolioOptimisers.NetworkClustersEstimatorType
struct NetworkClustersEstimator{__T_nte, __T_alg, __T_onc} <: AbstractClustersEstimator

Clusters assets by the pseudo-distances that a network's structure induces.

NetworkClustersEstimator encapsulates the configuration for clustering assets from a network, pairing the NetworkEstimator that builds the graph with the clustering algorithm and the optimal-number-of-clusters estimator applied to the pseudo-distance matrix it induces.

Fields

  • nte: Network estimator.
  • alg: Clustering algorithm.
  • onc: Optimal number of clusters estimator.

Constructors

NetworkClustersEstimator(;    nte::AbstractNetworkEstimator = NetworkEstimator(),    alg::AbstractClustersAlgorithm = HClustAlgorithm(),    onc::AbstractOptimalNumberClustersEstimator = OptimalNumberClusters()) -> NetworkClustersEstimator

Keywords correspond to the struct's fields.

Propagated parameters

When factory is called on this type, the following @fprop-tagged fields are automatically propagated:

  • nte: Recursively updated via factory.

The power sums both clusterise methods accumulate are indexed by nte.sep.n, so the separation budget reaches this estimator through its network estimator rather than being restated here. That also fixes which separations this estimator accepts: nte.sep must be a HopCount, since a power count is what the sums are indexed by. A PathLength is constructible here but has no clusterise method.

Examples

julia> NetworkClustersEstimator()NetworkClustersEstimator  nte ┼ NetworkEstimator      │    ce ┼ PortfolioOptimisersCovariance      │       │   ce ┼ Covariance      │       │      │    me ┼ SimpleExpectedReturns      │       │      │       │   w ┴ nothing      │       │      │    ce ┼ GeneralCovariance      │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)      │       │      │       │    w ┴ nothing      │       │      │   alg ┴ FullMoment()      │       │   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  alg ┼ HClustAlgorithm      │   linkage ┴ Symbol: :ward  onc ┼ OptimalNumberClusters      │   max_k ┼ nothing      │     alg ┼ SecondOrderDifference      │         │   alg ┼ StandardisedValue      │         │       │   mv ┼ MeanValue      │         │       │      │   w ┴ nothing      │         │       │   sv ┼ StdValue      │         │       │      │           w ┼ nothing      │         │       │      │   corrected ┴ Bool: true

Related

source
PortfolioOptimisers._clusteriseMethod
_clusterise(
    alg::HClustAlgorithm,
    onc::AbstractOptimalNumberClustersEstimator,
    S::AbstractMatrix{<:Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}},
    D::AbstractMatrix{<:Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}},
    P::AbstractMatrix{<:Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}};
    branchorder
) -> Clusters{Clustering.Hclust{T}, var"#s179", var"#s1791", <:AbstractMatrix{var"#s90"}, <:Integer} where {T<:Real, var"#s90"<:(Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}), var"#s179"<:AbstractMatrix{var"#s90"}, var"#s90"<:(Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}), var"#s1791"<:AbstractMatrix{var"#s90"}, var"#s90"<:(Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar})}

Internal dispatch helper for constructing a Clusters result within a network-based clustering workflow.

Selects the appropriate clustering routine based on alg, determines the optimal number of clusters, and returns a Clusters result encapsulating all relevant outputs.

Arguments

  • alg: Clustering algorithm.

    • alg::HClustAlgorithm: Applies hierarchical clustering via Clustering.hclust on the pseudo-distance matrix P.
    • alg::DBHT: Applies Direct Bubble Hierarchical Tree clustering via DBHTs on P and S.
    • alg::AbstractNonHierarchicalClusteringAlgorithm: Applies non-hierarchical clustering via optimal_number_clusters on P.
  • onc: Optimal number of clusters estimator.

  • S: Similarity matrix.

  • D: Distance matrix.

  • P::MatNum: Symmetric pseudo-distance matrix derived from the network or similarity structure.

  • branchorder: Branch ordering strategy for hierarchical clustering.

Returns

  • clr::Clusters: Clustering result containing the clustering object, similarity matrix, distance matrix, pseudo-distance matrix, and optimal number of clusters.

Related

source
PortfolioOptimisers.clusteriseMethod
clusterise(nte::NetworkClustersEstimator{<:NetworkEstimator{<:Any, <:Any,
                                                            <:AbstractTreeType,
                                                            <:HopCount}},
           X::MatNum; dims::Int = 1, branchorder::Symbol = :optimal, kwargs...)

Cluster assets using a minimum spanning tree (MST) network structure and return a Clusters result.

Builds the MST from the distance matrix, accumulates a symmetric pseudo-distance matrix P over the configured network depth n as $\sum_{i=0}^{n}(\mathbf{D}^i - \mathbf{A}^i)$, and dispatches to _clusterise to perform the actual clustering and select the optimal number of clusters.

$\mathbf{A}$ is calc_weighted_adjacency's matrix, read off calc_weighted_adjacency_graph's graph through its one-argument form, so this method reads the same structure as every other consumer of a network and carries weights, not 0/1 — the tree branch's polarity is the distance, which is what $\mathbf{D}^i - \mathbf{A}^i$ subtracts a like quantity from. The two-argument entry point is the one used, because D is already in hand, and the graph is kept rather than discarded so that a budget rule is answered over it instead of re-deriving the distance.

Only a hop count is admitted

The fourth type parameter is narrowed to HopCount, so a PathLength separation fails at dispatch. The power sum is indexed by nte.nte.sep.n, and a matrix power counts edges: there is no radius analogue of $\mathbf{D}^i - \mathbf{A}^i$, so the refusal is the honest answer rather than a gap. phylogeny_matrix does have a radius method, so the two consumers of a network differ here on purpose.

Arguments

  • nte: Network clustering estimator configured with an MST-based NetworkEstimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • branchorder: Branch ordering strategy for hierarchical clustering.
  • kwargs...: Additional keyword arguments passed to the underlying estimators.

Returns

  • clr::Clusters: Clustering result containing the clustering object, similarity matrix, distance matrix, pseudo-distance matrix, and optimal number of clusters.

Related

source
PortfolioOptimisers.clusteriseMethod
clusterise(nte::NetworkClustersEstimator{<:NetworkEstimator{<:Any, <:Any,
                                                            <:AbstractNonNegativeSimilarityMatrixAlgorithm,
                                                            <:HopCount}},
           X::MatNum; dims::Int = 1, branchorder::Symbol = :optimal, kwargs...)

Cluster assets using a Planar Maximally Filtered Graph (PMFG) network structure and return a Clusters result.

Builds the PMFG from the similarity matrix via PMFG_T2s, accumulates a symmetric pseudo-distance matrix P over the configured network depth n as $\sum_{i=0}^{n}(\mathbf{S}^i - \mathbf{A}^i)$, and dispatches to _clusterise to perform the actual clustering and select the optimal number of clusters.

$\mathbf{A}$ is calc_weighted_adjacency's matrix, read off the graph as on the tree method, and this branch's polarity is the similarity — so $\mathbf{S}^i - \mathbf{A}^i$ again subtracts a like quantity. The two-argument entry point is the one used, because S is already in hand, and the graph is kept for the same reason.

Only a hop count is admitted

The fourth type parameter is narrowed to HopCount, so a PathLength separation fails at dispatch. See the tree method: a matrix power counts edges, and there is no radius analogue of the power sum.

Arguments

  • nte: Network clustering estimator configured with a similarity-matrix-based NetworkEstimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • branchorder: Branch ordering strategy for hierarchical clustering.
  • kwargs...: Additional keyword arguments passed to the underlying estimators.

Returns

  • clr::Clusters: Clustering result containing the clustering object, similarity matrix, distance matrix, pseudo-distance matrix, and optimal number of clusters.

Related

source
PortfolioOptimisers.CentralityEstimatorType
struct CentralityEstimator{__T_pl, __T_ct} <: AbstractCentralityEstimator

Bundles a network source with the centrality algorithm that scores its assets.

CentralityEstimator encapsulates the configuration for computing centrality measures on a network, including the network estimator and the centrality algorithm.

The network is weighted where it can be. centrality_polarity answers which quantity ct needs — distances for the shortest-path algorithms, similarities for EigenvectorCentrality — and centrality_graph supplies it from pl.

The estimator carries no override of its own. A caller who wants the centrality over the network's topology alone configures ct itself, with TopologyOnly in its ov field, and this estimator is a pure bundle of pl and ct either way.

Warning

Five cases run on the unweighted graph, and none of them raises. A caller names a configured algorithm and never asks for weights, so an unweightable pairing has not been handed a request it cannot serve. TopologyOnly asks away from them, which every source can serve, so it adds no case to this list and is not one of the five.

  1. A clustering estimator or a precomputed Clusters as pl, or a precomputed PhylogenyResult passed to centrality_vector directly. A partition has no edge weights, and does not borrow any.
  2. DegreeCentrality. Graphs.jl ignores weights.
  3. Pagerank. Graphs.jl ignores weights.
  4. KatzCentrality. Graphs.katz_centrality binarises through adjacency_matrix(g, Bool).
  5. EigenvectorCentrality on a tree branch. The branch carries no similarity for it to read.

On the weighted routes the sep field of a NetworkEstimator is inert: they read the structure itself rather than the separation closure phylogeny_matrix builds. At the default HopCount(; n = 1) the two agree, because the closure of a graph at one hop is the graph.

BetweennessCentrality and StressCentrality do read the weights, and are nonetheless unchanged by them on a tree: a tree has exactly one path between any two vertices, so the shortest-path set is the same at any weights. That is a theorem about the graph rather than a limitation of the algorithm, and it does not hold on the similarity branch.

Fields

  • pl: Network estimator, phylogeny result, clustering estimator, or clustering result.
  • ct: Centrality algorithm.

Constructors

CentralityEstimator(;    pl::NwE_ClE = NetworkEstimator(),    ct::AbstractCentralityAlgorithm = DegreeCentrality()) -> CentralityEstimator

Keywords correspond to the struct's fields.

Examples

julia> CentralityEstimator()CentralityEstimator  pl ┼ NetworkEstimator     │    ce ┼ PortfolioOptimisersCovariance     │       │   ce ┼ Covariance     │       │      │    me ┼ SimpleExpectedReturns     │       │      │       │   w ┴ nothing     │       │      │    ce ┼ GeneralCovariance     │       │      │       │   ce ┼ StatsBase.SimpleCovariance: StatsBase.SimpleCovariance(true)     │       │      │       │    w ┴ nothing     │       │      │   alg ┴ FullMoment()     │       │   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()

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.5.1, Equation 13.6.
source
PortfolioOptimisers._phylogeny_matrixFunction
_phylogeny_matrix(sep::HopCount, nte::AbstractNetworkEstimator,
                  g::Graphs.AbstractGraph)
_phylogeny_matrix(sep::PathLength, nte::AbstractNetworkEstimator,
                  g::Graphs.AbstractGraph)

Internal dispatch helper carrying phylogeny_matrix's per-separation body.

The neighbourhood phylogeny_matrix selects is a question about the separation, not about the estimator, so the split lives here rather than on the public method's argument. Dispatching on the estimator instead would pin the choice to NetworkEstimator and leave every other AbstractNetworkEstimator on one branch — and this family's other kernels, separation_matrix and separation_budget, already take the separation first for the same reason.

The structure arrives built

g is separation_graph's, built once by the public method and shared with resolve_separation, so neither branch derives a distance of its own. nte stays for separation_budget's estimator channel and is otherwise inert here.

The two balls

  • HopCount: the hop ball, sum(A^i for i in 0:n) clamped to 0 or 1, over Graphs.adjacency_matrix(g) — binary, because separation_graph hands a hop count a binarised structure and a power of a weighted matrix would sum products of distances. sep.n is read directly as a matrix-power count rather than through separation_budget, which is what makes it a power count and not a budget.
  • PathLength: the radius ball, separation_matrix thresholded at separation_budget. No second traversal.

Arguments

  • sep: Separation algorithm, taken from nte.sep by the public method and resolved.
  • nte: Network estimator.
  • g: Structure to read, from separation_graph.

Returns

  • P::Matrix{Int}: Phylogeny matrix. 1 for a related pair, 0 otherwise, 0 on the diagonal.

Related

source
PortfolioOptimisers.phylogeny_matrixFunction
phylogeny_matrix(plr::PhylogenyResult{<:MatNum}, args...; kwargs...)

Fallback no-op for returning a validated phylogeny matrix result as-is.

This method provides a generic interface for handling precomputed phylogeny matrices wrapped in a PhylogenyResult. It simply returns the input object unchanged, enabling consistent downstream workflows for constraint generation and analysis.

Arguments

  • plr::PhylogenyResult{<:MatNum}: Phylogeny matrix result object.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • The input plr object.

Examples

julia> plr = PhylogenyResult(; X = [0 1 0; 1 0 1; 0 1 0]);julia> phylogeny_matrix(plr)PhylogenyResult  X ┴ 3×3 Matrix{Int64}

Related

source
phylogeny_matrix(nte::AbstractNetworkEstimator, X::MatNum; dims::Int = 1, kwargs...)

Compute the phylogeny matrix for a network estimator.

Builds the network from X and returns the binary matrix of the pairs nte.sep counts as related, with self-loops removed. Which neighbourhood that is comes from the separation, through _phylogeny_matrix: HopCount gives the hop ball, the clamped power sum sum(A^i for i in 0:n) the network family has always used; PathLength gives the radius ball, separation_matrix thresholded at separation_budget.

The hop ball is the range connection matrix

The hop branch computes the range connection matrix of walks of length at most n, which is NetworkEstimator's Equations 13.1 and 13.2. Writing $\mathbf{A}$ for the binary adjacency matrix and $\mathbf{I}_n$ for the identity,

\[\begin{align} \mathbf{B}_{k} &= \mathbf{1}_{x \geq 1}\left(\mathbf{A}^{k} + \mathbf{I}_n\right) - \mathbf{I}_n\,, \\ \mathbf{B}_{1,\,l} &= \mathbf{1}_{x \geq 1}\left(\sum_{k=1}^{l} \mathbf{B}_{k}\right)\,, \end{align}\]

Where:

  • $\mathbf{1}_{x \geq 1}(\cdot)$: Element-wise indicator of the entries that are at least one.
  • $\mathbf{B}_{k}$: Pairs joined by at least one walk of length exactly $k$.
  • $\mathbf{B}_{1,\,l}$: Pairs joined by at least one walk of length at most $l$.

The code accumulates sum(A^i for i in 0:n), clamps to 0 or 1, and subtracts the identity, which is the same selection written once rather than shell by shell. Measured over a 20-asset minimum spanning tree, the two agree entry for entry at n = 1, 2, 3, 4 — a maximum absolute difference of 0, over 19, 44, 71 and 99 related pairs.

The result is Int under either separation

Selection changes; the values do not. PhylogenyResult's matrix is Int here as everywhere else, because no consumer of one wants a number: SemiDefinitePhylogeny is weight-inert (A ⊙ W == 0 is the same constraint at any magnitude), IntegerPhylogeny counts an integer cardinality, and centrality_vector binarises before any centrality algorithm runs. The graded reading of a separation lives on Proximity instead.

What the radius ball buys, measured

It barely re-ranks, and on the PMFG not at all. Compare a hop shell against the equal-cardinality prefix of the path-length ordering: on a 20-asset PMFG the two sets are identical at every shell — 0 pairs differ out of 54, 121, 165 and 186. On the minimum spanning tree they are identical at the shells of 19 and 48, and differ by 1, 1, 3 and 2 pairs at the shells of 84, 115, 144 and 170. Both structures are selected by distance in the first place, so a path length refines a hop count rather than rivalling it. A reader who takes the radius ball for a conceptually different neighbourhood will be wrong.

What it buys is intermediate cardinalities between the shells. Over the same PMFG the hop knob relates 54, then 121, then 165 of the 190 pairs; a caller wanting about 100 cannot ask for it. Sweeping dmax across the same graph reaches 36, 55, 100, 122, 151 and 179. That is the whole gain, and it is real for SemiDefinitePhylogeny and IntegerPhylogeny, whose constraint strength is that cardinality.

PathLength's default budget relates everything reachable

PathLength() leaves dmax = nothing, which separation_budget resolves to the observed diameter — so no reachable pair sits outside it and the matrix is all ones off the diagonal. Measured: 190 of 190 pairs on both branches. This is the honest reading of an unstated budget rather than a fall-back, but it is the opposite end of the dial from HopCount's default n = 1: a caller who swaps one separation for the other and changes nothing else gets the maximal ball where they had the minimal one. State a numeric dmax to select anything narrower.

Arguments

  • nte: NetworkEstimator estimator.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • plr::PhylogenyResult{<:Matrix{Int}}: Phylogeny matrix representing asset relationships. 1 for a related pair, 0 otherwise, 0 on the diagonal.

Related

source
phylogeny_matrix(cle::ClE_Cl,
                 X::MatNum; branchorder::Symbol = :optimal, dims::Int = 1,
                 kwargs...)

Compute the phylogeny matrix for a clustering estimator or result.

This function clusterises the data, cuts the tree into the optimal number of clusters, and constructs a binary phylogeny matrix indicating shared cluster membership, with self-loops removed.

Arguments

  • cle: Clustering estimator or result.
  • X: Data matrix (observations × assets).
  • branchorder: Branch ordering strategy for hierarchical clustering.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • P::Matrix{Int}: Phylogeny matrix representing cluster relationships.

Related

source
phylogeny_matrix(pl::NwE_ClE_Cl, pr::AbstractPriorResult;
                 kwargs...)

Compute the phylogeny matrix from asset returns in a prior result using a network or clustering estimator.

phylogeny_matrix applies the specified network or clustering estimator to the asset returns matrix contained in the prior result object, producing a phylogeny matrix for use in constraint generation, centrality analysis, or portfolio construction.

Arguments

  • pl: Network estimator, res estimator, or clustering result.
  • pr: Prior result object.
  • kwargs...: Additional keyword arguments passed to the estimator.

Returns

  • plr::PhylogenyResult: Result object containing the phylogeny matrix.

Related

source
PortfolioOptimisers.centrality_vectorFunction
centrality_vector(plr::PhylogenyResult{<:VecNum}, args...; kwargs...)

Fallback no-op for returning a validated centrality vector result as-is.

This method provides a generic interface for handling precomputed centrality vectors wrapped in a PhylogenyResult. It simply returns the input object unchanged, enabling consistent downstream workflows for centrality-based analysis and constraint generation.

Arguments

  • plr::PhylogenyResult{<:VecNum}: Centrality vector result object.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • The input plr object.

Examples

julia> plr = PhylogenyResult(; X = [0.2, 0.5, 0.3]);julia> centrality_vector(plr)PhylogenyResult  X ┴ Vector{Float64}: [0.2, 0.5, 0.3]

Related

source
centrality_vector(
    plr::PhylogenyResult{<:AbstractMatrix{<:Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}}},
    ct::AbstractCentralityAlgorithm,
    args...;
    kwargs...
) -> PhylogenyResult{<:AbstractArray{var"#s90", N}} where {var"#s90"<:(Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}), N}

Compute the centrality vector from a matrix PhylogenyResult using the specified centrality algorithm.

Builds a graph from the phylogeny matrix and applies ct to compute node centrality scores.

The graph is always unweighted, whatever polarity ct declares. A precomputed PhylogenyResult is a matrix of 0s and 1s, so it is one of the weightless sources listed on centrality_vector's warning, and the weights it does not carry cannot be recovered from it. Pass the estimator instead of its result to get the weighted answer.

Related

source
centrality_vector(pl::NwE_ClE_Cl, ct::AbstractCentralityAlgorithm,
                  X::MatNum; dims::Int = 1, kwargs...)

Compute the centrality vector for a network and centrality algorithm.

This function builds the graph with centrality_graph — weighted in the polarity centrality_polarity answers for ct, where the source can supply it — and computes node centrality scores with calc_centrality.

Warning

Five cases run on the unweighted graph, and none of them raises. A caller names a configured algorithm and never asks for weights, so an unweightable pairing has not been handed a request it cannot serve. TopologyOnly asks away from them, which every source can serve, so it adds no case to this list and is not one of the five.

  1. A clustering estimator, a precomputed Clusters, or a precomputed PhylogenyResult as the source. A partition has no edge weights, and does not borrow any.
  2. DegreeCentrality. Graphs.jl ignores weights.
  3. Pagerank. Graphs.jl ignores weights.
  4. KatzCentrality. Graphs.katz_centrality binarises through adjacency_matrix(g, Bool).
  5. EigenvectorCentrality on a tree branch. The branch carries no similarity for it to read.

On the weighted routes the estimator's sep field is inert: they read the structure itself rather than the separation closure phylogeny_matrix builds. At the default HopCount(; n = 1) the two agree, because the closure of a graph at one hop is the graph.

Arguments

  • pl: Phylogeny estimator.
  • ct: Centrality algorithm.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • cv::VecNum: Centrality scores for each asset.

Related

source
centrality_vector(cte::CentralityEstimator, X::MatNum; dims::Int = 1, kwargs...)

Compute the centrality vector for a centrality estimator.

This function applies the centrality algorithm in the estimator to the network constructed from the data.

Arguments

  • cte: Centrality estimator.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • cv::VecNum: Centrality scores for each asset.

Related

source
centrality_vector(cte::CentralityEstimator, pr::AbstractPriorResult; kwargs...)

Compute the centrality vector for a centrality estimator and prior result.

centrality_vector applies the centrality algorithm in the estimator to the network constructed from the asset returns in the prior result, returning centrality scores for each asset.

Arguments

  • cte: Centrality estimator.
  • pr: Prior result object.
  • kwargs...: Additional keyword arguments.

Returns

  • plr::PhylogenyResult: Result object containing the centrality vector.

Related

source
centrality_vector(pl::NwE_ClE_Cl, ct::AbstractCentralityAlgorithm,
                  pr::AbstractPriorResult; kwargs...)

Compute the centrality vector for a network or clustering estimator and centrality algorithm.

centrality_vector constructs the phylogeny matrix from the asset returns in the prior result, builds a graph, and computes node centrality scores using the specified centrality algorithm.

Arguments

  • pl: Network estimator, res estimator, or clustering result.
  • ct: Centrality algorithm.
  • pr: Prior result object.
  • kwargs...: Additional keyword arguments.

Returns

  • plr::PhylogenyResult: Result object containing the centrality vector.

Related

source
PortfolioOptimisers.average_centralityFunction
average_centrality(pl::NwE_Pl_ClE_Cl,
                   ct::AbstractCentralityAlgorithm, w::VecNum, X::MatNum;
                   dims::Int = 1, kwargs...)

Compute the weighted average centrality for a network and centrality algorithm.

This function computes the centrality vector and returns the weighted average using the provided weights. It is the average centrality measure of CentralityEstimator's Equation 13.6,

\[\begin{align} \mathrm{CM}(\boldsymbol{x}) &= \boldsymbol{C}_n^{\intercal} \boldsymbol{x}\,, \end{align}\]

Where:

  • $\boldsymbol{C}_n$: Centrality score vector from centrality_vector.
  • $\boldsymbol{x}$: Portfolio weight vector.

There is no normalisation and no absolute value, so the average carries the units of the score. A DegreeCentrality score is divided by $n - 1$ before it arrives here. Measured over a 20-asset minimum spanning tree, the code and the formula agree exactly.

Arguments

  • pl: NetworkEstimator estimator.
  • ct: Centrality algorithm.
  • w: Weights vector.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • ac::Number: Average centrality.

Related

source
average_centrality(cte::CentralityEstimator, w::VecNum, X::MatNum;
                   dims::Int = 1, kwargs...)

Compute the weighted average centrality for a centrality estimator.

This function applies the centrality algorithm in the estimator to the network and returns the weighted average using the provided weights.

Arguments

  • cte: Centrality estimator.
  • w: Weights vector.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • ac::Number: Average centrality.

Related

source
average_centrality(pl::NwE_Pl_ClE_Cl,
                   ct::AbstractCentralityAlgorithm, w::VecNum,
                   pr::AbstractPriorResult; kwargs...)

Compute the weighted average centrality for a network or phylogeny result.

average_centrality computes the centrality vector using the specified network or phylogeny estimator and centrality algorithm, then returns the weighted average using the provided portfolio weights.

Arguments

  • pl: Network estimator or phylogeny result.
  • ct: Centrality algorithm.
  • w: Portfolio weights vector.
  • pr: Prior result object.
  • kwargs...: Additional keyword arguments.

Returns

  • ac::Number: Weighted average centrality.

Related

source
average_centrality(cte::CentralityEstimator, w::VecNum, pr::AbstractPriorResult;
                   kwargs...)

Compute the weighted average centrality for a centrality estimator.

average_centrality applies the centrality algorithm in the estimator to the network constructed from the asset returns in the prior result, then returns the weighted average using the provided portfolio weights.

Arguments

  • cte: Centrality estimator.
  • w: Portfolio weights vector.
  • pr: Prior result object.
  • kwargs...: Additional keyword arguments.

Returns

  • ac::Number: Weighted average centrality.

Related

source
PortfolioOptimisers.asset_phylogenyFunction
asset_phylogeny(w::VecNum, X::MatNum)

Compute the asset phylogeny score for a set of weights and a phylogeny matrix.

This function computes the weighted sum of the phylogeny matrix, normalised by the sum of absolute weights. The asset phylogeny score quantifies the degree of phylogenetic (network or cluster-based) structure present in the portfolio allocation. It is the percentage invested in connected assets of NetworkEstimator's Equation 13.7,

\[\begin{align} \mathrm{CA}(\boldsymbol{x}) &= \dfrac{\boldsymbol{1}_n^{\intercal} \left(\mathbf{B}_{1,\,l} \odot \lvert \boldsymbol{x}\boldsymbol{x}^{\intercal} \rvert\right) \boldsymbol{1}_n}{\boldsymbol{1}_n^{\intercal} \lvert \boldsymbol{x}\boldsymbol{x}^{\intercal} \rvert \boldsymbol{1}_n}\,, \end{align}\]

Where:

  • $\mathbf{B}_{1,\,l}$: Phylogeny matrix from phylogeny_matrix.
  • $\odot$: Hadamard, element-wise product.
  • $\boldsymbol{x}$: Portfolio weight vector.
  • $\boldsymbol{1}_n$: Column vector of ones of length $n$.

Two assets that are not related contribute nothing, and a pair contributes nothing when either weight is zero. Measured over a 20-asset minimum spanning tree at a two-hop budget, the code and the formula agree to 5.551115123125783e-17.

Arguments

  • w: Weights vector.
  • X: Phylogeny matrix.

Returns

  • p::Number: Asset phylogeny score.

Related

source
asset_phylogeny(pl::PhylogenyResult{<:MatNum}, w::VecNum, args...;
                kwargs...)

Compute the asset phylogeny score for a set of portfolio weights and a phylogeny matrix result, forwarding additional arguments.

This method provides compatibility with workflows that pass extra positional or keyword arguments. It extracts the phylogeny matrix from the PhylogenyResult and delegates to asset_phylogeny(w, pl), ignoring any additional arguments.

Arguments

  • pl::PhylogenyResult{<:MatNum}: Phylogeny matrix result object.
  • w::VecNum: Portfolio weights vector.
  • args...: Additional positional arguments (ignored).
  • kwargs...: Additional keyword arguments (ignored).

Returns

  • score::Number: Asset phylogeny score.

Related

source
asset_phylogeny(cle::NwE_ClE_Cl,
                w::VecNum, X::MatNum; dims::Int = 1, kwargs...)

Compute the asset phylogeny score for a set of weights and a network or clustering estimator.

This function computes the phylogeny matrix using the estimator and data, then computes the asset phylogeny score using the weights.

Arguments

  • cle: NetworkEstimator or clustering estimator.
  • w: Weights vector.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • p::Number: Asset phylogeny score.

Related

source
asset_phylogeny(pl::NwE_ClE_Cl,
                w::VecNum, pr::AbstractPriorResult; dims::Int = 1, kwargs...)

Compute the asset phylogeny score for a portfolio allocation using a phylogeny estimator or clustering result and a prior result.

This function computes the phylogeny matrix from the asset returns in the prior result using the specified phylogeny estimator or clustering result, then evaluates the asset phylogeny score for the given portfolio weights. The asset phylogeny score quantifies the degree of phylogenetic (network or cluster-based) structure present in the portfolio allocation.

Arguments

  • pl: Phylogeny estimator or clustering result used to compute the phylogeny matrix.
  • w: Portfolio weights vector.
  • pr: Prior result object containing asset returns.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments passed to the phylogeny matrix computation.

Returns

  • score::Number: Asset phylogeny score.

Details

  • Computes the phylogeny matrix from the asset returns in pr using pl.
  • Evaluates the weighted sum of the phylogeny matrix using the weights w.
  • Normalises the score by the sum of absolute weights.
  • Returns a real-valued score quantifying the phylogenetic structure of the allocation.

Related

source
PortfolioOptimisers.AbstractCentralityAlgorithmType
abstract type AbstractCentralityAlgorithm <: AbstractPhylogenyAlgorithm

Abstract supertype for the algorithms that score how central each asset is in a network.

Every member wraps one routine of Graphs.jl.

All concrete and/or abstract types implementing specific centrality algorithms (e.g., betweenness, closeness, degree, eigenvector, Katz, pagerank, radiality, stress) should be subtypes of AbstractCentralityAlgorithm.

Each member declares the weights it needs

A member says which quantity its edge weights must be, through centrality_polarity, and centrality_graph supplies it. The declaration is about correctness — a shortest path over similarities is backwards — and never about capability: a member that declares nothing, and a source that carries no weights, both run on the plain graph rather than raising. The fallback declares nothing, so a new member is unweighted until it opts in.

The five members that do declare one carry an ov field, and TopologyOnly in it withdraws the declaration for that instance. centrality_polarity therefore answers the effective polarity, not the declared one.

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.3.
  • [71] E. Estrada. The Structure of Complex Networks: Theory and Applications (Oxford University Press, 2011). Chapter 7.
source
PortfolioOptimisers.AbstractTreeTypeType
abstract type AbstractTreeType <: AbstractPhylogenyAlgorithm

Abstract supertype for all minimum spanning tree (MST) algorithm types.

All concrete and/or abstract types implementing specific MST algorithms (e.g., Kruskal, Boruvka, Prim) should be subtypes of AbstractTreeType.

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.4.1.
  • [70] R. N. Mantegna. Hierarchical structure in financial markets. The European Physical Journal B 11, 193–197 (1999).
source
PortfolioOptimisers.calc_mstFunction
calc_mst(alg::AbstractTreeType, g::Graphs.AbstractGraph)

Compute the minimum spanning tree (MST) of a graph using the specified algorithm.

This function dispatches to the appropriate MST computation from Graphs.jl based on the type of alg. Supported algorithms include Kruskal, Boruvka, and Prim.

Arguments

  • alg: MST algorithm to use.

    • alg::KruskalTree: Computes the MST using Kruskal's algorithm.
    • alg::BoruvkaTree: Computes the MST using Boruvka's algorithm.
    • alg::PrimTree: Computes the MST using Prim's algorithm.
  • g::Graphs.AbstractGraph: Graph to compute the MST on.

Returns

  • tree::Vector: Vector of edges representing the MST.

Related

source
PortfolioOptimisers.AbstractNetworkEstimatorType
abstract type AbstractNetworkEstimator <: AbstractPhylogenyEstimator

Abstract supertype for all network estimator types.

All concrete and/or abstract types implementing network-based estimation algorithms should be subtypes of AbstractNetworkEstimator.

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.
source
PortfolioOptimisers.AbstractCentralityEstimatorType
abstract type AbstractCentralityEstimator <: AbstractEstimator

Abstract supertype for all centrality estimator types.

All concrete and/or abstract types implementing centrality-based estimation algorithms should be subtypes of AbstractCentralityEstimator.

Related

References

  • [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 13.1.5.1.
source
PortfolioOptimisers.graph_weight_matrixFunction
graph_weight_matrix(D::MatNum)

Return D as a matrix whose off-diagonal entries are representable as SimpleWeightedGraphs edge weights.

A distance matrix and a weighted graph disagree about what 0 means. In the distance codomain 0 is the floor — two assets as close as they can be. In the graph representation 0 is the reserved value meaning absent: SimpleWeightedGraph sparsifies its input, and add_edge! with a zero weight refuses outright. Handing a zero distance straight to the constructor therefore deletes exactly the edge the minimum spanning tree most wants, and the two assets come out non-adjacent — the most related pair in the universe reported as unrelated, with no error raised.

A zero is not a symptom of bad data. SimpleAbsoluteDistance and LogDistance are defined on abs(rho), so a perfectly anti-correlated pair — a long/short leg, an inverse ETF, a pairs trade — is at distance zero and is genuinely maximally related. The square-root algorithms reach zero from the other side, since their clamp! maps any rho >= 1 to exactly zero.

So the zero is repaired, not rejected: each off-diagonal zero moves to nextfloat(zero(eltype(D))), the smallest representable positive value. That is the nearest value the representation can carry, it is orders of magnitude below any distance a caller could mean, and it is absorbed exactly by any sum it enters. D itself is returned untouched when no entry needs moving, so the copy is only paid for when it buys something.

Negative and NaN entries have no such nearest representable value and are rejected. A negative distance inverts the ordering it expresses and is unsound rather than merely wrong under the shortest-path routines that consume these weights — they return an answer instead of raising. A NaN — which a zero-variance asset produces, via a NaN correlation — silently fails every comparison the tree algorithms make.

Inf is left alone: it is the honest distance between uncorrelated assets under LogDistance, the graph accepts it, and a spanning tree simply takes those edges last.

Arguments

  • D: Symmetric distance matrix.

Validation

  • Throws a DomainError if any off-diagonal entry is negative or NaN.

Returns

  • W::MatNum: D itself, or a repaired copy of it.

Related

source
PortfolioOptimisers.calc_weighted_adjacency_graphFunction
calc_weighted_adjacency_graph(alg::AbstractTreeType, D::MatNum)
calc_weighted_adjacency_graph(alg::AbstractNonNegativeSimilarityMatrixAlgorithm,
                              S::MatNum)
calc_weighted_adjacency_graph(nte::NetworkEstimator, X::MatNum; dims::Int = 1,
                              kwargs...)

Build the weighted graph whose edges are the network structure.

This is the one construction site of that structure. calc_weighted_adjacency and calc_adjacency are each a single operation on the graph returned here, so how the structure is selected is decided in this function and nowhere else.

Polarity is per branch, and the two branches are not interchangeable

Each branch keeps the quantity that selected its own edges, and the two quantities run in opposite directions.

  • alg::AbstractTreeType: calc_mst minimises the distance, so the weights are distances. Small means closely related.
  • alg::AbstractNonNegativeSimilarityMatrixAlgorithm: PMFG_T2s maximises the gain over the similarity, so the weights are similarities. Large means closely related.

Re-weighting either branch with the other quantity would weight a structure by the quantity that did not select it, so neither is converted. The result carries no polarity tag, because the polarity is recoverable by dispatch on the algorithm.

A consumer that walks a path must therefore branch on nte.alg first. A shortest path over similarities inverts the ordering it is meant to express and returns an answer instead of raising, so the two graphs are interchangeable in shape but not in meaning.

Two entry points, because the selecting quantity is not always cheap

The two-argument methods take the selecting quantity itself — the distance on the tree branch, the similarity on the PMFG branch — and the three-argument method derives it from X and forwards. Which branch is which is decided by the same dispatch either way, so the polarity above is a property of the algorithm and not of the entry point.

The two-argument form exists for a caller that already holds that matrix. clusterise is one: it needs D and S for its own power sum and for the Clusters it returns, so re-deriving them here would compute the same correlation twice. That is not a rounding error — under VariationInfoDistance the derivation is 98% of clusterise's runtime, so the second one would almost double it.

The weights

  • Tree branch: strictly positive, and finite or Inf. graph_weight_matrix moves every zero distance off the value the representation reserves for absent, and rejects a negative or a NaN. Inf is legal — it is the honest LogDistance between two uncorrelated assets.
  • PMFG branch: strictly positive and finite. PMFG_T2s checks its input for non-negativity, and it inserts every remaining vertex whatever the gain, so it declines no edge. A zero weight would therefore be stored as an absent edge and silently shrink the structure, which is why assert_pmfg_weights refuses one here.

Arguments

  • alg: Tree or similarity matrix algorithm.
  • D: Distance matrix.
  • S: Similarity matrix.
  • nte: Network estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • G::SimpleWeightedGraphs.SimpleWeightedGraph: The network structure, carrying its branch's own weights.

Related

source
PortfolioOptimisers.calc_weighted_adjacencyFunction
calc_weighted_adjacency(G::Graphs.AbstractGraph)
calc_weighted_adjacency(alg::Tree_SimMat, W::MatNum)
calc_weighted_adjacency(nte::NetworkEstimator, X::MatNum; dims::Int = 1, kwargs...)

Compute the weighted adjacency matrix of the network structure.

Graphs.adjacency_matrix of a weighted graph returns the weights, not 0/1, so this is the matrix form of calc_weighted_adjacency_graph and inherits that function's per-branch polarity unchanged: distances on the tree branch, similarities on the PMFG branch. Read the polarity section of calc_weighted_adjacency_graph before consuming the values.

The sparsity pattern is the structure itself, so it is identical to calc_adjacency's on the same input. Only the stored values differ.

The entry points are calc_weighted_adjacency_graph's, one Graphs.adjacency_matrix call further on, plus one for a caller that already holds the graph itself. W is the selecting quantity — the distance on the tree branch, the similarity on the PMFG branch — and clusterise supplies it directly, having already paid for it; it then reads the matrix off the graph it keeps, through the one-argument form, because it needs that graph again to answer a budget rule.

Arguments

  • G: Network structure a caller already holds, from calc_weighted_adjacency_graph.
  • alg: Tree or similarity matrix algorithm.
  • W: Selecting quantity of alg's branch: a distance matrix under an AbstractTreeType, a similarity matrix under an AbstractNonNegativeSimilarityMatrixAlgorithm.
  • nte: Network estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • adj::SparseArrays.SparseMatrixCSC: Weighted adjacency matrix of the network, in its branch's own polarity.

Related

source
PortfolioOptimisers.calc_adjacencyFunction
calc_adjacency(nte::NetworkEstimator, X::MatNum; dims::Int = 1, kwargs...)

Compute the binary adjacency matrix for a network estimator.

The structure comes from calc_weighted_adjacency_graph; this function is the round trip through Graphs.SimpleGraph that discards the weights. Both branches share the one body, because the branch is decided in the tier below.

Consumers that need the weights call calc_weighted_adjacency instead. They must then observe the per-branch polarity documented on calc_weighted_adjacency_graph. The binarisation here is what exempts this function from it.

Arguments

  • nte: Network estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • adj::SparseArrays.SparseMatrixCSC{Int, Int}: Binary adjacency matrix representing the network.

Related

source
PortfolioOptimisers.calc_distance_weighted_graphFunction
calc_distance_weighted_graph(nte::NetworkEstimator, X::MatNum; dims::Int = 1,
                             kwargs...)

Build the network structure carrying distances on its edges, on either branch.

calc_weighted_adjacency_graph gives each branch the quantity that selected its edges, so its two branches hold opposite polarities. This function gives both branches the same one. The structure is unchanged — it is the same edge set, vertex for vertex — and only the weights differ, on the PMFG branch alone.

Why the PMFG branch may be re-weighted here, and may not be there

Re-weighting a structure with a quantity that did not select it is what calc_weighted_adjacency_graph refuses. This is not that. Every AbstractSimilarityMatrixAlgorithm is a strictly decreasing function of the distance, so the similarity that selected the PMFG's edges is a monotone image of D, not a foreign quantity: D is the selecting quantity's preimage, and the same PMFG comes out of it.

What must not happen is a path taken over the similarities themselves. A shortest path minimises the sum of its edge weights, so over similarities it seeks the route through the weakest links — the ordering it produces is backwards. It is also quiet about it: measured over the four similarity algorithms, the backwards answer correlates 0.95 to 0.97 with the right one, which is close enough to pass a glance and not close enough to be usable.

Arguments

  • nte: Network estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • G::SimpleWeightedGraphs.SimpleWeightedGraph: The network structure, weighted by distance on both branches.

Related

source
PortfolioOptimisers.separation_graphFunction
separation_graph(sep::HopCount, G::Graphs.AbstractGraph)
separation_graph(sep::HopCount, nte::AbstractNetworkEstimator, X::MatNum;
                 dims::Int = 1, kwargs...)
separation_graph(sep::PathLength, nte::AbstractNetworkEstimator, X::MatNum;
                 dims::Int = 1, kwargs...)

Build the structure a separation measures over.

One third of the extension contract of AbstractSeparationAlgorithm; separation_matrix and separation_budget are the other two. It exists as a kernel of its own so that the structure is built once per consumer call: separation_matrix and resolve_separation both take the graph, and a consumer that needs the separations and a budget rule answered would otherwise build the same structure twice, through two estimator-taking kernels that each derive it privately.

What each member measures over

  • HopCount: calc_adjacency's structure as a Graphs.SimpleGraph. Binary, because a hop count ignores the weights and its consumers do not — _phylogeny_matrix reads Graphs.adjacency_matrix off this graph for a power sum, where a weight would make A^i sum products of distances instead of counting walks.
  • PathLength: calc_distance_weighted_graph's structure. The same edge set, weighted by distance on either branch, because a shortest path over the PMFG's similarities seeks the route through the weakest links.

Two entry points, and why only the hop count gets the graph-taking one

The estimator-taking methods derive the structure from X. The graph-taking method is for a caller that already holds it: both clusterise methods build the structure from the selecting quantity they already paid for, through calc_weighted_adjacency_graph's own two-argument entry point, and would otherwise re-derive the distance — 98% of clusterise's runtime under VariationInfoDistance — to answer a budget rule.

PathLength has no graph-taking method, and cannot: a graph carries no polarity tag, so G is a distance-weighted structure on the tree branch and a similarity-weighted one on the PMFG branch, and nothing in the argument distinguishes them. Handing the PMFG's similarities to a shortest path returns an answer instead of raising — see calc_distance_weighted_graph. The hop count is exempt because it discards the weights.

Arguments

  • sep: Separation algorithm. Dispatched on, and its budget is not read — a member whose budget is still a rule measures over the same structure as one whose budget is a number, which is what lets resolve_separation be handed this graph.
  • G: Network structure a caller already holds, in either polarity.
  • nte: Network estimator.
  • X: Data matrix observations × assets if the dims keyword does not exist or dims = 1, assets × observations when dims = 2.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • g::Graphs.AbstractGraph: The structure sep measures over.

Related

source
PortfolioOptimisers.separation_matrixFunction
separation_matrix(sep::HopCount, g::Graphs.AbstractGraph)
separation_matrix(sep::PathLength, g::Graphs.AbstractGraph)
separation_matrix(sep::AbstractSeparationAlgorithm, nte::AbstractNetworkEstimator,
                  X::MatNum; dims::Int = 1, kwargs...)

Compute the dense assets × assets matrix of separations under a separation algorithm.

One third of the extension contract of AbstractSeparationAlgorithm; separation_graph and separation_budget are the other two.

The graph-taking form is the interface

The separations depend on the structure alone, so separation_matrix(sep, g) is where each member's method lives and the estimator-taking form is a wrapper that calls separation_graph first. A consumer holding a graph — because it built one for resolve_separation, or because a test chose one — enters at the graph, and pays for one structure rather than two.

The wrapper is generic rather than per-member: it is separation_graph that knows which structure the member reads, so there is nothing left for a member to say here.

The unreachable sentinel

An unreachable pair carries whatever sentinel the underlying routine uses, not a repaired value: Graphs.gdistances reports typemax(Int) for HopCount, and Graphs.floyd_warshall_shortest_paths reports typemax(T) for PathLength, which on the Float64 weights it is handed is Inf. A consumer therefore reads an entry through is_related rather than comparing it against the budget itself, and keeps the evaluation of anything it scores the entry with inside a short-circuiting branch — an ifelse evaluates both arms, and ReciprocalDecay overflows 1 + d at typemax(Int), which a fractional power turns into a DomainError rather than a discarded number.

The two shipped members read the same structure differently

HopCount counts the edges of the binarised structure; PathLength sums the distances along them. Which structure each reads is separation_graph's answer, not this function's. All-pairs shortest paths come from one floyd_warshall_shortest_paths call rather than a Dijkstra per vertex — measured about 7 times faster on this shape, and within about 1.3 times of the breadth-first loop the hop count uses.

Arguments

  • sep: Separation algorithm. Its budget is not read; a member whose budget is still a rule measures the same separations.
  • g: Structure to measure over, from separation_graph.
  • nte: Network estimator. On the wrapper only, where the structure is derived from X on every call.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments forwarded to the underlying phylogeny routines.

Returns

  • d::Matrix: Square matrix of separations. d[i, k] is the separation between assets i and k, d[i, i] is zero, and an unreachable pair carries the sentinel above.

Related

source
PortfolioOptimisers.separation_budgetFunction
separation_budget(sep::HopCount, nte::AbstractNetworkEstimator, d::MatNum)
separation_budget(sep::PathLength, nte::AbstractNetworkEstimator, d::MatNum)

Resolve the separation budget in scope: the separation beyond which a pair counts as unrelated.

One third of the extension contract of AbstractSeparationAlgorithm; separation_graph and separation_matrix are the other two. Split from separation_matrix because a consumer needs the budget on its own — to probe a decay before entering the assets × assets loop, or to threshold a matrix it already holds.

The separations are passed in, not recomputed

d is the matrix separation_matrix produced, so a member whose budget is observed rather than configured — the diameter of what the graph actually turned out to be — can read it without a second traversal. That is why the argument is the matrix and not a diameter: finding the largest finite entry is itself the assets² reduction, so passing a diameter would charge HopCount for one it ignores. Handing over d pushes the reduction into PathLength, the member that wants it.

nte is inert for what ships: it is the channel through which an extension budget can see the estimator that owns it. Inert arguments have precedent here — separation_decay's dmax is read by only one of five members.

The observed diameter is a ceiling, not only a default

PathLength clamps a chosen dmax to the observed diameter as well as substituting the diameter for nothing. The clamp truncates nothing — no pair sits beyond the diameter — so it is a scale-top correction and is visible only through LinearDecay, the one decay reading the budget. Without it, dmax = 100 on a graph of diameter 3.5 would flatten the scores towards a constant while forbidding no pair at all.

Arguments

  • sep: Separation algorithm.
  • nte: Network estimator that owns sep. Inert for the shipped members.
  • d: Separation matrix from separation_matrix. Inert for HopCount, whose budget is configured rather than observed; read by PathLength, whose budget is capped by what the graph turned out to be.

Returns

  • dmax::Number: Separation budget. Stated in the units sep measures in — hops for HopCount, the distance estimator's units for PathLength — so it is only ever compared against entries of d.

Related

source
PortfolioOptimisers.separation_quantileFunction
separation_quantile(
    sep::AbstractSeparationAlgorithm,
    d::AbstractMatrix{<:Union{var"#s89", var"#s88"} where {var"#s89"<:Number, var"#s88"<:AbstractJuMPScalar}},
    q::Number
) -> Number

Quantile of the reachable off-diagonal entries of a separation matrix.

The population is the pairs a budget can be about: the diagonal is zero by construction and an unreachable pair carries separation_matrix's sentinel, so neither is a separation. It is also the population phylogeny_matrix selects from, which is what makes q read as the fraction of pairs the resulting budget relates.

The sentinel test is the family's

The population excludes an unreachable pair through is_reachable rather than through a test written out here. isfinite alone would not do it: it is true for every Integer, so it admits HopCount's typemax(Int).

Arguments

  • sep: Separation algorithm the matrix was measured under, forwarded to is_reachable.
  • d: Separation matrix from separation_matrix.
  • q: Quantile in [0, 1].

Returns

  • dmax::Number: The q-quantile of the reachable off-diagonal separations.

Related

source
PortfolioOptimisers.HopCountQuantileType
struct HopCountQuantile{__T_q} <: HopCountAlgorithm

Places the hop budget at a quantile of the observed hop separations.

The shipped HopCountAlgorithm. HopCount(; n = HopCountQuantile(; q = 0.25)) asks for the hop budget that relates about a quarter of the reachable pairs, instead of naming a number of hops that was right for one universe.

What it holds still

A stated n holds the number of hops still and lets the related-pair count move with the graph. This rule holds the count still — about q of the reachable pairs — and lets the number of hops move. On a cross-validation fold or a subproblem of a meta optimiser the second is usually what the caller meant, because the constraint strength a downstream consumer feels is the cardinality, not the hop number.

The rounding is where the two stop matching

A hop count is an Integer and the quantile is not, so the budget is rounded to the nearest hop. The related-pair count therefore lands near q rather than on it, and on a small graph the shells are coarse enough that it can miss by a lot — a hop budget can only ever select one of a handful of cardinalities. PathLengthQuantile has no such step and hits q closely; that is the sharpest practical difference between the two separations.

It pays for a traversal, but not for a structure

Resolving this rule runs separation_matrix once, which the hop-ball branch of _phylogeny_matrix does not otherwise do — it walks matrix powers instead. A dynamic budget costs one all-pairs traversal that a stated one does not.

It does not cost a second structure. The rule is handed the graph its consumer already built, through separation_graph, so the distance derivation — 98% of clusterise's runtime under VariationInfoDistance — is paid once per consumer call whether the budget is a rule or a number.

Fields

  • q: Quantile of the observed separations to take as the budget. The reachable off-diagonal pairs are the population, so q is the fraction of them the budget relates.

Constructors

HopCountQuantile(;    q::Number = 0.25) -> HopCountQuantile

Keywords correspond to the struct's fields.

Validation

  • 0 <= q <= 1.

Examples

julia> HopCountQuantile()HopCountQuantile  q ┴ Float64: 0.25

Related

source
PortfolioOptimisers.PathLengthQuantileType
struct PathLengthQuantile{__T_q} <: PathLengthAlgorithm

Places the path-length budget at a quantile of the observed path separations.

The shipped PathLengthAlgorithm, and the direct answer to PathLength's own complaint that nobody has an intuition for a summed path in the units an AbstractDistanceEstimator emits. dmax = 0.37 is not a number a caller can reason about; "the budget that relates a quarter of the reachable pairs" is.

What it holds still

A stated dmax holds the radius still and lets the related-pair count move with the graph. This rule holds the count still and lets the radius move. Both are refitted per fold, so neither is stable in both senses at once — the choice is which of the two a downstream consumer is sensitive to, and for SemiDefinitePhylogeny and IntegerPhylogeny the constraint strength is the cardinality.

It reaches the cardinalities a hop count cannot

This is where the radius ball's one real gain becomes reachable by name. A hop budget steps through a handful of shell cardinalities and cannot stop between them; q is continuous, so PathLengthQuantile(; q = 0.3) asks for a cardinality directly and lands on it closely.

Fields

  • q: Quantile of the observed separations to take as the budget. The reachable off-diagonal pairs are the population, so q is the fraction of them the budget relates.

Constructors

PathLengthQuantile(;    q::Number = 0.25) -> PathLengthQuantile

Keywords correspond to the struct's fields.

Validation

  • 0 <= q <= 1.

Examples

julia> PathLengthQuantile()PathLengthQuantile  q ┴ Float64: 0.25

Related

source
PortfolioOptimisers.resolve_separationFunction
resolve_separation(sep::AbstractSeparationAlgorithm, nte::AbstractNetworkEstimator,
                   X::MatNum, g::Graphs.AbstractGraph; dims::Int = 1, kwargs...)
resolve_separation(sep::HopCount{<:HopCountRule}, nte::AbstractNetworkEstimator,
                   X::MatNum, g::Graphs.AbstractGraph; dims::Int = 1, kwargs...)
resolve_separation(sep::PathLength{<:PathLengthRule}, nte::AbstractNetworkEstimator,
                   X::MatNum, g::Graphs.AbstractGraph; dims::Int = 1, kwargs...)
resolve_separation(sep::AbstractSeparationAlgorithm, nte::AbstractNetworkEstimator,
                   X::MatNum; dims::Int = 1, kwargs...)
resolve_separation(sep::Union{<:HopCount{<:HopCountRule},
                              <:PathLength{<:PathLengthRule}},
                   nte::AbstractNetworkEstimator, X::MatNum; dims::Int = 1, kwargs...)

Replace a separation whose budget is a rule by one whose budget is a value.

The fourth kernel of AbstractSeparationAlgorithm, and the only one an extension does not have to write: the fallback on the abstract type returns sep unchanged, so a member whose budget is already a number passes through at no cost and gains nothing to maintain.

It is called by the consumer, not by the other kernels

Every shipped consumer of a network resolves nte.sep first and passes the resolved separation to separation_matrix and separation_budgetphylogeny_matrix, both clusterise methods, and phylogeny_features for Proximity.

The alternative was to resolve inside separation_budget, and it does not work: that kernel takes the separation matrix rather than the data, deliberately, so that HopCount never pays for a diameter reduction it ignores. A rule needs the structure, which is the one thing the budget kernel does not have. So separation_budget refuses an unresolved separation instead, and this kernel is where the structure is still in hand.

The rule is handed the structure, not asked to build one

g is separation_graph's structure, and the graph-taking methods are the interface: a consumer builds once, resolves the rule against that graph, and measures the separations over the same graph. The rule reads what it needs through separation_matrix(sep, g).

The estimator-taking methods are wrappers, and the resolved case has one of its own so that a stated budget builds nothing at all. Dispatching the wrapper on the rule-carrying parameterisation is what keeps that true — a single generic wrapper would derive a structure before discovering that the fallback ignores it.

The return check is a run-time one, and it has to be

A functor's return type is not part of its signature, so a HopCountAlgorithm cannot promise an Integer in the type system. This kernel checks the value and throws otherwise. The check earns its place: three readers use 0:n as a matrix-power count, where 0:1.5 drops a power silently.

Resolution goes back through the ordinary constructor, so the rule's answer meets exactly the validation a stated budget meets — n >= 1, n <= RESOURCE_LIMITS[].max_hop_count, and dmax > 0. That is why the resource cap needs no second check here: a rule that returns an absurd hop count is rejected by the same assert_resource_cap a stated one meets.

Arguments

  • sep: Separation algorithm, resolved or not.
  • nte: Network estimator that owns sep, handed to the rule as the channel to anything the graph does not carry.
  • X: Data matrix (observations × assets).
  • g: Structure the rule measures over, from separation_graph. Derived from X by the wrappers.
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments, forwarded to the rule.

Validation

  • A HopCountAlgorithm or Function in HopCount's n must return an Integer.
  • A PathLengthAlgorithm or Function in PathLength's dmax must return a Number. nothing is a stated budget, not a computed one.

Returns

  • sep::AbstractSeparationAlgorithm: The same member with a resolved budget. sep itself when the budget was already a value.

Related

source
PortfolioOptimisers.AbstractCentralityPolarityType
abstract type AbstractCentralityPolarity <: AbstractAlgorithm

Abstract supertype for the polarity of the edge weights a centrality algorithm reads.

A weighted network carries one of two opposite quantities on its edges. A distance runs small-is-close; a similarity runs large-is-close. Which one an algorithm needs is a fact about its own mathematics and not about the graph it is handed: on one and the same triangulated maximally filtered graph, closeness wants the distances and eigenvector centrality wants the similarities. So the polarity is declared per algorithm, by centrality_polarity, and the builder supplies the matching quantity.

Polarity never decides whether the call succeeds

It selects which weights an algorithm receives, and nothing else. An algorithm that declares no polarity, and a source that carries no weights, both run on the plain unweighted graph rather than raising — see the warning on centrality_vector for the full list. Weightedness is a property of the source, not of the request: there is no flag, so a caller names a configured algorithm and never asks for weights. The one request there is, TopologyOnly in the algorithm's ov field, asks away from them, and every source can serve it.

Related

source
PortfolioOptimisers.DistancePolarityType
struct DistancePolarity <: AbstractCentralityPolarity

Declares that an algorithm's edge weights must be distances: small means closely related.

Every algorithm that walks a shortest path needs this polarity, because a shortest path minimises the sum of the weights along it. Over similarities the same routine seeks the route through the weakest links and returns a backwards answer without raising.

Supplied by calc_distance_weighted_graph, which carries distances on both branches.

Related

source
PortfolioOptimisers.SimilarityPolarityType
struct SimilarityPolarity <: AbstractCentralityPolarity

Declares that an algorithm's edge weights must be similarities: large means closely related.

An algorithm that reads the weighted adjacency matrix directly, rather than walking a path, needs the entry to grow with relatedness — a stronger link must contribute more.

Supplied by calc_weighted_adjacency_graph, and only on its similarity branch. The tree branch is selected by calc_mst minimising a distance and holds no similarity, so an algorithm declaring this polarity runs unweighted there.

Related

source
PortfolioOptimisers.TopologyOnlyType
struct TopologyOnly <: AbstractAlgorithm

Withdraws an algorithm's polarity declaration, so it reads the network's topology alone.

An algorithm that declares a polarity is handed weights wherever the source carries them. TopologyOnly in its ov field withdraws that request: centrality_polarity then answers nothing, and centrality_graph routes to the plain Graphs.SimpleGraph of phylogeny_matrix. The computation is the one that already runs for DegreeCentrality, Pagerank and KatzCentrality, so this is a redirect and never a new estimator.

The override runs one way only

It removes weights and never supplies them. There is no value that forces a polarity onto an algorithm, and the field is deliberately not typed over AbstractCentralityPolarity. A forced polarity would succeed rather than raise — calc_distance_weighted_graph carries distances on both branches — and the algorithm would read a distance where it needs a similarity, reversing its own ordering in silence. Polarity correctness is not a runtime property, so nothing could catch it.

Every request is honoured, on every source

The answer over the topology alone is available from every source, so the override never warns and never goes inert. On a partition source, on a precomputed PhylogenyResult, and on the tree branch under SimilarityPolarity, the plain graph is what those routes already build, so the request is satisfied before it is made.

Only the five algorithms that declare a polarity carry an ov field. DegreeCentrality, Pagerank and KatzCentrality already return the topology-only answer, so there is nothing for them to override and DegreeCentrality(; ov = TopologyOnly()) is a MethodError.

It is a choice, not a simplification, and it moves no default

A topology-only centrality is often argued to be the more fold-stable of the two, by the same reasoning that makes a fixed dmax fold-stable under PathLength. That is not a reason to default to it.

The shipped default is already unweighted. CentralityEstimator's ct defaults to DegreeCentrality, which declares no polarity, so a caller who names no algorithm gets this answer already. Defaulting ov to TopologyOnly would change the answer only for a caller who named one of the five deliberately — and for those five, reading the weights the source carries is the correct answer, which is what AbstractCentralityPolarity exists to say.

The override re-arms sep. The plain-graph route reads the separation closure phylogeny_matrix builds, and the weighted routes bypass it. So the override trades the edge weights for a second knob rather than removing one: measured over twenty assets, all five algorithms answer differently at HopCount(; n = 1) and at n = 3 once they carry it, including the four that are inert to sep without it. Under a bare PathLength that knob is the observed diameter, which is the data-dependent quantity the fold-stability argument set out to avoid.

Examples

julia> ClosenessCentrality(; ov = TopologyOnly())ClosenessCentrality    args ┼ Tuple{}: ()  kwargs ┼ @NamedTuple{}: NamedTuple()      ov ┴ TopologyOnly()julia> isnothing(centrality_polarity(ClosenessCentrality(; ov = TopologyOnly())))truejulia> centrality_polarity(ClosenessCentrality())DistancePolarity()

Related

source
PortfolioOptimisers.centrality_polarityFunction
centrality_polarity(ct::AbstractCentralityAlgorithm)
centrality_polarity(ct::Union{<:BetweennessCentrality{<:Any, <:Any, Nothing},
                              <:ClosenessCentrality{<:Any, <:Any, Nothing},
                              <:StressCentrality{<:Any, <:Any, Nothing},
                              <:RadialityCentrality{Nothing}})
centrality_polarity(ct::Union{<:BetweennessCentrality{<:Any, <:Any, TopologyOnly},
                              <:ClosenessCentrality{<:Any, <:Any, TopologyOnly},
                              <:StressCentrality{<:Any, <:Any, TopologyOnly},
                              <:RadialityCentrality{TopologyOnly}})
centrality_polarity(ct::EigenvectorCentrality{Nothing})
centrality_polarity(ct::EigenvectorCentrality{TopologyOnly})

Answer which quantity a centrality algorithm's edge weights must be.

The extension contract of AbstractCentralityPolarity. centrality_graph reads it to decide what to weight the network with.

The answer is the effective polarity, not the declared one

A TopologyOnly in the algorithm's ov field withdraws the declaration, so this function answers nothing and the caller gets the plain graph. The override is resolved here rather than at the call site, because three algorithms carry no ov field at all and an inline read of ct.ov cannot be written for them. So this function keeps predicting the graph that centrality_graph builds, which is the property that makes it worth exporting.

The fallback declares nothing, so opting in is explicit

The method on AbstractCentralityAlgorithm returns nothing, which routes to the plain unweighted graph. A new algorithm therefore runs unweighted until it says otherwise, which is the safe default: a wrong polarity does not raise, it silently reverses the ordering the algorithm is reading.

What the shipped members declare, and why

The line between the first two groups and the third is Graphs.jl's own. The declaration is about correctness — which weights — and the absence of one is about capability.

Arguments

  • ct: Centrality algorithm.

Returns

  • polarity::Option{<:AbstractCentralityPolarity}: The effective polarity, or nothing for an algorithm that cannot read weights or has withdrawn its declaration. Each method returns one concrete type, never a Union.

Related

source
PortfolioOptimisers.centrality_graphFunction
centrality_graph(pl::ClE_Cl, ct::AbstractCentralityAlgorithm, X::MatNum;
                 dims::Int = 1, kwargs...)
centrality_graph(nte::AbstractNetworkEstimator, ct::AbstractCentralityAlgorithm,
                 X::MatNum; dims::Int = 1, kwargs...)
centrality_graph(polarity::Option{<:AbstractCentralityPolarity},
                 nte::AbstractNetworkEstimator, X::MatNum; dims::Int = 1, kwargs...)

Build the graph calc_centrality runs on, weighted in the polarity centrality_polarity answers for ct.

The one place where the source and the algorithm are both in scope, so it is the one place the pairing can be resolved. centrality_polarity says which quantity ct needs; the source says which quantities it has.

The routing

sourcepolaritygraph
AbstractNetworkEstimatorDistancePolaritycalc_distance_weighted_graph — distances, on either branch
NetworkEstimator on the similarity branchSimilarityPolaritycalc_weighted_adjacency_graph — the similarities that selected the edges
any sourcenothingplain Graphs.SimpleGraph of phylogeny_matrix
a clustering estimator or Clustersanyplain Graphs.SimpleGraph of phylogeny_matrix
AbstractNetworkEstimator on a tree branchSimilarityPolarityplain Graphs.SimpleGraph of phylogeny_matrix

The similarity route is narrower than the distance route on purpose. calc_distance_weighted_graph carries distances on both branches, but only the similarity branch is selected by a similarity — a tree is selected by calc_mst minimising a distance, and manufacturing a similarity from it would weight the structure with a quantity that did not choose it.

A partition carries no weights, and does not borrow any

A clustering source could reach a distance estimator through its own de, and does not. The triangulated maximally filtered graph selects each edge by a pairwise quantity, so a distance orders that selection; a partition selects by a dendrogram and a cut, and two assets in the same cluster may sit far apart in the distance. Co-membership is not ordered by the distance, so there is no quantity to borrow.

The separation is read on the unweighted route only

The unweighted route goes through phylogeny_matrix, so it sees the AbstractSeparationAlgorithm on the estimator — a HopCount of n = 2 gives centrality on the two-hop closure. The weighted routes bypass it and read the structure itself, because a closure is built by summing matrix powers and a power of a weighted matrix sums products of distances, which is not a separation. So the sep field is inert on the weighted routes. At the default HopCount(; n = 1) there is nothing to notice: the closure of a graph at one hop is the graph.

Two entry points, because the polarity is resolved once

The three-argument methods taking ct resolve centrality_polarity and forward to the methods taking the polarity itself, which is the same shape separation_matrix uses: the deciding algorithm comes first, and the estimator only supplies the graph.

Arguments

  • pl: Network estimator, phylogeny result, clustering estimator, or clustering result.
  • ct: Centrality algorithm.
  • polarity: Effective polarity of ct, from centrality_polarity.
  • nte: Network estimator.
  • X: Data matrix (observations × assets).
  • dims: Dimension along which to perform the computation.
  • kwargs...: Additional keyword arguments.

Returns

  • g::Graphs.AbstractGraph: A SimpleWeightedGraphs.SimpleWeightedGraph on a weighted route, a Graphs.SimpleGraph otherwise.

Related

source
PortfolioOptimisers.assert_no_weight_channel_argsFunction
assert_no_weight_channel_args(::Type{T}, args::Tuple, S::Type, shape::AbstractString,
                              channel::AbstractString) where {T}

Refuse an entry of args that would reach a second weighting channel.

The one refusal both splat guards make. A Graphs.jl entry point takes its weights in a positional slot, so an entry of args with that shape is a second channel answering a question the declared one already answered. assert_centrality_args and assert_tree_args differ only in which shape reaches a channel and in which declared channel they name, so both call this with their own S, shape and channel.

The index of the offending entry is reported with its type, because args is splatted and the caller sees no argument names.

Arguments

  • T: Algorithm type, named in the error message.
  • args: Positional arguments destined for the Graphs.jl function.
  • S: The shape that reaches a weight slot, e.g. AbstractMatrix.
  • shape: Name of that shape, used in the error message.
  • channel: Sentence naming the declared channel the caller must use instead.

Validation

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_centrality_argsFunction
assert_centrality_args(::Type{T}, args::Tuple) where {T}

Refuse a matrix inside a centrality algorithm's args.

args is splatted straight into the Graphs.jl centrality function, so a matrix in it is a distmx — a second, undeclared way to weight the graph. centrality_polarity is the declared one, and it picks the weights the algorithm's own mathematics needs, from the structure that was actually built. Two channels answering the same question is one too many, and this one was never safe:

  • Graphs.betweenness_centrality's distmx is its third positional argument, so a matrix in args binds to vs instead and the call overflows the stack inside Graphs.degree. The StackOverflowError is catchable and the process survives it, so what is lost is the call and not the session.
  • Graphs.closeness_centrality's is its second, so that one worked — silently overriding the polarity, and reporting a wrong-sized matrix as a BoundsError rather than a DimensionMismatch.
  • Graphs.stress_centrality has no distmx at all.

Non-matrix entries are untouched: a vertex list or a sample count is a genuine positional argument of those functions and says nothing about weights.

kwargs needs no companion guard. A keyword binds by name, so a matrix there cannot reach a positional slot: none of the four functions declares a matrix-valued keyword, and every one of normalize, endpoints, rng and seed refuses a matrix on its own. The whole family fails closed with a MethodError or a TypeError.

Arguments

  • T: Centrality algorithm type, named in the error message.
  • args: Positional arguments destined for the Graphs.jl centrality function.

Validation

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_tree_argsFunction
assert_tree_args(::Type{T}, args::Tuple, kwargs::NamedTuple) where {T}

Refuse a second weighting channel inside a spanning-tree algorithm's args and kwargs.

Both fields are splatted straight into the Graphs.jl spanning-tree function, and every channel they can reach re-weights or re-orients a tree that calc_weighted_adjacency_graph has already weighted. The graph it hands to calc_mst carries the distances the estimator's de and ce produced, and Graphs.jl defaults distmx to exactly those weights. A caller who fills these fields therefore answers a question that was already answered, and the wrong answer is silent:

  • kruskal_mst, boruvka_mst and prim_mst all take distmx as their second positional argument, so a matrix in args replaces the estimator's distances outright. It is correctly sized often enough to succeed, and the tree it builds is a legitimate-looking tree of the wrong graph.
  • kruskal_mst also takes a weight_vector there, which is the same override in the other shape.
  • minimize in kwargs inverts the sense of the search. The tree branch is defined by minimising a distance — calc_weighted_adjacency_graph and SimilarityPolarity both say so — and minimize = false yields a maximum spanning tree while everything downstream still reads it as a minimum one.

Non-matrix, non-vector entries are untouched, and so is every other keyword. Those reach no weighting channel, and the three functions declare none, so they fail closed at the call.

Arguments

  • T: Spanning-tree algorithm type, named in the error message.
  • args: Positional arguments destined for the Graphs.jl spanning-tree function.
  • kwargs: Keyword arguments destined for the same function.

Validation

Returns

  • nothing.

Related

source
PortfolioOptimisers.calc_centralityFunction
calc_centrality(ct::AbstractCentralityAlgorithm, g::Graphs.AbstractGraph)

Compute node centrality scores for a graph using the specified centrality algorithm.

This function dispatches to the appropriate centrality computation from Graphs.jl based on the type of ct. Supported algorithms include betweenness, closeness, degree, eigenvector, Katz, pagerank, radiality, and stress centrality.

g may be weighted or unweighted, and nothing here inspects which. Graphs.jl weights implicitly — the distmx of every routine that takes one defaults to weights(g) — so the choice is made once, by centrality_graph, and this function only forwards. Handing a weighted graph to an algorithm that declares no polarity is what centrality_graph exists to prevent: Graphs.katz_centrality throws an InexactError on one.

Arguments

  • ct: Centrality algorithm to use.

    • ct::BetweennessCentrality: Computes betweenness centrality.
    • ct::ClosenessCentrality: Computes closeness centrality.
    • ct::DegreeCentrality: Computes degree centrality.
    • ct::EigenvectorCentrality: Computes eigenvector centrality.
    • ct::KatzCentrality: Computes Katz centrality.
    • ct::Pagerank: Computes PageRank.
    • ct::RadialityCentrality: Computes radiality centrality.
    • ct::StressCentrality: Computes stress centrality.
  • g: Graph to compute centrality on.

Returns

  • ct::VecNum: Centrality scores for each node in the graph.

Related

source
PortfolioOptimisers.Tree_SimMatType
const Tree_SimMat = Union{<:AbstractNonNegativeSimilarityMatrixAlgorithm,
                          <:AbstractTreeType}

Alias for a tree or similarity matrix algorithm.

Matches either an AbstractNonNegativeSimilarityMatrixAlgorithm or an AbstractTreeType. Used for dispatch in phylogeny estimation where either a spanning tree or a similarity matrix approach may be used.

The similarity half is the narrow family, not AbstractSimilarityMatrixAlgorithm: the similarity branch builds a PMFG, whose consumers cannot take a negative weight. So MaximumDistanceSimilarity, ExponentialSimilarity, GeneralExponentialSimilarity and ComplementSimilarity match, and AngularSimilarity does not.

Related

source
PortfolioOptimisers.NwE_ClEType
const NwE_ClE = Union{<:AbstractNetworkEstimator, <:AbstractClustersEstimator}

Alias for a phylogeny source: a network estimator or a clustering estimator, and nothing precomputed.

This is the bound of the pl slot on SemiDefinitePhylogenyEstimator and IntegerPhylogenyEstimator, and the exclusion is the point. A constraint estimator answers "how do I build this constraint for whatever universe I am given"; a precomputed PhylogenyResult or Clusters in that slot answers a different question — "here is the answer for the universe I was built on" — and the two are only interchangeable while the universe never changes.

They stopped being interchangeable the moment a meta-optimiser handed a subproblem a subset of the assets. phylogeny_matrix returns a precomputed result unchanged, so the estimator emitted a full-universe constraint matrix for a three-asset subproblem, and every guard aimed at precomputed constraints missed it because the object presented as an estimator. The exclusion therefore lives in the type: the shape is not constructible, so there is no runtime check to write, to forget, or to route around. The only runtime guard left on this path is assert_external_optimiser, which now has just one remaining case to catch — a precomputed constraint result.

Precomputed structure has a home already: build the constraint once and pass the resultSemiDefinitePhylogeny or IntegerPhylogeny, whose A field takes a PhylogenyResult or a bare matrix — which is exactly what phylogeny_constraints(est, X) returns. Nothing is lost, and the guards that exist for results then apply.

Related

source
PortfolioOptimisers.HClE_HClType
const HClE_HCl = Union{<:ClustersEstimator{<:Any, <:Any,
                                           <:AbstractHierarchicalClusteringAlgorithm,
                                           <:Any},
                       <:Clusters{<:Clustering.Hclust, <:Any, <:Any, <:Any},
                       <:NetworkClustersEstimator{<:Any,
                                              <:AbstractHierarchicalClusteringAlgorithm}}

Alias for a hierarchical clustering estimator or result.

Matches either a ClustersEstimator parameterised with a hierarchical clustering algorithm, or a Clusters result wrapping a Clustering.Hclust. Used internally for dispatch in hierarchical clustering workflows.

Related

source

References

[4]
D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
[56]
[57]
M. Tumminello, T. Aste, T. Di Matteo and R. N. Mantegna. A tool for filtering information in complex systems. Proceedings of the National Academy of Sciences 102, 10421–10426 (2005).
[59]
L. C. Freeman. A set of measures of centrality based on betweenness. Sociometry 40, 35–41 (1977).
[60]
U. Brandes. A faster algorithm for betweenness centrality. The Journal of Mathematical Sociology 25, 163–177 (2001).
[61]
L. C. Freeman. Centrality in social networks conceptual clarification. Social Networks 1, 215–239 (1979).
[62]
P. Bonacich. Power and centrality: a family of measures. American Journal of Sociology 92, 1170–1182 (1987).
[63]
L. Katz. A new status index derived from sociometric analysis. Psychometrika 18, 39–43 (1953).
[64]
S. Brin and L. Page. The anatomy of a large-scale hypertextual Web search engine. Computer Networks and ISDN Systems 30, 107–117 (1998).
[65]
T. W. Valente and R. K. Foreman. Integration and radiality: measuring the extent of an individual's connectedness and reachability in a network. Social Networks 20, 89–105 (1998).
[66]
A. Shimbel. Structural parameters of communication networks. The Bulletin of Mathematical Biophysics 15, 501–507 (1953).
[67]
J. B. Kruskal. On the shortest spanning subtree of a graph and the traveling salesman problem. Proceedings of the American Mathematical Society 7, 48–50 (1956).
[68]
O. Borůvka. O jistém problému minimálním. Práce Moravské Přírodovědecké Společnosti 3, 37–58 (1926).
[69]
R. C. Prim. Shortest connection networks and some generalizations. The Bell System Technical Journal 36, 1389–1401 (1957).
[70]
R. N. Mantegna. Hierarchical structure in financial markets. The European Physical Journal B 11, 193–197 (1999).
[71]
E. Estrada. The Structure of Complex Networks: Theory and Applications (Oxford University Press, 2011).