Tools: private API

Utility functions

We strive to be as type-stable, inferrable, and immutable as possible in order to improve robustness, performance, and correctness. These functions help us achieve these goals.

PortfolioOptimisers.PROP_TAG_NAMESConstant
PROP_TAG_NAMES

The propagation tag set of @propagatable, as data.

One entry per field tag. The recognition layer is derived from this tuple rather than spelled out per tag: the macro names (PROP_TAG_MACRO_NAMES), the lookup (prop_tag), the gate (is_prop_tag_call), the peeler (peel_prop_tags) and the parser (propagatable_parse_body) all read it. A new propagation channel is one row here, one branch in prop_tag_expr, one entry in PROP_TAG_CHANNELS and one stub macro; check_prop_tag_macros refuses to load the module when a row lacks any of the three.

Related

source
PortfolioOptimisers.PROP_TAG_MACRO_NAMESConstant
PROP_TAG_MACRO_NAMES

The macro name of every tag of PROP_TAG_NAMES, in the same order.

Derived from the tag names, so a row of the table carries no second spelling. prop_tag matches a :macrocall head against this tuple.

Algorithm

  1. Map each tag of PROP_TAG_NAMES to Symbol("@", tag), which is the name Julia gives that tag's macro.

The map preserves the order, so the $k$-th entry here is the macro name of the $k$-th tag there. prop_tag and check_prop_tag_macros both walk the two tuples together, and that pairing is what the shared order guarantees.

Related

source
PortfolioOptimisers.PROP_TAG_CHANNELSConstant
PROP_TAG_CHANNELS

The propagation channels of @propagatable, with their tag precedence as data.

One entry per generated method. Each entry has two tuples of tags:

  • gate: the tags that make @propagatable emit the method at all.
  • precedence: the order in which a field's tags are consulted. The first tag of this tuple that the field carries decides the field's transform; the rest are ignored for that channel.

The factory channel prefers @fprop over @wprop. The prior channel prefers @pprop, then @cprop, then @wprop, then @fprop, so @pprop wins over @fprop on one field. The precedence used to live in two hand-written if/elseif chains that no comment linked; it is now read by prop_channel_pairs for every channel.

A tag means what its channel says it means. The obs channel reads the same @wprop and @fprop tags as factory and gives them different transforms: factory replaces a @wprop field with an incoming ObsWeights value, while obs indexes the value already there. This is why prop_tag_expr takes the channel as well as the tag. It also means a weights field opts into obs_weights_view by carrying @wprop, with no second tag to write and no second tag to forget.

Related

source
PortfolioOptimisers.concrete_typed_array_if_abstractFunction
concrete_typed_array_if_abstract(A::AbstractArray) -> AbstractArray

Narrow the element type of A with concrete_typed_array, but only when that element type is abstract.

The generic vector methods of factory and port_opt_view rebuild a vector field element by element. A comprehension over a heterogeneous vector infers an abstract element type, which costs a dynamic dispatch at every later use. This is the opt-in narrowing step for the families that want the concrete element type back.

Algorithm

  1. Test eltype(A) with isabstracttype.
  2. The element type is abstract: return concrete_typed_array of A, which copies.
  3. The element type is concrete: return A itself, which copies nothing.

Arguments

  • A: The rebuilt array.

Returns

Examples

julia> PortfolioOptimisers.concrete_typed_array_if_abstract([1, 2, 3])3-element Vector{Int64}: 1 2 3julia> PortfolioOptimisers.concrete_typed_array_if_abstract(Any[1, 2.0])2-element Vector{Union{Float64, Int64}}: 1 2.0

Related

source
PortfolioOptimisers.get_windowFunction
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
PortfolioOptimisers.prop_tagFunction
prop_tag(x) -> Union{Nothing, Symbol}

Return the tag of PROP_TAG_NAMES that x names, or nothing.

x is the head of a :macrocall node. It is a bare Symbol in a struct body written by hand, and a GlobalRef once another macro has expanded around it, so both spellings resolve. A name outside PROP_TAG_MACRO_NAMES gives nothing, which is how the callers tell a tag from any other macro; no tag falls through to another tag.

Algorithm

  1. Read name from x. A GlobalRef gives its name field, a Symbol gives itself, and any other value returns nothing at once.
  2. Walk PROP_TAG_NAMES and PROP_TAG_MACRO_NAMES together. Return the tag whose macro name is identical to name.
  3. No macro name matches: return nothing.

The comparison is === on a Symbol, so a macro whose name merely resembles a tag never matches.

Arguments

  • x: The first argument of a :macrocall expression.

Returns

  • tag::Symbol: The tag name, without the @.
  • nothing: If x names no tag.

Related

source
PortfolioOptimisers.is_prop_tag_callFunction
is_prop_tag_call(x) -> Bool

Return true if x is a macro call to any tag of PROP_TAG_NAMES.

Used by peel_prop_tags and propagatable_parse_body to detect tagged fields in a struct body.

Algorithm

  1. Return true when all three hold: x is an Expr, its head is :macrocall, and prop_tag of its first argument is not nothing.
  2. Return false otherwise.

Arguments

  • x: Any expression appearing in a struct body.

Related

source
PortfolioOptimisers.prop_tag_exprFunction
prop_tag_expr(
    channel::Symbol,
    tag::Symbol,
    fname::Symbol,
    xf,
    mod::Module,
    thread
) -> Expr

Return the expression that a tag substitutes for one field, inside a generated method.

This is the name → field transform half of the tag table: one branch per tag of PROP_TAG_NAMES, read by prop_channel_pairs for every channel. A tag of the table with no branch here errors, so it cannot silently take another tag's transform.

Algorithm

The channel is read first, and then the tag, because one tag has two transforms.

  1. channel is :obs:
    1. tag is :wprop: return nothing_scalar_array_getindex(xf, thread...). The field is the weights, so it is indexed to the selected observations. Indexing keeps the AbstractWeights subtype, which a view would not.
    2. tag is :fprop: return obs_weights_view(xf, thread...). The field is a composed child, so the verb recurses into it.
  2. channel is any other channel:
    1. tag is :fprop: return factory_child(xf, thread..., args...; kwargs...).
    2. tag is :vprop: return port_opt_view(xf, thread..., args...). This channel forwards no keywords.
    3. tag is :pprop: return sel(xf, getproperty(pr, fname)), which is why the field name is an argument. The prior result supplies the property of the same name.
    4. tag is :cprop: return sel(xf, _ctx(args...)), which reads the context out of the threaded arguments rather than the prior.
    5. tag is :wprop: return _wprop(xf, args...; kwargs...), which replaces the field with an incoming ObsWeights.
  3. No branch matched: raise an error naming the tag and the channel, and ask for a branch here.

Steps 1.1 and 2.5 are the same tag with two transforms, so the channel decides what @wprop means. Every emitted name is qualified against mod, because the expansion is escaped into the caller's module.

Arguments

  • tag::Symbol: A tag of PROP_TAG_NAMES.
  • fname::Symbol: The field name, needed by @pprop to name the prior property.
  • xf: The expression that reads the field off the incoming struct.
  • mod::Module: The module that defines @propagatable. Every emitted name is qualified against it, because the expansion is escaped into the caller.
  • thread: Extra positional arguments the channel threads before args....

Returns

  • expr::Expr: The value of the field in the generated constructor call.

Related

source
PortfolioOptimisers.prop_channel_activeFunction
prop_channel_active(
    channel::Symbol,
    tagged::AbstractDict
) -> Any

Return true if a channel of PROP_TAG_CHANNELS must emit a method.

A channel is active when at least one field carries a tag of the channel's gate.

Algorithm

  1. Read the gate tuple of the channel from PROP_TAG_CHANNELS.
  2. Return true when at least one tag of gate has a non-empty entry in tagged, and false otherwise.

The precedence tuple is not read here, so a tag that a channel consults but does not gate on never makes that channel emit a method on its own. The obs channel gates on @wprop alone and consults @fprop, so a type carrying @fprop and no @wprop gains no obs method.

Arguments

Related

source
PortfolioOptimisers.prop_channel_pairsFunction
prop_channel_pairs(
    channel::Symbol,
    tagged::AbstractDict,
    all_fields::AbstractVector{Symbol},
    obj::Symbol,
    mod::Module,
    thread
) -> Vector{Any}

Return the keyword pairs of the constructor call that one channel generates.

Every declared field gets one pair, in declaration order. The field's tags are consulted in the channel's precedence order; the first match gives the value through prop_tag_expr, and a field carrying no tag of the channel is passed through unchanged.

Algorithm

  1. Read the precedence tuple of the channel from PROP_TAG_CHANNELS.
  2. For each field name fname of all_fields, in declaration order:
    1. Build xf, the expression obj.fname that reads the field off the incoming struct.
    2. Find idx, the position of the first tag of precedence that fname carries.
    3. When idx is nothing, the field carries no tag of this channel: the value is xf itself.
    4. Otherwise the value is prop_tag_expr of that tag, in this channel.
    5. Push Expr(:kw, fname, value) onto pairs.
  3. Return pairs.

Step 2.2 is where the precedence decides one field's transform. A field carrying @pprop and @fprop takes the @pprop transform on the prior channel and the @fprop transform on the factory channel, because the two channels order the tags differently.

Arguments

  • channel::Symbol: A channel name of PROP_TAG_CHANNELS.
  • tagged::AbstractDict: Tag name to the field names that carry it.
  • all_fields::AbstractVector{Symbol}: Every declared field, in declaration order.
  • obj::Symbol: The struct the generated method reads the fields off.
  • mod::Module: The module that defines @propagatable.
  • thread: Extra positional arguments the channel threads before args....

Returns

  • pairs::Vector{Any}: One Expr(:kw, field, value) per declared field.

Related

source
PortfolioOptimisers.check_prop_tag_macrosFunction
check_prop_tag_macros()
check_prop_tag_macros(tags)
check_prop_tag_macros(tags, macro_names)
check_prop_tag_macros(tags, macro_names, channels)
check_prop_tag_macros(
    tags,
    macro_names,
    channels,
    mod::Module
)

Check that every tag of PROP_TAG_NAMES is complete.

A tag row is complete when it has a stub macro, a channel of PROP_TAG_CHANNELS that names it, and a field transform in prop_tag_expr for every channel that names it. A row that lacks one of the three is a tag that parses but never propagates, which is the failure the table exists to stop. The per-channel probe is what catches a tag added to a second channel without a transform there, now that a tag means what its channel says it means. All the violations are collected and reported together.

Runs once at the end of the module. Throws an ArgumentError listing every violation, so the package refuses to precompile rather than shipping a dead tag.

The three tables are arguments, and each one defaults to the table the module ships. The shipped tables are complete, so the call the module makes never reports a violation; a caller that passes a table of its own drives each of the three clauses and reads the message it gives.

Algorithm

  1. Make violations, an empty vector of strings.
  2. For each tag of PROP_TAG_NAMES, with its macro name from PROP_TAG_MACRO_NAMES:
    1. The macro name is not defined in this module: push a message that the tag declares no stub macro.
    2. The tag appears in the precedence of no channel of PROP_TAG_CHANNELS: push a message that the tag appears in no channel.
    3. For each channel whose precedence names the tag, call prop_tag_expr with the probe name :probe. When that call raises, push a message naming the tag and the channel.
  3. violations is not empty: throw an ArgumentError listing every one of them.
  4. Return nothing.

Step 2.3 probes each channel that names the tag, not the tag alone. This is what catches a tag added to a second channel with no transform there, which the whole-tag probe of an earlier design let through.

Arguments

  • tags: The tag names to check.
  • macro_names: The stub macro name of each tag, in the order of tags.
  • channels: The channel table whose precedence tuples are read.
  • mod::Module: The module the stub macros are looked up in, and the module that qualifies the names prop_tag_expr emits.

Returns

  • nothing: Every row of tags is complete.

Related

source
PortfolioOptimisers.is_doc_macroFunction
is_doc_macro(x) -> Union{Missing, Bool}

Return true if x is a reference to Julia's @doc macro (bare Symbol or GlobalRef).

Used by propagatable_parse_body to recognise docstring-prefixed fields in a struct body.

Algorithm

  1. Return true when x is a GlobalRef whose name is Symbol("@doc").
  2. Return true when x is equal to Symbol("@doc").
  3. Return false otherwise.

Both spellings are needed for the same reason prop_tag needs both: a struct body written by hand carries the bare Symbol, and one that another macro has already expanded carries the GlobalRef.

Related

source
PortfolioOptimisers._ctxFunction
_ctx(args...)

Locate the lone threaded optimiser context value (a solver, Slv_VecSlv) in the variadic tail of a prior factory call, returning nothing if none is present. Emitted by the @cprop tag as the source argument to sel. The tuple scan is unrolled by the compiler, so it is type-stable and allocation-free.

Related

source
PortfolioOptimisers._wpropFunction
_wprop(
    field,
    args...;
    kwargs...
) -> Union{DynamicAbstractWeights, AbstractWeights}

Resolve the new value of a @wprop-tagged observation-weights field during factory propagation.

When an ObsWeights argument is threaded through factory, the field is replaced by those weights; otherwise the existing field value is kept. This is distinct from factory_child (used by @fprop), which recurses into sub-estimators and leaves nothing/non-estimator values unchanged — a weights slot must not be confused with an optional sub-estimator that happens to be nothing.

Algorithm

The method that Julia selects is the algorithm, and the selection reads args..., never the field.

  1. The first threaded positional argument is an ObsWeights: return that value, whatever the field held.
  2. No such argument is threaded: return field unchanged.

The field's own value never selects the branch, so a field holding nothing and a field holding weights are both replaced by an incoming ObsWeights, and both are kept when none is threaded.

Related

source
PortfolioOptimisers.resolve_deferred_quantitiesFunction
resolve_deferred_quantities(x, ) -> StandardDeviation
resolve_deferred_quantities(x, , ) -> StandardDeviation

Resolve every Deferred Quantity held by x against prior result pr, returning a struct of the same type whose deferred slots hold plain values.

This resolves the deferred state and nothing else. A slot left unstated stays nothing, so whichever fallback the consumer already applies — sel on the factory path, chol_sigma_selector and its siblings on the JuMP path — keeps working unchanged. The two paths are separate: a JuMP model builder reads the risk measure's slots directly and never calls factory, so both entry points resolve.

Given a prior result the rule has two halves. Container recursion is derived from deferred_slots, so a type that only holds children needs no method at all. A type that resolves a quantity of its own defines a method, which overrides the derived one. Writing that half per type — rather than per field — is what lets slots that travel together be resolved together: a deferred sigma supplies chol from the same fit, so the pair is never mixed across two sources.

slv is the effective solver, and it is what a Calibration Rule in the same struct reads. It carries the value the optimisation settled on, so a rule resolves against one solver on both routes. On the factory route the @cprop selection has already put that solver on the struct, so the argument stays at its default. On the JuMP route no selection runs, so set_risk_constraints! reads the solver off the estimator and threads it here. A type that carries a solver of its own settles it locally as sel(x.slv, slv), beside the observation weights it already settles that way, and a type that carries none gives its rules none on either route.

Algorithm

  1. Return x unchanged. This method is the arm for a second argument that is not a prior result: with no prior in hand nothing can be fitted, so the deferred state travels on.

A more specific method dominates this one on a prior result: the one that deferred_slots derives for a container, and the hand-written one of a type that resolves a quantity of its own.

Related

source
resolve_deferred_quantities(x, pr::AbstractPriorResult, slv = nothing)

Resolve the children that deferred_slots declared and the slots that calibration_slots declared, and return x itself when none of them changed.

This is the derived half of the resolution rule. A container declares its children once and both entry points follow: factory reaches them through @fprop, and the JuMP builders reach them through this method. Neither needs a forwarding method per container.

A type that resolves a quantity of its own overrides this with its own method, which is more specific. So the derivation carries container recursion alone, and never guesses how a matrix, a tensor or the centre a moment was taken about comes out of a fit.

Both channels end in one rebuild: a measure that carries both kinds of slot must not be rebuilt twice. resolve_calibration_slots states the calibration half and returns its resolved slots rather than a rebuilt object, and the two answers merge here. The deferred half merges last, so it wins a key both channels declare. A container names one child in both, and the child the recursion resolved is the one to keep.

slv is the effective solver, and the recursion threads it to every child. A container states no solver of its own, so it changes none: each child settles the one it was handed against the one it carries.

Algorithm

  1. Read the slots x declares with deferred_slots, giving slots.
  2. Read the resolved calibration slots with resolve_calibration_slots, giving calibrated.
  3. Return x unchanged when both are empty. A type with neither kind of slot needs no method of its own.
  4. Resolve every entry of slots with resolve_deferred_child, threading pr and slv to each, giving resolved.
  5. Refuse a slot the recursion left unresolved with assert_declared_slot_resolver.
  6. Hand merge(calibrated, resolved) to rebuild_with_slots, which returns x itself when no entry moved and a rebuilt copy when one did.

Returns

  • x itself when no slot moved, and a rebuilt copy of x when one did.

Related

source
resolve_deferred_quantities(
    r::Variance,
    pr::AbstractPriorResult
) -> Variance
resolve_deferred_quantities(
    r::Variance,
    pr::AbstractPriorResult,
    
) -> Variance

Resolve a Deferred Quantity in Variance's sigma slot against prior result pr.

sigma and chol travel together, so both come from the same fit. A stated chol never reaches here: assert_derived_slot_has_source refuses it beside a deferred sigma at construction. A covariance estimator produces no factorisation, so chol becomes nothing and the consumer derives it from the resolved sigma. A prior estimator produces both, which is how a factor prior's sparse factorisation reaches the slot intact.

Related

source
resolve_deferred_quantities(
    r::StandardDeviation,
    pr::AbstractPriorResult
) -> StandardDeviation
resolve_deferred_quantities(
    r::StandardDeviation,
    pr::AbstractPriorResult,
    
) -> StandardDeviation

Resolve a Deferred Quantity in StandardDeviation's sigma slot against prior result pr. sigma and chol come from the same fit — see resolve_deferred_quantities(r::Variance, pr::AbstractPriorResult).

Related

source
resolve_deferred_quantities(
    r::UncertaintySetVariance,
    pr::AbstractPriorResult
) -> UncertaintySetVariance
resolve_deferred_quantities(
    r::UncertaintySetVariance,
    pr::AbstractPriorResult,
    
) -> UncertaintySetVariance

Resolve a Deferred Quantity in UncertaintySetVariance's sigma slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.

Related

source
resolve_deferred_quantities(
    r::LowOrderMoment,
    pr::AbstractPriorResult
) -> LowOrderMoment
resolve_deferred_quantities(
    r::LowOrderMoment,
    pr::AbstractPriorResult,
    
) -> LowOrderMoment

Resolve a Deferred Quantity in LowOrderMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.

Related

source
resolve_deferred_quantities(
    r::HighOrderMoment,
    pr::AbstractPriorResult
) -> HighOrderMoment
resolve_deferred_quantities(
    r::HighOrderMoment,
    pr::AbstractPriorResult,
    
) -> HighOrderMoment

Resolve a Deferred Quantity in HighOrderMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.

Related

source
resolve_deferred_quantities(
    r::Kurtosis,
    pr::AbstractPriorResult
) -> Kurtosis
resolve_deferred_quantities(
    r::Kurtosis,
    pr::AbstractPriorResult,
    
) -> Kurtosis

Resolve every Deferred Quantity held by Kurtosis r against prior result pr.

Three passes, in order.

  1. A deferred mu resolves on its own.
  2. A deferred kt resolves next, and it carries the centre with it. kt is a moment about a centre, so the two are one pair of quantities out of one object: when mu is still unstated, deferred_centre reads it off the cokurtosis estimator's own me, threads it into the fit as mean =, and it becomes the resolved mu. A stated mu wins and is threaded in its place. An AbstractPriorEstimator centres itself, so the centre is read back off the prior result it produced.
  3. pe fans out into whatever both passes left nothing.

A deferred slot therefore wins over pe, which is the map's precedence rule one level down. kt lives on a HighOrderPrior, so a prior estimator named in kt or pe must compute one.

Related

source
resolve_deferred_quantities(
    r::NegativeSkewness,
    pr::AbstractPriorResult
) -> NegativeSkewness
resolve_deferred_quantities(
    r::NegativeSkewness,
    pr::AbstractPriorResult,
    
) -> NegativeSkewness

Resolve a Deferred Quantity in NegativeSkewness's sk slot against prior result pr.

sk and V travel together, so both come from the same fit. V = negative_spectral_coskewness(sk, X, mp) is never a function of sk alone, so the fit's own processor builds it and is recorded in mp in place of the one the measure held. A CoskewnessEstimator supplies it through coskewness_processor; an AbstractPriorEstimator supplies it as the prior result's skmp, which is the field HighOrderPrior already carries for exactly this reason.

Recording it keeps the windowed rebuild in port_opt_view on the same processor that built the V it replaces. V is never rebuilt from a resolved sk: under a factor prior the negative spectral part is special, and a rebuild would throw that structure away. This is the sigma/chol rule on the sk/V pair.

The measure carries one deferrable slot, so there is no fan-out to make and it takes no pe. A coskewness estimator needs only a returns matrix, so sk resolves against a LowOrderPrior as readily as against a HighOrderPrior.

Related

source
resolve_deferred_quantities(
    alg::DistributionValueatRisk,
    pr::AbstractPriorResult
) -> DistributionValueatRisk
resolve_deferred_quantities(
    alg::DistributionValueatRisk,
    pr::AbstractPriorResult,
    
) -> DistributionValueatRisk

Resolve every Deferred Quantity held by DistributionValueatRisk alg against prior result pr.

The measure carries three prior-derived fields, and mu and sigma are independent of each other, so it takes a pe: one fit fills every slot the caller left unstated. chol is derived from sigma and travels with it — a sigma that names its own estimator supplies the factorisation from that same fit, and never from the pe's.

Related

source
resolve_deferred_quantities(
    x::ValueatRisk,
    pr::AbstractPriorResult
) -> ValueatRisk
resolve_deferred_quantities(
    x::ValueatRisk,
    pr::AbstractPriorResult,
    slv
) -> ValueatRisk

Resolve the significance level alpha of a ValueatRisk against prior result pr, and resolve the formulation alg beside it.

alpha takes a Calibration Rule in place of the number, so it resolves here. The rebuild goes through rebuild_with_slots, and the inner constructor it calls re-runs 0 < alpha < 1 on the calibrated number: a rule that returns a value the slot does not admit is refused at fold time, by the guard a caller's own number meets.

This method is more specific than the derived recursion, so it takes over the alg slot that deferred_slots declares. It resolves that slot through resolve_deferred_child, which is the verb the derivation would have used.

The effective observation weights are computed locally as sel(x.w, pr.w) and threaded to the rule. The measure carries no solver of its own, so it hands the rule the one it was given.

Related

source
resolve_deferred_quantities(
    x::ValueatRiskRange,
    pr::AbstractPriorResult
) -> ValueatRiskRange
resolve_deferred_quantities(
    x::ValueatRiskRange,
    pr::AbstractPriorResult,
    slv
) -> ValueatRiskRange

Resolve the two significance levels of a ValueatRiskRange against prior result pr, and resolve the formulation alg beside them.

Each tail carries its own slot and its own bound, so a stated tail rule and a stated head rule resolve independently. beta defaults to alpha, so a rule stated on the loss side alone reaches both ends: the rule states the method and the slot states the end. The rebuild goes through rebuild_with_slots, whose positional call runs the inner constructor and re-runs both range checks on the calibrated numbers.

This method is more specific than the derived recursion, so it takes over the alg slot that deferred_slots declares, through resolve_deferred_child.

Related

source
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalValueatRisk,
    pr::AbstractPriorResult
) -> DistributionallyRobustConditionalValueatRisk
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalValueatRisk,
    pr::AbstractPriorResult,
    slv
) -> DistributionallyRobustConditionalValueatRisk

Resolve the significance level alpha, the ambiguity radius r and the tail weight l of a DistributionallyRobustConditionalValueatRisk against prior result pr.

All three slots take a Calibration Rule in place of the number, so all three resolve here. The struct is rebuilt through rebuild_with_slots, and the inner constructor it calls is what re-runs the positivity check on the calibrated number: a rule that returns a value the slot does not admit is refused at fold time, by the same guard a caller's own number meets.

alpha resolves first, because the tail weight reads it. TailTermParity prices a tail term at the measure's own significance level, so alpha and l are a travelling pair and the number reaches the l slot in its CalibrationContext. A stated number, a plain function and a rule that reads no sibling all ignore the field, so the order costs nothing where no rule reads a sibling. The radius reads neither of the two, so its own order is free.

The effective observation weights are computed locally as sel(r.w, pr.w) and threaded to the rule, so a rule that reads a weighted sample size sees the weights the optimisation settled on. The measure carries no solver, so the rule receives none. That holds on both routes: the third argument carries the effective solver for a measure that has a slot for one, and this measure has none.

The series both slots price travels in the same context. It is the returns, which is the default calibration_series states, so this site names what the default context already holds. It is written all the same, for the reason every site writes it: the marker belongs to the measure, and no rule carries one of its own to be corrected.

A measure whose two slots both hold numbers is returned unchanged, so the common case allocates nothing.

Related

source
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalValueatRiskRange,
    pr::AbstractPriorResult
) -> DistributionallyRobustConditionalValueatRiskRange
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalValueatRiskRange,
    pr::AbstractPriorResult,
    slv
) -> DistributionallyRobustConditionalValueatRiskRange

Resolve the two ambiguity radii and the two tail weights of a DistributionallyRobustConditionalValueatRiskRange against prior result pr.

Each tail keeps its own pair, so four slots resolve here. It carries the reading of the scalar measure's method unchanged: the rebuild re-runs every positivity check, the effective observation weights are computed locally, and a measure whose four slots all hold numbers is returned unchanged.

Each end's tail weight reads that end's own probability. alpha and beta resolve first, and each is stated in the context of the tail weight beside it: l_a reads alpha and l_b reads beta. A skewed sample therefore resolves the two tail weights to two different numbers, which is the whole point of TailTermParity on a Range measure. The two radii read neither probability, so the four remaining slots resolve in one pass.

A radius names no end of the distribution, so a rule placed in the loss-side pair and the same rule placed in the gain-side pair resolve independently, and neither ambiguity slot defaults from the other.

Both ends price one series, which is the returns, so the same marker stands in the context of all four slots. The series is a property of the measure and not of an end, where the significance level is a property of the end.

Related

source
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalDrawdownatRisk,
    pr::AbstractPriorResult
) -> DistributionallyRobustConditionalDrawdownatRisk
resolve_deferred_quantities(
    x::DistributionallyRobustConditionalDrawdownatRisk,
    pr::AbstractPriorResult,
    slv
) -> DistributionallyRobustConditionalDrawdownatRisk

Resolve the significance level alpha, the ambiguity radius r and the tail weight l of a DistributionallyRobustConditionalDrawdownatRisk against prior result pr.

It carries the reading of resolve_deferred_quantities on the value-at-risk measure unchanged, alpha first and the tail weight reading it off its own CalibrationContext. A drawdown series holds one entry per observation, so a rule reads the same sample size here as it does there.

The series does not carry over, and the context is what says so. This measure prices the absolute drawdown series, so calibration_series states AbsoluteDrawdownSeries and the marker travels beside alpha to both ambiguity slots. TailTermParity then prices the mean drawdown of each column against the $\mathrm{CDaR}_{\alpha}$ of that column, and the radius rules read the error scale off the drawdown sample. The keys :l and :r name this measure's slots and the value-at-risk twin's slots alike, so nothing else could have told a rule which quantity it stands in front of.

Related

source
resolve_deferred_quantities(
    x::RelativisticValueatRisk,
    pr::AbstractPriorResult
) -> RelativisticValueatRisk
resolve_deferred_quantities(
    x::RelativisticValueatRisk,
    pr::AbstractPriorResult,
    slv
) -> RelativisticValueatRisk

Resolve the significance level alpha and the deformation parameter kappa of a RelativisticValueatRisk against prior result pr.

alpha and kappa are a travelling pair: EntropyBudget reads the significance level of its sibling slot. So alpha resolves first, and the number it produced is stated in the CalibrationContext of the kappa slot before that slot is resolved. A stated number, a plain function and a rule that reads no sibling all ignore the field, so the order costs nothing where no rule reads a sibling.

The series this measure prices travels in the same context. It is the returns, which is the default calibration_series states, so this site names what the default context already holds. It is written all the same, for the reason every site writes it: the marker belongs to the measure, and no rule carries one of its own to be corrected.

The solver is settled once, as sel(x.slv, slv), and handed to both rules, so a rule may call RRM itself. The rebuild goes through rebuild_with_slots, whose positional call runs the inner constructor and re-runs both range checks on the calibrated numbers.

Related

source
resolve_deferred_quantities(
    x::RelativisticValueatRiskRange,
    pr::AbstractPriorResult
) -> RelativisticValueatRiskRange
resolve_deferred_quantities(
    x::RelativisticValueatRiskRange,
    pr::AbstractPriorResult,
    slv
) -> RelativisticValueatRiskRange

Resolve the two significance levels and the two deformation parameters of a RelativisticValueatRiskRange against prior result pr.

Each end carries a travelling pair of its own: kappa_a reads alpha and kappa_b reads beta. The gain-side pair defaults to the loss-side pair, beta to alpha and kappa_b to kappa_a, so a pair stated on the loss side alone reaches both ends. The resolution runs the pair of the loss side and then the pair of the gain side, and neither side reads the other's number. That is the pairing range_tails builds and the functor evaluates.

The four slots carry four different bounds, so a rule of the wrong end or the wrong family is refused at construction. The solver is settled once and handed to all four rules.

Both ends price one series, which is the returns, so the same marker stands in the context of both kappa slots. The series is a property of the measure and not of an end, where the significance level is a property of the end.

Related

source
resolve_deferred_quantities(
    x::RelativisticDrawdownatRisk,
    pr::AbstractPriorResult
) -> RelativisticDrawdownatRisk
resolve_deferred_quantities(
    x::RelativisticDrawdownatRisk,
    pr::AbstractPriorResult,
    slv
) -> RelativisticDrawdownatRisk

Resolve the significance level alpha and the deformation parameter kappa of a RelativisticDrawdownatRisk against prior result pr.

It carries the reading of resolve_deferred_quantities on the value-at-risk twin unchanged: alpha resolves first and reaches the kappa slot in its CalibrationContext. The drawdown series has one entry per row of the sample, so a rule reads the same sample size here as it does there.

The series does not carry over, and the context is what says so. This measure prices the absolute drawdown series of the portfolio, so calibration_series states AbsoluteDrawdownSeries and the marker travels beside alpha. A rule that reads the shape of a series then reads the drawdown series of each column of the sample, in place of the columns themselves, and the alpha it reads is the level of that same drawdown series. The key :kappa names this slot and the twin's slot alike, so nothing else could have told the rule which quantity it stands in front of.

Related

source
resolve_deferred_quantities(
    x::RelativeRelativisticDrawdownatRisk,
    pr::AbstractPriorResult
) -> RelativeRelativisticDrawdownatRisk
resolve_deferred_quantities(
    x::RelativeRelativisticDrawdownatRisk,
    pr::AbstractPriorResult,
    slv
) -> RelativeRelativisticDrawdownatRisk

Resolve the significance level alpha and the deformation parameter kappa of a RelativeRelativisticDrawdownatRisk against prior result pr.

The measure is a hierarchical one, so it reaches no JuMP model and the factory route is its only resolution. The travelling pair is resolved in the order the absolute twin states.

The series is the twin's reading in its own units: this measure compounds the path, so calibration_series states RelativeDrawdownSeries and the context carries it. The two markers name two different series of the same column, and a rule that reads the shape of a series answers differently on each.

Related

source
resolve_deferred_quantities(
    x::ComposedFunction{typeof(reverse), <:AbstractOrderedWeightsArrayFunction},
    pr::AbstractPriorResult
) -> ComposedFunction{typeof(reverse)}
resolve_deferred_quantities(
    x::ComposedFunction{typeof(reverse), <:AbstractOrderedWeightsArrayFunction},
    pr::AbstractPriorResult,
    slv
) -> ComposedFunction{typeof(reverse)}

Resolve the weight builder inside the reversal that OrderedWeightsArrayRange wraps its w2 in.

The Range constructor stores w2 as reverse ∘ w2 whenever the caller has not already reversed it, so the object the w2 slot holds is a composition and the builder a rule sits in is its inner half. Without this method the recursion would stop at the composition and a rule in the gain-side builder would never resolve, while the same rule in w1 did.

The composition is rebuilt around the resolved half, so the reversal survives. The bound names reverse and an AbstractOrderedWeightsArrayFunction, so no other composition reaches it.

Related

source
resolve_deferred_quantities(
    r::Skewness,
    pr::AbstractPriorResult
) -> Skewness
resolve_deferred_quantities(
    r::Skewness,
    pr::AbstractPriorResult,
    
) -> Skewness

Resolve every Deferred Quantity held by Skewness r against prior result pr.

Three passes, in order.

  1. A deferred mu resolves on its own.
  2. A deferred sk resolves next, and it carries the centre with it. sk is a moment about a centre, so the two are one pair of quantities out of one object: when mu is still unstated, deferred_centre reads it off the coskewness estimator's own me, threads it into the fit as mean =, and it becomes the resolved mu. A stated mu wins and is threaded in its place. An AbstractPriorEstimator centres itself, so the centre is read back off the prior result it produced.
  3. pe fans out into whatever both passes left nothing.

A deferred slot therefore wins over pe, which is the map's precedence rule one level down. The measure reads no V, so only the sk half of the coskewness pair is kept — see NegativeSkewness for the half that needs both.

Related

source
resolve_deferred_quantities(
    r::VarianceSkewKurtosis,
    pr::AbstractPriorResult
) -> VarianceSkewKurtosis
resolve_deferred_quantities(
    r::VarianceSkewKurtosis,
    pr::AbstractPriorResult,
    slv
) -> VarianceSkewKurtosis

Resolve every Deferred Quantity held by VarianceSkewKurtosis r against prior result pr.

Two levels, in order. Each child resolves whatever it holds of its own, then the container's pe fans out into every child slot still unstated — sigma and its chol on vr, sk and mu on sk, kt and mu on kt — all from one fit.

A child that names its own quantity keeps it. This is the map's precedence rule applied one level down, and it treats a deferred child slot exactly as it already treats a stated one.

The composed measure adds a variance, a skewness and a kurtosis term, so a caller who wants the three to describe one distribution names pe on the container and states nothing on the children.

Related

source
resolve_deferred_quantities(
    r::MedianAbsoluteDeviation,
    pr::AbstractPriorResult
) -> Any
resolve_deferred_quantities(
    r::MedianAbsoluteDeviation,
    pr::AbstractPriorResult,
    
) -> Any

Resolve a Deferred Quantity in MedianAbsoluteDeviation's mu slot against prior result pr.

This is the only thing factory does to mu. The field is tagged @vprop and not @pprop, so the prior never fills it — a bare MedianAbsoluteDeviation() inside a JuMPOptimiser keeps median-centring rather than silently taking pr.mu.

Related

source
resolve_deferred_quantities(
    r::ThirdCentralMoment,
    pr::AbstractPriorResult
) -> ThirdCentralMoment
resolve_deferred_quantities(
    r::ThirdCentralMoment,
    pr::AbstractPriorResult,
    
) -> ThirdCentralMoment

Resolve a Deferred Quantity in ThirdCentralMoment's mu slot against prior result pr. The measure carries one prior-derived slot, so the slot itself admits the estimator and there is no fan-out to make.

Related

source
resolve_deferred_quantities(
    rt::ArithmeticReturn,
    pr::AbstractPriorResult
) -> ArithmeticReturn
resolve_deferred_quantities(
    rt::ArithmeticReturn,
    pr::AbstractPriorResult,
    
) -> ArithmeticReturn

Resolve a Deferred Quantity in ArithmeticReturn's mu slot against prior result pr. The estimator carries one prior-derived slot, so the slot itself admits the Estimator and there is no fan-out to make.

Every JuMP path reaches this through factory, which processed_jump_optimiser_attributes calls on opt.ret before any model is built. A return term needs no second entry point, unlike a risk measure.

Related

source
PortfolioOptimisers.selFunction
sel(risk_variable, source_variable)

Unified risk-measure selector emitted by the @pprop/@cprop tags. Prefers the risk-measure value risk_variable when present, otherwise falls back to source_variable (a prior moment for @pprop, or a threaded optimiser value for @cprop). Dispatches on operand types to the appropriate leaf selector and inlines to zero cost:

  • solvers (Slv_VecSlv) → solver_selector
  • uncertainty sets (UcSE_UcS) → ucs_selector
  • a Deferred Quantity or a Calibration Rule → kept, because a slot the caller filled with the method that computes the value is a stated slot
  • everything else (moments) → nothing_scalar_array_selector

The Deferred-Quantity arm exists because @propagatable runs the selection before resolve_deferred_quantities, so a @pprop slot that admits a Deferred Quantity reaches here still holding one. The prior must not fill such a slot: the caller stated the method, and the resolution that follows replaces it with the value that method produced. The same reading covers a Calibration Rule.

Note: the solver_selector both-nothing "cannot solve" error is not reachable through sel (both-nothing routes to the moment selector and returns nothing); the JuMPOptimiser solver-required invariant makes that case unreachable in the pipeline.

Related

source
PortfolioOptimisers.extract_field_nameFunction
extract_field_name(expr) -> Any

Extract the field name Symbol from a bare field or field::Type expression.

Errors with a descriptive message when expr is neither a bare Symbol nor a field::Type annotation, since only those forms are valid after @fprop.

Algorithm

  1. expr is a Symbol: return it.
  2. expr is an Expr whose head is :(::): return its first argument, which is the field name.
  3. expr is anything else: raise an error naming the expression.

Step 3 is what separates this function from try_field_name, which returns nothing in the same case. A tag states that the node is a field, so a node that is not one is a defect in the struct body and not a node to skip.

Arguments

  • expr: A Symbol, an Expr with head :(::), or any other expression (triggers an error).

Returns

  • name::Symbol: The field name.

Related

source
PortfolioOptimisers.propagatable_find_structFunction
propagatable_find_struct(
    expr
) -> Union{Tuple{Expr, typeof(identity)}, Tuple{Expr, Union{PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, typeof(identity)}, PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, PortfolioOptimisers.var"#propagatable_find_struct##0#propagatable_find_struct##1"{Vector{Any}, rebuild}} where rebuild}}}

Recursively unwrap macro call chains to locate the innermost :struct node.

Returns (struct_node, rebuild_fn) where rebuild_fn(new_struct) reconstructs the original macro chain with new_struct in place of the original struct. This allows @propagatable to inject modified struct definitions back into arbitrary macro wrappers such as @concrete.

Algorithm

  1. expr is not an Expr: raise an error naming its type.
  2. expr has head :struct: return expr itself and identity, which rebuilds nothing.
  3. expr has head :macrocall: take inner, its last argument, and call this function again on it. That call gives struct_node and rebuild. Read prefix, every argument of expr except the last. Return struct_node and the function s -> Expr(:macrocall, prefix..., rebuild(s)).
  4. expr has any other head: raise an error naming the head.

Step 3 rebuilds the chain from the inside out, so a struct wrapped in several macros comes back wrapped in the same macros, in the same order, with the same arguments. The prefix carries the macro's own arguments and its LineNumberNode, so nothing of the call is lost.

Arguments

  • expr: A :struct expression or a :macrocall expression wrapping one.

Returns

  • struct_node::Expr: The innermost :struct expression.
  • rebuild_fn::Function: A function that, given a replacement :struct, returns the full macro chain with the replacement in place of the original.

Related

source
PortfolioOptimisers.propagatable_bare_nameFunction
propagatable_bare_name(n) -> Symbol

Extract the plain struct name Symbol from a potentially parameterised or supertype-constrained name expression.

Handles the forms Name, Name{T, ...}, and Name{T, ...} <: SuperType by recursively peeling :curly and :<: wrappers until a bare Symbol is reached.

Algorithm

  1. n is a Symbol: return it.
  2. n has head :curly: call this function again on its first argument, which drops the type parameters.
  3. n has head :<:: call this function again on its first argument, which drops the supertype.
  4. n is anything else: raise an error naming the expression.

Steps 2 and 3 compose, so Name{T} <: Super peels the supertype first and then the parameters.

Arguments

  • n: A Symbol, or an Expr with head :curly or :<:.

Returns

  • name::Symbol: The plain struct name.

Related

source
PortfolioOptimisers.try_field_nameFunction
try_field_name(expr) -> Any

Return the field name Symbol for a plain field declaration, or nothing for non-field nodes.

Recognises bare Symbol fields and field::Type annotations. Returns nothing for LineNumberNodes, inner constructors, and any other expression that does not declare a single named field.

Algorithm

  1. expr is a Symbol: return it.
  2. expr has head :(::) and its first argument is a Symbol: return that argument.
  3. expr is anything else: return nothing.

Step 2 tests the first argument as well as the head, which extract_field_name does not. A node such as ::Type, which annotates no name, therefore gives nothing here and reaches step 3.

Arguments

  • expr: Any expression appearing in a struct body.

Returns

  • name::Symbol: The field name, if expr is a plain field declaration.
  • nothing: If expr is not a plain field declaration.

Related

source
PortfolioOptimisers.peel_prop_tagsFunction
peel_prop_tags(expr) -> Tuple{Set{Symbol}, Any}

Peel any stack of tag macrocalls off a field expression, recording which tags were present.

Tags may be stacked in either order (@pprop @fprop field), which parses as nested :macrocall nodes; this unwraps them all and returns the bare field expression. Each tag is looked up with prop_tag, so an untagged macro stops the peel and no tag is reached by falling through the others.

Algorithm

  1. Make tags, an empty Set{Symbol}.
  2. While is_prop_tag_call of expr holds, push prop_tag of its first argument onto tags, and replace expr with its last argument, which is the expression the tag wraps.
  3. Return tags and the peeled expr.

tags is a set, so a tag written twice on one field is recorded once. The loop stops at the first node that is not a tag call, so a non-tag macro between two tags hides the tags below it.

Arguments

  • expr: A field expression, with or without tag macrocalls around it.

Returns

  • tags::Set{Symbol}: The tags of PROP_TAG_NAMES that expr carries.
  • stripped: The field expression with all tags removed.

Related

source
PortfolioOptimisers.propagatable_parse_bodyFunction
propagatable_parse_body(
    body
) -> Tuple{Dict{Symbol, Vector{Symbol}}, Vector{Symbol}, Expr}

Walk a struct body, collecting the tagged field names (and all field names) and stripping the tags from the body.

Handles bare tagged fields (@fprop field, …), stacked tags (@pprop @fprop field, in any order), and docstring-prefixed forms ("doc" \n @fprop field). Non-field nodes (line numbers, inner constructors) are carried through unchanged. The tags are the rows of PROP_TAG_NAMES, so a new tag needs no change here.

Algorithm

  1. Make tagged, one empty vector per tag of PROP_TAG_NAMES; all_fields, an empty vector; and new_args, an empty vector for the stripped body.
  2. For each node arg of the struct body, in declaration order, take one of three branches:
    1. arg is a @doc macrocall, which is how a documented field parses. Peel the tags off inner, its last argument.
      1. The field carries at least one tag: record the field name under each of its tags and in all_fields, then push a rebuilt @doc node whose last argument is the stripped field.
      2. The field carries no tag: record its name in all_fields when try_field_name finds one, and push arg unchanged.
    2. arg is a tag macrocall with no docstring: peel the tags, record the field name under each of them and in all_fields, and push the stripped field expression.
    3. arg is anything else — a LineNumberNode, an untagged field, an inner constructor: record its name in all_fields when try_field_name finds one, and push arg unchanged.
  3. Return tagged, all_fields, and the new body as one :block expression.

all_fields holds every declared field, tagged or not, and it is what makes the generated constructor call name every keyword. The returned body carries no tag, so the wrapped macros and Julia itself never see one.

Arguments

  • body::Expr: The :block expression forming the struct body.

Returns

  • tagged::Dict{Symbol, Vector{Symbol}}: One entry per tag of PROP_TAG_NAMES, holding the names of the fields that carry it, in declaration order.
  • all_fields::Vector{Symbol}: Names of every declared field (tagged or not).
  • new_body::Expr: The struct body with all tags stripped.

Related

source
PortfolioOptimisers.propagatable_register!Function
propagatable_register!(
    T::Type,
    pprops::Tuple{Vararg{Symbol}}
)

Record type T and its @pprop-tagged field names in PROPAGATABLE_CONTRACTS.

Called by @propagatable at the declaration itself. It only records: the outer keyword constructor is written below the struct, so it does not exist yet and cannot be checked here. check_propagatable_contracts does the checking once the module is complete.

Algorithm

  1. Push the pair (T, pprops) onto PROPAGATABLE_CONTRACTS.
  2. Return nothing.

T is @nospecialized, so one method serves every registered type and the registration costs no compilation.

Related

source
PortfolioOptimisers.propagatable_keywordsFunction
propagatable_keywords(T::Type) -> Vector{Symbol}

Return the keyword names accepted by the outer constructors of T, unioned over its methods.

A kwargs... slurp is dropped rather than counted. A slurp accepts field = value and then discards it, which is the silent failure this check exists to catch, so it must not satisfy the contract.

Algorithm

  1. Make kws, an empty vector of symbols.
  2. For each method m of the constructor T, append Base.kwarg_decl(m) to kws. The union runs over every outer constructor, so a keyword that any one of them names counts.
  3. Remove the repeats from kws.
  4. Remove every name whose string ends in ..., which is how Base.kwarg_decl reports a slurp.
  5. Return kws.

Step 4 is the whole point of the function. A constructor that carries kwargs... reports the slurp as a keyword name, and counting it would let every field satisfy the contract.

Related

source
PortfolioOptimisers.propagatable_contract_violationsFunction
propagatable_contract_violations(
    T::Type,
    pprops,
    pool
) -> Vector{String}

Return the broken clauses of one type's @propagatable contract, as messages.

Two clauses are checked, and both are properties of the code the macro emits:

  • Every field name is a keyword of the outer constructor. Each generated method rebuilds the struct with StructName(; field = …) over all fields, tagged or not, so one field that the keyword constructor does not name is a MethodError at the first factory or port_opt_view call.
  • Every @pprop field is a property of a prior result. The generated factory(x, pr::AbstractPriorResult, args...) reads getproperty(pr, :field), so a name absent from prior_result_property_pool throws when a prior is threaded.

The messages carry a suggest_declared_key suggestion, so a transposed or mistyped field name names its intended neighbour.

Algorithm

  1. Make msgs, an empty vector of strings.
  2. Read kws, the keywords of the outer constructors of T, with propagatable_keywords.
  3. For each field name of T that is absent from kws, push a message naming the type, the field and the suggest_declared_key suggestion drawn from kws.
  4. For each name of pprops that is absent from pool, push a message naming the type, the field and the suggestion drawn from pool.
  5. Return msgs.

Every clause is collected, and none stops the walk, so one call reports every violation of one type at once. An empty result means that the type's contract holds.

Related

source
PortfolioOptimisers.check_propagatable_contractsFunction
check_propagatable_contracts()
check_propagatable_contracts(contracts)
check_propagatable_contracts(contracts, pool)

Check the @propagatable contract of every type in PROPAGATABLE_CONTRACTS.

Runs once at the end of the module, so the contract behind every generated method is enforced where the structs are declared rather than at the first factory call. The violations of every type are collected and reported together, because a run that stops at the first one hides the rest. See propagatable_contract_violations for the two clauses.

Throws an ArgumentError listing every violation; the package refuses to precompile rather than shipping a type whose generated methods throw on first use. A package that declares its own @propagatable types calls this at the end of its own module to get the same guarantee.

Algorithm

  1. Read pool, the property names that a prior result can carry, with prior_result_property_pool.
  2. Make msgs, an empty vector of strings.
  3. For each pair (T, pprops) of PROPAGATABLE_CONTRACTS, append the messages that propagatable_contract_violations reports for that type.
  4. msgs is not empty: throw an ArgumentError naming the count and listing every message.
  5. Return nothing.

Step 3 collects and never stops, so one run reports the violations of every registered type. The registry is filled by propagatable_register! at each declaration, so this function must run after the last one, which is why the module calls it at its end.

Both the registry and the pool are arguments, and each defaults to the value the module ships. A caller that passes a registry of its own reads the message a broken contract gives, without registering a broken type.

Arguments

  • contracts: The pairs of a type and its @pprop-tagged field names to check.
  • pool: The property names a prior result can carry.

Returns

  • nothing: Every pair of contracts satisfies the contract.

Related

source
PortfolioOptimisers.forward_nonnothingFunction
forward_nonnothing(v, _::Type{T}, pathstr, nodestr) -> Any

Guard one intermediate node of a @forward_properties nested path.

Return v unchanged when it is not nothing; otherwise throw a PropertyPathError naming the receiver type T, the full declared path pathstr, and the nodestr node that resolved to nothing. Called once per intermediate hop in the descent generated for a depth-≥2 locator.

Algorithm

  1. v is nothing: throw a PropertyPathError whose message names pathstr, the type T and the node nodestr.
  2. v is anything else: return v.

The guard runs on the intermediate hops only, so a path whose last hop gives nothing returns that nothing rather than raising. That is deliberate: an absent leaf is a value, and an absent intermediate is a path that cannot be walked.

Related

source
PortfolioOptimisers.forward_flatten_pathFunction
forward_flatten_path(expr) -> Vector{Symbol}

Flatten a @forward_properties locator into its path of field symbols.

A bare identifier a becomes [:a]; a dotted expression a.b.c becomes [:a, :b, :c]. Any other expression raises an error.

Algorithm

  1. expr is a Symbol: return the one-element vector holding it.
  2. expr is an Expr with head :. and two arguments:
    1. Read leaf, its second argument, and unwrap a QuoteNode to its value.
    2. leaf is not a Symbol: raise an error naming the leaf.
    3. Call this function again on the first argument, and append leaf to the result.
  3. expr is anything else: raise an error naming the expression.

The recursion of step 2.3 is what makes the path any depth: a.b.c parses as (a.b).c, so the walk descends to the bare name and rebuilds the path from the left.

Related

source
PortfolioOptimisers.forward_walk_exprFunction
forward_walk_expr(
    path,
    struct_name,
    broadcast::Bool
) -> Expr

Build the expression that descends a @forward_properties path (a vector of field symbols) on the receiver x, returning the value at the path.

A depth-1 path is a single getfield. A depth-≥2 path descends hop by hop, guarding every intermediate with forward_nonnothing (keyed on the receiver type struct_name) so a nothing node throws a path-naming PropertyPathError. When broadcast is true, the final hop maps over the penultimate value if it is an AbstractVector (the scalar-or-vector solution case), otherwise it is a plain access.

Algorithm

  1. The path holds one name: return getfield(x, name) and stop. getfield is used rather than getproperty, so the generated Base.getproperty never re-enters itself.
  2. Build pathstr, the whole path joined by dots, for the error message.
  3. Start stmts with __v = getfield(x, first_name).
  4. For each further hop k of the path:
    1. Push __v = forward_nonnothing(__v, struct_name, pathstr, nodestr), where nodestr names the part of the path walked so far.
    2. k is the last hop and broadcast is true: push an assignment that reads the leaf with getproperty. when __v is an AbstractVector, and with getproperty otherwise.
    3. Otherwise: push __v = getproperty(__v, leaf).
  5. Push __v as the value of the block.
  6. Return the statements wrapped in a let block, so __v never escapes into the caller.

Step 4.1 runs before every hop after the first, so the guard covers each intermediate exactly once and never the leaf. Only the leaf of step 4.2 broadcasts, so an intermediate vector is still a path error rather than a silent map.

Related

source

Mathematical functions

PortfolioOptimisers.jl makes use of various mathematical operators, some of which are generic to support the variety of inputs supported by the library.

PortfolioOptimisers.:⊗Function
(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}

Tensor product of two arrays. Returns a matrix of size (length(A), length(B)) where each element is the product of elements from A and B.

Mathematical definition

\[\begin{align} (\boldsymbol{a} \otimes \boldsymbol{b})_{ij} &= a_{i} b_{j}\,. \end{align}\]

Where:

  • $\boldsymbol{a}$: Vectorised first array A, of length $n$.
  • $\boldsymbol{b}$: Vectorised second array B, of length $m$.
  • $a_{i}$, $b_{j}$: Entries of $\boldsymbol{a}$ and $\boldsymbol{b}$ in linear index order.

The result is the outer product $\boldsymbol{a} \boldsymbol{b}^\intercal$, an $n \times m$ matrix. A and B may carry any shape, because both are read in linear index order.

Arguments

  • A::ArrNum: First array.
  • B::ArrNum: Second array.

Examples

julia> PortfolioOptimisers.:([1, 2], [3, 4])2×2 Matrix{Int64}: 3  4 6  8

Related

source
PortfolioOptimisers.:⊙Function
(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B) -> promote_type(eltype(A), eltype(B))

Elementwise (Hadamard) multiplication.

Mathematical definition

\[\begin{align} (\boldsymbol{a} \odot \boldsymbol{b})_{i} &= a_{i} b_{i}\,, \\ (\boldsymbol{a} \odot \beta)_{i} &= a_{i} \beta\,, \\ (\alpha \odot \boldsymbol{b})_{i} &= \alpha b_{i}\,, \\ \alpha \odot \beta &= \alpha \beta\,. \end{align}\]

Where:

  • $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
  • $\alpha$, $\beta$: Scalar operands.
  • $i$: Linear index, $i = 1,\ldots,n$.

Both array operands must carry the same length. A scalar operand multiplies every entry of the array operand.

Arguments

  • A: First operand (array or scalar).
  • B: Second operand (array or scalar).

Examples

julia> PortfolioOptimisers.:([1, 2], [3, 4])2-element Vector{Int64}: 3 8julia> PortfolioOptimisers.:([1, 2], 2)2-element Vector{Int64}: 2 4julia> PortfolioOptimisers.:(2, [3, 4])2-element Vector{Int64}: 6 8julia> PortfolioOptimisers.:(2, 3)6

Related

source
PortfolioOptimisers.:⊘Function
(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B) -> promote_type(eltype(A), eltype(B))

Elementwise (Hadamard) division.

Mathematical definition

\[\begin{align} (\boldsymbol{a} \oslash \boldsymbol{b})_{i} &= \frac{a_{i}}{b_{i}}\,, \\ (\boldsymbol{a} \oslash \beta)_{i} &= \frac{a_{i}}{\beta}\,, \\ (\alpha \oslash \boldsymbol{b})_{i} &= \frac{\alpha}{b_{i}}\,, \\ \alpha \oslash \beta &= \frac{\alpha}{\beta}\,. \end{align}\]

Where:

  • $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
  • $\alpha$, $\beta$: Scalar operands.
  • $i$: Linear index, $i = 1,\ldots,n$.

Both array operands must carry the same length. The division is not guarded, so a zero divisor gives an infinity or a NaN.

Arguments

  • A: Dividend (array or scalar).
  • B: Divisor (array or scalar).

Examples

julia> PortfolioOptimisers.:([4, 9], [2, 3])2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.:([4, 6], 2)2-element Vector{Float64}: 2.0 3.0julia> PortfolioOptimisers.:(8, [2, 4])2-element Vector{Float64}: 4.0 2.0julia> PortfolioOptimisers.:(8, 2)4.0

Related

source
PortfolioOptimisers.:⊕Function
(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B) -> promote_type(eltype(A), eltype(B))

Elementwise (Hadamard) addition.

Mathematical definition

\[\begin{align} (\boldsymbol{a} \oplus \boldsymbol{b})_{i} &= a_{i} + b_{i}\,, \\ (\boldsymbol{a} \oplus \beta)_{i} &= a_{i} + \beta\,, \\ (\alpha \oplus \boldsymbol{b})_{i} &= \alpha + b_{i}\,, \\ \alpha \oplus \beta &= \alpha + \beta\,. \end{align}\]

Where:

  • $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
  • $\alpha$, $\beta$: Scalar operands.
  • $i$: Linear index, $i = 1,\ldots,n$.

Both array operands must carry the same length. A scalar operand is added to every entry of the array operand, which the built-in + refuses.

Arguments

  • A: First summand (array or scalar).
  • B: Second summand (array or scalar).

Examples

julia> PortfolioOptimisers.:([1, 2], [3, 4])2-element Vector{Int64}: 4 6julia> PortfolioOptimisers.:([1, 2], 2)2-element Vector{Int64}: 3 4julia> PortfolioOptimisers.:(2, [3, 4])2-element Vector{Int64}: 5 6julia> PortfolioOptimisers.:(2, 3)5

Related

source
PortfolioOptimisers.:⊖Function
(A::ArrNum, B::ArrNum) -> Matrix{promote_type(eltype(A), eltype(B))}
(A::ArrNum, B) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B::ArrNum) -> Vector{promote_type(eltype(A), eltype(B))}
(A, B) -> promote_type(eltype(A), eltype(B))

Elementwise (Hadamard) subtraction.

Mathematical definition

\[\begin{align} (\boldsymbol{a} \ominus \boldsymbol{b})_{i} &= a_{i} - b_{i}\,, \\ (\boldsymbol{a} \ominus \beta)_{i} &= a_{i} - \beta\,, \\ (\alpha \ominus \boldsymbol{b})_{i} &= \alpha - b_{i}\,, \\ \alpha \ominus \beta &= \alpha - \beta\,. \end{align}\]

Where:

  • $\boldsymbol{a}$, $\boldsymbol{b}$: Array operands, read in linear index order.
  • $\alpha$, $\beta$: Scalar operands.
  • $i$: Linear index, $i = 1,\ldots,n$.

Both array operands must carry the same length. A scalar operand is subtracted from every entry of the array operand, which the built-in - refuses.

Arguments

  • A: Minuend (array or scalar).
  • B: Subtrahend (array or scalar).

Examples

julia> PortfolioOptimisers.:([4, 6], [1, 2])2-element Vector{Int64}: 3 4julia> PortfolioOptimisers.:([4, 6], 2)2-element Vector{Int64}: 2 4julia> PortfolioOptimisers.:(8, [2, 4])2-element Vector{Int64}: 6 4julia> PortfolioOptimisers.:(8, 2)6

Related

source
PortfolioOptimisers.dot_scalarFunction
dot_scalar(a::Union{<:Number, <:JuMP.AbstractJuMPScalar}, b::VecNum) -> Number
dot_scalar(a::VecNum, b::Union{<:Number, <:JuMP.AbstractJuMPScalar}) -> Number
dot_scalar(a::VecNum, b::VecNum) -> Number

Efficient scalar and vector dot product utility.

  • If one argument is a Union{<:Number, <:JuMP.AbstractJuMPScalar} and the other an VecNum, returns the scalar times the sum of the vector.
  • If both arguments are VecNums, returns their dot product.

Mathematical definition

\[\begin{align} \mathrm{dot\_scalar}(\alpha, \boldsymbol{b}) &= \alpha \sum_{i=1}^{n} b_{i}\,, \\ \mathrm{dot\_scalar}(\boldsymbol{a}, \beta) &= \beta \sum_{i=1}^{n} a_{i}\,, \\ \mathrm{dot\_scalar}(\boldsymbol{a}, \boldsymbol{b}) &= \boldsymbol{a}^\intercal \boldsymbol{b}\,. \end{align}\]

Where:

  • $\alpha$, $\beta$: Scalar operand, a number or a JuMP scalar.
  • $\boldsymbol{a}$, $\boldsymbol{b}$: Vector operand of length $n$.

The first two forms are the dot product of the vector with a constant vector of value $\alpha$, so the scalar stands for a uniform vector. The sum replaces that constant vector, so no $n$-length array is built.

Arguments

  • a: First operand, a scalar or a vector.
  • b: Second operand, a scalar or a vector.

Returns

  • res::Number: The resulting scalar.

Examples

julia> PortfolioOptimisers.dot_scalar(2.0, [1.0, 2.0, 3.0])12.0julia> PortfolioOptimisers.dot_scalar([1.0, 2.0, 3.0], 2.0)12.0julia> PortfolioOptimisers.dot_scalar([1.0, 2.0, 3.0], [4.0, 5.0, 6.0])32.0

Related

source

View functions

NestedClustered optimisations need to index the asset universe in order to produce the inner optimisations. These indexing operations are implemented as views, indexing, and custom index generators.

PortfolioOptimisers.nothing_scalar_array_viewFunction
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_orderFunction
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_getindexFunction
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_orderFunction
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_generatorFunction
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

Summary statistics

Some estimators and constraints are based on summary statistics of vectors. These types are used to dispatch the appropriate functions and encapsulate auxiliary data such as weights.

PortfolioOptimisers.Num_VecToScaMType
const Num_VecToScaM = Union{<:Number, <:VectorToScalarMeasure, <:Function}

Union type representing a numeric value, a VectorToScalarMeasure, or a Function.

This type lets functions and fields accept all three, so a caller can give a fixed number, an object that implements the VectorToScalarMeasure interface, or a plain reduction function. vec_to_real_measure returns a Number unchanged, dispatches a VectorToScalarMeasure to its reduction, and applies a Function to the vector.

Related

source