Separation: private API

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

Build the structure a separation measures over.

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

What each member measures over

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

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

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

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

Algorithm

  1. Build the structure X implies, through the branch that sep selects.

  2. Rebuild a HopCount's structure as a Graphs.SimpleGraph, which discards the weights. The graph-taking method is handed G and starts here.

Arguments

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

Returns

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

Related

source
PortfolioOptimisers.separation_quantileFunction
separation_quantile(
    sep::AbstractSeparationAlgorithm,
    d::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<: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).

Algorithm

  1. Collect the off-diagonal entries of d that is_reachable admits, giving v, the population of the quantile.
  2. Throw an ArgumentError when v is empty.
  3. Take the q-quantile of v with Statistics.quantile.

Arguments

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

Validation

  • Throws an ArgumentError if no off-diagonal entry of d is reachable, because a budget cannot be placed at a quantile of an empty population.

Returns

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

Related

source