Array views: private API

Utility functions

Most functions below serve the macros that declare the types of the library. A field tagged @pprop or @cprop takes its value from the prior or from the optimiser when the caller leaves it empty, and sel makes that choice. @propagatable records the tagged fields of each type it declares, and @forward_properties gives a type properties that read through to the fields it holds. The rest are helpers that many files share, such as get_window, which gives the index range of an observation window.

PortfolioOptimisers.get_window — Function
get_window(window::Option{<:Colon}, args...) -> Option{<:Colon}
get_window(window::Integer, X::MatNum, dims::Int = 1) -> VecInt
get_window(window::Integer, X::VecNum, args...) -> VecInt
get_window(window::VecInt, args...) -> VecInt

Get the observation window index range for a data array.

Algorithm

The type of window names the rule, and each rule is one step.

  1. window is nothing or a Colon: return Colon(), which selects every observation.
  2. window is an integer: read start, the first index of X along the observation axis, and stop, its last index. Return the range max(start, stop - window + 1):stop, which is the last window observations.
  3. window is a vector of integers: return window itself, so the caller states the observations directly.

Step 2 clamps the lower end at start, so a window larger than the number of observations gives every observation rather than an error. The observation axis of a matrix is dims, and a vector carries one axis, so its method drops dims.

Arguments

  • window: Observation window. An integer selects the last window observations, and a vector of indices selects those observations.
    • ::Option{<:Colon}: Returns Colon().
    • ::Integer: Returns the last window observations. This operation is safe, so it doesn't error if window is larger than the number of observations.
    • ::VecInt: Returns the window argument.
  • X: Data matrix or vector.
  • dims: Dimension along which to perform the computation.

Returns

  • window::Option{Union{Colon, <:VecInt}}: The window index range.

Related

source

View functions

An optimisation over a subset of the assets, such as each inner optimisation of NestedClustered, needs the part of every input that belongs to those assets. The functions below take that part of a vector, a matrix or a higher moment, and return nothing or a scalar unchanged. fourth_moment_index_generator gives the indices that select the entries of the subset from a fourth-moment matrix. view_child views one field of an estimator for port_opt_view, and keeps a precomputed optimisation result, such as a fallback, as it is.

PortfolioOptimisers.nothing_scalar_array_view — Function
nothing_scalar_array_view(
    x::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict,
             <:AbstractEstimatorValueAlgorithm,
             <:DynamicAbstractWeights, <:AbstractEstimator, <:AbstractAlgorithm,
             <:StatsBase.CovarianceEstimator},
    ::Any
) -> x
nothing_scalar_array_view(x::AbstractVector, i) -> view(x, i)
nothing_scalar_array_view(x::VecScalar, i) -> VecScalar(; v = view(x.v, i), s = x.s)
nothing_scalar_array_view(x::AbstractMatrix, i) -> view(x, i, i)
nothing_scalar_array_view(
    x::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}},
    i
) -> [nothing_scalar_array_view(xi, i) for xi in x]

Utility for safely viewing into possibly nothing, scalar, or array values.

Algorithm

The method that Julia selects is the algorithm. Each step is one method, and no method allocates a copy of the data.

  1. x carries no asset axis, because it is nothing, a scalar, a pair, a dictionary, a value algorithm, a set of dynamic weights, an estimator, an algorithm or a StatsBase.CovarianceEstimator: return x itself.
  2. x is a vector: return view(x, i), one entry per selected asset.
  3. x is a VecScalar: return a new VecScalar whose vector part is view(x.v, i) and whose scalar part x.s is carried through. The scalar part carries no asset axis.
  4. x is a matrix: return view(x, i, i), which selects the same index on both axes. This is the rule for a square per-asset matrix, such as a covariance matrix or a similarity matrix. A matrix whose two axes are different needs nothing_scalar_array_view_odd_order instead.
  5. x is a vector of vectors, matrices or VecScalars: apply step 2, 3 or 4 to each element, and collect the views into a new vector. The outer vector is rebuilt, so its own length is unchanged. The vector's element type selects this step, and it must be a subtype of the Union the signature names. A vector holding both a vector and a matrix has the element type Array{T}, which is a subtype of neither AbstractVector nor AbstractMatrix, so it resolves on step 2 and the index selects the elements of the outer vector.

Arguments

  • x: Input value.
  • i: Index or indices to view.

Returns

  • x: Input value.

    • ::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict, <:AbstractEstimatorValueAlgorithm, <:DynamicAbstractWeights, <:AbstractEstimator, <:AbstractAlgorithm, <:StatsBase.CovarianceEstimator}: Returns x unchanged.
    • ::AbstractVector: Returns view(x, i).
    • ::VecScalar: Returns VecScalar(; v = view(x.v, i), s = x.s).
    • ::AbstractMatrix: Returns view(x, i, i).
    • ::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of views for each element in x.

Examples

julia> PortfolioOptimisers.nothing_scalar_array_view(nothing, 1:2)julia> PortfolioOptimisers.nothing_scalar_array_view(3.0, 1:2)3.0julia> PortfolioOptimisers.nothing_scalar_array_view([1.0, 2.0, 3.0], 2:3)2-element view(::Vector{Float64}, 2:3) with eltype Float64: 2.0 3.0julia> PortfolioOptimisers.nothing_scalar_array_view([[1, 2], [3, 4]], 1)2-element Vector{SubArray{Int64, 0, Vector{Int64}, Tuple{Int64}, true}}: fill(1) fill(3)

Related

source
nothing_scalar_array_view(
    x::MedianCenteringFunction,
    _
) -> MedianCenteringFunction

Return the MedianCenteringFunction x unchanged.

Identity pass-through: centering functions are not sliced by asset index.

Related

source
nothing_scalar_array_view(
    td::TimeDependent,
    i
) -> TimeDependent

Slice a TimeDependent schedule of scalar-or-array field values (warm starts, initial weights) to asset indices i.

Vector schedules slice each per-fold entry, and the default when one is set; callable schedules pass through — they see the sliced universe via their fold context's rd.

Related

source
PortfolioOptimisers.nothing_scalar_array_view_odd_order — Function
nothing_scalar_array_view_odd_order(::Nothing, i, j)
nothing_scalar_array_view_odd_order(x::AbstractMatrix, i, j) -> view(x, i, j)

Utility for safely viewing into possibly nothing or array values with two indices.

  • If x is nothing, returns nothing.
  • Otherwise, returns view(x, i, j).

Algorithm

  1. x is nothing: return nothing.
  2. x is a matrix: return view(x, i, j), which selects i on the row axis and j on the column axis.

The two axes take different indices, which is what separates this verb from nothing_scalar_array_view. An odd-order co-moment matrix is $N \times N^{k}$ for an odd order $k$, so the row index selects assets and the column index selects the tuples of assets that the columns hold. The caller supplies j, and this verb does not derive it; fourth_moment_index_generator is the counterpart that builds such a column index.

Arguments

  • x: Input value.
  • i, j: Indices to view.

Returns

  • The corresponding view or nothing.

Examples

julia> PortfolioOptimisers.nothing_scalar_array_view_odd_order(nothing, 1, 2)julia> PortfolioOptimisers.nothing_scalar_array_view_odd_order([1 2; 3 4], 1, 2)0-dimensional view(::Matrix{Int64}, 1, 2) with eltype Int64:2

Related

source
PortfolioOptimisers.nothing_scalar_array_getindex — Function
nothing_scalar_array_getindex(
    x::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict,
             <:AbstractEstimatorValueAlgorithm,
             <:DynamicAbstractWeights},
    ::Any
) -> x
nothing_scalar_array_getindex(x::AbstractVector, i) -> x[i]
nothing_scalar_array_getindex(x::VecScalar, i) -> VecScalar(; v = x.v[i], s = x.s)
nothing_scalar_array_getindex(x::AbstractMatrix, i) -> x[i, i]
nothing_scalar_array_getindex(
    x::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}},
    i
) -> [nothing_scalar_array_getindex(xi, i) for xi in x]

Utility for safely viewing into possibly nothing, scalar, or array values.

Algorithm

The method that Julia selects is the algorithm. It is the copying twin of nothing_scalar_array_view: every step returns a new array rather than a view.

  1. x carries no asset axis, because it is nothing, a scalar, a pair, a dictionary, a value algorithm or a set of dynamic weights: return x itself.
  2. x is a vector: return x[i], a new vector with one entry per selected asset.
  3. x is a VecScalar: return a new VecScalar whose vector part is x.v[i] and whose scalar part x.s is carried through.
  4. x is a matrix: return x[i, i], which selects the same index on both axes. This is the rule for a square per-asset matrix. A matrix whose two axes are different needs nothing_scalar_array_getindex_odd_order instead.
  5. x is a vector of vectors, matrices or VecScalars: apply step 2, 3 or 4 to each element, and collect the results into a new vector. The vector's element type selects this step, and it must be a subtype of the Union the signature names. A vector holding both a vector and a matrix has the element type Array{T}, which is a subtype of neither AbstractVector nor AbstractMatrix, so it resolves on step 2 and the index selects the elements of the outer vector.

The type list of step 1 is shorter than the one nothing_scalar_array_view carries: an estimator, an algorithm and a StatsBase.CovarianceEstimator reach the view verb and not this one, because only the view verb is the leaf of port_opt_view.

Arguments

  • x: Input value.
  • i: Index or indices to view.

Returns

  • x: Input value.

    • ::Union{Nothing, <:Number, <:Pair, <:VecPair, <:Dict, <:AbstractEstimatorValueAlgorithm, <:DynamicAbstractWeights}: Returns x unchanged.
    • ::AbstractVector: Returns x[i].
    • ::VecScalar: Returns VecScalar(; v = x.v[i], s = x.s).
    • ::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of elements indexed by i.
    • ::AbstractMatrix: Returns x[i, i].

Examples

julia> PortfolioOptimisers.nothing_scalar_array_getindex(nothing, 1:2)julia> PortfolioOptimisers.nothing_scalar_array_getindex(3.0, 1:2)3.0julia> PortfolioOptimisers.nothing_scalar_array_getindex([1.0, 2.0, 3.0], 2:3)2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.nothing_scalar_array_getindex([[1, 2], [3, 4]], 1)2-element Vector{Int64}: 1 3

Related

source
PortfolioOptimisers.nothing_scalar_array_getindex_odd_order — Function
nothing_scalar_array_getindex_odd_order(::Nothing, i, j)
nothing_scalar_array_getindex_odd_order(x::AbstractMatrix, i, j) -> x[i, j]

Utility for safely indexing into possibly nothing or array values with two indices.

  • If x is nothing, returns nothing.
  • Otherwise, returns x[i, j].

Algorithm

  1. x is nothing: return nothing.
  2. x is a matrix: return x[i, j], which selects i on the row axis and j on the column axis, and copies.

This is the copying twin of nothing_scalar_array_view_odd_order, and it takes different indices on the two axes for the same reason: an odd-order co-moment matrix is $N \times N^{k}$ for an odd order $k$.

Arguments

  • x: Input value.
  • i, j: Indices to access.

Returns

  • The corresponding matrix element or nothing.

Examples

julia> PortfolioOptimisers.nothing_scalar_array_getindex_odd_order(nothing, 1, 2)julia> PortfolioOptimisers.nothing_scalar_array_getindex_odd_order([1 2; 3 4], 1, 2)2

Related

source
PortfolioOptimisers.fourth_moment_index_generator — Function
fourth_moment_index_generator(
    N::Integer,
    i
) -> Vector{Int64}

Constructs an index vector for extracting the fourth moment submatrix corresponding to indices i from a covariance matrix of size N × N.

Mathematical definition

\[\begin{align} \mathrm{idx} &= \left( (c - 1) N + r \right)_{c \in \boldsymbol{i},\ r \in \boldsymbol{i}}\,. \end{align}\]

Where:

  • $N$: Number of assets.
  • $\boldsymbol{i}$: The selected asset indices, of length $n$.
  • $r$, $c$: The row and the column of an asset pair in the $N \times N$ grid of pairs.

$(c - 1) N + r$ is the column-major linear index of the pair $(r, c)$ in that grid, which is the axis of the square cokurtosis matrix $\mathbf{K}$, of size $N^{2} \times N^{2}$. So idx selects the $n^{2}$ pairs that the $n$ selected assets make, on either axis of $\mathbf{K}$. c runs on the outside, so the order of idx is the column-major order of the sub-grid too.

Algorithm

  1. Make idx, an empty vector of integers, with room for length(i)^2 entries.
  2. For each c in i, take the linear index range of column c, which is ((c - 1) * N + 1):(c * N), and select the entries i of that range.
  3. Append the selected entries to idx.
  4. Return idx.

Arguments

  • N: Size of the full covariance matrix.
  • i: Indices of the variables of interest.

Returns

  • idx::VecInt: Indices for extracting the fourth moment submatrix.

Examples

julia> PortfolioOptimisers.fourth_moment_index_generator(3, [1, 2])4-element Vector{Int64}: 1 2 4 5
source
PortfolioOptimisers.view_child — Function
view_child(v, i, args...)

Per-field view helper called by @propagatable-generated port_opt_view methods, and by the hand-written view of every optimiser that holds a fallback fb.

It is the view twin of factory_child. A child is viewed at the asset index, with one exception: a precomputed optimisation result is kept as it is. A result sits in a field as a fallback fb, and the fallback loop of optimise answers it without a solve, on the universe it was solved on. A door such as investable_reduction views an optimiser whose own fallback it never reads, so a refusal there would reject a fallback that nothing reaches.

A TimeDependent schedule is not a result, so it reaches port_opt_view, and a schedule that holds a result still refuses a subset view.

Algorithm

The method that Julia selects is the algorithm.

  1. v is a precomputed optimisation result: return v unchanged.
  2. v is anything else: return port_opt_view of v at i, and forward args....

Arguments

  • v: The field value.
  • i: Index selection.
  • args...: Threaded tail, forwarded to port_opt_view.

Returns

  • The viewed field value, or v itself when it is a precomputed optimisation result.

Related

source