Multi-energy coupling

A multi-energy system (MES) combines two or more energy carriers (typically electricity, gas, and heat) into a single network. monee models the coupling points between these carriers as specialised compound components that sit at the intersection of grids.

Note

Compound components are created automatically by the express API functions below. You do not need to assemble them manually unless you are writing a custom coupling model.


Available coupling components

Power-to-Heat (P2H)

EL → Water

Heat pump or electric boiler. Draws electricity from a bus and injects thermal energy into a district heating supply pipe.

monee.express.create_p2h()PowerToHeat

Power-to-Gas (P2G)

EL → Gas

Electrolysis unit. Converts electricity to gas (e.g. green hydrogen).

monee.express.create_p2g()PowerToGas

Gas-to-Power (G2P)

Gas → EL

Gas turbine or engine. Converts gas to electricity.

monee.express.create_g2p()GasToPower

Gas-to-Heat (G2H)

Gas → Water

Gas boiler. Converts gas to district heat without generating electricity.

monee.express.create_g2h()GasToHeat

Combined Heat and Power (CHP)

Gas → EL + Water

Simultaneously produces electricity and heat from gas. Connects to three grids.

monee.express.create_chp()CHP

Heat exchanger

Water ↔ Water

Transfers thermal energy between two water circuits without mass transfer.

monee.express.create_heat_exchanger()HeatExchanger


Power-to-Heat (P2H)

A P2H unit (e.g. a heat pump or electric boiler) draws electrical power from a bus and injects thermal power into a district heating supply pipe. The return pipe closes the thermal loop.

import monee.express as mx

net = mx.create_multi_energy_network()

# Electricity side
bus_el = mx.create_bus(net)
mx.create_ext_power_grid(net, bus_el)

# Heating side (supply/return loop)
junc_supply = mx.create_water_junction(net)
junc_return = mx.create_water_junction(net)
mx.create_ext_hydr_grid(net, junc_supply)

# Couple electricity to heat
mx.create_p2h(
    net,
    power_node_id=bus_el,
    heat_node_id=junc_supply,
    heat_return_node_id=junc_return,
    heat_energy_mw=0.5,  # thermal output setpoint [MW]
    diameter_m=0.1,      # heat-exchange pipe diameter
    efficiency=0.95,     # electrical-to-thermal efficiency
)

Key parameters:

  • heat_energy_mw: thermal output setpoint in megawatts. The solver derives the required electrical input as heat_energy_mw / efficiency.

  • efficiency: ratio of thermal output to electrical input (≤ 1 for resistive heating, > 1 for heat pumps modelled with a fixed COP).

  • diameter_m: internal pipe diameter of the connecting heat-exchange branch.

Power-to-Gas (P2G)

A P2G unit (electrolysis) converts electricity into gas (e.g. green hydrogen).

import monee.express as mx

net_p2g  = mx.create_multi_energy_network()
bus_el   = mx.create_bus(net_p2g)
junc_gas = mx.create_gas_junction(net_p2g)

mx.create_p2g(
    net_p2g,
    from_node_id=bus_el,
    to_node_id=junc_gas,
    efficiency=0.7,               # electrical-to-chemical efficiency
    mass_flow_setpoint_kgs=0.05,      # target gas production [kg/s]
)

P2G is a plain two-endpoint branch coupler; its conversion loss is exposed as loss_percent(), which returns 1 - efficiency.

Gas-to-Power (G2P)

A G2P unit (gas turbine, gas engine) converts gas to electricity.

import monee.express as mx

net_g2p  = mx.create_multi_energy_network()
junc_gas = mx.create_gas_junction(net_g2p)
bus_el   = mx.create_bus(net_g2p)

mx.create_g2p(
    net_g2p,
    from_node_id=junc_gas,
    to_node_id=bus_el,
    efficiency=0.40,         # gas-to-electrical efficiency
    p_mw_setpoint=2.0,       # target electrical output [MW]
    q_mvar_setpoint=0.0,
)

Like P2G, G2P is a branch coupler and reports its conversion loss via loss_percent() (= 1 - efficiency).

Combined Heat and Power (CHP)

A CHP unit simultaneously produces electricity and heat from gas. It connects to three grids: gas (fuel input), electricity (power output), and district heating (heat output).

import monee.express as mx

net = mx.create_multi_energy_network()

# Gas grid
junc_gas = mx.create_gas_junction(net)
mx.create_source(net, junc_gas, mass_flow_kgs=0.5)

# Electricity grid
bus_el = mx.create_bus(net)
mx.create_ext_power_grid(net, bus_el)

# Heating grid (supply + return)
junc_heat_supply = mx.create_water_junction(net)
junc_heat_return = mx.create_water_junction(net)
mx.create_ext_hydr_grid(net, junc_heat_supply)

# CHP coupling
mx.create_chp(
    net,
    power_node_id=bus_el,
    heat_node_id=junc_heat_supply,
    heat_return_node_id=junc_heat_return,
    gas_node_id=junc_gas,
    diameter_m=0.15,
    efficiency_power=0.35,   # gas → electricity
    efficiency_heat=0.45,    # gas → heat
    mass_flow_setpoint_kgs=0.1,  # gas consumption [kg/s]
)

Key parameters:

  • efficiency_power and efficiency_heat: individual efficiencies for electrical and thermal output. Their sum must not exceed 1 (total fuel utilisation).

  • mass_flow_setpoint_kgs: gas consumption setpoint in kg/s.

  • regulation: a factor in [0, 1] that scales all outputs. Set as a Var to let the optimiser dispatch the unit.

Compound couplers such as CHP also implement set_active(flag), which deactivate() uses to switch the whole unit off cleanly (zeroing gas flow and regulation) and to restore the previous setpoints on reactivation.

Gas-to-Heat (G2H)

A gas boiler converts gas to district heat without producing electricity.

import monee.express as mx

net_g2h          = mx.create_multi_energy_network()
junc_gas         = mx.create_gas_junction(net_g2h)
junc_heat_supply = mx.create_water_junction(net_g2h)
junc_heat_return = mx.create_water_junction(net_g2h)

mx.create_g2h(
    net_g2h,
    gas_node_id=junc_gas,
    heat_node_id=junc_heat_supply,
    heat_return_node_id=junc_heat_return,
    heat_energy_mw=0.5,  # thermal output [MW]
    diameter_m=0.1,
    efficiency=0.90,
)

Node-based heat couplers (HG variants)

The heat-producing couplers above (CHP, G2H, P2H) deliver their thermal output through an internal heat-exchanger branch that bridges the supply and return sides of the heating network. That is why they require a heat_return_node_id and a diameter_m. Each of them has a node-based “HG” variant (heat generator) that injects the thermal power directly at a single heat junction. The heat appears as a q_mw_heat term that the Junction heat balance picks up, exactly like a node-level HeatGenerator child.

CHP-HG

Gas → EL + Water

CHP variant. Compound with an internal control node; injects heat via a hidden SubHG child at the heat node.

monee.express.create_chp_hg()CHPHG

P2H-HG

EL → Water

Power-to-Heat variant. A plain two-endpoint multi-grid branch from a bus to a heat junction.

monee.express.create_p2h_hg()PowerToHeatHG

G2H-HG

Gas → Water

Gas boiler variant. A plain two-endpoint multi-grid branch from a gas junction to a heat junction.

monee.express.create_g2h_hg()GasToHeatHG

The variants differ structurally from their classic counterparts:

  • CHPHG is still a compound: create(gas_node, heat_node, power_node) wires gas and power through an internal control node, but the heat side is a subordinate SubHG heat-generator child placed at heat_node instead of a heat-exchanger branch. It therefore needs no heat_return_node and no diameter_m.

  • GasToHeatHG (heat_energy_mw, efficiency, regulation=1) and PowerToHeatHG (heat_energy_mw, efficiency, q_mvar_setpoint=0, regulation=1) are plain two-endpoint branch couplers, just like G2P and P2G. They withdraw gas or electricity at the from-node and carry q_mw_heat directly on the branch, where the junction heat balance at the to-node picks it up. Both also expose loss_percent() (= 1 - efficiency).

Prefer the HG variants when

  • you want simpler wiring: one heat junction instead of a supply/return pair, no heat-exchange pipe geometry to choose; or

  • you use the McCormick-DHS formulation (HEAT_CONVEX_MILP_FORMULATION) or a network built with node-based heat loads (e.g. node_based_heat_loads=True in the monee.network heat builders), where they are required: this formulation owns the nodal heat balance and only understands node-based heat injections, not heat-exchanger branches.

import monee.express as mx

net_hg = mx.create_multi_energy_network()

# Gas grid (fuel input)
junc_gas = mx.create_gas_junction(net_hg)
mx.create_gas_ext_grid(net_hg, junc_gas)

# Electricity grid (power output)
bus_el = mx.create_bus(net_hg)
mx.create_ext_power_grid(net_hg, bus_el)

# Heating grid - a single supply junction; no return node needed
junc_heat = mx.create_water_junction(net_hg)
mx.create_water_ext_grid(net_hg, junc_heat)

# CHP coupling, node-based heat injection
mx.create_chp_hg(
    net_hg,
    power_node_id=bus_el,
    heat_node_id=junc_heat,
    gas_node_id=junc_gas,
    efficiency_power=0.35,   # gas → electricity
    efficiency_heat=0.45,    # gas → heat
    mass_flow_setpoint_kgs=0.1,  # gas consumption [kg/s]
)

The single-branch variants follow the same pattern as create_g2p():

mx.create_p2h_hg(
    net_hg,
    power_node_id=bus_el,
    heat_node_id=junc_heat,
    heat_energy_mw=0.5,  # thermal output setpoint [MW]
    efficiency=0.95,
)
mx.create_g2h_hg(
    net_hg,
    gas_node_id=junc_gas,
    heat_node_id=junc_heat,
    heat_energy_mw=0.5,
    efficiency=0.90,
)

Note

The internal control-node models (CHPControlNode, CHPHGControlNode, GasToHeatControlNode, PowerToHeatControlNode) and the subordinate SubHE / SubHG components you may encounter in result dataframes are implementation details. They are wired up automatically by the compounds’ create(); you never instantiate them directly.

Heat exchanger

A heat exchanger transfers thermal energy between two water circuits (for example between a primary transmission network and a secondary distribution loop) without mass transfer.

import monee.express as mx

net_hx         = mx.create_multi_energy_network()
junc_primary   = mx.create_water_junction(net_hx)
junc_secondary = mx.create_water_junction(net_hx)

mx.create_heat_exchanger(
    net_hx,
    from_node_id=junc_primary,
    to_node_id=junc_secondary,
    q_mw=0.2,  # heat transfer setpoint [MW]; positive draws heat at from-node
)

Regulation and dispatch

All coupling components have a regulation attribute that scales their output between 0 (off) and 1 (full setpoint). In a plain energy flow it is fixed at 1. For optimisation, declare it as a solver variable:

import monee.problem as mp

problem = mp.OptimizationProblem()
problem.controllable_cps([
    (
        "regulation",
        mp.AttributeParameter(
            min=lambda a, v: 0,
            max=lambda a, v: 1,
            val=lambda a, v: 1,
        ),
    )
])

The optimiser then freely dispatches each coupling unit within its capacity.

Tip

See 01 · Minimum-cost load curtailment and Load shedding for end-to-end worked examples of coupling unit dispatch in an optimisation problem.