Sienna-Platform / Sienna-Platform/InfrastructureOptimizationModels.jl
OptimizationContainer's type-erased storage forces per-timestep dynamic dispatch in consumer build loops
Nobody has claimed this yet.
- Dominant language
- Julia
- Stars
- 1
- Forks
- 0
- Avg merge
- 8h 51m
- Merged PRs (30d)
- 9
Description
Summary
OptimizationContainer's field containers are type-erased, so every per-timestep read of a variable, parameter, or expression in a consumer's build loop is an ::Any access resolved by dynamic dispatch. This is the dominant type-instability in the optimization stack — every other hot-path finding downstream compounds through it.
Details
src/core/optimization_container.jl:74-80:
variables/aux_variables/duals/constraints/expressionsareOrderedDict{<KeyType>, JuMPArray}whereJuMPArray = Union{JuMP.Containers.DenseAxisArray, JuMP.Containers.SparseAxisArray}(src/core/definitions.jl:80) — the union carries no element/axis parameters, so retrieval erases everything.parameters::OrderedDict{ParameterKey, ParameterContainer}(:80) storesParameterContainer{T,U,A}values with the parameters stripped.default_time_series_type::Type(:95) is an abstractTypefield; every consumer keying off it (get_default_time_series_typecall sites in parameter/expression construction) pays a method lookup per call.
Consequence: get_variable / get_parameter / get_expression return values whose type parameters are gone, so indexing like multiplier[name, t] or variable[name, t] inside for t in time_steps loops returns ::Any and every downstream operation redispatches dynamically per (device, timestep).
Measured with a faithful minimal reproduction of the ParameterContainer{T,U,A}-in-erased-dict shape (Julia 1.12.5): Base.return_types gives Any on the read path, and a 200-iteration read loop allocates 6480 bytes vs 0 bytes for the concretely-typed equivalent.
Secondary, same mechanism at lower multiplicity:
Dict{String, AbstractArray}staging in consumers'_add_time_series_parameters!-style code erases the concreteVector{Float64}the accessors actually return, forcing per-step redispatch into_set_parameter_at!(which itself has clean concrete dispatch arms — the callers hand itAny).
Suggested fix direction
This is architecture-level, not a local patch:
- A typed per-
(EntryType, ComponentType)cache — the dict values become concretely parameterized per key type, function-barrier style, so the container hand-off into the timestep loop specializes; or - A return-type-asserting barrier at the
get_variable/get_parameter/get_expressionboundary (callers state the expected concrete container type), keeping the storage erased but the loop bodies monomorphic; and - Parameterize the container (or thread a
Type{T}/Val{T}argument) fordefault_time_series_typeinstead of the abstractTypefield.
Found during a type-stability audit of the time-series chain (details reproducible with @code_warntype on any consumer build-loop read).
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with src/core/optimization_container.jl:74-80 and :95, then inspect the JuMPArray definition in src/core/definitions.jl:80 and the get_variable, get_parameter, and get_expression call sites in consumer build loops. Use @code_warntype and the stated minimal reproduction to trace the erased types and allocations. Done means the selected architecture removes the per-timestep Any dispatch while preserving the container and consumer behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- julia
- Domain
- backend, performance
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 28/100