Distance
PortfolioOptimisers.Distance — Type
struct Distance{__T_power, __T_alg} <: AbstractDistanceEstimatorPairs a distance algorithm with an optional integer power, and applies it to a correlation matrix or to the data.
This is the estimator every clustering, network and phylogeny routine reaches for a distance matrix. alg chooses the transform; power raises the quantity that transform is built on to the integer power $p$, which sharpens the contrast between a strong relationship and a weak one.
power = 1 reproduces the base distance exactly, for every algorithm. It is the neutral position of the knob, not a way to select the generalised estimator. Only $p \geq 2$ changes the result. power = nothing and power = 1 are kept apart by dispatch alone, so that the base case never raises a matrix to a power.
Mathematical definition
The four correlation-based algorithms (see RhoDistanceAlgorithm) raise the correlation to $p$ inside their own base formula.
\[\begin{align} _{g}d_{i,\,j}^{\mathrm{S}} &= \sqrt{\mathrm{clamp}\left(s\left(1 - \rho_{i,\,j}^{p}\right),\, 0,\, 1\right)}\\ _{g}d_{i,\,j}^{\mathrm{SA}} &= \sqrt{\mathrm{clamp}\left(1 - \lvert\rho_{i,\,j}\rvert^{p},\, 0,\, 1\right)}\\ _{g}d_{i,\,j}^{\mathrm{L}} &= \max\left(-\log{\lvert\rho_{i,\,j}\rvert^{p}},\, 0\right)\\ _{g}d_{i,\,j}^{\mathrm{C}} &= \sqrt{\mathrm{clamp}\left(1 - \rho_{i,\,j}^{p},\, 0,\, 1\right)}\\ s &= \begin{cases} 1/2 & \text{if } p \mod 2 \neq 0\\ 1 & \text{otherwise} \end{cases}\,, \end{align}\]
VariationInfoDistance has no correlation to raise, so it raises the distance itself.
\[\begin{align} _{g}d_{i,\,j}^{\mathrm{VI}} &= \left(d_{i,\,j}^{\mathrm{VI}}\right)^{p}\,, \end{align}\]
Where:
- $_{g}d_{i,\,j}$: Generalised distance between assets $i$ and $j$, superscripted by the algorithm:
SimpleDistance(S),SimpleAbsoluteDistance(SA),LogDistance(L),CorrelationDistance(C),VariationInfoDistance(VI). - $d_{i,\,j}$: Base distance computed using the specified distance algorithm.
- $\rho_{i,\,j}$: Pairwise correlation coefficient between assets $i$ and $j$.
- $p$: Integer power.
- $s$: Scaling factor of
SimpleDistancealone ($s = 1/2$ if $p \bmod 2 \neq 0$, else $s = 1$).
The two cases of $s$ are not a convention. $s$ is the normalisation that keeps the radicand inside $[0,\,1]$ over the reachable range of $\rho_{i,\,j}^{p}$, so it is $1 / (1 - m)$, where $m$ is the smallest value $\rho_{i,\,j}^{p}$ can take. An odd $p$ keeps the sign, so $m = -1$ and $s = 1/2$. An even $p$ cannot be negative, so $m = 0$ and $s = 1$.
The clamp is inert for the first two algorithms at every $p$: $s(1 - \rho_{i,\,j}^{p})$ and $1 - \lvert\rho_{i,\,j}\rvert^{p}$ never leave $[0,\,1]$. It binds for CorrelationDistance at every odd $p$, where $\rho_{i,\,j}^{p}$ keeps its sign and the radicand runs over $[0,\,2]$; that algorithm's own docstring measures the truncation.
CanonicalDistance is a redirect and owns no formula. It forwards power to the algorithm it selects.
The field is one field, and the reader must not assume one meaning. For a correlation-based algorithm it raises the correlation, inside the transform. For VariationInfoDistance there is no correlation to raise, so it raises the distance the algorithm returns. So $_{g}d^{\mathrm{S}}$ at $p = 2$ is not $\left(d^{\mathrm{S}}\right)^{2}$, while $_{g}d^{\mathrm{VI}}$ at $p = 2$ is $\left(d^{\mathrm{VI}}\right)^{2}$.
The source carries the four base formulas alone. Section 6.2 of the reference below ends at the tail dissimilarity, so the $p$ generalisation and the scaling $s$ are this library's own extension of it.
Fields
power: Optional matrix exponent.nothingand1both give the base distance, so onlypower >= 2changes the result.
alg: Distance algorithm.
Constructors
Distance(; power::Option{<:Integer} = nothing, alg::AbstractDistanceAlgorithm = SimpleDistance()) -> DistanceKeywords correspond to the struct's fields.
CanonicalDistance picks an algorithm from the covariance estimator, and falls back to SimpleDistance when the estimator carries no preference. A bare Distance() also serves the matrix entry point distance(de, rho), which holds no estimator to pick from. The two therefore agree except on the estimators CanonicalDistance treats specially. Library entry points that always hold a covariance estimator default to Distance(; alg = CanonicalDistance()) instead, so that the special cases are honoured.
Validation
- If
poweris notnothing,power >= 1.
Examples
julia> Distance()Distance power ┼ nothing alg ┴ SimpleDistance()Related
distancecor_and_distSimpleDistanceSimpleAbsoluteDistanceLogDistanceCorrelationDistanceCanonicalDistanceVariationInfoDistance
References
- [4] D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025). Section 6.2.
PortfolioOptimisers.distance — Function
distance(de::Distance{<:Any,
<:Union{<:SimpleDistance, <:SimpleAbsoluteDistance, <:LogDistance,
<:CorrelationDistance, <:CanonicalDistance}},
ce::StatsBase.CovarianceEstimator, X::MatNum; dims::Int = 1, kwargs...)Compute the correlation matrix with ce and X, and transform it into a distance matrix with the algorithm de names.
This is the data entry point of the correlation-based family. cor_and_dist is the same computation with the correlation returned alongside the distance, so a caller that needs both pays for the correlation once.
Algorithm
- Compute the correlation matrix from
ceandXwithStatistics.cor, alongdims.kwargsare forwarded to it, and it is what refuses adimsoutside $(1,\, 2)$. - Transform that correlation matrix with
_dist_from_cor, underde.algandde.power.
A CanonicalDistance de takes one step first: it rebuilds de with SimpleDistance, carrying de.power over, because a ce that is a plain StatsBase.CovarianceEstimator carries no preference. The four estimators that do carry one have their own methods; see the redirect table on CanonicalDistance.
Arguments
de: Distance estimator.de::Distance{<:Any, <:SimpleDistance}: Use theSimpleDistancealgorithm.de::Distance{<:Any, <:SimpleAbsoluteDistance}: Use theSimpleAbsoluteDistancealgorithm.de::Distance{<:Any, <:LogDistance}: Use theLogDistancealgorithm.de::Distance{<:Any, <:CorrelationDistance}: Use theCorrelationDistancealgorithm.de::Distance{<:Any, <:CanonicalDistance}: Use theCanonicalDistancealgorithm.
ce: Covariance estimator.X: Data matrix (observations × assets).dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments passed to the correlation computation.
Validation
dims in (1, 2).
Returns
D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
dimsis enforced byStatistics.cor, not by this method. Adimsoutside $(1,\, 2)$ raises aDomainErrorfrom there.
Related
distance(de::Distance{Nothing, <:VariationInfoDistance}, ::Any, X::MatNum;
dims::Int = 1, kwargs...)
distance(de::Distance{<:Integer, <:VariationInfoDistance}, ::Any, X::MatNum;
dims::Int = 1, kwargs...)Compute the variation of information distance matrix from the data matrix alone.
This is the one algorithm of the family that reads X rather than a correlation matrix, so the covariance estimator is a placeholder that the method ignores. It captures a non-linear relationship that no correlation coefficient sees.
The two methods above are the two power cases, and they are the reason the trap exists. For a correlation-based algorithm power raises the correlation inside the transform. There is no correlation here, so power raises the distance the algorithm returns: the result is variation_info(...) .^ de.power. One field, two quantities.
Algorithm
- Orient
Xwithdims_oriented, which transposes it whendimsis2and refuses any other value. - Read
de.alg.binsandde.alg.normaliseoff the algorithm, and pass both tovariation_info, which builds the joint histograms and forms the distance.VariationInfoDistancestates those steps. - When
de.poweris anInteger, raise the distance matrix of step 2 to it entry by entry. Whende.powerisnothing, return that matrix as it stands.
Arguments
de: Distance estimator carrying theVariationInfoDistancealgorithm.::Any: Covariance estimator placeholder for API compatibility. It is ignored.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. They are ignored.
Validation
dims in (1, 2).
Returns
D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
dimsis enforced bydims_oriented. Adimsoutside $(1,\, 2)$ raises aDomainErrorfrom there.binsandnormalisecome from the algorithm, never from a keyword.CanonicalDistanceis what copies them off aMutualInfoCovariance.
Related
distance(::Distance{<:Any,
<:Union{<:SimpleDistance, <:SimpleAbsoluteDistance, <:LogDistance,
<:CorrelationDistance, <:CanonicalDistance}},
rho::MatNum, args...; kwargs...)Compute the distance matrix from a correlation matrix, or from a covariance matrix.
This is the matrix entry point of the correlation-based family, for a caller that already holds the matrix and needs no covariance estimator. The value of the diagonal decides which of the two it was given; see _as_correlation.
Algorithm
- Coerce
rhoto a correlation matrix with_as_correlation, which also checks that it is square. - Transform that correlation matrix with
_dist_from_cor, underde.algandde.power.
A CanonicalDistance de takes one step first: it rebuilds de with SimpleDistance, carrying de.power over. There is no covariance estimator here to select from, so the redirect table cannot apply and the fallback of the table is taken.
Arguments
de: Distance estimator.de::Distance{<:Any, <:SimpleDistance}: Use theSimpleDistancealgorithm.de::Distance{<:Any, <:SimpleAbsoluteDistance}: Use theSimpleAbsoluteDistancealgorithm.de::Distance{<:Any, <:LogDistance}: Use theLogDistancealgorithm.de::Distance{<:Any, <:CorrelationDistance}: Use theCorrelationDistancealgorithm.de::Distance{<:Any, <:CanonicalDistance}: Use theCanonicalDistancealgorithm.
rho: Correlation or covariance matrix.args...: Additional arguments (ignored).kwargs...: Additional keyword arguments. They are ignored.
Validation
rhois square.
Returns
D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
- The distance is the one the algorithm defines, and it is not Euclidean under any of the four.
SimpleDistanceandSimpleAbsoluteDistancereturn an angular distance, andLogDistancean unbounded dissimilarity. - A covariance matrix is converted with
StatsBase.cov2cor, and the conversion allocates rather than writing into the argument. argsandkwargsexist so that this method and thece-and-Xmethod above take the same call. Neither is read.
Related
distance(de::Distance{<:Any, <:CanonicalDistance},
ce::Union{<:MutualInfoCovariance,
<:AllInternalMutualInfoCov,
<:LTDCov_AllInternalLTDCov,
<:DistCov_AllInternalDistCov},
X::MatNum; dims::Int = 1, kwargs...)Rebuild de with the distance algorithm that the covariance estimator's own range calls for, and call distance again.
The redirect owns no formula. It exists so that a codependence measure reaches the transform its range needs: a signed correlation must be halved, a mutual information has no correlation to transform at all, and a tail dependence coefficient wants an unbounded distance. de.power is carried over unchanged onto the algorithm that is selected.
| Covariance estimator | Algorithm selected | Read from ce |
|---|---|---|
MutualInfoCovariance | VariationInfoDistance | ce.bins, ce.normalise |
PortfolioOptimisersCovariance wrapping it | VariationInfoDistance | ce.ce.bins, ce.ce.normalise |
LowerTailDependenceCovariance, wrapped or not | LogDistance | nothing |
DistanceCovariance, wrapped or not | CorrelationDistance | nothing |
any other StatsBase.CovarianceEstimator | SimpleDistance | nothing |
Algorithm
- Select the row of the table above by the type of
ce. Dispatch does the selection, so a wrapper reaches the same row as the estimator it wraps. - Build a fresh
Distancecarryingde.powerand the algorithm of that row. - On the two mutual-information rows, copy
binsandnormaliseoffceonto the newVariationInfoDistance, one field level deeper for the wrapper. Without the copy the algorithm would take its own defaults, and the distance would be a different number. - Call
distancewith the rebuilt estimator, the samece, and the sameX,dimsandkwargs.
Arguments
de: Distance estimator carrying theCanonicalDistancealgorithm.ce: Covariance 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 passed to the selected algorithm.
Returns
D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
- Step 3 is invisible to a reader of the signature, and it is load-bearing. A
MutualInfoCovariancebuilt with a non-defaultbinsgives a different distance matrix from one built with the default, and the redirect is what carries that setting across. - The last row is the fallback, and it is also what a bare
DistancewithSimpleDistancegives. The two agree on every estimator outside the table. cor_and_distcarries the same table, over the same five rows.
Related
distance(de::DistanceDistance, ce::StatsBase.CovarianceEstimator, X::MatNum;
dims::Int = 1, kwargs...)Compute the distance-of-distances matrix from a covariance estimator and data matrix.
This method first computes a base distance matrix using Distance with the specified power and algorithm, then applies the provided metric to compute a second-level distance matrix.
Arguments
de: Distance-of-distances estimator.ce: Covariance estimator.X: Data matrix (observations × assets).dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments passed to the base distance computation.
Returns
D::Matrix{<:Number}: Matrix of pairwise distances of distances.
Related
distance(de::DistanceDistance, rho::MatNum, args...; kwargs...)Compute the distance-of-distances matrix from a correlation or covariance matrix.
This method first computes a base distance matrix using Distance with the specified power and algorithm, then applies the provided metric to compute a second-level distance matrix.
Arguments
de: Distance-of-distances estimator.rho: Correlation or covariance matrix.args...: Additional arguments (ignored).kwargs...: Additional keyword arguments passed to the base distance computation.
Returns
D::Matrix{<:Number}: Matrix of pairwise distances of distances.
Related
distance(de::FeatureDistance, Z::MatNum; dims::Int = 1, kwargs...)
distance(de::FeatureDistance, Z::Arr3Num; dims::Int = 1, kwargs...)Compute the distance matrix from a feature matrix.
Arguments
de: Feature distance estimator.Z: Feature matrixassets × featuresifdims = 1,features × assetswhendims = 2. May also be a 3-D array of time-varying features, in which case the observation axis always leads:observations × assets × featuresifdims = 1,observations × features × assetswhendims = 2.dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments (ignored).
Validation
dims in (1, 2).!isempty(Z).all(isfinite, Z).Zlies inde.metric's domain (seeassert_metric_domain).
Returns
D::Matrix{<:Number}: Matrix of pairwise distances,assets × assets.
Details
- The 2-D method never consults
de.alg: a static feature matrix has no observation axis to collapse, so the collapse algorithm is inert rather than an error. The 3-D method dispatches on it. - Assets whose feature vector is entirely zero are given the convention documented in
patch_zero_feature_vectors!.
Examples
julia> Z = [1.0 0.0; 0.0 1.0; 1.0 1.0];julia> distance(FeatureDistance(), Z)3×3 Matrix{Float64}: 0.0 0.5 0.25 0.5 0.0 0.25 0.25 0.25 0.0Related
distance(de::FeatureDistance, ::Any, ::Any; Z::Option{<:ArrNum} = nothing,
z_src::Symbol = :none, kwargs...)
cor_and_dist(de::FeatureDistance, ::Any, ::Any; Z::Option{<:ArrNum} = nothing,
z_src::Symbol = :none, kwargs...)Three-argument entry points, for the clustering and network estimators.
Every consumer in the clustering and network stack calls cor_and_dist(de, ce, X; …) or distance(de, pl, X; …), passing a covariance estimator (or, in logo!'s case, a similarity matrix) and a returns matrix. FeatureDistance uses neither: it measures a feature matrix, which travels beside them on the Z keyword argument, resolved from a carrier by feature_matrix_picker. Both positionals are therefore ignored, and typed ::Any rather than bounded — logo! puts a similarity matrix where the others put a covariance estimator.
Details
dimsis ignored and the kernel is called withdims = 1. The ambientdimsdescribes the returns matrixX, and a carriedZis canonically assets-major regardless of it.dimsstays meaningful only at the raw-matrix entry pointdistance(de, Z; dims).- A missing
ZthrowsIsNothingErrornamingz_src(seeassert_feature_matrix_supplied).
Related
PortfolioOptimisers.cor_and_dist — Function
cor_and_dist(de::Distance{<:Any,
<:Union{<:SimpleDistance, <:SimpleAbsoluteDistance,
<:LogDistance, <:CorrelationDistance,
<:VariationInfoDistance, <:CanonicalDistance}},
ce::StatsBase.CovarianceEstimator, X::MatNum; dims::Int = 1, kwargs...)Compute the correlation matrix and the distance matrix together, from one pass over the data.
It returns the same D that distance returns for the same arguments; that agreement is the claim of having two entry points. Take this one when both matrices are wanted, because the correlation-based family then computes the correlation once instead of twice.
Algorithm
Which of the three routes runs is decided by de.alg.
- A correlation-based algorithm computes the correlation matrix with
Statistics.cor, then transforms that same matrix with_dist_from_cor. This is the route that saves the second correlation. VariationInfoDistancechecksdimswithassert_dims, computes the correlation matrix for the caller, and callsdistancefor the distance. The distance readsX, not the correlation, so nothing is shared between the two.CanonicalDistancerebuildsdewith the algorithm its redirect table names force, then re-enters at route 1 or route 2. Seedistancefor the table and for the fields the rebuild copies.
Arguments
de: Distance estimator.ce: Covariance 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 passed to the correlation computation.
Validation
dims in (1, 2).
Returns
rho::MatNum: Correlation matrixassets x assets.D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
- Route 2 checks
dimsitself withassert_dims. Route 1 leaves the check toStatistics.cor, which raises the sameDomainError. - The correlation returned is the one
cecomputes, untransformed. It is not the magnitude thatSimpleAbsoluteDistanceandLogDistancetake, nor the power of it that ade.powerraises.
Related
cor_and_dist(de::DistanceDistance, ce::StatsBase.CovarianceEstimator, X::MatNum;
dims::Int = 1, kwargs...)Compute both the correlation matrix and the distance-of-distances matrix from a covariance estimator and data matrix.
This method first computes the correlation and base distance matrices using Distance, then applies the provided metric to the base distance matrix.
Arguments
de: Distance-of-distances estimator.ce: Covariance estimator.X: Data matrix (observations × assets).dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments passed to the base distance computation.
Returns
(rho::Matrix{<:Number}, D::Matrix{<:Number}): Tuple of correlation matrix and distance-of-distances matrix.
Related
cor_and_dist(de::FeatureDistance, Z::MatNum; dims::Int = 1, kwargs...)
cor_and_dist(de::FeatureDistance, Z::Arr3Num; dims::Int = 1, kwargs...)Compute the similarity and distance matrices from a feature matrix.
The similarity shares the distance's provenance: it is distance_to_similarity(de.sim; D = D), derived from the distance matrix this call just produced, so S and D are two views of one measurement rather than two independent estimates. Deriving it from the aggregated distance is also what keeps the zero-feature-vector convention consistent under AggregateDistances, since $\mathrm{mean}(\cos(\pi D_{t})) \neq \cos(\pi\,\mathrm{mean}(D_{t}))$.
Arguments
de: Feature distance estimator.Z: Feature matrixassets × featuresifdims = 1,features × assetswhendims = 2. May also be a 3-D array of time-varying features, in which case the observation axis always leads:observations × assets × featuresifdims = 1,observations × features × assetswhendims = 2.dims: Dimension along which to perform the computation.kwargs...: Additional keyword arguments (ignored).
Returns
S::Matrix{<:Number}: Similarity matrix,assets × assets.D::Matrix{<:Number}: Distance matrix,assets × assets.
Examples
julia> Z = [1.0 0.0; 0.0 1.0; 1.0 1.0];julia> S, D = cor_and_dist(FeatureDistance(), Z);julia> S3×3 Matrix{Float64}: 1.0 6.12323e-17 0.707107 6.12323e-17 1.0 0.707107 0.707107 0.707107 1.0Related
PortfolioOptimisers.LTDCov_AllInternalLTDCov — Type
const LTDCov_AllInternalLTDCov = Union{<:LowerTailDependenceCovariance,
<:PortfolioOptimisersCovariance{<:LowerTailDependenceCovariance}}Alias for all internal lower tail dependence covariance estimator types.
Matches LowerTailDependenceCovariance or any PortfolioOptimisersCovariance wrapping it. Used internally for dispatch in distance computation.
Related
PortfolioOptimisers.AllInternalMutualInfoCov — Type
const AllInternalMutualInfoCov = Union{<:PortfolioOptimisersCovariance{<:MutualInfoCovariance}}Alias for all internal mutual information covariance wrapper types.
Matches any PortfolioOptimisersCovariance wrapping a MutualInfoCovariance. Used internally for dispatch in canonical distance computation.
Related
PortfolioOptimisers.DistCov_AllInternalDistCov — Type
const DistCov_AllInternalDistCov = Union{<:DistanceCovariance,
<:PortfolioOptimisersCovariance{<:DistanceCovariance}}Alias for all internal distance covariance estimator types.
Matches DistanceCovariance or any PortfolioOptimisersCovariance wrapping it. Used internally for dispatch in canonical distance computation.
Related
PortfolioOptimisers._as_correlation — Function
_as_correlation(rho::MatNum, sym::Symbol = :rho) -> MatNumCoerce a square matrix to a correlation matrix, converting it from a covariance matrix when its diagonal says it is one.
The value of the diagonal decides, never the type. A matrix whose diagonal is all ones is already a correlation matrix and is returned as the same object; any other diagonal is read as the variances of a covariance matrix. This is the same test the matrix processing pipeline applies, so the two layers agree on what a correlation matrix is. The square-matrix check runs here, once, for every correlation-based algorithm's matrix entry point.
Algorithm
- Check that
rhois square, reporting the failure under the namesym. - Read the diagonal of
rhointos.LinearAlgebra.diagallocates, sorhois never written to. - When every entry of
sis one, returnrhoitself. Steps 4 and 5 do not run. - Otherwise replace
swith its square roots, giving the standard deviations. - Divide
rhoby the outer product ofswithStatsBase.cov2cor, giving a new correlation matrix.
Arguments
rho: Correlation matrixassets × assets, or the covariance matrix to convert.sym: Name to report the square-matrix failure under.
Validation
rhois square.
Returns
rho::MatNum: Correlation matrixassets x assets.
Details
- The argument is never mutated on either route. Step 2 copies the diagonal, and step 5 builds a new matrix.
- The conversion round-trips: the correlation of a covariance matrix built from a correlation matrix and a vector of standard deviations is that correlation matrix again.
Related
PortfolioOptimisers._absguard — Function
_absguard(rho::MatNum) -> MatNumSupply the magnitude of rho to the two algorithms that are defined on it, without allocating when the magnitude is already rho.
This is an allocation guard, not a branch in the mathematics. abs.(rho) equals rho entry for entry whenever no entry of rho is negative, so both arms return the same numbers for every input, -0.0 included; the guard only decides whether a second matrix is built. Shared by SimpleAbsoluteDistance and LogDistance.
Algorithm
- Test every entry of
rhoagainst zero. - When no entry is negative, return
rhoitself, the same object the caller passed. - Otherwise return
abs.(rho), a new matrix.
Arguments
rho: Correlation matrixassets × assets.
Returns
rho::MatNum: The magnitude of the argument. It is the argument itself when the argument holds no negative entry.
Details
- The test reads the whole matrix, so one negative entry allocates the copy for all of them. That is the intended reading of the two algorithms, which take the magnitude of every entry.
NaNcompares false against zero, so a matrix holding one takes the allocating arm.abs(NaN)isNaN, so that entry isNaNon either arm.
Related
PortfolioOptimisers._dist_from_cor — Function
_dist_from_cor(alg::RhoDistanceAlgorithm, power::Option{<:Integer}, rho::MatNum) -> MatNumTurn a correlation matrix into a distance matrix, for one of the four correlation-based algorithms.
This is the shared kernel behind the distance and cor_and_dist entry points: they differ only in how they obtain rho, never in the transform they apply to it. Eight methods cover the four algorithms of RhoDistanceAlgorithm at each of the two power cases.
Mathematical definition
Distance states the eight closed forms and the scaling $s$. Each algorithm's own docstring states the base case and the range it is defined on.
Algorithm
power selects the method, so the base case never raises rho to a power.
SimpleAbsoluteDistanceandLogDistancereplacerhowith its magnitude through_absguard.SimpleDistanceandCorrelationDistancedo not, and read the signed correlation.- When
poweris anInteger, raiserhoto it entry by entry. Whenpowerisnothing, leaverhoas it is. SimpleDistancescales $1 - \rho$ by1//2for an oddpowerand by1//1for an even one, and by1//2in the base case. The other three apply no scaling.- The three square-root algorithms clamp the radicand into $[0,\,1]$ with
clamp!and take its square root.LogDistanceinstead takes $-\log$ and floors the result at zero withmax.
Arguments
alg: Distance algorithm.power: Optional matrix exponent.nothingand1both give the base distance, so onlypower >= 2changes the result.rho: Correlation matrixassets × assets.
Returns
D::MatNum: Distance matrixassets x assets, in the units the distance algorithm defines.
Details
- Every method allocates its own result, and
clamp!writes only into that allocation.rhois never mutated. - The scale of step 3 is a
Rational, so the element type ofrhois carried through: aFloat32correlation matrix gives aFloat32distance matrix, as it does under the other three algorithms. CanonicalDistanceandVariationInfoDistancenever reach this kernel. The first is a redirect that resolves to one of the four before the call, and the second reads the data matrix and holds no correlation.
Related
PortfolioOptimisers.RhoDistanceAlgorithm — Type
const RhoDistanceAlgorithm = Union{SimpleDistance, SimpleAbsoluteDistance,
LogDistance, CorrelationDistance}Union of the correlation-based distance algorithms: those whose distance matrix is a pure function of a correlation matrix via _dist_from_cor. Excludes VariationInfoDistance (information-theoretic, computed from the data matrix) and CanonicalDistance (a redirect that selects one of the others from the covariance estimator).
Related
PortfolioOptimisers.assert_dims — Function
assert_dims(dims::Integer)
assert_dims(
dims::Integer,
sym::Union{AbstractString, Symbol}
)
Assert that dims selects a valid matrix dimension (dims in (1, 2)).
Arguments
dims: Dimension selector to check.sym: Symbolic name used in the error message.
Returns
nothing.
Details
- Throws
DomainErrorifdims ∉ (1, 2).
Related
PortfolioOptimisers.dims_oriented — Function
dims_oriented(
dims::Integer,
A::Union{Nothing, AbstractMatrix}
) -> Any
Validate dims and return the matrices with the observations along the rows.
Arguments
dims: Dimension along which the observations lie.A,B,Cs...: Matrices to orient. Anothingpasses through unchanged, so an optional matrix needs no branch of its own.
Validation
dims in (1, 2), byassert_dims.
Returns
A: The oriented matrix, when one matrix is given.(A, B, Cs...): A tuple of the oriented matrices, when more than one is given.
Details
dims == 1returns the input untouched.dims == 2returns itstranspose.- The guard and the orientation are one call, so a caller cannot orient a matrix without validating
dims. This is the single decision point: a leaf that spelled the guard and thetransposeby hand could omit the guard and answer adimsof3with the raw input.
Related
References
- [4]
- D. Cajas. Advanced Portfolio Optimization: A Cutting-edge Quantitative Approach (Springer Nature Switzerland, 2025).