Bulk topology builders¶
monee.express provides structure builders that create whole line,
ring, and star topologies (and paired district-heating supply and return
networks) in a few calls, instead of one mx.create_* call per node,
branch, and load.
Each *_structure factory returns a builder that remembers carrier-level
defaults (pipe diameter, segment length, line impedance, and so on) so the
shape methods stay short. Shape methods return lightweight segment handles
that you pass back via start_from= to compose larger structures.
Quick start¶
Build a five-bus radial feeder with a 0.1 MW load on every bus, attach the slack bus, and run the energy flow:
import monee.model as mm
import monee.express as mx
from monee import run_energy_flow
net = mm.Network()
# One builder call fixes the line parameters for every branch ...
s = mx.el_structure(net, length_m=100, r_ohm_per_m=7e-5, x_ohm_per_m=7e-5)
# ... one shape call creates 5 buses, 4 lines, and 5 loads.
seg = s.line(5, load_p_mw=0.1)
s.attach_ext_grid(seg.first)
result = run_energy_flow(net)
print(result.dataframes["Bus"][["vm_pu"]].shape)
print(len(result.dataframes["PowerLine"]))
(5, 1)
4
Builders and their defaults¶
Every builder takes the network first, then keyword-only carrier parameters. These are stored at construction and applied to every branch the builder creates. Per-call overrides are not supported, so create a second builder on the same network if you need different parameters.
Factory |
Required parameters |
Optional parameters (defaults) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
same as |
The builders delegate to the regular express creators
(create_bus() and
create_line(),
create_gas_junction() and
create_gas_pipe(),
create_water_junction() and
create_water_pipe()), so all units and sign
conventions match the rest of the express API.
Shapes: line, ring, star¶
All single-carrier builders (ElStructure, GasStructure,
WaterStructure) share three shape methods:
.line(n, *, start_from=None, **load_kwargs)A chain of
nnodes (n >= 1) connected byn - 1branches..ring(n, *, start_from=None, **load_kwargs)A line of
nnodes (n >= 3) plus one closing branch from the last node back to the first..star(arms, *, start_from=None, **load_kwargs)A hub node with one radial arm per entry of
arms, where each entry is the arm length in nodes (excluding the hub), e.g.[2, 3, 2].
Passing start_from=<node id> composes a new shape onto an existing
node. That node becomes the first node of the line or ring (or the hub of
the star), no new node is created in its place, and the per-node loads are
not re-attached to it.
The load_kwargs attach a child to every newly created node:
Carrier |
Consumption kwargs |
Injection kwargs |
|---|---|---|
Electrical |
|
|
Gas |
|
|
Water |
|
|
Pass generator and source magnitudes as positive numbers. The underlying
express creators negate them internally, exactly as
create_power_generator() and
create_gas_source() do.
Finally, each builder offers .attach_ext_grid(node_id, **kwargs), which
forwards to the carrier’s external-grid creator
(create_ext_power_grid(),
create_gas_ext_grid(), or
create_water_ext_grid()).
Composing shapes¶
A gas star fed at the hub, with a three-junction extension grafted onto the end of the middle arm:
import monee.model as mm
import monee.express as mx
from monee import run_energy_flow
net = mm.Network()
g = mx.gas_structure(net, diameter_m=0.3, length_m=200)
star = g.star([2, 3, 2], sink_mass_flow=0.05)
g.attach_ext_grid(star.hub)
# Extend the middle arm by a 3-junction line (2 new junctions).
feeder = g.line(3, start_from=star.arms[1].last, sink_mass_flow=0.02)
print(feeder.first == star.arms[1].last)
result = run_energy_flow(net)
print(len(result.dataframes["GasPipe"]))
True
9
Segment handles¶
Shape methods return handles that record everything they created, so you can keep wiring without tracking node ids by hand:
Handle |
Contents |
|---|---|
Returned by |
|
Returned by |
|
Returned by the DHS builder. |
Tip
The handles store plain ids, so anything you build with a structure can
be refined afterwards with the regular express API. For example,
attach a CHP to seg.last or convert a load on star.hub into a
controllable demand.
District heating structures¶
dhs_structure() returns a
DhsStructure that mirrors every shape on
two water networks at once: a supply side and a return side. Both are
regular WaterStructure builders, reachable as .supply and
.return_. Its shape methods accept three extra keyword arguments:
start_from_supply=andstart_from_return=: compose onto existing supply or return nodes, analogous tostart_from=above.heat_exchanger_q_mw=: bridge each supply node to its return counterpart with a heat exchanger (create_heat_exchanger(), oriented supply to return, hot side into cold side). Pass a scalar to put the same consumer on every node pair, or a list with one entry per node. Entries ofNoneor0skip that node, and negative values create heat-exchanger generators.
A ring of four consumers supplied by one heat plant:
import monee.model as mm
import monee.express as mx
from monee import run_energy_flow
net = mm.Network()
dhs = mx.dhs_structure(net, diameter_m=0.15, length_m=100)
# 4-node supply ring, 4-node return ring, and 4 heat exchangers.
seg = dhs.ring(4, heat_exchanger_q_mw=0.01)
# Heat plant: pressure and temperature reference on the supply side,
# consumption point on the return side.
dhs.attach_heat_plant(seg.supply.first, seg.return_.first, name="plant")
result = run_energy_flow(net)
print(len(seg.supply), len(seg.return_), len(seg.heat_exchangers))
print(result.dataframes["Junction"][["t_k"]].shape)
4 4 4
(8, 1)
.attach_heat_plant(supply_node, return_node, *, t_k=358.0, name=None)
closes the hydraulic loop. It attaches an ExtHydrGrid at
supply_node (via create_water_ext_grid()), holding
the feed temperature t_k, and a ConsumeHydrGrid at return_node
(via create_consume_hydr_grid()). It returns the tuple
(supply_child_id, return_child_id). If name is given, the children
are named <name>_supply and <name>_return.
Note
dhs_structure creates unidirectional pipes by default, oriented from
the first node towards the last. On a ring the loop closes itself, so
the plant can sit on any node pair. On a line, water leaving the heat
exchangers must be able to flow towards the plant’s return connection:
attach it at seg.return_.last (not seg.return_.first), or pass
unidirectional=False to the builder so the solver picks flow
directions freely.
See also¶
Connect the carriers you just built with P2H, G2P, P2G, and CHP coupling components.
Ready-made multi-energy benchmark networks if you would rather not build a topology at all.
The node-by-node express workflow that the structure builders condense.