Phylogeny
PortfolioOptimisers.PhylogenyResult — Type
struct PhylogenyResult{__T_X} <: AbstractPhylogenyResultCarries 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) -> PhylogenyResultKeywords 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)).
- Must be symmetric,
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
PortfolioOptimisers.BetweennessCentrality — Type
struct BetweennessCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithmScores 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.TopologyOnlyasks for the centrality over the network's topology alone, socentrality_polarityanswersnothingandcentrality_graphbuilds the plain graph.nothingleaves the algorithm's declared polarity in force.
Constructors
BetweennessCentrality(; args::Tuple = (), kwargs::NamedTuple = (;), ov::Option{TopologyOnly} = nothing) -> BetweennessCentralityKeywords correspond to the struct's fields.
Examples
julia> BetweennessCentrality()BetweennessCentrality args ┼ Tuple{}: () kwargs ┼ @NamedTuple{}: NamedTuple() ov ┴ nothingRelated
AbstractCentralityAlgorithmcentrality_polarityDistancePolarityTopologyOnlyGraphs.betweenness_centrality
References
PortfolioOptimisers.ClosenessCentrality — Type
struct ClosenessCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithmScores 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.TopologyOnlyasks for the centrality over the network's topology alone, socentrality_polarityanswersnothingandcentrality_graphbuilds the plain graph.nothingleaves the algorithm's declared polarity in force.
Constructors
ClosenessCentrality(; args::Tuple = (), kwargs::NamedTuple = (;), ov::Option{TopologyOnly} = nothing) -> ClosenessCentralityKeywords correspond to the struct's fields.
Examples
julia> ClosenessCentrality()ClosenessCentrality args ┼ Tuple{}: () kwargs ┼ @NamedTuple{}: NamedTuple() ov ┴ nothingRelated
AbstractCentralityAlgorithmcentrality_polarityDistancePolarityTopologyOnlyGraphs.closeness_centrality
References
- [61] L. C. Freeman. Centrality in social networks conceptual clarification. Social Networks 1, 215–239 (1979).
PortfolioOptimisers.DegreeCentrality — Type
struct DegreeCentrality{__T_kind, __T_kwargs} <: AbstractCentralityAlgorithmCounts 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 = (;)) -> DegreeCentralityKeywords correspond to the struct's fields.
Validation
0 <= kind <= 2.
Examples
julia> DegreeCentrality(; kind = 1)DegreeCentrality kind ┼ Int64: 1 kwargs ┴ @NamedTuple{}: NamedTuple()Related
References
PortfolioOptimisers.EigenvectorCentrality — Type
struct EigenvectorCentrality{__T_ov} <: AbstractCentralityAlgorithmScores 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.TopologyOnlyasks for the centrality over the network's topology alone, socentrality_polarityanswersnothingandcentrality_graphbuilds the plain graph.nothingleaves the algorithm's declared polarity in force.
Constructors
EigenvectorCentrality(; ov::Option{TopologyOnly} = nothing) -> EigenvectorCentralityKeywords correspond to the struct's fields.
Examples
julia> EigenvectorCentrality()EigenvectorCentrality ov ┴ nothingRelated
AbstractCentralityAlgorithmcentrality_polaritySimilarityPolarityTopologyOnlyGraphs.eigenvector_centrality
References
PortfolioOptimisers.KatzCentrality — Type
struct KatzCentrality{__T_alpha} <: AbstractCentralityAlgorithmScores 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) -> KatzCentralityKeywords correspond to the struct's fields.
Validation
alpha > 0.
Examples
julia> KatzCentrality(; alpha = 0.1)KatzCentrality alpha ┴ Float64: 0.1Related
References
- [63] L. Katz. A new status index derived from sociometric analysis. Psychometrika 18, 39–43 (1953).
PortfolioOptimisers.Pagerank — Type
struct Pagerank{__T_n, __T_alpha, __T_epsilon} <: AbstractCentralityAlgorithmScores 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) -> PagerankKeywords 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-8Related
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).
PortfolioOptimisers.RadialityCentrality — Type
struct RadialityCentrality{__T_ov} <: AbstractCentralityAlgorithmScores 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.TopologyOnlyasks for the centrality over the network's topology alone, socentrality_polarityanswersnothingandcentrality_graphbuilds the plain graph.nothingleaves the algorithm's declared polarity in force.
Constructors
RadialityCentrality(; ov::Option{TopologyOnly} = nothing) -> RadialityCentralityKeywords correspond to the struct's fields.
Examples
julia> RadialityCentrality()RadialityCentrality ov ┴ nothingRelated
AbstractCentralityAlgorithmcentrality_polarityDistancePolarityTopologyOnlyGraphs.radiality_centrality
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).
PortfolioOptimisers.StressCentrality — Type
struct StressCentrality{__T_args, __T_kwargs, __T_ov} <: AbstractCentralityAlgorithmCounts 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.TopologyOnlyasks for the centrality over the network's topology alone, socentrality_polarityanswersnothingandcentrality_graphbuilds the plain graph.nothingleaves the algorithm's declared polarity in force.
Constructors
StressCentrality(; args::Tuple = (), kwargs::NamedTuple = (;), ov::Option{TopologyOnly} = nothing) -> StressCentralityKeywords correspond to the struct's fields.
Examples
julia> StressCentrality()StressCentrality args ┼ Tuple{}: () kwargs ┼ @NamedTuple{}: NamedTuple() ov ┴ nothingRelated
References
- [66] A. Shimbel. Structural parameters of communication networks. The Bulletin of Mathematical Biophysics 15, 501–507 (1953).
PortfolioOptimisers.KruskalTree — Type
struct KruskalTree{__T_args, __T_kwargs} <: AbstractTreeTypeGrows 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, soassert_tree_argsrefuses a matrix or a vector here: the weights arrive with the graph.
kwargs: Keyword arguments for the spanning tree function.assert_tree_argsrefusesminimize, which would invert the minimisation the tree branch is defined by.
Constructors
KruskalTree(; args::Tuple = (), kwargs::NamedTuple = (;)) -> KruskalTreeKeywords 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).
PortfolioOptimisers.BoruvkaTree — Type
struct BoruvkaTree{__T_args, __T_kwargs} <: AbstractTreeTypeGrows 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, soassert_tree_argsrefuses a matrix or a vector here: the weights arrive with the graph.
kwargs: Keyword arguments for the spanning tree function.assert_tree_argsrefusesminimize, which would invert the minimisation the tree branch is defined by.
Constructors
BoruvkaTree(; args::Tuple = (), kwargs::NamedTuple = (;)) -> BoruvkaTreeKeywords 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).
PortfolioOptimisers.PrimTree — Type
struct PrimTree{__T_args, __T_kwargs} <: AbstractTreeTypeGrows 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, soassert_tree_argsrefuses a matrix or a vector here: the weights arrive with the graph.
kwargs: Keyword arguments for the spanning tree function.assert_tree_argsrefusesminimize, which would invert the minimisation the tree branch is defined by.
Constructors
PrimTree(; args::Tuple = (), kwargs::NamedTuple = (;)) -> PrimTreeKeywords 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).
PortfolioOptimisers.NetworkEstimator — Type
struct NetworkEstimator{__T_ce, __T_de, __T_alg<:(Union{var"#s936", var"#s935"} where {var"#s936"<:AbstractNonNegativeSimilarityMatrixAlgorithm, var"#s935"<:AbstractTreeType}), __T_sep} <: AbstractNetworkEstimatorBuilds 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 andAngularSimilarityis 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()) -> NetworkEstimatorKeywords correspond to the struct's fields.
Propagated parameters
When factory is called on this type, the following @fprop-tagged fields are automatically propagated:
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: 1Related
AbstractNetworkEstimatorAbstractTreeTypeAbstractNonNegativeSimilarityMatrixAlgorithmTree_SimMatfactory
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).
PortfolioOptimisers.NetworkClustersEstimator — Type
struct NetworkClustersEstimator{__T_nte, __T_alg, __T_onc} <: AbstractClustersEstimatorClusters 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()) -> NetworkClustersEstimatorKeywords 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 viafactory.
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: trueRelated
PortfolioOptimisers._clusterise — Method
_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 viaClustering.hcluston the pseudo-distance matrixP.alg::DBHT: Applies Direct Bubble Hierarchical Tree clustering viaDBHTsonPandS.alg::AbstractNonHierarchicalClusteringAlgorithm: Applies non-hierarchical clustering viaoptimal_number_clustersonP.
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
PortfolioOptimisers.clusterise — Method
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-basedNetworkEstimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.clusterise — Method
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-basedNetworkEstimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.CentralityEstimator — Type
struct CentralityEstimator{__T_pl, __T_ct} <: AbstractCentralityEstimatorBundles 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.
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.
- A clustering estimator or a precomputed
Clustersaspl, or a precomputedPhylogenyResultpassed tocentrality_vectordirectly. A partition has no edge weights, and does not borrow any. DegreeCentrality.Graphs.jlignores weights.Pagerank.Graphs.jlignores weights.KatzCentrality.Graphs.katz_centralitybinarises throughadjacency_matrix(g, Bool).EigenvectorCentralityon 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()) -> CentralityEstimatorKeywords 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.
PortfolioOptimisers._phylogeny_matrix — Function
_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 to0or1, overGraphs.adjacency_matrix(g)— binary, becauseseparation_graphhands a hop count a binarised structure and a power of a weighted matrix would sum products of distances.sep.nis read directly as a matrix-power count rather than throughseparation_budget, which is what makes it a power count and not a budget.PathLength: the radius ball,separation_matrixthresholded atseparation_budget. No second traversal.
Arguments
sep: Separation algorithm, taken fromnte.sepby the public method and resolved.nte: Network estimator.g: Structure to read, fromseparation_graph.
Returns
P::Matrix{Int}: Phylogeny matrix.1for a related pair,0otherwise,0on the diagonal.
Related
PortfolioOptimisers.phylogeny_matrix — Function
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
plrobject.
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
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.1for a related pair,0otherwise,0on the diagonal.
Related
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
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
PortfolioOptimisers.centrality_vector — Function
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
plrobject.
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
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
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.
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.
- A clustering estimator, a precomputed
Clusters, or a precomputedPhylogenyResultas the source. A partition has no edge weights, and does not borrow any. DegreeCentrality.Graphs.jlignores weights.Pagerank.Graphs.jlignores weights.KatzCentrality.Graphs.katz_centralitybinarises throughadjacency_matrix(g, Bool).EigenvectorCentralityon 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
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
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
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
PortfolioOptimisers.average_centrality — Function
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
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
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
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
PortfolioOptimisers.asset_phylogeny — Function
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
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
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
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
prusingpl. - 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
PortfolioOptimisers.AbstractCentralityAlgorithm — Type
abstract type AbstractCentralityAlgorithm <: AbstractPhylogenyAlgorithmAbstract 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
centrality_polaritycentrality_graphTopologyOnlyBetweennessCentralityClosenessCentralityDegreeCentralityEigenvectorCentralityKatzCentralityPagerankRadialityCentralityStressCentrality
References
PortfolioOptimisers.AbstractTreeType — Type
abstract type AbstractTreeType <: AbstractPhylogenyAlgorithmAbstract 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
PortfolioOptimisers.calc_mst — Function
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
PortfolioOptimisers.AbstractNetworkEstimator — Type
abstract type AbstractNetworkEstimator <: AbstractPhylogenyEstimatorAbstract 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.
PortfolioOptimisers.AbstractCentralityEstimator — Type
abstract type AbstractCentralityEstimator <: AbstractEstimatorAbstract 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.
PortfolioOptimisers.graph_weight_matrix — Function
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
DomainErrorif any off-diagonal entry is negative orNaN.
Returns
W::MatNum:Ditself, or a repaired copy of it.
Related
PortfolioOptimisers.calc_weighted_adjacency_graph — Function
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_mstminimises the distance, so the weights are distances. Small means closely related.alg::AbstractNonNegativeSimilarityMatrixAlgorithm:PMFG_T2smaximises 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_matrixmoves every zero distance off the value the representation reserves for absent, and rejects a negative or aNaN.Infis legal — it is the honestLogDistancebetween two uncorrelated assets. - PMFG branch: strictly positive and finite.
PMFG_T2schecks 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 whyassert_pmfg_weightsrefuses one here.
Arguments
alg: Tree or similarity matrix algorithm.D: Distance matrix.S: Similarity matrix.nte: Network estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.calc_weighted_adjacency — Function
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, fromcalc_weighted_adjacency_graph.alg: Tree or similarity matrix algorithm.W: Selecting quantity ofalg's branch: a distance matrix under anAbstractTreeType, a similarity matrix under anAbstractNonNegativeSimilarityMatrixAlgorithm.nte: Network estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.calc_adjacency — Function
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 matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.calc_distance_weighted_graph — Function
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 matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 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
PortfolioOptimisers.separation_graph — Function
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 aGraphs.SimpleGraph. Binary, because a hop count ignores the weights and its consumers do not —_phylogeny_matrixreadsGraphs.adjacency_matrixoff this graph for a power sum, where a weight would makeA^isum 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 letsresolve_separationbe handed this graph.G: Network structure a caller already holds, in either polarity.nte: Network estimator.X: Data matrixobservations × assetsif thedimskeyword does not exist ordims = 1,assets × observationswhendims = 2.dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments.
Returns
g::Graphs.AbstractGraph: The structuresepmeasures over.
Related
PortfolioOptimisers.separation_matrix — Function
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, fromseparation_graph.nte: Network estimator. On the wrapper only, where the structure is derived fromXon 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 assetsiandk,d[i, i]is zero, and an unreachable pair carries the sentinel above.
Related
PortfolioOptimisers.separation_budget — Function
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 ownssep. Inert for the shipped members.d: Separation matrix fromseparation_matrix. Inert forHopCount, whose budget is configured rather than observed; read byPathLength, whose budget is capped by what the graph turned out to be.
Returns
dmax::Number: Separation budget. Stated in the unitssepmeasures in — hops forHopCount, the distance estimator's units forPathLength— so it is only ever compared against entries ofd.
Related
PortfolioOptimisers.separation_quantile — Function
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 tois_reachable.d: Separation matrix fromseparation_matrix.q: Quantile in[0, 1].
Returns
dmax::Number: Theq-quantile of the reachable off-diagonal separations.
Related
PortfolioOptimisers.HopCountQuantile — Type
struct HopCountQuantile{__T_q} <: HopCountAlgorithmPlaces 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, soqis the fraction of them the budget relates.
Constructors
HopCountQuantile(; q::Number = 0.25) -> HopCountQuantileKeywords correspond to the struct's fields.
Validation
0 <= q <= 1.
Examples
julia> HopCountQuantile()HopCountQuantile q ┴ Float64: 0.25Related
PortfolioOptimisers.PathLengthQuantile — Type
struct PathLengthQuantile{__T_q} <: PathLengthAlgorithmPlaces 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, soqis the fraction of them the budget relates.
Constructors
PathLengthQuantile(; q::Number = 0.25) -> PathLengthQuantileKeywords correspond to the struct's fields.
Validation
0 <= q <= 1.
Examples
julia> PathLengthQuantile()PathLengthQuantile q ┴ Float64: 0.25Related
PortfolioOptimisers.resolve_separation — Function
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_budget — phylogeny_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 ownssep, 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, fromseparation_graph. Derived fromXby the wrappers.dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments, forwarded to the rule.
Validation
- A
HopCountAlgorithmorFunctioninHopCount'snmust return anInteger. - A
PathLengthAlgorithmorFunctioninPathLength'sdmaxmust return aNumber.nothingis a stated budget, not a computed one.
Returns
sep::AbstractSeparationAlgorithm: The same member with a resolved budget.sepitself when the budget was already a value.
Related
PortfolioOptimisers.AbstractCentralityPolarity — Type
abstract type AbstractCentralityPolarity <: AbstractAlgorithmAbstract 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
PortfolioOptimisers.DistancePolarity — Type
struct DistancePolarity <: AbstractCentralityPolarityDeclares 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
PortfolioOptimisers.SimilarityPolarity — Type
struct SimilarityPolarity <: AbstractCentralityPolarityDeclares 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
PortfolioOptimisers.TopologyOnly — Type
struct TopologyOnly <: AbstractAlgorithmWithdraws 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
PortfolioOptimisers.centrality_polarity — Function
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
DistancePolarity—BetweennessCentrality,ClosenessCentrality,RadialityCentrality,StressCentrality. All four are defined over shortest paths.SimilarityPolarity—EigenvectorCentrality. It is the leading eigenvector of the adjacency matrix itself, so a larger entry must mean a stronger link.nothing—DegreeCentrality,Pagerank,KatzCentrality.Graphs.jlcannot use weights in any of the three: the first two ignore them, andGraphs.katz_centralitybinarises throughadjacency_matrix(g, Bool)and throws anInexactErrorwhen handed a weighted graph.nothing— any of the first five carryingov = TopologyOnly(). Those five are the only ones that carry the field, because the other three already answernothingand have nothing to withdraw.
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, ornothingfor an algorithm that cannot read weights or has withdrawn its declaration. Each method returns one concrete type, never aUnion.
Related
PortfolioOptimisers.centrality_graph — Function
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
| source | polarity | graph |
|---|---|---|
AbstractNetworkEstimator | DistancePolarity | calc_distance_weighted_graph — distances, on either branch |
NetworkEstimator on the similarity branch | SimilarityPolarity | calc_weighted_adjacency_graph — the similarities that selected the edges |
| any source | nothing | plain Graphs.SimpleGraph of phylogeny_matrix |
a clustering estimator or Clusters | any | plain Graphs.SimpleGraph of phylogeny_matrix |
AbstractNetworkEstimator on a tree branch | SimilarityPolarity | plain 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 ofct, fromcentrality_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: ASimpleWeightedGraphs.SimpleWeightedGraphon a weighted route, aGraphs.SimpleGraphotherwise.
Related
PortfolioOptimisers.assert_no_weight_channel_args — Function
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 theGraphs.jlfunction.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
- Throws a
ConflictingArgumentErrorif any entry ofargsis anS.
Returns
nothing.
Related
PortfolioOptimisers.assert_centrality_args — Function
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'sdistmxis its third positional argument, so a matrix inargsbinds tovsinstead and the call overflows the stack insideGraphs.degree. TheStackOverflowErroris 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 aBoundsErrorrather than aDimensionMismatch.Graphs.stress_centralityhas nodistmxat 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 theGraphs.jlcentrality function.
Validation
- Throws a
ConflictingArgumentErrorif any entry ofargsis anAbstractMatrix.
Returns
nothing.
Related
PortfolioOptimisers.assert_tree_args — Function
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_mstandprim_mstall takedistmxas their second positional argument, so a matrix inargsreplaces 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_mstalso takes aweight_vectorthere, which is the same override in the other shape.minimizeinkwargsinverts the sense of the search. The tree branch is defined by minimising a distance —calc_weighted_adjacency_graphandSimilarityPolarityboth say so — andminimize = falseyields 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 theGraphs.jlspanning-tree function.kwargs: Keyword arguments destined for the same function.
Validation
- Throws a
ConflictingArgumentErrorif any entry ofargsis anAbstractMatrixor anAbstractVector. - Throws a
ConflictingArgumentErrorifkwargscontainsminimize.
Returns
nothing.
Related
PortfolioOptimisers.calc_centrality — Function
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
PortfolioOptimisers.Tree_SimMat — Type
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
PortfolioOptimisers.NwE_ClE — Type
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 result — SemiDefinitePhylogeny 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
PortfolioOptimisers.NwE_ClE_Cl — Type
const NwE_ClE_Cl = Union{<:AbstractNetworkEstimator, <:ClE_Cl}Alias for a network estimator or clustering estimator/result.
Used for dispatch in phylogeny workflows that accept either a network estimator or a clustering estimator/result.
Related
PortfolioOptimisers.NwE_Pl_ClE_Cl — Type
const NwE_Pl_ClE_Cl = Union{<:AbstractNetworkEstimator, <:PhylogenyResult, <:ClE_Cl}Alias for a network estimator, phylogeny result, or clustering estimator/result.
Used internally for dispatch in phylogeny and network estimation workflows that accept any of these forms.
Related
PortfolioOptimisers.HClE_HCl — Type
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
References
- [4]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).
- [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), arXiv:https://academic.oup.com/comnet/article-pdf/5/2/161/13794756/cnw015.pdf.
- [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).