Skip to content

Add add_device_terms_to_expression! driver for device-injection methods#112

Open
luke-kiernan wants to merge 2 commits into
mainfrom
lk/add-device-terms-driver
Open

Add add_device_terms_to_expression! driver for device-injection methods#112
luke-kiernan wants to merge 2 commits into
mainfrom
lk/add-device-terms-driver

Conversation

@luke-kiernan

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

Copy link
Copy Markdown
Collaborator

What

Adds add_device_terms_to_expression! — a generic driver for device-injection add_to_expression! methods — plus its internal _apply_term_to_targets! helper, and exports the driver.

Why

Downstream (POM) has a large family of add_to_expression! methods that are the same device × time loop differing only in (a) which expression entries a device contributes to (network-model dependent) and (b) the variable/parameter/constant term. This driver factors out the loop, so those methods collapse to a small target resolver + a term closure. The POM-side consolidation lands separately (POM #135).

Design

  • targets_fn(d) returns a 1- or 2-element tuple of (expression_matrix, row_index) targets (one for single-bus/area/system models; two for PTDF/AreaPTDF).
  • term_fn(d) returns a per-device t -> (value, multiplier) closure.
  • _apply_term_to_targets! consumes the targets via Base.tail recursion rather than a for loop: this stays type-stable and allocation-free even when the tuple is heterogeneous (AreaPTDF mixes a String-keyed area expression and an Int-keyed nodal expression, which a for loop would box). Verified identical native codegen and 0 allocations.

Target branch

Into main. (Additive — the two touched files are identical between main and ac/canonical-key-component-type, so it applies cleanly to either.)

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

Performance Results
Main

Network: 10 nodes, 13 edges, 3 cost segments
Generators: 5, Demands: 5
Loss coefficients (a, b, c) per generator:
  n1: a=0.007287  b=0.005968  c=0.003850
  n2: a=0.001094  b=0.009002  c=0.007028
  n3: a=0.007897  b=0.008552  c=0.003001
  n4: a=0.008179  b=0.002530  c=0.009637
  n5: a=0.009778  b=0.009934  c=0.009099

Solver logs: /home/runner/work/InfrastructureOptimizationModels.jl/InfrastructureOptimizationModels.jl/main/test/performance/logs/solver_2026-06-09T17-31-55.log

==============================================================================================================================================
Bilinear Approximation Benchmarks
  Refinement = depth for all methods
==============================================================================================================================================
Method          R   Vars Constrs   Bins    Objective   Gap(%) MIPGap(%)     LowerBnd  rmse δbi   max δbi   rmse δq    max δq  build_t  solve_t
----------------------------------------------------------------------------------------------------------------------------------------------
NLP (Ipopt)     -     40     105      0     0.956760        -         -            -  0.00e+00  0.00e+00  0.00e+00  0.00e+00   0.0347   0.0046

NLP (Uno)       -     40     105      0     0.956760   0.0000         -            -  0.00e+00  0.00e+00  0.00e+00  0.00e+00   0.0014   0.0061

Bin2+sSOS       4    190     535      0     1.180700  23.4061    0.0000     1.180700  8.07e-02  1.41e-01  2.49e+01  5.48e+01   0.0061   1.8311
Bin2+sSOS       6    250     655      0     1.098175  14.7806    0.0000     1.098175  1.26e-01  2.86e-01  5.47e+00  9.07e+00   0.0059   7.6098
Bin2+sSOS       8    310     775      0     1.053069  10.0661    0.0088     1.052976  8.62e-02  2.43e-01  4.18e+01  9.32e+01   0.0061  26.8613

Bin2+mSOS       4    310     805    120     1.180700  23.4061    0.0000     1.180700  8.07e-02  1.41e-01  2.49e+01  5.48e+01   0.0025   1.7945
Bin2+mSOS       6    430    1045    180     1.098175  14.7806    0.0000     1.098175  1.26e-01  2.86e-01  5.47e+00  9.07e+00   0.0025   5.8416
Bin2+mSOS       8    550    1285    240     1.053069  10.0661    0.0080     1.052984  8.62e-02  2.43e-01  4.18e+01  9.32e+01   0.0027  19.2438

Bin2+Saw        4    310    1075    120     0.985859   3.0414    0.0055     0.985805  1.15e-01  2.48e-01  2.65e+02  5.18e+02   0.0028  11.7262
Bin2+Saw        6    430    1495    180     0.958677   0.2004    0.0077     0.958603  7.52e-02  1.68e-01  8.94e+01  1.71e+02   0.0031  13.8386
Bin2+Saw        8    550    1915    240     0.956996   0.0247    0.0072     0.956927  7.52e-02  1.68e-01  1.37e+02  2.09e+02   0.0034  50.5614

HybS+sSOS       4    310    1165      0     0.812261  15.1030    0.0000     0.812261  5.52e-01  1.00e+00  2.04e+02  2.97e+02   0.0070   2.7335
HybS+sSOS       6    410    1545      0     0.891208   6.8515    0.0000     0.891208  5.49e-01  1.00e+00  3.69e+02  7.09e+02   0.0067   4.9199
HybS+sSOS       8    510    1925      0     0.934789   2.2965    0.0087     0.934707  5.48e-01  1.00e+00  3.47e+02  5.22e+02   0.0078  15.8334

HybS+mSOS       4    390    1345     80     0.812261  15.1030    0.0046     0.812224  5.52e-01  1.00e+00  2.04e+02  2.97e+02   0.0032   2.8913
HybS+mSOS       6    530    1805    120     0.891208   6.8515    0.0000     0.891208  5.49e-01  1.00e+00  3.69e+02  7.09e+02   0.0037   6.3382
HybS+mSOS       8    670    2265    160     0.934789   2.2965    0.0047     0.934744  5.48e-01  1.00e+00  3.47e+02  5.22e+02   0.0039  16.4221

HybS+Saw        4    390    1525     80     0.951869   0.5113    0.0084     0.951789  5.48e-01  1.00e+00  2.16e+03  4.23e+03   0.0034  10.6113
HybS+Saw        6    530    2105    120     0.956386   0.0391    0.0099     0.956291  5.48e-01  1.00e+00  4.57e+02  7.57e+02   0.0040  33.9572
HybS+Saw        8    670    2685    160     0.956734   0.0027    0.0078     0.956659  5.48e-01  1.00e+00  6.08e+00  1.07e+01   0.0047 102.6344

DNMDT           4    395    1640     80     0.954878   0.1967    0.0004     0.954874  1.39e-03  3.13e-03  4.70e-04  1.05e-03   0.0030   4.1882
DNMDT           6    550    2315    120     0.956636   0.0130    0.0098     0.956541  6.75e-05  1.57e-04  4.26e+03  9.54e+03   0.0036  18.0040
DNMDT           8    705    2990    160     0.956754   0.0007    0.0096     0.956662  4.62e-06  1.14e-05  9.42e+01  1.49e+02   0.0039  56.5630

==============================================================================================================================================

This branch

Network: 10 nodes, 13 edges, 3 cost segments
Generators: 5, Demands: 5
Loss coefficients (a, b, c) per generator:
  n1: a=0.007287  b=0.005968  c=0.003850
  n2: a=0.001094  b=0.009002  c=0.007028
  n3: a=0.007897  b=0.008552  c=0.003001
  n4: a=0.008179  b=0.002530  c=0.009637
  n5: a=0.009778  b=0.009934  c=0.009099

Solver logs: /home/runner/work/InfrastructureOptimizationModels.jl/InfrastructureOptimizationModels.jl/branch/test/performance/logs/solver_2026-06-09T17-40-15.log

==============================================================================================================================================
Bilinear Approximation Benchmarks
  Refinement = depth for all methods
==============================================================================================================================================
Method          R   Vars Constrs   Bins    Objective   Gap(%) MIPGap(%)     LowerBnd  rmse δbi   max δbi   rmse δq    max δq  build_t  solve_t
----------------------------------------------------------------------------------------------------------------------------------------------
NLP (Ipopt)     -     40     105      0     0.956760        -         -            -  0.00e+00  0.00e+00  0.00e+00  0.00e+00   0.0350   0.0045

NLP (Uno)       -     40     105      0     0.956760   0.0000         -            -  0.00e+00  0.00e+00  0.00e+00  0.00e+00   0.0015   0.0056

Bin2+sSOS       4    190     535      0     1.180700  23.4061    0.0000     1.180700  8.07e-02  1.41e-01  2.49e+01  5.48e+01   0.0061   1.8404
Bin2+sSOS       6    250     655      0     1.098175  14.7806    0.0000     1.098175  1.26e-01  2.86e-01  5.47e+00  9.07e+00   0.0061   7.6209
Bin2+sSOS       8    310     775      0     1.053069  10.0661    0.0088     1.052976  8.62e-02  2.43e-01  4.18e+01  9.32e+01   0.0060  26.9939

Bin2+mSOS       4    310     805    120     1.180700  23.4061    0.0000     1.180700  8.07e-02  1.41e-01  2.49e+01  5.48e+01   0.0025   1.7847
Bin2+mSOS       6    430    1045    180     1.098175  14.7806    0.0000     1.098175  1.26e-01  2.86e-01  5.47e+00  9.07e+00   0.0025   5.8413
Bin2+mSOS       8    550    1285    240     1.053069  10.0661    0.0080     1.052984  8.62e-02  2.43e-01  4.18e+01  9.32e+01   0.0026  19.2261

Bin2+Saw        4    310    1075    120     0.985859   3.0414    0.0055     0.985805  1.15e-01  2.48e-01  2.65e+02  5.18e+02   0.0028  11.7048
Bin2+Saw        6    430    1495    180     0.958677   0.2004    0.0077     0.958603  7.52e-02  1.68e-01  8.94e+01  1.71e+02   0.0032  13.8473
Bin2+Saw        8    550    1915    240     0.956996   0.0247    0.0072     0.956927  7.52e-02  1.68e-01  1.37e+02  2.09e+02   0.0034  51.0024

HybS+sSOS       4    310    1165      0     0.812261  15.1030    0.0000     0.812261  5.52e-01  1.00e+00  2.04e+02  2.97e+02   0.0075   2.7793
HybS+sSOS       6    410    1545      0     0.891208   6.8515    0.0000     0.891208  5.49e-01  1.00e+00  3.69e+02  7.09e+02   0.0074   4.9531
HybS+sSOS       8    510    1925      0     0.934789   2.2965    0.0087     0.934707  5.48e-01  1.00e+00  3.47e+02  5.22e+02   0.0074  15.8753

HybS+mSOS       4    390    1345     80     0.812261  15.1030    0.0046     0.812224  5.52e-01  1.00e+00  2.04e+02  2.97e+02   0.0031   2.9095
HybS+mSOS       6    530    1805    120     0.891208   6.8515    0.0000     0.891208  5.49e-01  1.00e+00  3.69e+02  7.09e+02   0.0036   6.3447
HybS+mSOS       8    670    2265    160     0.934789   2.2965    0.0047     0.934744  5.48e-01  1.00e+00  3.47e+02  5.22e+02   0.0041  16.3634

HybS+Saw        4    390    1525     80     0.951869   0.5113    0.0084     0.951789  5.48e-01  1.00e+00  2.16e+03  4.23e+03   0.0034  10.5924
HybS+Saw        6    530    2105    120     0.956386   0.0391    0.0099     0.956291  5.48e-01  1.00e+00  4.57e+02  7.57e+02   0.0041  33.9683
HybS+Saw        8    670    2685    160     0.956734   0.0027    0.0078     0.956659  5.48e-01  1.00e+00  6.08e+00  1.07e+01   0.0047 102.5823

DNMDT           4    395    1640     80     0.954878   0.1967    0.0004     0.954874  1.39e-03  3.13e-03  4.70e-04  1.05e-03   0.0029   4.1635
DNMDT           6    550    2315    120     0.956636   0.0130    0.0098     0.956541  6.75e-05  1.57e-04  4.26e+03  9.54e+03   0.0034  17.9684
DNMDT           8    705    2990    160     0.956754   0.0007    0.0096     0.956662  4.62e-06  1.14e-05  9.42e+01  1.49e+02   0.0037  56.4344

==============================================================================================================================================

Generic driver that applies a per-(device, time) proportional term to one or
two balance-expression targets, separating the network-model axis (targets_fn)
from the variable/parameter/constant source (term_fn). This lets downstream
add_to_expression! methods share a single device/time loop instead of
re-implementing it. Stays type-stable / allocation-free on heterogeneous target
tuples (e.g. AreaPTDF's String-keyed area expression plus Int-keyed nodal
expression) via Base.tail recursion over the statically-sized targets tuple.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@luke-kiernan luke-kiernan force-pushed the lk/add-device-terms-driver branch from 0ca9dc3 to 42ccd82 Compare June 9, 2026 17:24
@luke-kiernan luke-kiernan changed the base branch from ac/canonical-key-component-type to main June 9, 2026 17:24
@luke-kiernan

luke-kiernan commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

I intend to add tests, but I'm waiting until I see if Jose is on board with how I've refactored things in the companion POM PR. I'm using closures, which we usually try to avoid.

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

Adds a new exported helper API to factor out the common device × time looping pattern used by downstream “device injection” add_to_expression! implementations, centralizing the looping logic in IOM while letting downstream code provide target selection and per-device term generation.

Changes:

  • Export add_device_terms_to_expression! from the main module.
  • Add add_device_terms_to_expression! generic driver to apply per-device/per-time proportional terms to 1–2 expression targets.
  • Add internal _apply_term_to_targets! helper using tuple tail recursion to keep heterogeneous target tuples type-stable.

Reviewed changes

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

File Description
src/InfrastructureOptimizationModels.jl Exports the new public driver function.
src/common_models/add_jump_expressions.jl Implements the new driver and its internal target-application helper.

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

Comment on lines +100 to +101
# nodal entry plus a system/area entry). Tail recursion ensures type stability for
# the hetereogeneous length 2 tuples.
Comment on lines +102 to +110
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,
@jd-lara

jd-lara commented Jun 14, 2026

Copy link
Copy Markdown
Member

Lets merge this once POM is cleared up from the pending work

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.

4 participants