powerio_tx/operations/
subset.rs1use std::collections::{HashMap, HashSet};
4use std::sync::Arc;
5
6use powerio_core::ComponentId;
7
8use crate::network::{
9 BalancedNetwork, CalculatedBus, DetailedConnectivity, TerminalReference, TopologyEndpoint,
10};
11
12#[derive(Debug, Clone)]
20#[non_exhaustive]
21pub struct BusSubset {
22 pub network: BalancedNetwork,
23 pub bus_rows: Vec<Option<usize>>,
24 pub load_rows: Vec<Option<usize>>,
25 pub shunt_rows: Vec<Option<usize>>,
26 pub static_var_compensator_rows: Vec<Option<usize>>,
27 pub branch_rows: Vec<Option<usize>>,
28 pub switch_rows: Vec<Option<usize>>,
29 pub generator_rows: Vec<Option<usize>>,
30 pub storage_rows: Vec<Option<usize>>,
31 pub hvdc_rows: Vec<Option<usize>>,
32 pub transformer_3w_rows: Vec<Option<usize>>,
33 pub area_rows: Vec<Option<usize>>,
34}
35
36pub(super) fn filter_rows<T: Clone>(
37 source: &[T],
38 keep: impl Fn(&T) -> bool,
39) -> (Vec<T>, Vec<Option<usize>>) {
40 let mut result = Vec::new();
41 let rows = source
42 .iter()
43 .map(|value| {
44 keep(value).then(|| {
45 let row = result.len();
46 result.push(value.clone());
47 row
48 })
49 })
50 .collect();
51 (result, rows)
52}
53
54#[expect(clippy::too_many_lines)]
58pub(crate) fn filter_connectivity(source: &BalancedNetwork, cut: &mut BalancedNetwork) {
59 let Some(original) = source.detailed_connectivity().as_deref() else {
60 return;
61 };
62 let mut detail = original.clone();
63 let kept: HashSet<_> = cut.buses().iter().map(|bus| bus.id).collect();
64 let mut removed = HashSet::<ComponentId>::new();
65 macro_rules! removed_records {
66 ($table:ident, $kind:literal) => {{
67 let ids: HashSet<_> = cut
68 .$table()
69 .iter()
70 .filter_map(|v| v.uid.as_deref())
71 .collect();
72 removed.extend(
73 source
74 .$table()
75 .iter()
76 .filter_map(|v| v.uid.as_deref())
77 .filter(|uid| !ids.contains(uid))
78 .filter_map(|uid| ComponentId::new($kind, uid).ok()),
79 );
80 }};
81 }
82 removed_records!(buses, "bus");
83 removed_records!(loads, "load");
84 removed_records!(shunts, "shunt");
85 removed_records!(static_var_compensators, "static_var_compensator");
86 removed_records!(branches, "branch");
87 removed_records!(switches, "switch");
88 removed_records!(generators, "generator");
89 removed_records!(storage, "storage");
90 removed_records!(hvdc, "hvdc");
91 removed_records!(transformers_3w, "transformer_3w");
92
93 for level in &mut detail.voltage_levels {
94 level.buses.retain(|bus| kept.contains(bus));
95 }
96 for bus in &detail.calculated_buses {
97 if !kept.contains(&bus.calculated_bus) {
98 removed.extend(bus.nodes.iter().cloned());
99 }
100 }
101 detail
102 .calculated_buses
103 .retain(|bus| kept.contains(&bus.calculated_bus));
104 detail.bus_breaker_buses.retain(|bus| {
105 let keep = bus.calculated_bus.is_none_or(|id| kept.contains(&id));
106 if !keep {
107 removed.insert(bus.component.clone());
108 }
109 keep
110 });
111 detail.connectivity_nodes.retain(|node| {
112 let keep = !removed.contains(&node.component)
113 && node.calculated_bus.is_none_or(|id| kept.contains(&id));
114 if !keep {
115 removed.insert(node.component.clone());
116 }
117 keep
118 });
119 for bus in &mut detail.calculated_buses {
120 bus.nodes.retain(|node| !removed.contains(node));
121 }
122 detail.busbar_sections.retain(|busbar| {
123 let keep = !removed.contains(&busbar.node);
124 if !keep {
125 removed.insert(busbar.component.clone());
126 }
127 keep
128 });
129 let endpoint = |value: &TopologyEndpoint| match value {
130 TopologyEndpoint::Bus(id) | TopologyEndpoint::Node(id) => id.clone(),
131 };
132 detail.switches.retain(|switch| {
133 let keep = !removed.contains(&switch.component)
134 && !removed.contains(&endpoint(&switch.endpoint1))
135 && !removed.contains(&endpoint(&switch.endpoint2));
136 if !keep {
137 removed.insert(switch.component.clone());
138 }
139 keep
140 });
141 detail
142 .internal_connections
143 .retain(|edge| !removed.contains(&edge.node1) && !removed.contains(&edge.node2));
144 let source_equipment: HashSet<_> = original
147 .terminals
148 .iter()
149 .filter(|t| t.bus.is_some() || t.node.is_some() || t.connectable_bus.is_some())
150 .map(|t| t.equipment.clone())
151 .collect();
152 detail.terminals.retain_mut(|terminal| {
153 if removed.contains(&terminal.equipment) {
154 return false;
155 }
156 let mut lost_connection = false;
157 for connection in [&mut terminal.bus, &mut terminal.node] {
158 if connection.as_ref().is_some_and(|id| removed.contains(id)) {
159 *connection = None;
160 lost_connection = true;
161 }
162 }
163 if terminal
164 .connectable_bus
165 .as_ref()
166 .is_some_and(|id| removed.contains(id))
167 {
168 terminal.connectable_bus = None;
169 }
170 if lost_connection {
171 terminal.connected = false;
172 }
173 true
174 });
175 let attached: HashSet<_> = detail
178 .terminals
179 .iter()
180 .filter(|t| t.bus.is_some() || t.node.is_some() || t.connectable_bus.is_some())
181 .map(|t| t.equipment.clone())
182 .collect();
183 macro_rules! detailed_equipment {
184 ($table:ident) => {
185 detail.$table.retain(|value| {
186 let keep = !removed.contains(&value.component)
187 && (!source_equipment.contains(&value.component)
188 || attached.contains(&value.component));
189 if !keep {
190 removed.insert(value.component.clone());
191 }
192 keep
193 });
194 };
195 }
196 detailed_equipment!(junctions);
197 detailed_equipment!(boundary_lines);
198 detailed_equipment!(voltage_source_converters);
199 detailed_equipment!(line_commutated_converters);
200 detail.boundary_lines.retain(|line| {
201 let keep = !line
202 .calculation_load
203 .as_ref()
204 .is_some_and(|id| removed.contains(id))
205 && !line
206 .calculation_generator
207 .as_ref()
208 .is_some_and(|id| removed.contains(id));
209 if !keep {
210 removed.insert(line.component.clone());
211 }
212 keep
213 });
214 detail.tie_lines.retain(|line| {
215 let keep = !removed.contains(&line.boundary_line1)
216 && !removed.contains(&line.boundary_line2)
217 && !line
218 .calculation_branch
219 .as_ref()
220 .is_some_and(|id| removed.contains(id));
221 if !keep {
222 removed.insert(line.component.clone());
223 }
224 keep
225 });
226 detail.terminals.retain(|t| !removed.contains(&t.equipment));
227 for terminal in &original.terminals {
228 if removed.contains(&terminal.equipment)
229 && let Some(id) = &terminal.component
230 {
231 removed.insert(id.clone());
232 }
233 }
234 let terminals: HashSet<_> = detail
235 .terminals
236 .iter()
237 .map(|t| (t.equipment.clone(), t.terminal))
238 .collect();
239 let clear_reference = |reference: &mut Option<TerminalReference>| {
240 if reference
241 .as_ref()
242 .is_some_and(|r| !terminals.contains(&(r.equipment.clone(), r.terminal)))
243 {
244 *reference = None;
245 }
246 };
247 detail.tap_changers.retain(|tap| {
248 let keep = !removed.contains(&tap.transformer);
249 if !keep && let Some(id) = &tap.component {
250 removed.insert(id.clone());
251 }
252 keep
253 });
254 for tap in &mut detail.tap_changers {
255 clear_reference(&mut tap.regulation_terminal);
256 }
257 for converter in &mut detail.voltage_source_converters {
258 clear_reference(&mut converter.pcc_terminal);
259 }
260 for converter in &mut detail.line_commutated_converters {
261 clear_reference(&mut converter.pcc_terminal);
262 }
263 let used_levels: HashSet<_> = detail
266 .bus_breaker_buses
267 .iter()
268 .map(|b| b.voltage_level.clone())
269 .chain(
270 detail
271 .connectivity_nodes
272 .iter()
273 .map(|n| n.voltage_level.clone()),
274 )
275 .chain(detail.terminals.iter().map(|t| t.voltage_level.clone()))
276 .chain(
277 detail
278 .boundary_lines
279 .iter()
280 .map(|b| b.voltage_level.clone()),
281 )
282 .collect();
283 let originally_empty: HashSet<_> = original
284 .voltage_levels
285 .iter()
286 .filter(|level| level.buses.is_empty())
287 .map(|level| &level.component)
288 .collect();
289 detail.voltage_levels.retain(|level| {
290 let keep = !level.buses.is_empty()
291 || used_levels.contains(&level.component)
292 || originally_empty.contains(&level.component);
293 if !keep {
294 removed.insert(level.component.clone());
295 }
296 keep
297 });
298 detail
299 .operational_limit_groups
300 .retain(|g| !removed.contains(&g.equipment));
301 detail
302 .equipment_reactive_limits
303 .retain(|g| !removed.contains(&g.equipment));
304 detail
305 .omitted_fields
306 .retain(|f| !removed.contains(&f.component));
307 detail
308 .component_metadata
309 .retain(|m| !removed.contains(&m.component));
310 for metadata in &mut detail.component_metadata {
311 if metadata
312 .equipment_container
313 .as_ref()
314 .is_some_and(|id| removed.contains(id))
315 {
316 metadata.equipment_container = None;
317 }
318 }
319 macro_rules! clear_removed_containers {
320 ($($table:ident),+) => {$(
321 for record in &mut detail.$table {
322 if record.equipment_container.as_ref().is_some_and(|id| removed.contains(id)) {
323 record.equipment_container = None;
324 }
325 }
326 )+};
327 }
328 clear_removed_containers!(
329 dc_grounds,
330 dc_busbars,
331 dc_lines,
332 dc_series_devices,
333 dc_switches
334 );
335 for subnetwork in &mut detail.subnetworks {
336 subnetwork.components.retain(|id| !removed.contains(id));
337 }
338 if !removed.is_empty() || cut.buses().len() != source.buses().len() {
339 preserve_orphaned_buses(cut, &mut detail);
340 }
341 *cut.detailed_connectivity_mut() = Some(Arc::new(detail));
342}
343
344fn preserve_orphaned_buses(cut: &BalancedNetwork, detail: &mut DetailedConnectivity) {
348 let mut anchored: HashSet<_> = detail
349 .calculated_buses
350 .iter()
351 .flat_map(|b| b.nodes.iter().cloned())
352 .chain(detail.busbar_sections.iter().map(|b| b.node.clone()))
353 .chain(detail.terminals.iter().filter_map(|t| t.node.clone()))
354 .chain(
355 detail
356 .internal_connections
357 .iter()
358 .flat_map(|e| [e.node1.clone(), e.node2.clone()]),
359 )
360 .collect();
361 for switch in &detail.switches {
362 for end in [&switch.endpoint1, &switch.endpoint2] {
363 if let TopologyEndpoint::Node(node) = end {
364 anchored.insert(node.clone());
365 }
366 }
367 }
368 let mut nodes_by_bus: HashMap<_, Vec<_>> = HashMap::new();
369 for node in &detail.connectivity_nodes {
370 if let Some(bus) = node.calculated_bus {
371 nodes_by_bus.entry(bus).or_default().push(node);
372 }
373 }
374 for bus in cut.buses() {
375 let Some(nodes) = nodes_by_bus.get(&bus.id) else {
376 continue;
377 };
378 if nodes.iter().any(|node| anchored.contains(&node.component))
379 || nodes
380 .iter()
381 .any(|node| node.voltage_level != nodes[0].voltage_level)
382 {
383 continue;
384 }
385 detail.calculated_buses.push(CalculatedBus {
386 voltage_level: nodes[0].voltage_level.clone(),
387 calculated_bus: bus.id,
388 nodes: nodes.iter().map(|node| node.component.clone()).collect(),
389 voltage_kv: Some(bus.vm * bus.base_kv),
390 angle_degrees: Some(if cut.is_normalized() {
391 bus.va.to_degrees()
392 } else {
393 bus.va
394 }),
395 });
396 }
397}