Array views

View functions

NestedClustered runs one inner optimisation for each cluster of assets, so it needs each estimator, constraint and result restricted to the assets of that cluster. port_opt_view restricts an object to a subset of the assets. obs_weights_view restricts it to a subset of the observations.

PortfolioOptimisers.port_opt_view — Method
port_opt_view(x, i, args...; kwargs...) -> nothing_scalar_array_view(x, i)

Sub-select an estimator, result, or algorithm to the asset/observation index i.

port_opt_view is the index-selection counterpart of factory: where factory threads runtime values down a composed struct tree, port_opt_view threads an index selection — restricting every data-bearing field and composed child to the subset i. It is the mechanism that makes meta-optimisers (NestedClustered, SubsetResampling) and cross-validation variants operate on subproblems with identical struct shapes.

Callers do not normally call port_opt_view directly; it is driven by meta-optimisers and cross-validation internals. It is public (not exported) because extension authors who implement a new composed estimator may need to define a method. Use @vprop on data-bearing fields to have the method generated automatically.

This universal fallback handles leaf values: arrays are sliced via nothing_scalar_array_view; scalars, nothing, estimators without data fields, and algorithms pass through unchanged. Composed structs that recurse into children define their own (more specific) method — emitted by @vprop or hand-written.

The threaded tail args... (typically the returns matrix X for the JuMP families) and any kwargs are accepted and dropped here, so a macro-threaded port_opt_view(child, i, X) never MethodErrors on a leaf field.

Algorithm

  1. Drop args... and kwargs.... This method is the leaf of the recursion, so it threads nothing further.
  2. Return nothing_scalar_array_view of x at i, whose own algorithm names the rule for each leaf type.

Related

source
PortfolioOptimisers.port_opt_view — Method
port_opt_view(x::VecScalar, i, args...) -> nothing_scalar_array_view(x, i)

First-class port_opt_view method for VecScalar: slices the vector component and preserves the scalar component, delegating to nothing_scalar_array_view.

Algorithm

  1. Drop args....
  2. Return nothing_scalar_array_view of x at i, whose VecScalar method views x.v at i and carries x.s through.

This method exists so that a VecScalar reaching the verb with a threaded tail resolves here rather than through the universal leaf method. Both routes give the same value.

Related

source
PortfolioOptimisers.port_opt_view — Method
port_opt_view(x::AbstractVector{<:Union{Nothing, <:AbstractEstimator, <:AbstractAlgorithm, <:AbstractResult}}, i, args...; kwargs...) -> Vector

Generic vector method for port_opt_view: view each element of x at the index selection i.

This is the index-selection twin of the vector factory method, and it is the one forwarding contract for every vector-valued propagation field. The tail args... (typically the returns matrix X) and every keyword reach each element unchanged, so a family that admits a vector of estimators, algorithms, or results needs no method of its own.

Without it such a vector falls through to the universal leaf method port_opt_view(x, i, args...), which slices the vector itself through nothing_scalar_array_view — the asset index would select elements instead of assets. A family that needs more than the forward, such as a concrete element type (concrete_typed_array_if_abstract) or a passthrough, defines its own more specific method.

Algorithm

  1. For each element xi of x, call port_opt_view on xi at i, and forward args... and kwargs... unchanged.
  2. Collect the results into a new vector, in the order of x, and return it.

The length of x is unchanged, because i reaches the elements and never the outer vector. The result is a comprehension, so its element type is whatever Julia infers; a family that needs a concrete element type wraps the call in concrete_typed_array_if_abstract.

Arguments

  • x: Vector of estimators, algorithms, results, or nothing.
  • i: Index selection.
  • args...: Threaded tail, forwarded to each element.
  • kwargs...: Keyword arguments, forwarded to each element.

Returns

  • v::Vector: The element-wise views.

Related

source
PortfolioOptimisers.obs_weights_view — Method
obs_weights_view(x, i) -> typeof(x)

Sub-select an estimator's observation weights to the observations i.

obs_weights_view is the observation-axis counterpart of port_opt_view, and it is generated by @propagatable from the tags a struct already carries: @wprop marks the field that holds the weights, so that field is indexed, and @fprop marks a composed child, so the verb recurses into it. Every other field is carried through unchanged, and the struct's type does not change.

Why the observation axis needs its own verb

port_opt_view threads one index into every @vprop-tagged field, and at its call sites — the meta-optimisers and the cross-validation splitters — that index selects assets. An observation weight is one value per row of the sample, so slicing it there would be wrong. The two axes are told apart by which verb is called, not by the index.

factory reads the same @wprop tag on the same field, and does a different thing with it: it replaces the field with an incoming ObsWeights value, at every level of the tree at once. That is why a slice cannot go through factory. A SimpleVariance holding a weighted mean and an unweighted dispersion comes back from factory with both weighted, which is a different estimator; here each field is indexed on its own, so a field that held nothing still holds nothing.

Algorithm

  1. Return x unchanged. This universal fallback reads neither its index nor the fields of x. An estimator that carries no weights, and one whose struct is not @propagatable, therefore behave as they did before the verb existed.

A @propagatable struct with at least one @wprop-tagged field carries a generated method that dominates this one. That method rebuilds the struct with the same constructor, indexing each @wprop field to i through nothing_scalar_array_getindex and recursing into each @fprop field through this verb. A hand-written type that holds weights outside that tag must define its own method, or its weights keep their full-sample length and the windowed call raises.

Arguments

  • x: Estimator, algorithm, result, weights vector, or nothing. The argument is untyped, because the variance estimators subtype StatsBase.CovarianceEstimator while the expected returns estimators subtype AbstractEstimator, and both reach this verb.
  • i: Index or indices of the observations to keep.

Returns

  • x: The value, with every observation-weights field indexed to i.

Related

source
PortfolioOptimisers.obs_weights_view — Method
obs_weights_view(
    x::AbstractVector{<:Union{Nothing, var"#s53", var"#s52", var"#s51"} where {var"#s53"<:AbstractEstimator, var"#s52"<:AbstractAlgorithm, var"#s51"<:AbstractResult}},
    i
) -> Any

Vector overload of obs_weights_view. Applies the verb to every element, so an @fprop-tagged field holding a vector of composed children is not silently skipped.

Algorithm

  1. For each element xi of x, call obs_weights_view on xi at i.
  2. Collect the results into a new vector, in the order of x, and return it.

The length of x is unchanged, because i selects observations inside each element and never elements of x. Without this method such a vector reaches the universal fallback and comes back with full-sample weights.

Related

source