Norm-Ball Uncertainty Sets: private API

PortfolioOptimisers.assert_norm_ball_axisFunction
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 a DimensionMismatch.

Returns

  • nothing.

Related

source
PortfolioOptimisers.assert_norm_ball_valFunction
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, val is a matrix and size(val, 1) == size(val, 2), else a DimensionMismatch.

Returns

  • nothing.

Related

source
PortfolioOptimisers.dual_norm_orderFunction
dual_norm_order(p::Number) -> Any

Return the dual norm order q of the norm order p, with 1 / p + 1 / q == 1.

Algorithm

  1. When p is infinite, return one(p), because the dual of the infinity norm is the one norm.
  2. Otherwise return p / (p - 1). At p = 1 the division is by zero and returns Inf, the dual of the one norm, and at p = 2 it returns 2, which is its own dual.

Arguments

  • p::Number: Norm order, p >= 1.

Returns

  • q::Number: Dual norm order.

Related

source
PortfolioOptimisers.norm_ball_factorFunction
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

  1. When diagonal is true, return LinearAlgebra.Diagonal(sqrt.(LinearAlgebra.diag(cov))), the square root of the diagonal shape. The result is stored as a Diagonal, not as a dense matrix.
  2. 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 of NormBallUncertaintySet builds.

Arguments

  • diagonal: Whether to discard the off-diagonal entries of cov before the factorisation.
  • cov: Asymptotic covariance of the statistic, positive definite.

Returns

  • L::MatNum: Geometry map, square and of full column rank.

Related

source
PortfolioOptimisers.norm_ball_deviation_factorFunction
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

  1. When diagonal is true, return LinearAlgebra.Diagonal(sqrt.(vec(Statistics.var(X; dims = 1)))), the square root of the entrywise variances. It is $m \times m$ and of full rank.
  2. Otherwise subtract the column means from X, transpose the result, and divide by sqrt(size(X, 1) - 1). A sample of one row divides by zero and the NormBallUncertaintySet constructor 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$ under diagonal = false and $m \times m$ under diagonal = true.

Related

source
PortfolioOptimisers.k_norm_ballFunction
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

  1. On NormalKUncertaintyAlgorithm, solve L \ 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.
  2. Take the squared column norms, giving one squared distance per sample, and return the square root of their 1 - q quantile.
  3. On ChiSqKUncertaintyAlgorithm, return the square root of the 1 - q chi-squared quantile at df degrees of freedom.
  4. On GeneralKUncertaintyAlgorithm, return sqrt((1 - q) / q), Cantelli's bound, which reads no geometry.
  5. On a Number, return it unchanged.

Arguments

  • km, type: Radius algorithm, or the radius itself as a Number.
  • 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 reads size(L, 2) from a full-rank map and LinearAlgebra.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.
source
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

Returns

  • Never returns.

Related

source
PortfolioOptimisers.norm_ball_setFunction
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

  1. Build the map with norm_ball_factor under alg.diagonal, giving L.
  2. Fit the radius with k_norm_ball on alg.method, passing size(L, 2) as the degrees of freedom.
  3. Build a NormBallUncertaintySet from alg.p, L, the radius, class and val.

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 container alg.method reads. 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

source
PortfolioOptimisers.norm_ball_deviation_setFunction
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

  1. Build the map with norm_ball_deviation_factor under alg.diagonal, giving L.
  2. Fit the radius with k_norm_ball on alg.method, passing LinearAlgebra.rank(L) as the degrees of freedom, because the map is rank deficient whenever the sample is.
  3. Build a NormBallUncertaintySet from alg.p, L, the radius, class and val.

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

source
PortfolioOptimisers.expand_investable_ucsMethod
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

  1. Allocate a zero frame of length(imsk) rows and size(set.L, 2) columns, and write set.L at the rows the mask keeps, giving L.
  2. Build a NormBallUncertaintySet from it, carrying kappa, p and class through unchanged and pr.mu as val.

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

source
PortfolioOptimisers.expand_investable_ucsMethod
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

  1. Take the positions the investable pairs occupy in the vectorised covariance with coverage_pair_index, which orders them as fourth_moment_index_generator does.
  2. Allocate a zero frame of length(imsk)^2 rows and size(set.L, 2) columns, and write set.L at those rows.
  3. Build a NormBallUncertaintySet from it, carrying kappa, p and class through unchanged and pr.sigma as val.

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

source

References

[89]
D. Goldfarb and G. Iyengar. Robust Portfolio Selection Problems. Mathematics of Operations Research 28, 1–38 (2003).