Network#
popo.network describes a circuit as it is defined: its lines, loads, transformers, PV systems
and storage units. Solving the circuit and reading its results is covered in Power flow.
Loading a circuit#
Circuit.load clears the OpenDSS engine, compiles a master file
and solves it. The returned Circuit is a view of the compiled circuit.
from pathlib import Path
from popo.network import Circuit
FEEDER = Path("tests/data/dss_uk_network_17_1_1149/master_network_17_1_1149.dss")
circuit = Circuit.load(FEEDER)
circuit.summary()
{'converged': True,
'n_buses': 380,
'n_lines': 378,
'n_loads': 188,
'n_transformers': 1,
'total_p_loss_kw': 10.05532368243081,
'total_q_loss_kvar': 10.047733903634096}
To build a circuit from OpenDSS commands instead of a file, create Circuit() and pass each command to
command(), as the quickstart does.
Elements as records#
The circuit yields its elements as records: lines,
loads, transformers,
pvsystems and storage_units. Each
record is a small dataclass, so a collection converts directly to a DataFrame.
lines = pd.DataFrame(circuit.lines)
lines.head()
| name | bus1 | bus2 | phases | length | |
|---|---|---|---|---|---|
| 0 | sourcez | sourcebus | 1149 | 3 | 1.000 |
| 1 | feeder1_1_11491 | 1_1149_1 | 1_1149_171_1 | 3 | 0.001 |
| 2 | line1_1149_171_55 | 1_1149_171_53 | 1_1149_171_56 | 3 | 6.710 |
| 3 | line1_1149_171_101 | 1_1149_171_96 | 1_1149_171_102 | 3 | 3.364 |
| 4 | line1_1149_171_103 | 1_1149_171_97 | 1_1149_171_104 | 3 | 4.033 |
list(circuit.transformers)
[Transformer(name='tr171_11491_1', rating_kva=500.0, low_voltage_bus='1_1149_1', secondary_kv=0.416, normal_rating_kva=550.0)]
A Transformer keeps its nameplate rating (500 kVA here) separate from its
normal thermal rating (550 kVA), because a loading study needs to know which of the two it is comparing
against.
For a property that no record covers, command() passes an OpenDSS command
through and returns its answer:
circuit.command("? Line.line1_1149_171_55.length")
'6.7095'
Customers#
residential_loads() lists a feeder’s low-voltage residential loads as
(load name, bus) pairs. Every scenario function in Scenarios takes its customers in this form.
from popo.network import residential_loads
customers = residential_loads(circuit.dss)
len(customers), customers[:3]
(188,
[('load1_1149_171_1', '1_1149_171_102'),
('load1_1149_171_2', '1_1149_171_104'),
('load1_1149_171_3', '1_1149_171_141')])
Describing a feeder#
profile_circuit() summarises a circuit at its defined loading.
from dataclasses import asdict
from popo.network import measure_sensitivity, profile_circuit
pd.Series(asdict(profile_circuit(circuit.dss, "UK feeder")))
name UK feeder
customers 188
buses 380
lines 378
transformers 1
rating_kva 500.0
single_phase_share 1.0
converged True
min_voltage_pu 0.942
max_voltage_pu 1.033
dtype: object
measure_sensitivity() raises the loading, solves again and measures how far the
lowest bus voltage moves per megawatt of substation demand. A high sensitivity means a voltage limit is
likely to bind before the transformer’s thermal rating does.
pd.Series(asdict(measure_sensitivity(circuit.dss, "UK feeder")))
name UK feeder
base_multiplier 1.0
step_multiplier 2.0
min_voltage_base_pu 0.942
min_voltage_step_pu 0.857
active_base_kw 197.857
active_step_kw 387.504
reactive_base_kvar 71.775
sensitivity_pu_per_mw 0.446
dtype: object
Doubling the loading lowers the minimum voltage from 0.942 pu to 0.857 pu, a sensitivity of 0.45 pu per MW.
Feeders with many transformers#
A North American feeder can have hundreds of pole-mounted transformers. Recording all of them in every scenario is slow, so popo provides three steps for choosing which to monitor:
read_transformers()reads the transformers from an OpenDSS definition file, keeping the nameplate and normal ratings separate.screen()steps the feeder through a short window and returns each transformer’s peak apparent power and voltage extremes.select_by_utilisation()takes the most heavily loaded transformers from the screen, where a thermal limit can bind, and keeps that selection fixed across scenarios so a sweep compares the same assets.
Limitations#
OpenDSS runs one circuit per process. A
Circuitis a view of whatever is compiled now, so loading another circuit changes what every existingCircuitsees.Records describe the circuit as defined, at its nominal ratings. Values that change during a simulation, such as a load’s demand at a given step, are read from Power flow.
residential_loadsidentifies customers by their nominal voltage, below 0.3 kV by default. A feeder modelled with a different convention needs a differentsecondary_kv.