pub struct SeNetwork {
pub ybus: YBusSparse,
pub terminals: Vec<[CurrentFunctional; 2]>,
pub shunt_y: Vec<Complex<f64>>,
pub source_branches: Vec<Vec<usize>>,
pub zero_injection: Vec<bool>,
pub energized: Vec<bool>,
}Expand description
Everything h(x) needs about the network, beyond the state itself.
Assembled once per topology. The per-bus shunt admittance and the per-bus list of source branches are kept explicitly because both are otherwise only recoverable by inspecting Y-bus entries, which cannot distinguish a shunt from a line’s charging susceptance.
Fields§
§ybus: YBusSparse§terminals: Vec<[CurrentFunctional; 2]>Per branch, the current functional at each terminal: [0] is the from
side, [1] the to side.
Stored resolved rather than as branch parameters, because a three-phase
terminal is not a two-bus quantity: phase p of one end depends on all
six phasors of the branch, through the 3×3 blocks. A coefficient list
expresses both cases, and the estimator above it cannot tell them apart —
which is what makes the phase domain a matter of longer lists rather than
of a second Jacobian.
In the phase domain the index is 3·branch + phase, exactly as a bus
index is 3·node + phase. That is what keeps
Target unchanged between the two.
shunt_y: Vec<Complex<f64>>Total shunt admittance stamped at each bus, or zero.
source_branches: Vec<Vec<usize>>For each bus, the synthesized source branches delivering into it.
zero_injection: Vec<bool>Per-bus zero-injection flags, carried through from the conversion — see
gridoxide’s pgm::PgmNetwork::zero_injection.
energized: Vec<bool>Whether each bus is reachable from any source.
A connected component containing no source carries no voltage that any
measurement can determine: every quantity in it is identically zero, and
the sensors that sit there — a network can perfectly well have a voltage
sensor on a de-energized node — describe nothing. power-grid-model
reports such nodes as energized: 0 with a state of exactly zero, and
decides it topologically, ignoring whatever sensors are attached.
Without this the gain matrix comes back singular rather than merely
uninformative, because those buses’ columns are touched — by their own
sensors, or by a zero-injection constraint — while being determined by
nothing. jacobian::mask_untouched only pins what nothing touches at
all, which catches a fully isolated bus and not this.
solver::PersistentSolver has done the equivalent for power flow all
along, via network::connected_components and mark_unreferenced_islands.
Implementations§
Source§impl SeNetwork
impl SeNetwork
Sourcepub fn functional(&self, target: Target) -> Option<CurrentFunctional>
pub fn functional(&self, target: Target) -> Option<CurrentFunctional>
The current functional a measurement target names, or None if the
target does not resolve in this network.
This is the single description of what each target measures. Everything
downstream — h(x), the Jacobian, the iterative-linear row builder —
reads it rather than restating it.
Source§impl SeNetwork
impl SeNetwork
Sourcepub fn new(
ybus: YBusSparse,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
source_branch_idx: &HashMap<u64, usize>,
zero_injection: &[bool],
) -> Self
pub fn new( ybus: YBusSparse, lines: &[Line], transformers: &[Transformer], shunts: &[ShuntAdm], source_branch_idx: &HashMap<u64, usize>, zero_injection: &[bool], ) -> Self
Builds the model from a converted network plus the shunt list that was stamped into its Y-bus.
shunts must be the same slice passed to
network::stamp_shunts — the shunt
measurement function needs the admittance itself, and the Y-bus has
already summed it together with everything else on that diagonal.
source_branch_idx maps each source to the branch synthesized for it,
and zero_injection flags the buses known to inject nothing; the PGM
converter records both, and
gridoxide’s pgm::PgmNetwork::se_network passes
them on.
Sourcepub fn from_node_breaker(net: &NodeBreakerNetwork, ybus: YBusSparse) -> Self
pub fn from_node_breaker(net: &NodeBreakerNetwork, ybus: YBusSparse) -> Self
The measurement model for state estimation over a node-breaker network’s bus view.
ybus must be the one built from its lines and transformers with
stamp_shunts applied — the estimator
reads the branch list a second time to build its functionals, and a
disagreement produces an estimate that converges confidently to the
wrong answer.
The retained switches are already in the branch list, so a measurement on
a breaker is an ordinary branch-terminal measurement at
branch_of_switch
— as with power flow, the estimator never learns that any of its
branches is a switch.
What is specific to this view is the constraint set. Almost every bus
the node-breaker topology adds is a bay-internal node with nothing
attached, so
zero_injection
is dense here in a way it never is on a bus-branch model, and those
buses are observable only because of it.
Sourcepub fn from_bus_network(
buses: &[Bus],
ybus: YBusSparse,
lines: &[Line],
transformers: &[Transformer],
shunts: &[ShuntAdm],
zero_injection: Vec<bool>,
) -> Self
pub fn from_bus_network( buses: &[Bus], ybus: YBusSparse, lines: &[Line], transformers: &[Transformer], shunts: &[ShuntAdm], zero_injection: Vec<bool>, ) -> Self
Builds the model for a network whose sources are bus types rather
than synthesized source branches — everything that does not come through
gridoxide’s pgm, which for now means CGMES, including the
node-breaker view.
The two differ in exactly one thing, and it is not the measurement model:
power-grid-model gives a source its own virtual bus behind a Thévenin
impedance, so “is this bus fed?” is answered by a branch list, while
CGMES marks a reference bus as BusType::Slack and injects nothing extra.
source_branches is therefore empty here, and every place that consulted
it for a reference goes through SeNetwork::reference_bus instead.
zero_injection is the caller’s: which buses carry no appliance at all.
It has to be structural, not a test on p_spec/q_spec — a load that
happens to sit at zero in one snapshot is not a bus that injects nothing,
and a hard constraint saying otherwise biases the estimate.
Sourcepub fn reference_bus(&self, buses: &[Bus]) -> usize
pub fn reference_bus(&self, buses: &[Bus]) -> usize
The bus whose angle is held fixed when nothing measures phase.
With no angle measurement anywhere, the state is determined only up to a common rotation: one angle has to be pinned or the gain matrix is singular by construction. Which one is arbitrary mathematically, but it must be a bus that carries voltage at all — pinning a de-energized bus pins nothing and leaves the rotation free.
A source’s own bus is the natural choice and is what a power flow would
use. The Slack fallback is what makes a CGMES network work, since it
has no source branches; the final 0 is unreachable in practice and is
there so this never panics on a degenerate network that the estimator
will reject a moment later anyway.
Sourcepub fn from_3ph(
ybus: YBusSparse,
lines: &[Line3Ph],
transformers: &[Transformer3PhSeq],
shunts: &[ShuntAdm3Ph],
source_branch_idx: &HashMap<u64, usize>,
zero_injection_per_node: &[bool],
) -> Self
pub fn from_3ph( ybus: YBusSparse, lines: &[Line3Ph], transformers: &[Transformer3PhSeq], shunts: &[ShuntAdm3Ph], source_branch_idx: &HashMap<u64, usize>, zero_injection_per_node: &[bool], ) -> Self
Builds the model for a three-phase network.
Everything above this is unchanged by the phase domain. Bus k phase p
is index 3k + p, as lib.rs documents for power flow, and a branch
terminal is indexed 3b + p to match — so Target, StateLayout, the
Jacobian, the constraints and both estimator methods carry over without
knowing there are phases at all. What changes is the length of a
coefficient list: six terms where the scalar case has two, because phase
p of one end of a branch depends on all six phasors through the 3×3
blocks.
lines, transformers and shunts must be the same slices stamped into
ybus — this reads their blocks a second time to build the measurement
model, and a disagreement between the two would produce an estimate that
converges confidently to the wrong answer.
Sourcepub fn constrained_buses(&self) -> Vec<bool>
pub fn constrained_buses(&self) -> Vec<bool>
Buses a zero-injection constraint should apply to.
A de-energized bus’s injection is zero too, but asserting it adds a row that reaches columns nothing determines — which is how a constraint turns an uninformative island into a singular matrix.
Auto Trait Implementations§
impl Freeze for SeNetwork
impl RefUnwindSafe for SeNetwork
impl Send for SeNetwork
impl Sync for SeNetwork
impl Unpin for SeNetwork
impl UnsafeUnpin for SeNetwork
impl UnwindSafe for SeNetwork
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
Source§impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T> DistributionExt for Twhere
T: ?Sized,
impl<T, U> Imply<T> for U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more