Skip to main content

gridoxide_core/se/
case.rs

1//! A network, a starting state and a measurement set that agree with each
2//! other — what an estimate is run on.
3//!
4//! The estimator itself is domain-agnostic — [`Target`](crate::measurement::Target),
5//! [`StateLayout`](super::jacobian::StateLayout), the Jacobian, the constraints,
6//! both methods and the batch solver are the same code whether a bus is a node
7//! or a node's phase. [`SeCase`] carries which of the two it is, and the
8//! document's node ids, so a report can name its rows.
9//!
10//! Building one from a PGM document is
11//! gridoxide's `pgm::se`'s job.
12
13use std::collections::HashMap;
14
15use crate::measurement::Measurement;
16use crate::types::Bus;
17
18use super::SeNetwork;
19
20/// A network, a starting state and a measurement set, assembled together.
21///
22/// The three have to agree about what a bus index means, which is the whole
23/// reason they are built in one place rather than three.
24pub struct SeCase {
25    pub network: SeNetwork,
26    /// The buses, in the layout the network is indexed by. **Not** an estimate:
27    /// a flat start, which [`linear_start`](super::nr::linear_start) improves on
28    /// before the first iteration.
29    pub buses: Vec<Bus>,
30    pub measurements: Vec<Measurement>,
31    /// Document node id to *node* index. In the phase domain a node owns three
32    /// buses; use [`bus_of`](Self::bus_of) rather than this directly.
33    pub node_idx: HashMap<u64, usize>,
34    /// 1 for the symmetric domain, 3 for the phase domain.
35    ///
36    /// The one thing a caller downstream of this genuinely needs to know, and it
37    /// is here so that reporting can say `node 7 phase b` rather than `bus 22`.
38    pub phases: usize,
39}
40
41impl SeCase {
42    /// The bus index carrying one phase of one document node.
43    ///
44    /// `phase` is ignored in the symmetric domain, where a node is one bus.
45    pub fn bus_of(&self, node: u64, phase: usize) -> Option<usize> {
46        let idx = *self.node_idx.get(&node)?;
47        Some(self.phases * idx + if self.phases == 1 { 0 } else { phase })
48    }
49
50    /// The document's node ids, in index order — what a report iterates to name
51    /// its rows.
52    pub fn nodes(&self) -> Vec<(u64, usize)> {
53        let mut out: Vec<(u64, usize)> = self.node_idx.iter().map(|(&id, &i)| (id, i)).collect();
54        out.sort_unstable();
55        out
56    }
57}
58
59/// The phase labels, in the order the index arithmetic uses.
60pub const PHASE_NAMES: [&str; 3] = ["a", "b", "c"];