Skip to content

Problem Scaling - #1403

Open
eranschweitzer wants to merge 9 commits into
sylvan-energy:developfrom
eranschweitzer:scaling
Open

Problem Scaling#1403
eranschweitzer wants to merge 9 commits into
sylvan-energy:developfrom
eranschweitzer:scaling

Conversation

@eranschweitzer

Copy link
Copy Markdown
Contributor

This merge request allows to scale power/energy quantities and cost/dollar quantities by a factor.
For example, passing --power_scale_factor 1000 to the run command will convert all MW and MWh quantities to GW and GWh quantities. Passing --dollar_scale_factor 1000000 will scale dollars to millions of dollars.

There are two modes possible:
*--scale_mode out_of_place creates a scaled clone of the model.
*--scale_mode in_place modifies the pyomo model in place (can be a bit faster).

NOTE: this only impacts the problem seen by the solver (HiGHs, gurobi, etc.). The pyomo model is transformed and the re-transformed so solution values do not change.

As part of the testing, all the example tests are re-run with scaling. The tests are setup, such that any new example test will automatically be inherited by the scaling tests. This way, in the event that a new feature breaks under scaling, as long as an example test is written for it, this will be flagged.

Finally, note this does not scale other quantities like MMBTU etc. but it would be a fairly trivial extension to do so in the future if/when desired.

eranschweitzer and others added 9 commits July 1, 2026 21:47
Makes it possible for a transmission line to have no lower or upper limit.
In these cases, the constraints are skipped for that line and period.
This is useful for modeling lines where the flow capacity is known to
not be a limiting factor. An example may be an radial connection where
congestion in or out is not expected to occur.

This is also useful for using the simultaneous flow constraints to model
PTDFs by splitting up zones into multiple sub-zones with unlimited
radial connection to the "main" zone.
Add --power_scale_factor and --dollar_scale_factor run arguments that
solve the model in scaled units (e.g. GW/GWh, millions of dollars) for
better solver conditioning, then map the solution back to native units.

Scaling is an exact affine reformulation applied via Pyomo's
core.scale_model transformation, driven by a scaling_factor Suffix that
gridpath/auxiliary/scaling.py builds from GridPath's naming conventions
(power/energy vars, dollar vars, integer vars left unscaled; constraint
factors inferred from their variables). Because the reformulation is
exact for any positive factor assignment, the classifier only affects
conditioning, never the optimum.

solve_problem clones the built instance, solves the scaled clone, and
maps values and duals back onto the original (native-unit) instance, so
all downstream export/objective/dual code is unchanged. The default
(1.0, 1.0) is an exact no-op that skips the clone entirely. A custom
propagate_scaled_solution tolerates constraints the solver leaves
without a dual (Pyomo's own propagate_solution raises on those).

Co-Authored-By: Claude <noreply@anthropic.com>
Fast, DB-free tests for gridpath/auxiliary/scaling.py: variable-unit
classification, factor assignment (container granularity, integer vars
left unscaled, objective/constraint factors), exact solve equivalence
including duals via the real solve_problem path, the no-op default path,
and the guard rejecting scaling combined with load-solution / lp-only
flags.

Co-Authored-By: Claude <noreply@anthropic.com>
TestScaledExamples subclasses TestExamples and re-runs every example
scenario with scaling on, checking the objective still matches the
stored expected value to a relative tolerance (1e-6) instead of the
parent's absolute places=1 (which only holds for bit-deterministic
unscaled runs). It overrides only the comparison hook, so it inherits
the test database setup and all example test_* methods automatically --
any example that later breaks under scaling fails here without being
added by hand.

Co-Authored-By: Claude <noreply@anthropic.com>
Degenerate LPs can settle on a different equally-optimal vertex once
scaling perturbs the coefficients, especially scenarios dominated by
commodity chains the classifier leaves unscaled (fuel, emissions,
water). The observed worst case across the example suite is ~2.85e-6
(test_new_solar_carbon_cap_2zones_tx_hydrogen_prod_new), which exceeded
the initial 1e-6. 1e-5 covers it with margin while real breakage (a
crash or a gross objective difference) still fails loudly.

Co-Authored-By: Claude <noreply@anthropic.com>
The default out-of-place scaling clones the built model (create_using),
which for large models is a significant fraction of setup time and
doubles peak memory. Add --scale_mode {out_of_place,in_place}: in_place
scales the model itself (apply_to, rename=False), solves it, then
restores native units via invert_scaled_solution_in_place -- reversing
the objective substitution and dividing out the objective factor,
un-scaling variable values, and rescaling duals (skipping any the solver
left unset). No clone, ~half the peak memory.

Both modes produce identical native-unit results; test_scaling.py checks
the equivalence directly, and TestScaledExamples gains a SCALE_MODE
class attribute so the full example sweep can be run through either path
by subclassing. Default remains out_of_place (keeps the original model
pristine).

Co-Authored-By: Claude <noreply@anthropic.com>
# Conflicts:
#	gridpath/run_scenario.py
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.

2 participants