Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/InfrastructureOptimizationModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ export add_to_expression!
export add_constant_to_jump_expression!
export add_proportional_to_jump_expression!
export add_linear_to_jump_expression!
export add_device_terms_to_expression!
# Cost term helpers (generic objective function building blocks)
export add_cost_term_to_expression!
export add_cost_term_invariant!
Expand Down
60 changes: 60 additions & 0 deletions src/common_models/add_jump_expressions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,63 @@ function add_linear_to_jump_expression!(
add_proportional_to_jump_expression!(expression, var, multiplier)
return
end

"""
Generic driver for device-injection `add_to_expression!` methods.

For each device in `devices` and each time step, adds a proportional term to one or
more expression entries. Two closures separate the axes that distinguish these
methods, so the network-model and the term-source can vary independently while the
loop itself is written once:

- `targets_fn(d)` returns a 1- or 2-element tuple of `(expression_matrix, row_index)`
targets identifying which expression entries device `d` contributes to: one entry
for single-bus/area/system network models, two for PTDF/AreaPTDF (a nodal entry
plus a system/area entry). This is where the network-model dependence lives.
- `term_fn(d)` returns a per-device closure `t -> (value, multiplier)` giving the
term added at each time step, where `value` is a JuMP variable/parameter
reference or a `Float64` constant and `multiplier::Float64`. This is where the
variable/parameter/constant source lives. Per-device setup (name lookups, bounds,
warnings) belongs in `term_fn` so it runs once per device rather than per step.

The same `value * multiplier` term is added to every target via
[`add_proportional_to_jump_expression!`](@ref).
"""
function add_device_terms_to_expression!(
container::OptimizationContainer,
targets_fn::F,
term_fn::G,
devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}},
) where {F <: Function, G <: Function, D}
time_steps = get_time_steps(container)
for d in devices
targets = targets_fn(d)
term = term_fn(d)
for t in time_steps
value, multiplier = term(t)
_apply_term_to_targets!(targets, value, multiplier, t)
end
end
return
end

# Apply a term to each `(expression_matrix, row_index)` target. A device contributes to
# either one target (single-bus/area/system network models) or two (PTDF/AreaPTDF: a
# nodal entry plus a system/area entry). Tail recursion ensures type stability for
# the hetereogeneous length 2 tuples.
Comment on lines +100 to +101
const _BalanceTermValue = Union{Float64, JuMP.AbstractJuMPScalar}

_apply_term_to_targets!(::Tuple{}, ::_BalanceTermValue, ::Float64, ::Int) = nothing

function _apply_term_to_targets!(
targets::Tuple,
value::_BalanceTermValue,
multiplier::Float64,
t::Int,
Comment on lines +102 to +110
)
expression, row = targets[1]
add_proportional_to_jump_expression!(expression[row, t], value, multiplier)
# perf note: only called with length 1 or 2 tuples, but writing separate methods
# has no advantage, because compiler unrolls the tail recursion.
return _apply_term_to_targets!(Base.tail(targets), value, multiplier, t)
end
Loading