Skip to content

Consolidate add_to_expression! device-injection methods; fix AreaBalance bugs#135

Open
luke-kiernan wants to merge 2 commits into
mainfrom
lk/add-to-expression-consolidation
Open

Consolidate add_to_expression! device-injection methods; fix AreaBalance bugs#135
luke-kiernan wants to merge 2 commits into
mainfrom
lk/add-to-expression-consolidation

Conversation

@luke-kiernan

@luke-kiernan luke-kiernan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

In the initial PSI into IOM+POM split, the 90+ variations on add_to_expression! was one thing where I told myself "I'll fix this properly later." This PR is to get the ball rolling on that fix: refactor so as to minimize code spaghetti and ensure good test coverage.

Consolidates the device-injection family of add_to_expression! methods onto a shared, network-model-aware target resolver (_balance_expression_targets) plus IOM's new add_device_terms_to_expression! driver. ~17 hand-rolled device × time loops collapse to thin delegations. Net −81 lines in add_to_expression.jl.

Depends on IOM PR Sienna-Platform/InfrastructureOptimizationModels.jl#112 (the driver), which targets IOM main.

What's consolidated

  • variable → balance (StaticInjection), across nodal / AreaBalance / CopperPlate / PTDF / AreaPTDF.
  • parameter → balance (TimeSeriesParameter, ElectricLoad TS-param, MotorLoad constant, OnStatusParameter) for nodal / AreaBalance / CopperPlate.
  • compact-UC OnVariable: a single resolver-based _add_compact_on_to_balance! (must-run aware) replaces the nodal-only _add_to_expression! and both AreaBalance variants; CopperPlate/PTDF use a new scale kwarg on _add_variable_to_balance! for the per-device p_min.

PTDF parameter methods are intentionally left out (their AbstractPTDFModel/security-constrained semantics differ from the variable family and need a separate decision).

Bug fixes (port of PSI 22d2059c2, which POM missed by branching before it — see #134)

  • must-run compact-UC OnVariable called a non-existent 2-arg add_proportional_to_jump_expression!MethodError. Now add_constant_to_jump_expression! (On≡1 → P_min is a constant).
  • OnVariable/ThermalGen + AreaBalance indexed an ACBus container AreaBalance never creates → KeyError. Now PSY.Area.
  • FlowActivePowerToFromVariable/TwoTerminalHVDC + AreaBalance used an undefined network_reduction and unbound WUndefVarError. Now Area-indexed, HVDCTwoTerminalDispatch.
  • compact-UC OnVariable under AreaBalance/AreaPTDF: routing through the resolver fixes a KeyError (AreaBalance) and a silent missing-area-contribution (AreaPTDF, where the generic method previously added to nodal only).

Type stability / perf

The driver's inner loop is allocation-free even on heterogeneous target tuples (AreaPTDF mixes a String-keyed area expression and an Int-keyed nodal expression) via Base.tail recursion. Cold build time unchanged.

Tests

Constructor suites pass against IOM main + the driver branch (thermal/load/renewable/network/branch/HVDC; 5244/5244 on the thermal+network subset). The fixed paths above are currently unexercised by any test system — tracked in #134 for follow-up coverage.

Follow-ups

🤖 Generated with Claude Code

…nce bugs

Refactors the device-injection family of add_to_expression! methods to share a
network-model-aware target resolver (_balance_expression_targets) plus IOM's
add_device_terms_to_expression! driver, replacing ~17 hand-rolled device/time
loops with thin delegations. Covers the variable->balance, parameter->balance
(nodal/area/system), and compact-UC OnVariable families.

Also ports PSI fix 22d2059c2 and related corrections POM missed by branching
before it (see #134):
- must-run compact-UC OnVariable called a non-existent 2-arg
  add_proportional_to_jump_expression! (MethodError); now add_constant_... .
- OnVariable/ThermalGen + AreaBalance indexed an ACBus container AreaBalance
  never creates (KeyError); now PSY.Area.
- FlowActivePowerToFromVariable/TwoTerminalHVDC + AreaBalance used an undefined
  network_reduction and unbound W (UndefVarError); now Area-indexed with
  HVDCTwoTerminalDispatch.
- compact-UC OnVariable under AreaBalance/AreaPTDF now routes through the
  resolver, fixing a KeyError (AreaBalance) and a silent missing-area
  contribution (AreaPTDF).

IOM is pinned to the lk/add-device-terms-driver branch until its PR merges into
ac/canonical-key-component-type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors device-injection add_to_expression! methods by centralizing network-model-specific balance target resolution into _balance_expression_targets and delegating device×time looping to IOM’s add_device_terms_to_expression!, while also porting several AreaBalance/compact-UC bug fixes that previously caused runtime errors or mis-indexing.

Changes:

  • Added _balance_expression_targets plus shared helper drivers (_add_variable_to_balance!, _add_ts_parameter_to_balance!, _add_compact_on_to_balance!, etc.) to reduce duplicated injection loops across network models.
  • Fixed multiple AreaBalance bugs (incorrect expression container indexing, undefined variables/types) and corrected compact-UC must-run handling.
  • Temporarily repinned InfrastructureOptimizationModels to a driver branch to pick up the new IOM driver dependency.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/common_models/add_to_expression.jl Introduces shared balance-target resolver + helper adders; migrates many injection methods to the IOM driver; fixes AreaBalance/compact-UC/HVDC injection bugs.
Project.toml Repins IOM source to lk/add-device-terms-driver and updates pinning notes (one TODO comment remains inconsistent).
test/Project.toml Same IOM repin for the test environment, with corresponding pinning comments (one TODO comment remains inconsistent).
Comments suppressed due to low confidence (1)

src/common_models/add_to_expression.jl:1102

  • This FlowActivePowerToFromVariable/TwoTerminalHVDC + AreaBalancePowerModel path was previously failing (undefined vars / wrong indexing) and appears to be a bug-fix. It would be good to add a regression test that builds an AreaBalancePowerModel with a TwoTerminalHVDC line and verifies the area balance receives the flow term, so this doesn’t silently regress again.
function add_to_expression!(
    container::OptimizationContainer,
    ::Type{T},
    ::Type{U},
    devices::IS.FlattenIteratorWrapper{V},
    ::DeviceModel{V, HVDCTwoTerminalDispatch},
    network_model::NetworkModel{AreaBalancePowerModel},
) where {
    T <: ActivePowerBalance,
    U <: FlowActivePowerToFromVariable,
    V <: PSY.TwoTerminalHVDC,
}
    variable = get_variable(container, U, V)
    expression = get_expression(container, T, PSY.Area)
    time_steps = get_time_steps(container)
    for d in devices
        name = PSY.get_name(d)
        area_name = PSY.get_name(PSY.get_area(PSY.get_arc(d).to))
        for t in time_steps
            add_proportional_to_jump_expression!(
                expression[area_name, t],
                variable[name, t],
                get_variable_multiplier(U, V, HVDCTwoTerminalDispatch),
            )
        end
    end
    return

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/common_models/add_to_expression.jl Outdated
Comment on lines +95 to +97
::DeviceModel{V, W};
scale::Union{Nothing, Function} = nothing,
) where {T <: ExpressionType, U <: VariableType, V <: PSY.StaticInjection, W}
Comment thread test/Project.toml Outdated
Comment thread src/common_models/add_to_expression.jl
@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

Performance Results

Version Precompile Time
Main 2.963144802
This Branch 3.030093972
Version Build Time
Main-Build Time Precompile 89.567351589
Main-Build Time Postcompile 1.993001366
This Branch-Build Time Precompile 85.860877729
This Branch-Build Time Postcompile 1.779423332
Version Solve Time
Main-Solve Time Precompile FAILED TO TEST
Main-Solve Time Postcompile FAILED TO TEST
This Branch-Solve Time Precompile FAILED TO TEST
This Branch-Solve Time Postcompile FAILED TO TEST

Comment thread src/common_models/add_to_expression.jl Outdated
devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}},
network_model::NetworkModel,
::DeviceModel{V, W};
scale::Union{Nothing, Function} = nothing,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iirc positional args are better than keyword args for compilation purposes

# - AreaBalancePowerModel -> area only
# - PTDFPowerModel/AreaPTDF... -> system/area + nodal bus
#################################################################################

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. Dramatically better, but still some redundacy here.

  1. First thing in return value in _balance_expression_targets is always get_expression(container, T, [something])
  2. Final version of _balance_expression_targets is basically combination of nodal + area. Also it's the only spot where _system_expression_type is called anywhere in this file.
  3. All the _add_foo_to_balance! functions only differ in what they pass as the 3rd argument:
add_device_terms_to_expression!(container,
        d -> _balance_expression_targets(container, T, network_model, d),
        function (d)
           ... # this differs
        end,
        devices,
 )

That being said, I'm not sure how feasible it is to captialize on this: we do want the get_{variable/multiplier/parameter} calls to happen once, not per-device.

- Make `scale` a positional arg bound to type parameter `S` instead of a
  `Union{Nothing, Function}` keyword, so the concrete closure type is known
  at compile time (no dynamic dispatch on `scale(d)`).
- Extract shared `add_device_terms_to_expression!` boilerplate into
  `_add_balance_terms!`, leaving each `_add_*_to_balance!` to supply only its
  per-device term closure (getter lookups still hoisted, once per call).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants