1pub(crate) mod balanced;
20pub(crate) mod multiconductor;
21
22pub use balanced::{
23 BalancedOperatingPointBuilder, BalancedOperatingPointFlag, BalancedOperatingPointQuantity,
24};
25pub use multiconductor::{
26 MulticonductorOperatingPointBuilder, MulticonductorOperatingPointFlag,
27 MulticonductorOperatingPointQuantity,
28};
29
30#[derive(Clone, Debug)]
34pub struct OperatingPoint<N> {
35 pub(crate) network: N,
36 pub(crate) columns: SharedColumns,
37 pub(crate) index: usize,
38}
39
40impl<N> OperatingPoint<N> {
41 pub fn network(&self) -> &N {
43 &self.network
44 }
45
46 fn value(&self, quantity: &'static str, identity: &str) -> Option<f64> {
47 self.columns
48 .quantities
49 .get(quantity)?
50 .value(self.index, identity)
51 }
52
53 fn iter_values(&self, quantity: &'static str) -> Option<OperatingPointValues<'_>> {
54 Some(OperatingPointValues {
55 quantity: self.columns.quantities.get(quantity)?,
56 point: self.index,
57 column: 0,
58 })
59 }
60
61 fn iter_flags(&self, quantity: &'static str) -> Option<OperatingPointFlags<'_>> {
62 Some(OperatingPointFlags(self.iter_values(quantity)?))
63 }
64
65 pub(crate) fn identity_order(
66 &self,
67 quantity: &str,
68 ) -> Option<impl ExactSizeIterator<Item = &str>> {
69 Some(self.columns.quantities.get(quantity)?.layout.order())
70 }
71}
72
73#[derive(Clone, Debug)]
75pub struct OperatingPointValues<'a> {
76 quantity: &'a Quantity,
77 point: usize,
78 column: usize,
79}
80
81impl<'a> Iterator for OperatingPointValues<'a> {
82 type Item = (&'a str, f64);
83
84 fn next(&mut self) -> Option<Self::Item> {
85 let identity = self.quantity.layout.order.get(self.column)?;
86 let value =
87 self.quantity
88 .storage
89 .value(self.point, self.column, self.quantity.layout.len());
90 self.column += 1;
91 Some((identity.as_ref(), value))
92 }
93
94 fn size_hint(&self) -> (usize, Option<usize>) {
95 let remaining = self.quantity.layout.len() - self.column;
96 (remaining, Some(remaining))
97 }
98}
99
100impl ExactSizeIterator for OperatingPointValues<'_> {}
101
102#[derive(Clone, Debug)]
104pub struct OperatingPointFlags<'a>(OperatingPointValues<'a>);
105
106impl<'a> Iterator for OperatingPointFlags<'a> {
107 type Item = (&'a str, bool);
108
109 fn next(&mut self) -> Option<Self::Item> {
110 self.0
111 .next()
112 .map(|(identity, value)| (identity, value != 0.0))
113 }
114
115 fn size_hint(&self) -> (usize, Option<usize>) {
116 self.0.size_hint()
117 }
118}
119
120impl ExactSizeIterator for OperatingPointFlags<'_> {}
121
122use std::collections::HashMap;
123use std::sync::Arc;
124
125use powerio_core::Error;
126
127use crate::diagnostics::codes;
128
129#[derive(Clone, Debug, Default)]
132pub(crate) struct QuantityLayout {
133 index: HashMap<Box<str>, u32>,
135 order: Vec<Box<str>>,
137}
138
139impl QuantityLayout {
140 pub(crate) fn from_order(
141 quantity: &'static str,
142 order: impl IntoIterator<Item = String>,
143 ) -> Result<Self, Error> {
144 let mut layout = Self::default();
145 for identity in order {
146 let column = u32::try_from(layout.order.len()).map_err(|_| {
147 Error::new(
148 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
149 format!("{quantity}: more than u32::MAX elements"),
150 )
151 })?;
152 if layout
153 .index
154 .insert(identity.clone().into_boxed_str(), column)
155 .is_some()
156 {
157 return Err(Error::new(
158 &codes::BUILD_OPERATING_POINT_IDENTITY_UNKNOWN,
159 format!("{quantity}: duplicate element identity `{identity}`"),
160 ));
161 }
162 layout.order.push(identity.into_boxed_str());
163 }
164 Ok(layout)
165 }
166
167 pub(crate) fn len(&self) -> usize {
168 self.order.len()
169 }
170
171 pub(crate) fn column(&self, identity: &str) -> Option<usize> {
172 self.index.get(identity).map(|column| *column as usize)
173 }
174
175 pub(crate) fn order(&self) -> impl ExactSizeIterator<Item = &str> {
176 self.order.iter().map(AsRef::as_ref)
177 }
178}
179
180pub(crate) type PointChanges = Box<[(u32, f64)]>;
182
183#[derive(Clone, Debug)]
186pub(crate) enum QuantityStorage {
187 Dense {
188 values: Box<[f64]>,
190 },
191 Sparse {
192 base: Box<[f64]>,
194 changes: Box<[PointChanges]>,
196 },
197}
198
199impl QuantityStorage {
200 pub(crate) fn value(&self, point: usize, column: usize, width: usize) -> f64 {
201 match self {
202 Self::Dense { values } => values[point * width + column],
203 Self::Sparse { base, changes } => {
204 let column32 = column as u32;
205 match changes[point].binary_search_by_key(&column32, |(c, _)| *c) {
206 Ok(found) => changes[point][found].1,
207 Err(_) => base[column],
208 }
209 }
210 }
211 }
212
213 pub(crate) fn replace(
214 &mut self,
215 point: usize,
216 column: usize,
217 width: usize,
218 replacement: f64,
219 ) -> bool {
220 let previous = self.value(point, column, width);
221 if previous.to_bits() == replacement.to_bits() {
222 return false;
223 }
224 match self {
225 Self::Dense { values } => values[point * width + column] = replacement,
226 Self::Sparse { base, changes } => {
227 let column = column as u32;
228 let row = &mut changes[point];
229 let mut updated = row.to_vec();
230 match updated.binary_search_by_key(&column, |(entry, _)| *entry) {
231 Ok(found) if replacement.to_bits() == base[column as usize].to_bits() => {
232 updated.remove(found);
233 }
234 Ok(found) => updated[found].1 = replacement,
235 Err(_) if replacement.to_bits() == base[column as usize].to_bits() => {}
236 Err(insert_at) => updated.insert(insert_at, (column, replacement)),
237 }
238 *row = updated.into_boxed_slice();
239 }
240 }
241 true
242 }
243}
244
245#[derive(Clone, Debug)]
247pub(crate) struct Quantity {
248 pub(crate) layout: QuantityLayout,
249 pub(crate) storage: QuantityStorage,
250}
251
252impl Quantity {
253 pub(crate) fn value(&self, point: usize, identity: &str) -> Option<f64> {
254 let column = self.layout.column(identity)?;
255 Some(self.storage.value(point, column, self.layout.len()))
256 }
257}
258
259#[derive(Clone, Debug)]
261pub(crate) struct OperatingPointColumns {
262 pub(crate) point_count: usize,
263 pub(crate) quantities: HashMap<&'static str, Quantity>,
264}
265
266pub(crate) type SharedColumns = Arc<OperatingPointColumns>;
267
268impl<N> OperatingPoint<N> {
269 pub(crate) fn replace_value(
270 &mut self,
271 quantity_name: &'static str,
272 layout: QuantityLayout,
273 defaults: &[f64],
274 identity: &str,
275 replacement: f64,
276 ) -> Result<bool, Error> {
277 let columns = Arc::make_mut(&mut self.columns);
278 if !columns.quantities.contains_key(quantity_name) {
279 if defaults.len() != layout.len() {
280 return Err(Error::new(
281 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
282 format!(
283 "{quantity_name}: {} defaults supplied for {} components",
284 defaults.len(),
285 layout.len()
286 ),
287 ));
288 }
289 let mut values = Vec::with_capacity(defaults.len() * columns.point_count);
290 for _ in 0..columns.point_count {
291 values.extend_from_slice(defaults);
292 }
293 columns.quantities.insert(
294 quantity_name,
295 dense_quantity(quantity_name, layout, columns.point_count, values)?,
296 );
297 }
298 let quantity = columns
299 .quantities
300 .get_mut(quantity_name)
301 .expect("the quantity was inserted above");
302 let Some(column) = quantity.layout.column(identity) else {
303 return Err(Error::new(
304 &codes::BUILD_OPERATING_POINT_IDENTITY_UNKNOWN,
305 format!("{quantity_name}: unknown component identity `{identity}`"),
306 ));
307 };
308 Ok(quantity
309 .storage
310 .replace(self.index, column, quantity.layout.len(), replacement))
311 }
312}
313
314pub(crate) fn dense_quantity(
316 quantity: &'static str,
317 layout: QuantityLayout,
318 point_count: usize,
319 values: Vec<f64>,
320) -> Result<Quantity, Error> {
321 let expected = point_count.checked_mul(layout.len()).ok_or_else(|| {
322 Error::new(
323 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
324 format!(
325 "{quantity}: {point_count} points by {} elements exceeds addressable memory",
326 layout.len()
327 ),
328 )
329 })?;
330 if values.len() != expected {
331 return Err(Error::new(
332 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
333 format!(
334 "{quantity}: {} values supplied; {point_count} points by {} elements needs {expected}",
335 values.len(),
336 layout.len()
337 ),
338 ));
339 }
340 Ok(Quantity {
341 layout,
342 storage: QuantityStorage::Dense {
343 values: values.into_boxed_slice(),
344 },
345 })
346}
347
348pub(crate) fn sparse_quantity(
351 quantity: &'static str,
352 layout: QuantityLayout,
353 point_count: usize,
354 base: Vec<f64>,
355 changes: Vec<Vec<(String, f64)>>,
356) -> Result<Quantity, Error> {
357 if base.len() != layout.len() {
358 return Err(Error::new(
359 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
360 format!(
361 "{quantity}: base row has {} values; the layout has {} elements",
362 base.len(),
363 layout.len()
364 ),
365 ));
366 }
367 if changes.len() != point_count {
368 return Err(Error::new(
369 &codes::BUILD_OPERATING_POINT_SHAPE_MISMATCH,
370 format!(
371 "{quantity}: {} change sets supplied for {point_count} points",
372 changes.len()
373 ),
374 ));
375 }
376 let mut resolved = Vec::with_capacity(point_count);
377 for point in changes {
378 let mut row: Vec<(u32, f64)> = Vec::with_capacity(point.len());
379 for (identity, value) in point {
380 let Some(column) = layout.column(&identity) else {
381 return Err(Error::new(
382 &codes::BUILD_OPERATING_POINT_IDENTITY_UNKNOWN,
383 format!("{quantity}: unknown element identity `{identity}`"),
384 ));
385 };
386 row.push((column as u32, value));
387 }
388 row.sort_unstable_by_key(|(column, _)| *column);
389 row.dedup_by_key(|(column, _)| *column);
390 resolved.push(row.into_boxed_slice());
391 }
392 Ok(Quantity {
393 layout,
394 storage: QuantityStorage::Sparse {
395 base: base.into_boxed_slice(),
396 changes: resolved.into_boxed_slice(),
397 },
398 })
399}
400
401pub(crate) fn row_identity(uid: Option<&str>, table: &str, row: usize) -> String {
405 match uid {
406 Some(uid) => uid.to_owned(),
407 None => format!("{table}:{row}"),
408 }
409}