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...) -> VecIntGet the observation window index range for a data array.
Algorithm
The type of window names the rule, and each rule is one step.
windowisnothingor aColon: returnColon(), which selects every observation.windowis an integer: readstart, the first index ofXalong the observation axis, andstop, its last index. Return the rangemax(start, stop - window + 1):stop, which is the lastwindowobservations.windowis a vector of integers: returnwindowitself, 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 lastwindowobservations, and a vector of indices selects those observations.::Option{<:Colon}: ReturnsColon().::Integer: Returns the lastwindowobservations. This operation is safe, so it doesn't error ifwindowis larger than the number of observations.::VecInt: Returns thewindowargument.
X: Data matrix or vector.dims: Dimension along which to perform the computation.
Returns
window::Option{Union{Colon, <:VecInt}}: The window index range.
Related
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.
xcarries no asset axis, because it isnothing, a scalar, a pair, a dictionary, a value algorithm, a set of dynamic weights, an estimator, an algorithm or aStatsBase.CovarianceEstimator: returnxitself.xis a vector: returnview(x, i), one entry per selected asset.xis aVecScalar: return a newVecScalarwhose vector part isview(x.v, i)and whose scalar partx.sis carried through. The scalar part carries no asset axis.xis a matrix: returnview(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 needsnothing_scalar_array_view_odd_orderinstead.xis a vector of vectors, matrices orVecScalars: 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 theUnionthe signature names. A vector holding both a vector and a matrix has the element typeArray{T}, which is a subtype of neitherAbstractVectornorAbstractMatrix, 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}: Returnsxunchanged.::AbstractVector: Returnsview(x, i).::VecScalar: ReturnsVecScalar(; v = view(x.v, i), s = x.s).::AbstractMatrix: Returnsview(x, i, i).::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of views for each element inx.
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
nothing_scalar_array_view(
x::MedianCenteringFunction,
_
) -> MedianCenteringFunction
Return the MedianCenteringFunction x unchanged.
Identity pass-through: centering functions are not sliced by asset index.
Related
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
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
xisnothing, returnsnothing. - Otherwise, returns
view(x, i, j).
Algorithm
xisnothing: returnnothing.xis a matrix: returnview(x, i, j), which selectsion the row axis andjon 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:2Related
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.
xcarries no asset axis, because it isnothing, a scalar, a pair, a dictionary, a value algorithm or a set of dynamic weights: returnxitself.xis a vector: returnx[i], a new vector with one entry per selected asset.xis aVecScalar: return a newVecScalarwhose vector part isx.v[i]and whose scalar partx.sis carried through.xis a matrix: returnx[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 needsnothing_scalar_array_getindex_odd_orderinstead.xis a vector of vectors, matrices orVecScalars: 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 theUnionthe signature names. A vector holding both a vector and a matrix has the element typeArray{T}, which is a subtype of neitherAbstractVectornorAbstractMatrix, 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}: Returnsxunchanged.::AbstractVector: Returnsx[i].::VecScalar: ReturnsVecScalar(; v = x.v[i], s = x.s).::AbstractVector{<:Union{<:AbstractVector, <:AbstractMatrix, <:VecScalar}}: Returns a vector of elements indexed byi.::AbstractMatrix: Returnsx[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 3Related
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
xisnothing, returnsnothing. - Otherwise, returns
x[i, j].
Algorithm
xisnothing: returnnothing.xis a matrix: returnx[i, j], which selectsion the row axis andjon 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)2Related
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
- Make
idx, an empty vector of integers, with room forlength(i)^2entries. - For each
cini, take the linear index range of columnc, which is((c - 1) * N + 1):(c * N), and select the entriesiof that range. - Append the selected entries to
idx. - 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 5PortfolioOptimisers.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.
vis a precomputed optimisation result: returnvunchanged.vis anything else: returnport_opt_viewofvati, and forwardargs....
Arguments
v: The field value.i: Index selection.args...: Threaded tail, forwarded toport_opt_view.
Returns
- The viewed field value, or
vitself when it is a precomputed optimisation result.
Related