Skip to content

Translate a committable Link into a ConversionUnitBlock, and the carriers of the buses into those of the UCBlock - #69

Open
dmeoli wants to merge 26 commits into
SPSUnipi:mainfrom
dmeoli:feature/conversion-unit-block
Open

dmeoli wants to merge 26 commits into
SPSUnipi:mainfrom
dmeoli:feature/conversion-unit-block

Conversation

@dmeoli

@dmeoli dmeoli commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

On main with #73. With the SMS++ of develop of 2026-10-30 (smspp_ucblock_solver, smspp_investmentblock_solver, smspp_tssb_solver, smspp_mssb_solver), PyPSA 1.2.3 and pySMSpp 0.1.0, the whole suite gives 141 passed, 103 skipped (the cases the InvestmentBlock sweep leaves out, and the carrier form of the networks whose buses are all electric), 0 failed.

With the new option enable_conversion_units of Transformation (off by default), a committable Link becomes a ConversionUnitBlock of UCBlock: one commitment, one generator per port, each on the node of its bus. The input is a generator on bus0 with power -p0, each output k a generator on bus k with power efficiency_k p0, tied by the rows efficiency_k p_0 + p_k = 0. All the other links, multi-links included, remain (hyper)arcs as before. Time-varying efficiencies (written at the arrival instant), delays (in snapshots, cyclic or not) and modules (p_nom_mod: the commitment counts the modules on) are translated as well. An extendable committable link has a design variable in the UCBlock form (modules built if modular, else a continuous capacity with big-M rows), read back as p_nom_opt; in the InvestmentBlock form it is a 1 MW unit scaled by its design, as a ThermalUnitBlock.

PyPSA ConversionUnitBlock
p_nom, p_min_pu, p_max_pu MinPower, MaxPower of every port
marginal_cost, marginal_cost_quadratic, stand_by_cost LinearTerm, QuadTerm of the input, ConstTerm
start_up_cost, shut_down_cost StartUpCost, ShutDownCost
min_up_time, min_down_time, up_time_before, down_time_before MinUpTime, MinDownTime, InitUpDownTime
p_init InitialPower
ramp_limit_up, ramp_limit_down one ramp row on p0
ramp_limit_start_up, ramp_limit_shut_down start-up and shut-down limits of the input

The solution is read back as Link-p.

Tests (skipped if the installed SMS++ lacks ConversionUnitBlock, or for _0l the network with no line, UCBlock 4892479b):

sweep case reference value SMS++
ucblock chp_3n_3g_1cl: back-pressure CHP PyPSA 30630.6818 30630.6818
ucblock chp_3n_3g_1cl_0l: same, nodes joined only by the CHP PyPSA 31500.0 31500.0
ucblock chp_4n_3g_2s_1cl: heat store, CO2 output, weighted snapshots PyPSA 76442.0455 76442.0455
ucblock chp_3n_3g_1cl_ramp: ramps, p_init, empty start-up range PyPSA 85300.0 85300.0
ucblock chp_5n_3g_1cl_3l_1ml: AC triangle, plus a non-committable CHP PyPSA 32500.0 32500.0
ucblock chp_3n_2g_1gext_1cl: extendable boiler PyPSA 31380.6818 31380.6818
tssb chp_tssb_3n_4g_1cl, demand / renewables / both sum of scenarios by PyPSA 1357425.34 / 24322.3169 / 1436936.67 1357430 / 24322.3 / 1436940
mssb two-level tree, CHP in every leaf (heater / no line) sum of leaves by PyPSA 1292628.96 / 1711485.81 1292630 / 1711490
investmentblock chp_3n_2g_1gext_1cl / chpx_3n_3g_1clext / chp_tssb_3n_3g_1gext_1cl PyPSA, linearized UC 29476.6818 / 38010.6818 / 20818.125 29476.6821 / 38010.7018 / 20818.1253
ucblock chpx_3n_3g_2cl_cop: committable heat pump, COP 3 / 2.5 / 4 / 3.5 PyPSA 28382.8947 28382.8947
ucblock chpx_3n_3g_1cl_delay: CHP heat delayed by 1 snapshot PyPSA 75136.8421 75136.8421
ucblock chpx_3n_3g_2cl_cop_delay: heat pump, time-varying COP, cyclic delay PyPSA 28263.9063 28263.9062
ucblock chpx_3n_3g_1clmod: CHP of 3 modules PyPSA 26730.6818 26730.6818
ucblock chpx_3n_3g_1clmod_build: up to 4 modules built (3) PyPSA 35930.6818 35930.6818
ucblock chpx_3n_3g_1clext: continuous design (p_nom_opt 300) PyPSA 39930.6818 39930.6818
ucblock chpy_3n_3g_1clext_ramp: continuous design, ramps and start-up/shut-down limits, off before PyPSA 46760.0 46760.0
ucblock chpy_3n_3g_1clext_ramp_on: the same, on before at p_init 60 PyPSA 44800.0 44800.0
ucblock chpy_3n_3g_1clmod_build_on: modules built, one on before PyPSA 35730.6818 35730.6818
ucblock chpx_3n_3g_1clmodext: up to 4 modules built PyPSA 35930.6818 35930.6818
investmentblock chpx_3n_3g_1clmodext: integer design scaling one module min over k of PyPSA linearized with k modules 35889.4091 35889.4091
mssb, investment_outside tssb and mssb, extendable solar cost of the design by PyPSA (wait-and-see 1199729.19 below) 1203646.0739 1203646.0739

Notes:

  • PyPSA (1.2.3, 1.2.4) builds no stochastic network with a committable component (KeyError in constraints.py:1450), hence the scenario-by-scenario references; with a here-and-now design the reference is the cost of the design found.
  • The InvestmentFunction relaxes the commitment (intRelaxIntVars 1 in the template), hence the linearized UC of PyPSA as reference.
  • The existing cases are unchanged (the SMS++ files are identical), and the dispatch matches PyPSA at every snapshot.

Difference: an extendable committable link on before the horizon has, in SMS++, a capacity that covers p_init; PyPSA does not impose it (p_init 380, no ramps: PyPSA builds 300 at 39930.68, SMS++ builds 380 at 62330.68).

Not covered:

  • modular committable links with ramps, with min_down_time > 0 or p_min_pu > 0, or forced on by min_up_time: PyPSA writes these rows on the whole link (e.g. status + sum of shut-downs <= 1 at constraints.py:482, and p_min_pu times the total p_nom at :261), while a ConversionUnitBlock writes them per module, so the optima differ (3 modules with min_down_time 2: PyPSA 39040.0, SMS++ 26730.6818);
  • in the InvestmentBlock form, an extendable committable link on before the horizon with p_init is refused (its initial power would be per MW of an unknown design), and one with ramps is not compared with PyPSA (the scaled unit is tighter than PyPSA's big-M relaxation; the UCBlock form is checked against PyPSA's MILP); delays that are not a whole number of snapshots or with non-uniform weightings are refused;
  • extraction-condensing CHPs have no PyPSA component;
  • a TSSB with a here-and-now design has no PyPSA reference (only a finite optimum is checked).

Carriers

With the option multi_carrier of Transformation (off by default), the buses of each carrier of the network are the nodes of a carrier of the UCBlock (UCBlock a84a90a7: "NumberCarriers", "CarrierName", a group "Carrier_c" per carrier, "GeneratorNode" over the generators and the carriers). The AC, DC and carrier-less buses are the electricity, carrier 0; the others are one carrier per bus carrier, H2 being "hydrogen".

PyPSA UCBlock with carriers
Link between buses of two or more carriers IntermittentUnitBlock with power p0, one output per port: -1 on the carrier of bus0, efficiency, efficiency2, ... on those of bus1, bus2, ... (time-varying if they are)
committable such Link (enable_conversion_units) ConversionUnitBlock, each generator with its output on the carrier of its bus
such Link with two ports on one carrier (a pump, a compressor) or with a delay (enable_conversion_units) ConversionUnitBlock, a generator per port on the node of its bus; if not committable, always on (on before the horizon, MinUpTime T + 1) and with no start-up, shut-down or stand-by cost, as PyPSA imposes p_min_pu at every snapshot; a modular non-committable one is refused, and without enable_conversion_units both are refused as before
Link or Line within one carrier line of the network of that carrier, with a DesignNetworkBlock if extendable (UCBlock form) or an asset of the InvestmentBlock, its lines numbered carrier after carrier (InvestmentBlock form)
Generator, Store, StorageUnit unit with its output on the carrier of its bus
Load demand of its node, "EnergyDemand" of its carrier; a stochastic demand goes through UCBlock::set_energy_demand

The solution is read back as before, a link between carriers as Link-p, the flows of the lines of every carrier included.

Tests, each against the same reference as the network form (skipped if the installed SMS++ does not read the carriers):

sweep cases result
ucblock, investmentblock, tssb, mssb every xlsx case with buses that are not AC, -carriers variant of the existing tests 39 passed, the 82 of the network form unchanged
test_carriers.py two regions, AC line, extendable H2 pipeline (p_nom_opt 52.5), UCBlock and InvestmentBlock form PyPSA 5234.375, SMS++ the same, p_nom_opt read back
test_carriers.py pump from water A to water B drawing electricity, tank at B, without and with p_min_pu 0.2 (which binds when the users take nothing) PyPSA 4700 / 4772, SMS++ the same; the pump is a ConversionUnitBlock with 3 generators on water A, water B and el, its flow read back as Link-p (the optimum is not unique, the tank shifting the pumping between the two cheap snapshots)

The instances of the SMS++ batteries are written in this form too (pypsa-eur-instances --carriers): the PyPSA-Eur network of the stochastic instances, with 10 carriers and its extendable CO2 pipeline, gives the optimum of PyPSA as MSSB, TSSB and InvestmentBlock over them.

Refused with multi_carrier: a link with two ports on the same carrier, a link between carriers that delays its output and is not committable, and merge_links.

…ber of its modules, tested as an xlsx case of the sweep
…teger design too, the number of its modules, and solves it with the BundleSolver of InvestmentBlock/BSPar-int.txt
…AC network into a transport model without Kirchhoff's voltage law
… no capital cost, was fixed at a large capacity before the merge, accepts a preset or a list of presets, and the merged links are read back also per scenario
…ltage law since the lines have their susceptance, is left out where the solver is a released SMS++, whose DCNetworkBlock finds it infeasible
…e scenarios are in the InnerSolution of the InvestmentBlock, and its design goes to every scenario of the asset; tested against PyPSA on a TSSB and an MSSB
…arginal_cost, Cost being a price on the net injection
…_conversion_units): the input on bus0 with power - p0, an output on each further bus with power efficiency * p0 by equality rows of the operating region, the costs, the commitment, the ramps and the start-up and shut-down ranges of PyPSA; tested against PyPSA on four xlsx cases of the sweep, which are left out where the solver does not know the ConversionUnitBlock
…versionUnitBlock of one MW that the design scales; the ConversionUnitBlock is tested in the TSSB sweep against the optima of the scenarios solved by PyPSA, in an MSSB against those of the leaves, and in the InvestmentBlock sweep against the continuous relaxation of the commitment that the InvestmentFunction solves
…Block in every leaf is tested against the value of its design, the leaves being solved by PyPSA one by one with the commitment relaxed, and against the wait-and-see value, a lower bound, since PyPSA does not build a stochastic network with a committable component
…no line, is tested in the UCBlock sweep and in an MSSB; it is left out where the solver does not solve a network of several nodes with no line, which it does since UCBlock 4892479b
…hat changes over time (an OperatingMatrix over the instants, evaluated at the instant of the delivery as PyPSA does), with delays (in instants of the snapshot weighting, cyclic or not), with modules (p_nom_mod) and, in the UCBlock form, with the design of its extendable capacity (modules built, or MW with the big-M rows); a unit off before the horizon with no down_time_before is off since long enough, as in PyPSA; tested against PyPSA on six xlsx cases, which are left out where the solver does not read these data
…ps and start-up and shut-down limits (its continuous design in the big-M form of PyPSA) and on before the horizon (its capacity covering p_init, one module on if it is modular), and in the InvestmentBlock form also if modular (an integer design scaling the unit of one module); tested against PyPSA on four xlsx cases, the InvestmentBlock form of the modular one against the networks with a given number of modules, PyPSA not relaxing a modular commitment
… of a carrier of the UCBlock, a link between two carriers a unit with an output on the carrier of each port (an IntermittentUnitBlock, or the ConversionUnitBlock of a committable link with each generator on the carrier of its bus), the lines and links within a carrier the network of that carrier, with a DesignNetworkBlock if extendable, in the UCBlock, InvestmentBlock, TSSB and MSSB forms; tested against PyPSA in both forms of the buses
…tive_constant, whose default PyPSA warns about, the FutureWarnings being errors since SPSUnipi#67
…build of SMS++ has not the Solver of its configuration, which asks for GUROBI
…it-block

# Conflicts:
#	pypsa2smspp/transformation.py
#	test/conftest.py
#	test/test_investmentblock.py
#	test/test_mssb.py
…the same carrier (a pump, a compressor) or with a delay, which a unit with one generator per carrier cannot express, is a ConversionUnitBlock with a generator per port on the node of its bus; a non-committable link is a ConversionUnitBlock always on, with no start-up, shut-down or stand-by cost, and a modular non-committable link is refused; tested against PyPSA on a pump between two nodes of the water carrier, with and without a minimum flow
…it-block

The optimizations of PyPSA in the tests of this branch state include_objective_constant, whose default PyPSA warns about, the FutureWarnings being errors since SPSUnipi#78.
…it-block

test_tssb: with a committable component the reference stays the sum of the scenarios solved by PyPSA, which builds no stochastic network with one, and otherwise it is the stochastic network solved by PyPSA, as in SPSUnipi#73.
@dmeoli dmeoli changed the title Translate a committable Link into a ConversionUnitBlock Translate a committable Link into a ConversionUnitBlock, and the carriers of the buses into those of the UCBlock Oct 30, 2026
@dmeoli
dmeoli marked this pull request as ready for review October 30, 2026 12:17

This branch has not been deployed

No deployments
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.

1 participant