mambo_power.numerics¶
Network matrices over scipy.sparse. See the manual page for the formulas and conventions.
mambo_power.numerics
¶
Network matrices over scipy.sparse: the positional pu view, Ybus, Bbus, PTDF and LODF.
The only package module that holds positional indices. NetworkArrays is the single
per-unit conversion site; every builder here takes that view, never a Network directly.
effective_roles derives the roles a solver must use from the declared ones (W3).
NetworkArrays
dataclass
¶
NetworkArrays(
base_mva: float,
bus_ids: list[str],
bus_index: dict[str, int],
n_bus: int,
slack: int,
bus_type: IntArray,
branch_ids: list[str],
branch_index: dict[str, int],
n_branch: int,
f: IntArray,
t: IntArray,
r: FloatArray,
x: FloatArray,
b: FloatArray,
tap: FloatArray,
shift_rad: FloatArray,
rating_pu: FloatArray,
p_load_pu: FloatArray,
q_load_pu: FloatArray,
g_shunt_pu: FloatArray,
b_shunt_pu: FloatArray,
p_gen_pu: FloatArray,
q_gen_pu: FloatArray,
p_min_pu: FloatArray,
p_max_pu: FloatArray,
q_min_pu: FloatArray,
q_max_pu: FloatArray,
v_set: FloatArray,
gen_ids: list[str] = list(),
gen_bus: IntArray = (lambda: zeros(0, dtype=int64))(),
gen_p_pu: FloatArray = (lambda: zeros(0))(),
gen_q_pu: FloatArray = (lambda: zeros(0))(),
gen_p_min_pu: FloatArray = (lambda: zeros(0))(),
gen_p_max_pu: FloatArray = (lambda: zeros(0))(),
gen_q_min_pu: FloatArray = (lambda: zeros(0))(),
gen_q_max_pu: FloatArray = (lambda: zeros(0))(),
gen_v_set: FloatArray = (lambda: zeros(0))(),
load_ids: list[str] = list(),
load_bus: IntArray = (lambda: zeros(0, dtype=int64))(),
load_p_min_pu: FloatArray = (lambda: zeros(0))(),
load_p_max_pu: FloatArray = (lambda: zeros(0))(),
storage_ids: list[str] = list(),
storage_bus: IntArray = (
lambda: zeros(0, dtype=int64)
)(),
storage_p_max_pu: FloatArray = (lambda: zeros(0))(),
storage_energy_pu: FloatArray = (lambda: zeros(0))(),
storage_soc_initial: FloatArray = (lambda: zeros(0))(),
storage_efficiency_charge: FloatArray = (
lambda: zeros(0)
)(),
storage_efficiency_discharge: FloatArray = (
lambda: zeros(0)
)(),
)
Frozen positional arrays over the in-service subset of a network, in per unit.
Positions are 0-based. bus_ids[i] is the id at bus position i; branch_ids[k]
and gen_ids[g] likewise. Order follows the network's collection order with the
excluded elements removed.
b
instance-attribute
¶
Total line charging susceptance per branch (pu); builders apply b / 2 per end.
tap
instance-attribute
¶
Tap ratio magnitude on the from side; 1.0 where the branch has none.
rating_pu
instance-attribute
¶
Thermal rating in pu of base_mva; inf where the branch has none.
g_shunt_pu
instance-attribute
¶
Shunt conductance per bus in pu (MATPOWER GS sign: positive consumes).
b_shunt_pu
instance-attribute
¶
Shunt susceptance per bus in pu (MATPOWER BS sign: positive injects).
v_set
instance-attribute
¶
First in-service generator's v_set_pu at each bus; 1.0 where there is none.
load_p_max_pu
class-attribute
instance-attribute
¶
Per-load [0, p_mw] bound in pu: the natural bound for a bid-load's demand
dispatch is zero up to its own fixed historical p_mw. Built uniformly for every
in-service load regardless of Load.bid — Load carries no
p_min_mw/p_max_mw fields to mirror the generator-side source data, and this bound
formula does not depend on bid presence; whether/how a given load's bound is actually used
by opf.dc_opf is decided per-load elsewhere, not here. p_load_pu/q_load_pu (the
bus aggregate) are untouched by this addition.
storage_p_max_pu
class-attribute
instance-attribute
¶
Charge/discharge power limit (Storage.p_max_mw), pu of base_mva.
storage_energy_pu
class-attribute
instance-attribute
¶
Energy capacity (Storage.energy_mwh), pu of base_mva (pu-hours) — the same
base_mva-division convention every other physical field in this class already uses
(ADR-005: physical units in the model, pu in numerics).
storage_soc_initial
class-attribute
instance-attribute
¶
Initial state of charge, already a fraction of energy_mwh in [0, 1] on the
entity — dimensionless, so unlike storage_p_max_pu/storage_energy_pu it carries
through unconverted.
storage_efficiency_discharge
class-attribute
instance-attribute
¶
Charge/discharge efficiency, already dimensionless ratios in (0, 1] — unconverted.
from_network
classmethod
¶
from_network(net: Network) -> NetworkArrays
Build the in-service positional view; the one pu-conversion site.
Source code in src/mambo_power/numerics/arrays.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
NoSlackGeneratorError
¶
Bases: Exception
The slack bus has no in-service generator, so nothing can close the power balance.
Raised by mambo_power.numerics.effective_roles. MATPOWER's bustypes would
silently hand the reference role to the first PV bus; the M2 spec rejects that
re-slacking (Not Doing: "MATPOWER slack-limiting re-slack") and names the condition
instead. bus_id is the slack bus id, position its index in the arrays.
Source code in src/mambo_power/numerics/errors.py
SetpointConflictWarning
¶
Bases: UserWarning
Several in-service generators at one bus carry different voltage setpoints.
Emitted by mambo_power.numerics.effective_roles (via warnings.warn). The
last generator's setpoint is used, following MATPOWER; pandapower raises a
UserWarning and aborts in the same situation, which is why this is surfaced rather
than resolved silently.
UnsolvableNetworkError
¶
Bases: Exception
A Network that passes validate_network but cannot be
solved by the numerics it was handed to — e.g. DC susceptance is undefined when a branch
carries x == 0 with r != 0 (legal under the model, since BAD_RANGE only rejects
r == x == 0). This is user data, not a solver bug: mambo_power.jobs.run maps it
to the structured UNSOLVABLE_NETWORK failure code rather than INTERNAL.
EffectiveRoles
dataclass
¶
EffectiveRoles(
bus_type: IntArray,
v_set: FloatArray,
demoted_pv: IntArray,
setpoint_conflicts: list[
tuple[str, list[str], list[float]]
],
)
The roles and setpoints a solver must use, positional over the arrays' bus order.
bus_type
instance-attribute
¶
Effective role per bus: 1 = pq, 2 = pv, 3 = slack (same codes as the arrays).
v_set
instance-attribute
¶
Effective voltage setpoint per bus (pu): last in-service generator's; 1.0 if none.
demoted_pv
instance-attribute
¶
Positions of buses declared PV but solved as PQ (no in-service generator), ascending.
setpoint_conflicts
instance-attribute
¶
(bus_id, gen_ids, setpoints) for every bus whose in-service generators disagree.
bf
¶
bf(arr: NetworkArrays) -> Any
Bf: n_branch × n_bus CSC matrix; Bf @ θ is the from-side DC flow (pu).
Source code in src/mambo_power/numerics/bbus.py
flow_from_ptdf
¶
flow_from_ptdf(
ptdf: FloatArray,
injection_mw: FloatArray,
arr: NetworkArrays,
) -> FloatArray
Branch flow, MW, from a PTDF matrix and a full bus net-injection vector, MW.
flow = ptdf @ (injection_mw − p_shift·base_mva) + pf_shift·base_mva — the phase-shifter
injection is subtracted out of the bus injection before the PTDF product, then each
branch's own from-side shift flow is added back on. This is exactly :func:mambo_power.pf.dc.
solve's construction (its module docstring: rhs = P − p_shift feeds the angle solve, and
p_from = Bf·θ + pf_shift; combined with θ = B'⁻¹(P − p_shift) and PTDF = Bf·B'⁻¹
on the reduced system, p_from = PTDF·(P − p_shift) + pf_shift) — the model every DC
PTDF-based flow in this package must match. Omitting the − p_shift term (all of
opf.dc_opf, opf.solve_dc_opf and market._clearing did until M8 finding F1 / A19)
reproduces pf.solve_dc's flow only when no branch has a shift, since
p_shift(arr) == 0 identically in that case.
injection_mw must be the full net injection per bus (generation minus load minus
shunt, MW) — callers that instead fold some of that into a decision-variable-relative LP
constant (opf.dc_opf's own flow-limit rows) derive the identical correction by hand
rather than calling this helper, since their injection is not one vector (see that
module's own derivation).
Source code in src/mambo_power/numerics/bbus.py
p_shift
¶
p_shift(arr: NetworkArrays) -> FloatArray
Per-bus phase-shifter injection Cftᵀ · pf_shift (pu); P = Bbus·θ + p_shift.
Source code in src/mambo_power/numerics/bbus.py
bridges
¶
bridges(arr: NetworkArrays) -> list[int]
Positions of branches whose removal disconnects the in-service graph (sorted).
Iterative Tarjan lowpoint search over the multigraph; parallel branches between the same pair of buses are never bridges because the search skips only the edge it arrived by.
Source code in src/mambo_power/numerics/lodf.py
effective_roles
¶
effective_roles(arr: NetworkArrays) -> EffectiveRoles
Derive the effective roles and setpoints from arr (see the module docstring).
Raises NoSlackGeneratorError when the slack bus has no in-service generator.
Emits one SetpointConflictWarning per bus whose generators disagree.
Source code in src/mambo_power/numerics/roles.py
yf_yt
¶
yf_yt(arr: NetworkArrays) -> tuple[Any, Any]
(Yf, Yt): n_branch × n_bus complex CSC matrices giving from/to branch currents.
Yf @ V is the current injected into each branch at its from bus, Yt @ V at its to
bus — the inputs M2 needs for branch flows.
Source code in src/mambo_power/numerics/ybus.py
NetworkArrays¶
mambo_power.numerics.arrays
¶
NetworkArrays: the positional, per-unit view of a Network.
This is the only place in the package that holds positional indices and the single site
where physical units (MW, MVAr, MVA) are divided by base_mva (wave M1 design items 1
and 7). Every matrix builder in mambo_power.numerics consumes this view; nothing else
in the package divides by base_mva.
Scope: the in-service subset. Out-of-service buses are dropped, and so is every branch, generator, load or shunt that is itself out of service or attached to a dropped bus. The network's own validation guarantees the surviving buses form one connected component with exactly one slack.
BUS_TYPE_CODE
module-attribute
¶
MATPOWER bus type codes used in NetworkArrays.bus_type.
NetworkArrays
dataclass
¶
NetworkArrays(
base_mva: float,
bus_ids: list[str],
bus_index: dict[str, int],
n_bus: int,
slack: int,
bus_type: IntArray,
branch_ids: list[str],
branch_index: dict[str, int],
n_branch: int,
f: IntArray,
t: IntArray,
r: FloatArray,
x: FloatArray,
b: FloatArray,
tap: FloatArray,
shift_rad: FloatArray,
rating_pu: FloatArray,
p_load_pu: FloatArray,
q_load_pu: FloatArray,
g_shunt_pu: FloatArray,
b_shunt_pu: FloatArray,
p_gen_pu: FloatArray,
q_gen_pu: FloatArray,
p_min_pu: FloatArray,
p_max_pu: FloatArray,
q_min_pu: FloatArray,
q_max_pu: FloatArray,
v_set: FloatArray,
gen_ids: list[str] = list(),
gen_bus: IntArray = (lambda: zeros(0, dtype=int64))(),
gen_p_pu: FloatArray = (lambda: zeros(0))(),
gen_q_pu: FloatArray = (lambda: zeros(0))(),
gen_p_min_pu: FloatArray = (lambda: zeros(0))(),
gen_p_max_pu: FloatArray = (lambda: zeros(0))(),
gen_q_min_pu: FloatArray = (lambda: zeros(0))(),
gen_q_max_pu: FloatArray = (lambda: zeros(0))(),
gen_v_set: FloatArray = (lambda: zeros(0))(),
load_ids: list[str] = list(),
load_bus: IntArray = (lambda: zeros(0, dtype=int64))(),
load_p_min_pu: FloatArray = (lambda: zeros(0))(),
load_p_max_pu: FloatArray = (lambda: zeros(0))(),
storage_ids: list[str] = list(),
storage_bus: IntArray = (
lambda: zeros(0, dtype=int64)
)(),
storage_p_max_pu: FloatArray = (lambda: zeros(0))(),
storage_energy_pu: FloatArray = (lambda: zeros(0))(),
storage_soc_initial: FloatArray = (lambda: zeros(0))(),
storage_efficiency_charge: FloatArray = (
lambda: zeros(0)
)(),
storage_efficiency_discharge: FloatArray = (
lambda: zeros(0)
)(),
)
Frozen positional arrays over the in-service subset of a network, in per unit.
Positions are 0-based. bus_ids[i] is the id at bus position i; branch_ids[k]
and gen_ids[g] likewise. Order follows the network's collection order with the
excluded elements removed.
b
instance-attribute
¶
Total line charging susceptance per branch (pu); builders apply b / 2 per end.
tap
instance-attribute
¶
Tap ratio magnitude on the from side; 1.0 where the branch has none.
rating_pu
instance-attribute
¶
Thermal rating in pu of base_mva; inf where the branch has none.
g_shunt_pu
instance-attribute
¶
Shunt conductance per bus in pu (MATPOWER GS sign: positive consumes).
b_shunt_pu
instance-attribute
¶
Shunt susceptance per bus in pu (MATPOWER BS sign: positive injects).
v_set
instance-attribute
¶
First in-service generator's v_set_pu at each bus; 1.0 where there is none.
load_p_max_pu
class-attribute
instance-attribute
¶
Per-load [0, p_mw] bound in pu: the natural bound for a bid-load's demand
dispatch is zero up to its own fixed historical p_mw. Built uniformly for every
in-service load regardless of Load.bid — Load carries no
p_min_mw/p_max_mw fields to mirror the generator-side source data, and this bound
formula does not depend on bid presence; whether/how a given load's bound is actually used
by opf.dc_opf is decided per-load elsewhere, not here. p_load_pu/q_load_pu (the
bus aggregate) are untouched by this addition.
storage_p_max_pu
class-attribute
instance-attribute
¶
Charge/discharge power limit (Storage.p_max_mw), pu of base_mva.
storage_energy_pu
class-attribute
instance-attribute
¶
Energy capacity (Storage.energy_mwh), pu of base_mva (pu-hours) — the same
base_mva-division convention every other physical field in this class already uses
(ADR-005: physical units in the model, pu in numerics).
storage_soc_initial
class-attribute
instance-attribute
¶
Initial state of charge, already a fraction of energy_mwh in [0, 1] on the
entity — dimensionless, so unlike storage_p_max_pu/storage_energy_pu it carries
through unconverted.
storage_efficiency_discharge
class-attribute
instance-attribute
¶
Charge/discharge efficiency, already dimensionless ratios in (0, 1] — unconverted.
from_network
classmethod
¶
from_network(net: Network) -> NetworkArrays
Build the in-service positional view; the one pu-conversion site.
Source code in src/mambo_power/numerics/arrays.py
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
Bus admittance matrix¶
mambo_power.numerics.ybus
¶
Bus admittance matrix and branch admittance matrices (MATPOWER makeYbus conventions).
Per branch with series admittance y = 1 / (r + jx), total charging b and from-side
complex tap a = tap · e^{j·shift}::
Yff = (y + j·b/2) / |a|² Yft = -y / conj(a)
Ytf = -y / a Ytt = y + j·b/2
Ybus = Cfᵀ·Yf + Ctᵀ·Yt + diag(g_shunt + j·b_shunt) with the shunt admittance already in pu
(NetworkArrays divided by base_mva).
branch_admittances
¶
branch_admittances(
arr: NetworkArrays,
) -> tuple[
ComplexArray, ComplexArray, ComplexArray, ComplexArray
]
Per-branch (Yff, Yft, Ytf, Ytt) vectors.
Source code in src/mambo_power/numerics/ybus.py
yf_yt
¶
yf_yt(arr: NetworkArrays) -> tuple[Any, Any]
(Yf, Yt): n_branch × n_bus complex CSC matrices giving from/to branch currents.
Yf @ V is the current injected into each branch at its from bus, Yt @ V at its to
bus — the inputs M2 needs for branch flows.
Source code in src/mambo_power/numerics/ybus.py
ybus
¶
ybus(arr: NetworkArrays) -> Any
The n_bus × n_bus complex CSC bus admittance matrix over the in-service subset.
Source code in src/mambo_power/numerics/ybus.py
DC susceptance matrices¶
mambo_power.numerics.bbus
¶
DC susceptance matrices and phase-shift injections (MATPOWER makeBdc conventions).
Per branch b = 1 / (x · tap) (tap magnitude only; r and line charging ignored). With
Cft the n_branch × n_bus from-minus-to incidence matrix::
Bf = diag(b) · Cft Pf = Bf · θ + pf_shift
Bbus = Cftᵀ · Bf P = Bbus · θ + p_shift
where pf_shift = -b · shift_rad and p_shift = Cftᵀ · pf_shift. A DC solve therefore
reads Bbus · θ = P - p_shift.
branch_susceptance
¶
branch_susceptance(arr: NetworkArrays) -> FloatArray
Per-branch DC susceptance 1 / (x · tap).
Source code in src/mambo_power/numerics/bbus.py
incidence
¶
incidence(arr: NetworkArrays) -> Any
Cft: n_branch × n_bus sparse matrix with +1 at the from bus and -1 at the to bus.
Source code in src/mambo_power/numerics/bbus.py
bf
¶
bf(arr: NetworkArrays) -> Any
Bf: n_branch × n_bus CSC matrix; Bf @ θ is the from-side DC flow (pu).
Source code in src/mambo_power/numerics/bbus.py
bbus
¶
bbus(arr: NetworkArrays) -> Any
Bbus: n_bus × n_bus real CSC DC susceptance matrix (Cftᵀ · Bf).
pf_shift
¶
pf_shift(arr: NetworkArrays) -> FloatArray
Per-branch phase-shifter flow injection -b · shift_rad (pu), at the from bus.
p_shift
¶
p_shift(arr: NetworkArrays) -> FloatArray
Per-bus phase-shifter injection Cftᵀ · pf_shift (pu); P = Bbus·θ + p_shift.
Source code in src/mambo_power/numerics/bbus.py
flow_from_ptdf
¶
flow_from_ptdf(
ptdf: FloatArray,
injection_mw: FloatArray,
arr: NetworkArrays,
) -> FloatArray
Branch flow, MW, from a PTDF matrix and a full bus net-injection vector, MW.
flow = ptdf @ (injection_mw − p_shift·base_mva) + pf_shift·base_mva — the phase-shifter
injection is subtracted out of the bus injection before the PTDF product, then each
branch's own from-side shift flow is added back on. This is exactly :func:mambo_power.pf.dc.
solve's construction (its module docstring: rhs = P − p_shift feeds the angle solve, and
p_from = Bf·θ + pf_shift; combined with θ = B'⁻¹(P − p_shift) and PTDF = Bf·B'⁻¹
on the reduced system, p_from = PTDF·(P − p_shift) + pf_shift) — the model every DC
PTDF-based flow in this package must match. Omitting the − p_shift term (all of
opf.dc_opf, opf.solve_dc_opf and market._clearing did until M8 finding F1 / A19)
reproduces pf.solve_dc's flow only when no branch has a shift, since
p_shift(arr) == 0 identically in that case.
injection_mw must be the full net injection per bus (generation minus load minus
shunt, MW) — callers that instead fold some of that into a decision-variable-relative LP
constant (opf.dc_opf's own flow-limit rows) derive the identical correction by hand
rather than calling this helper, since their injection is not one vector (see that
module's own derivation).
Source code in src/mambo_power/numerics/bbus.py
PTDF¶
mambo_power.numerics.ptdf
¶
Power transfer distribution factors from the DC model.
PTDF = Bf · Bbus⁻¹ with the slack row and column removed before the inverse and the slack
column of the result set to zero: flows = PTDF @ P for any injection vector P (the
slack absorbs the imbalance). The reduced Bbus is factorised once with a sparse LU and
solved against the dense transposed Bf; the full matrix is never inverted densely.
ptdf
¶
ptdf(
arr: NetworkArrays, slack: int | None = None
) -> FloatArray
Dense n_branch × n_bus PTDF with a zero column at slack (default: the network's).
Source code in src/mambo_power/numerics/ptdf.py
LODF and bridges¶
mambo_power.numerics.lodf
¶
Line outage distribution factors and graph-theoretic bridge detection.
LODF[l, k] is the fraction of branch k's pre-outage flow that appears on branch l
after k is removed. With h_k = PTDF·(e_f(k) − e_t(k)) the flows caused by a unit
transfer across k::
LODF[l, k] = h_k[l] / (1 − h_k[k]) (l ≠ k) LODF[k, k] = −1
A branch whose removal disconnects the network (a bridge) has h_k[k] = 1 and no finite
LODF; its whole column is NaN. The numeric test (|1 − h_kk| < 1e-10) and the
graph-theoretic bridges are independent and must agree — the test suite checks that.
BRIDGE_TOL
module-attribute
¶
|1 − PTDF_kk| < BRIDGE_TOL marks branch k as a bridge in lodf.
lodf
¶
lodf(
arr: NetworkArrays,
ptdf_matrix: FloatArray | None = None,
) -> FloatArray
Dense n_branch × n_branch LODF; bridge columns are NaN, diagonal is −1.
Source code in src/mambo_power/numerics/lodf.py
bridges
¶
bridges(arr: NetworkArrays) -> list[int]
Positions of branches whose removal disconnects the in-service graph (sorted).
Iterative Tarjan lowpoint search over the multigraph; parallel branches between the same pair of buses are never bridges because the search skips only the edge it arrived by.