Norm-Ball Uncertainty Sets: private API
PortfolioOptimisers.assert_norm_ball_axis — Function
assert_norm_ball_axis(::MuUncertaintySetClass, L::MatNum)
assert_norm_ball_axis(::SigmaUncertaintySetClass, L::MatNum)Check that the rows of L fit the axis the tag names.
The method is a refusal rather than a procedure, so it carries no # Algorithm section. The mean tag accepts any row count, and the covariance tag needs a perfect square, because a set on that axis bounds a vectorised $N \times N$ covariance and port_opt_view recovers $N$ from the row count.
Arguments
class: Axis tag.L: Geometry map.
Validation
- On the covariance tag,
isqrt(size(L, 1))^2 == size(L, 1), else aDimensionMismatch.
Returns
nothing.
Related
PortfolioOptimisers.assert_norm_ball_val — Function
assert_norm_ball_val(::MuUncertaintySetClass, val::ArrNum)
assert_norm_ball_val(::SigmaUncertaintySetClass, val::ArrNum)Check the shape of the carried centre against the axis the tag names.
The method is a refusal rather than a procedure, so it carries no # Algorithm section. The constructor has already matched length(val) to the rows of L, so the mean tag has nothing left to check, and the covariance tag checks that the centre is a square matrix.
Arguments
class: Axis tag.val: Carried centre.
Validation
- On the covariance tag,
valis a matrix andsize(val, 1) == size(val, 2), else aDimensionMismatch.
Returns
nothing.
Related
PortfolioOptimisers.dual_norm_order — Function
dual_norm_order(p::Number) -> Any
Return the dual norm order q of the norm order p, with 1 / p + 1 / q == 1.
Algorithm
- When
pis infinite, returnone(p), because the dual of the infinity norm is the one norm. - Otherwise return
p / (p - 1). Atp = 1the division is by zero and returnsInf, the dual of the one norm, and atp = 2it returns2, which is its own dual.
Arguments
p::Number: Norm order,p >= 1.
Returns
q::Number: Dual norm order.
Related
PortfolioOptimisers.norm_ball_factor — Function
norm_ball_factor(
diagonal::Bool,
cov::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Any
Factorise an asymptotic covariance into the geometry map of a NormBallUncertaintySet.
The map is square and of full column rank, because cov reaches this method already repaired to positive definiteness, so a caller reads its degrees of freedom off size(L, 2) and needs no rank measurement. Its sibling norm_ball_deviation_factor builds the map from a sample instead, and factorises nothing.
Algorithm
- When
diagonalistrue, returnLinearAlgebra.Diagonal(sqrt.(LinearAlgebra.diag(cov))), the square root of the diagonal shape. The result is stored as aDiagonal, not as a dense matrix. - Otherwise return
LinearAlgebra.cholesky(cov).L, the lower Cholesky factor, which satisfies $\mathbf{L}\mathbf{L}^{\intercal} = \mathbf{S}$ and is the map the converter constructor ofNormBallUncertaintySetbuilds.
Arguments
diagonal: Whether to discard the off-diagonal entries ofcovbefore the factorisation.cov: Asymptotic covariance of the statistic, positive definite.
Returns
L::MatNum: Geometry map, square and of full column rank.
Related
PortfolioOptimisers.norm_ball_deviation_factor — Function
norm_ball_deviation_factor(
diagonal::Bool,
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}
) -> Any
Build the geometry map of a NormBallUncertaintySet from a sample of estimation errors, without forming their covariance.
The map is the sample itself, scaled. It satisfies $\mathbf{L}\mathbf{L}^{\intercal} = \operatorname{Cov}(\mathbf{X})$ exactly, so the set is the one the sample covariance names, and no square matrix of side $N^{2}$ is built and no positive definite repair is applied. Its rank is at most $M - 1$, because the sample is centred, so a caller measures the degrees of freedom with LinearAlgebra.rank rather than reading size(L, 2). Its sibling norm_ball_factor factorises an already-built covariance instead.
Mathematical definition
\[\mathbf{L} = \dfrac{\left(\mathbf{X} - \boldsymbol{1}\bar{\mathbf{x}}^{\intercal}\right)^{\intercal}}{\sqrt{M - 1}}\,, \qquad \mathbf{L}\mathbf{L}^{\intercal} = \operatorname{Cov}(\mathbf{X})\,.\]
Where:
- $\mathbf{L}$: Geometry map, $m \times M$.
- $\mathbf{X}$: Sample of estimation errors, $M \times m$, one row per simulation.
- $\bar{\mathbf{x}}$: Column means of $\mathbf{X}$.
- $M$: Number of simulations.
- $m$: Number of entries of the statistic, $N$ on the mean axis and $N^{2}$ on the covariance axis.
Algorithm
- When
diagonalistrue, returnLinearAlgebra.Diagonal(sqrt.(vec(Statistics.var(X; dims = 1)))), the square root of the entrywise variances. It is $m \times m$ and of full rank. - Otherwise subtract the column means from
X, transpose the result, and divide bysqrt(size(X, 1) - 1). A sample of one row divides by zero and theNormBallUncertaintySetconstructor then refuses the non-finite map.
Arguments
diagonal: Whether to keep only the entrywise variances.X: Sample of estimation errors, one row per simulation.
Returns
L::MatNum: Geometry map, $m \times M$ underdiagonal = falseand $m \times m$ underdiagonal = true.
Related
PortfolioOptimisers.k_norm_ball — Function
k_norm_ball(km::NormalKUncertaintyAlgorithm, q::Number, X::MatNum, L::MatNum, ::Integer)
k_norm_ball(::GeneralKUncertaintyAlgorithm, q::Number, args...)
k_norm_ball(::ChiSqKUncertaintyAlgorithm, q::Number, ::Any, ::MatNum, df::Integer)
k_norm_ball(type::Number, args...)Radius $\kappa$ of a NormBallUncertaintySet, read against a geometry map rather than against a shape matrix.
It is the norm-ball twin of k_ucs, and the two differ on the two algorithms that read the geometry. NormalKUncertaintyAlgorithm solves in the factor rather than inverting a shape, so it serves a rank-deficient map, where a shape matrix would have to be repaired first. ChiSqKUncertaintyAlgorithm takes its degrees of freedom from df, the dimension of the ball, and not from the side of a shape matrix, because a flat set is a confidence region of its own subspace and not of the ambient space. GeneralKUncertaintyAlgorithm and a plain number read neither, and absorb the trailing arguments.
Mathematical definition
\[\begin{align} \kappa_{\mathrm{normal}} &= \sqrt{Q_{1-q}\left(\left\lVert \mathbf{L}^{+}\mathbf{x}_{i} \right\rVert_{2}^{2}\right)}\,, \\ \kappa_{\chi^{2}} &= \sqrt{\chi^{2,-1}_{\mathrm{df}}(1 - q)}\,, \\ \kappa_{\mathrm{general}} &= \sqrt{\dfrac{1 - q}{q}}\,. \end{align}\]
Where:
- $\kappa$: Radius of the ball.
- $\mathbf{L}^{+}$: Moore-Penrose pseudo-inverse of the geometry map, applied by a least-squares solve.
- $\mathbf{x}_{i}$: One sampled estimation error, the $i$-th row of $\mathbf{X}$.
- $Q_{1-q}$: Empirical quantile at $1 - q$.
- $\mathrm{df}$: Degrees of freedom, the dimension of the ball.
- $q$: Significance level.
$\left\lVert \mathbf{L}^{+}\mathbf{x} \right\rVert_{2}^{2} = \mathbf{x}^{\intercal}\left(\mathbf{L}\mathbf{L}^{\intercal}\right)^{+}\mathbf{x}$, so on a full-rank square map the first line is the Mahalanobis distance k_ucs measures against $\mathbf{L}\mathbf{L}^{\intercal}$, and the two radii agree.
Algorithm
- On
NormalKUncertaintyAlgorithm, solveL \ transpose(X), giving one column of ball coordinates per sampled error. Julia returns the minimum-norm solution, which is the pseudo-inverse applied to each error. - Take the squared column norms, giving one squared distance per sample, and return the square root of their
1 - qquantile. - On
ChiSqKUncertaintyAlgorithm, return the square root of the1 - qchi-squared quantile atdfdegrees of freedom. - On
GeneralKUncertaintyAlgorithm, returnsqrt((1 - q) / q), Cantelli's bound, which reads no geometry. - On a
Number, return it unchanged.
Arguments
km,type: Radius algorithm, or the radius itself as aNumber.q: Significance level.X: Sample of estimation errors, one row per simulation.L: Geometry map.df: Degrees of freedom, the dimension of the ball. Its caller readssize(L, 2)from a full-rank map andLinearAlgebra.rank(L)from a deviation map.
Returns
kappa::Number: Radius of the ball.
Related
References
- [89] D. Goldfarb and G. Iyengar. Robust Portfolio Selection Problems. Mathematics of Operations Research 28, 1–38 (2003). Section 5.
k_norm_ball(::NormalKUncertaintyAlgorithm, ::Number, ::Nothing, ::MatNum, ::Integer)Always throw. The Orthogonal Subspace fit simulates no estimation errors, so the sampled radius has nothing to read.
The method is a refusal rather than a procedure, so it carries no # Algorithm section. NormalKUncertaintyAlgorithm reads a sample of Mahalanobis distances and takes their quantile, and OrthogonalUncertaintySet draws none: it derives its geometry from the fitted loadings alone. The refusal shadows the MethodError the missing sample would otherwise raise.
Arguments
- The radius algorithm, the significance level, the absent sample, the scaling and the degrees of freedom, all ignored.
Validation
- The method always throws an
ArgumentError. The message names the two algorithms that read no sample,ChiSqKUncertaintyAlgorithmandGeneralKUncertaintyAlgorithm, and the plain number that states the radius outright.
Returns
- Never returns.
Related
PortfolioOptimisers.norm_ball_set — Function
norm_ball_set(
alg::NormBallUncertaintySetAlgorithm,
q::Number,
samples,
cov::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
class::AbstractUncertaintySetClass
) -> NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass}
norm_ball_set(
alg::NormBallUncertaintySetAlgorithm,
q::Number,
samples,
cov::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
class::AbstractUncertaintySetClass,
val::Union{Nothing, AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}}
) -> Union{NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass}, NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
Assemble a NormBallUncertaintySet from an already-computed asymptotic covariance cov.
It is the norm-ball twin of ellipsoidal_set, and it takes the same two steps in the same order: the diagonal is taken before the radius is fitted, so an empirical radius is measured against whichever geometry the set carries. The map is square and of full column rank, so the degrees of freedom are size(L, 2). Its sibling norm_ball_deviation_set reads a sample instead of a covariance, and is the route that spares the $N^{2} \times N^{2}$ matrix.
Algorithm
- Build the map with
norm_ball_factorunderalg.diagonal, givingL. - Fit the radius with
k_norm_ballonalg.method, passingsize(L, 2)as the degrees of freedom. - Build a
NormBallUncertaintySetfromalg.p,L, the radius,classandval.
Arguments
alg: Norm-ball uncertainty set algorithm, which carries the radius algorithm, the diagonal switch and the norm order.q: Significance level.samples: Sampled estimation errors, or whatever containeralg.methodreads. An algorithm that runs no simulation absorbs it.cov: Asymptotic covariance of the statistic, positive definite.class: Axis tag, which fixes the row count of the map and the index a view applies.val: Quantity the set is a neighbourhood of, the fitted characteristic vector on the mean axis and the fitted covariance on the covariance axis.
Returns
ucs::NormBallUncertaintySet: The assembled set.
Related
PortfolioOptimisers.norm_ball_deviation_set — Function
norm_ball_deviation_set(
alg::NormBallUncertaintySetAlgorithm,
q::Number,
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
class::AbstractUncertaintySetClass
) -> NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass}
norm_ball_deviation_set(
alg::NormBallUncertaintySetAlgorithm,
q::Number,
X::AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}},
class::AbstractUncertaintySetClass,
val::Union{Nothing, AbstractArray{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}}
) -> Union{NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass}, NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", C, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, C<:AbstractUncertaintySetClass, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
Assemble a NormBallUncertaintySet from a sample of estimation errors, without forming their covariance.
This is the one route that never builds a square matrix of the statistic's own size. On the covariance axis the sample is $M \times N^{2}$, so the ellipsoid's shape is $N^{2} \times N^{2}$, and it is rank deficient at every sample size: a vectorised symmetric matrix spans only $N(N+1)/2$ coordinates, so the sample covariance has rank at most $\min(M - 1, N(N+1)/2)$ and the default matrix processing repairs it into a matrix the sample never named. The map this method builds carries the same second moment exactly, at that rank, and no repair is applied. Its sibling norm_ball_set factorises an already-built covariance instead.
Algorithm
- Build the map with
norm_ball_deviation_factorunderalg.diagonal, givingL. - Fit the radius with
k_norm_ballonalg.method, passingLinearAlgebra.rank(L)as the degrees of freedom, because the map is rank deficient whenever the sample is. - Build a
NormBallUncertaintySetfromalg.p,L, the radius,classandval.
Arguments
alg: Norm-ball uncertainty set algorithm, which carries the radius algorithm, the diagonal switch and the norm order.q: Significance level.X: Sample of estimation errors, one row per simulation. It is both the geometry and the sample an empirical radius reads.class: Axis tag, which fixes the row count of the map and the index a view applies.val: Quantity the set is a neighbourhood of, the fitted characteristic vector on the mean axis and the fitted covariance on the covariance axis.
Returns
ucs::NormBallUncertaintySet: The assembled set.
Related
PortfolioOptimisers.expand_investable_ucs — Method
expand_investable_ucs(
set::NormBallUncertaintySet{<:Any, <:AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, <:Any, <:MuUncertaintySetClass},
imsk::BitVector,
pr::AbstractPriorResult
) -> Union{NormBallUncertaintySet{var"#s185", <:AbstractMatrix{var"#s137"}, <:Number, MuUncertaintySetClass, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", MuUncertaintySetClass, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
Write a mean NormBallUncertaintySet fitted on the Investable Mask back onto the full asset universe.
The expansion is the inverse of port_opt_view: the view drops rows of the map and touches nothing else, so a zero row outside the mask is dropped without a trace and the view recovers the fitted set. A zero row states that the set moves nothing on an asset the prior could not estimate, and the centre is the full pr.mu, NaN frame and all, because the set is a neighbourhood of the prior it was calibrated on and that prior lives on the full universe.
Algorithm
- Allocate a zero frame of
length(imsk)rows andsize(set.L, 2)columns, and writeset.Lat the rows the mask keeps, givingL. - Build a
NormBallUncertaintySetfrom it, carryingkappa,pandclassthrough unchanged andpr.muasval.
Arguments
set: Mean norm-ball uncertainty set fitted on the reduced prior.imsk: The Investable Mask of the full prior.pr: Prior result.
Returns
set::NormBallUncertaintySet: The set on the full asset universe.
Related
PortfolioOptimisers.expand_investable_ucs — Method
expand_investable_ucs(
set::NormBallUncertaintySet{<:Any, <:AbstractMatrix{<:Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}}, <:Any, <:SigmaUncertaintySetClass},
imsk::BitVector,
pr::AbstractPriorResult
) -> Union{NormBallUncertaintySet{var"#s185", <:AbstractMatrix{var"#s137"}, <:Number, SigmaUncertaintySetClass, Nothing} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar})}, NormBallUncertaintySet{var"#s185", var"#s1851", var"#s1852", SigmaUncertaintySetClass, <:AbstractArray{var"#s137", N}} where {var"#s185"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), var"#s1851"<:AbstractMatrix{var"#s137"}, var"#s1852"<:Number, var"#s137"<:(Union{var"#s136", var"#s53"} where {var"#s136"<:Number, var"#s53"<:AbstractJuMPScalar}), N}}
Write a covariance NormBallUncertaintySet fitted on the Investable Mask back onto the full asset universe.
The expansion is the inverse of port_opt_view: the view slices the map at the fourth-moment index of the selected assets and the centre at the plain one, so a zero row written outside the mask is dropped without a trace and the view recovers the fitted set. A zero row states that the set moves nothing on a pair the prior could not estimate, and the map must be finite, so the frame is zero and not NaN. The centre is the full pr.sigma.
Algorithm
- Take the positions the investable pairs occupy in the vectorised covariance with
coverage_pair_index, which orders them asfourth_moment_index_generatordoes. - Allocate a zero frame of
length(imsk)^2rows andsize(set.L, 2)columns, and writeset.Lat those rows. - Build a
NormBallUncertaintySetfrom it, carryingkappa,pandclassthrough unchanged andpr.sigmaasval.
Arguments
set: Covariance norm-ball uncertainty set fitted on the reduced prior.imsk: The Investable Mask of the full prior.pr: Prior result.
Returns
set::NormBallUncertaintySet: The set on the full asset universe.
Related
References
- [89]
- D. Goldfarb and G. Iyengar. Robust Portfolio Selection Problems. Mathematics of Operations Research 28, 1–38 (2003).