1use std::collections::HashMap;
8
9use serde::{Deserialize, Serialize};
10
11pub use crate::network::dc_line::{LccConverterTerminal, LccHvdcControlMode, LccHvdcLink};
12pub use crate::network::dc_network_types::{DcBranch, DcBus, DcConverterStation};
13pub use crate::network::vsc_dc_line::{
14 VscConverterAcControlMode, VscConverterTerminal, VscHvdcControlMode, VscHvdcLink,
15};
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19#[serde(tag = "technology", rename_all = "snake_case")]
20pub enum HvdcLink {
21 Lcc(LccHvdcLink),
22 Vsc(VscHvdcLink),
23}
24
25impl HvdcLink {
26 pub fn name(&self) -> &str {
28 match self {
29 Self::Lcc(link) => &link.name,
30 Self::Vsc(link) => &link.name,
31 }
32 }
33
34 pub fn is_blocked(&self) -> bool {
36 match self {
37 Self::Lcc(link) => link.mode == LccHvdcControlMode::Blocked,
38 Self::Vsc(link) => link.mode == VscHvdcControlMode::Blocked,
39 }
40 }
41
42 pub fn as_lcc(&self) -> Option<&LccHvdcLink> {
43 match self {
44 Self::Lcc(link) => Some(link),
45 Self::Vsc(_) => None,
46 }
47 }
48
49 pub fn as_lcc_mut(&mut self) -> Option<&mut LccHvdcLink> {
50 match self {
51 Self::Lcc(link) => Some(link),
52 Self::Vsc(_) => None,
53 }
54 }
55
56 pub fn as_vsc(&self) -> Option<&VscHvdcLink> {
57 match self {
58 Self::Lcc(_) => None,
59 Self::Vsc(link) => Some(link),
60 }
61 }
62
63 pub fn as_vsc_mut(&mut self) -> Option<&mut VscHvdcLink> {
64 match self {
65 Self::Lcc(_) => None,
66 Self::Vsc(link) => Some(link),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
73pub enum LccDcConverterRole {
74 #[default]
75 Rectifier,
76 Inverter,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct LccDcConverter {
82 #[serde(default, skip_serializing_if = "String::is_empty")]
84 pub id: String,
85 pub dc_bus: u32,
87 pub ac_bus: u32,
89 #[serde(alias = "num_bridges")]
91 pub n_bridges: u32,
92 pub alpha_max_deg: f64,
94 pub alpha_min_deg: f64,
96 pub gamma_min_deg: f64,
98 pub commutation_resistance_ohm: f64,
100 pub commutation_reactance_ohm: f64,
102 pub base_voltage_kv: f64,
104 pub turns_ratio: f64,
106 pub tap_ratio: f64,
108 pub tap_max: f64,
110 pub tap_min: f64,
112 pub tap_step: f64,
114 pub scheduled_setpoint: f64,
116 pub power_share_percent: f64,
118 pub current_margin_percent: f64,
120 pub role: LccDcConverterRole,
122 pub in_service: bool,
124}
125
126impl Default for LccDcConverter {
127 fn default() -> Self {
128 Self {
129 id: String::new(),
130 dc_bus: 0,
131 ac_bus: 0,
132 n_bridges: 1,
133 alpha_max_deg: 90.0,
134 alpha_min_deg: 5.0,
135 gamma_min_deg: 15.0,
136 commutation_resistance_ohm: 0.0,
137 commutation_reactance_ohm: 0.0,
138 base_voltage_kv: 0.0,
139 turns_ratio: 1.0,
140 tap_ratio: 1.0,
141 tap_max: 1.1,
142 tap_min: 0.9,
143 tap_step: 0.00625,
144 scheduled_setpoint: 0.0,
145 power_share_percent: 0.0,
146 current_margin_percent: 0.0,
147 role: LccDcConverterRole::Rectifier,
148 in_service: true,
149 }
150 }
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(tag = "technology", rename_all = "snake_case")]
156pub enum DcConverter {
157 Lcc(LccDcConverter),
158 Vsc(DcConverterStation),
159}
160
161impl From<LccDcConverter> for DcConverter {
162 fn from(value: LccDcConverter) -> Self {
163 Self::Lcc(value)
164 }
165}
166
167impl From<DcConverterStation> for DcConverter {
168 fn from(value: DcConverterStation) -> Self {
169 Self::Vsc(value)
170 }
171}
172
173impl DcConverter {
174 pub fn id(&self) -> &str {
175 match self {
176 Self::Lcc(converter) => &converter.id,
177 Self::Vsc(converter) => &converter.id,
178 }
179 }
180
181 pub fn id_mut(&mut self) -> &mut String {
182 match self {
183 Self::Lcc(converter) => &mut converter.id,
184 Self::Vsc(converter) => &mut converter.id,
185 }
186 }
187
188 pub fn ac_bus(&self) -> u32 {
189 match self {
190 Self::Lcc(converter) => converter.ac_bus,
191 Self::Vsc(converter) => converter.ac_bus,
192 }
193 }
194
195 pub fn dc_bus(&self) -> u32 {
196 match self {
197 Self::Lcc(converter) => converter.dc_bus,
198 Self::Vsc(converter) => converter.dc_bus,
199 }
200 }
201
202 pub fn is_in_service(&self) -> bool {
203 match self {
204 Self::Lcc(converter) => converter.in_service,
205 Self::Vsc(converter) => converter.status,
206 }
207 }
208
209 pub fn is_lcc(&self) -> bool {
210 matches!(self, Self::Lcc(_))
211 }
212
213 pub fn as_lcc(&self) -> Option<&LccDcConverter> {
214 match self {
215 Self::Lcc(converter) => Some(converter),
216 Self::Vsc(_) => None,
217 }
218 }
219
220 pub fn as_lcc_mut(&mut self) -> Option<&mut LccDcConverter> {
221 match self {
222 Self::Lcc(converter) => Some(converter),
223 Self::Vsc(_) => None,
224 }
225 }
226
227 pub fn as_vsc(&self) -> Option<&DcConverterStation> {
228 match self {
229 Self::Lcc(_) => None,
230 Self::Vsc(converter) => Some(converter),
231 }
232 }
233
234 pub fn as_vsc_mut(&mut self) -> Option<&mut DcConverterStation> {
235 match self {
236 Self::Lcc(_) => None,
237 Self::Vsc(converter) => Some(converter),
238 }
239 }
240
241 pub fn ac_bus_mut(&mut self) -> &mut u32 {
242 match self {
243 Self::Lcc(converter) => &mut converter.ac_bus,
244 Self::Vsc(converter) => &mut converter.ac_bus,
245 }
246 }
247
248 pub fn dc_bus_mut(&mut self) -> &mut u32 {
249 match self {
250 Self::Lcc(converter) => &mut converter.dc_bus,
251 Self::Vsc(converter) => &mut converter.dc_bus,
252 }
253 }
254}
255
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
258pub struct DcGrid {
259 pub id: u32,
261 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub name: Option<String>,
264 #[serde(default, skip_serializing_if = "Vec::is_empty")]
266 pub buses: Vec<DcBus>,
267 #[serde(default, skip_serializing_if = "Vec::is_empty")]
269 pub converters: Vec<DcConverter>,
270 #[serde(default, skip_serializing_if = "Vec::is_empty")]
272 pub branches: Vec<DcBranch>,
273}
274
275impl DcGrid {
276 pub fn new(id: u32, name: Option<String>) -> Self {
277 Self {
278 id,
279 name,
280 buses: Vec::new(),
281 converters: Vec::new(),
282 branches: Vec::new(),
283 }
284 }
285
286 pub fn is_empty(&self) -> bool {
287 self.buses.is_empty() && self.converters.is_empty() && self.branches.is_empty()
288 }
289
290 pub fn find_bus(&self, bus_id: u32) -> Option<&DcBus> {
291 self.buses.iter().find(|bus| bus.bus_id == bus_id)
292 }
293
294 pub fn find_bus_mut(&mut self, bus_id: u32) -> Option<&mut DcBus> {
295 self.buses.iter_mut().find(|bus| bus.bus_id == bus_id)
296 }
297
298 pub fn bus_index_map(&self) -> HashMap<u32, usize> {
299 self.buses
300 .iter()
301 .enumerate()
302 .map(|(index, bus)| (bus.bus_id, index))
303 .collect()
304 }
305
306 pub fn canonicalize_converter_ids(&mut self) {
307 for (index, converter) in self.converters.iter_mut().enumerate() {
308 let trimmed = converter.id().trim().to_string();
309 if trimmed.is_empty() {
310 *converter.id_mut() = format!("dc_grid_{}_converter_{}", self.id, index + 1);
311 } else if trimmed != converter.id() {
312 *converter.id_mut() = trimmed;
313 }
314 }
315 }
316
317 pub fn canonicalize_branch_ids(&mut self) {
318 for (index, branch) in self.branches.iter_mut().enumerate() {
319 let trimmed = branch.id.trim().to_string();
320 if trimmed.is_empty() {
321 branch.id = format!("dc_grid_{}_branch_{}", self.id, index + 1);
322 } else if trimmed != branch.id {
323 branch.id = trimmed;
324 }
325 }
326 }
327}
328
329#[derive(Debug, Clone, Default, Serialize, Deserialize)]
331pub struct HvdcModel {
332 #[serde(default, skip_serializing_if = "Vec::is_empty")]
334 pub links: Vec<HvdcLink>,
335 #[serde(default, skip_serializing_if = "Vec::is_empty")]
337 pub dc_grids: Vec<DcGrid>,
338}
339
340impl HvdcModel {
341 pub fn is_empty(&self) -> bool {
342 self.links.is_empty() && self.dc_grids.iter().all(DcGrid::is_empty)
343 }
344
345 pub fn has_point_to_point_links(&self) -> bool {
346 !self.links.is_empty()
347 }
348
349 pub fn has_explicit_dc_topology(&self) -> bool {
350 self.dc_grids.iter().any(|grid| !grid.is_empty())
351 }
352
353 pub fn push_link(&mut self, link: HvdcLink) {
354 self.links.push(link);
355 }
356
357 pub fn push_lcc_link(&mut self, link: LccHvdcLink) {
358 self.links.push(HvdcLink::Lcc(link));
359 }
360
361 pub fn push_vsc_link(&mut self, link: VscHvdcLink) {
362 self.links.push(HvdcLink::Vsc(link));
363 }
364
365 pub fn ensure_dc_grid(&mut self, id: u32, name: Option<String>) -> &mut DcGrid {
366 if let Some(index) = self.dc_grids.iter().position(|grid| grid.id == id) {
367 let grid = &mut self.dc_grids[index];
368 if grid.name.is_none() {
369 grid.name = name;
370 }
371 return grid;
372 }
373 self.dc_grids.push(DcGrid::new(id, name));
374 self.dc_grids.last_mut().expect("grid just inserted")
375 }
376
377 pub fn find_dc_grid(&self, id: u32) -> Option<&DcGrid> {
378 self.dc_grids.iter().find(|grid| grid.id == id)
379 }
380
381 pub fn find_dc_grid_mut(&mut self, id: u32) -> Option<&mut DcGrid> {
382 self.dc_grids.iter_mut().find(|grid| grid.id == id)
383 }
384
385 pub fn find_dc_grid_by_bus(&self, bus_id: u32) -> Option<&DcGrid> {
386 self.dc_grids
387 .iter()
388 .find(|grid| grid.buses.iter().any(|bus| bus.bus_id == bus_id))
389 }
390
391 pub fn find_dc_grid_by_bus_mut(&mut self, bus_id: u32) -> Option<&mut DcGrid> {
392 self.dc_grids
393 .iter_mut()
394 .find(|grid| grid.buses.iter().any(|bus| bus.bus_id == bus_id))
395 }
396
397 pub fn find_dc_bus(&self, bus_id: u32) -> Option<&DcBus> {
398 self.dc_grids.iter().find_map(|grid| grid.find_bus(bus_id))
399 }
400
401 pub fn find_dc_bus_mut(&mut self, bus_id: u32) -> Option<&mut DcBus> {
402 self.dc_grids
403 .iter_mut()
404 .find_map(|grid| grid.find_bus_mut(bus_id))
405 }
406
407 pub fn dc_bus_count(&self) -> usize {
408 self.dc_grids.iter().map(|grid| grid.buses.len()).sum()
409 }
410
411 pub fn dc_converter_count(&self) -> usize {
412 self.dc_grids.iter().map(|grid| grid.converters.len()).sum()
413 }
414
415 pub fn dc_branch_count(&self) -> usize {
416 self.dc_grids.iter().map(|grid| grid.branches.len()).sum()
417 }
418
419 pub fn next_dc_grid_id(&self) -> u32 {
420 self.dc_grids.iter().map(|grid| grid.id).max().unwrap_or(0) + 1
421 }
422
423 pub fn next_dc_bus_id(&self) -> u32 {
424 self.dc_grids
425 .iter()
426 .flat_map(|grid| grid.buses.iter().map(|bus| bus.bus_id))
427 .max()
428 .unwrap_or(0)
429 + 1
430 }
431
432 pub fn clear_dc_grids(&mut self) {
433 self.dc_grids.clear();
434 }
435
436 pub fn canonicalize_converter_ids(&mut self) {
437 for grid in &mut self.dc_grids {
438 grid.canonicalize_converter_ids();
439 grid.canonicalize_branch_ids();
440 }
441 }
442
443 pub fn dc_buses(&self) -> impl Iterator<Item = &DcBus> {
444 self.dc_grids.iter().flat_map(|grid| grid.buses.iter())
445 }
446
447 pub fn dc_buses_mut(&mut self) -> impl Iterator<Item = &mut DcBus> {
448 self.dc_grids
449 .iter_mut()
450 .flat_map(|grid| grid.buses.iter_mut())
451 }
452
453 pub fn dc_converters(&self) -> impl Iterator<Item = &DcConverter> {
454 self.dc_grids.iter().flat_map(|grid| grid.converters.iter())
455 }
456
457 pub fn dc_converters_mut(&mut self) -> impl Iterator<Item = &mut DcConverter> {
458 self.dc_grids
459 .iter_mut()
460 .flat_map(|grid| grid.converters.iter_mut())
461 }
462
463 pub fn dc_branches(&self) -> impl Iterator<Item = &DcBranch> {
464 self.dc_grids.iter().flat_map(|grid| grid.branches.iter())
465 }
466
467 pub fn dc_branches_mut(&mut self) -> impl Iterator<Item = &mut DcBranch> {
468 self.dc_grids
469 .iter_mut()
470 .flat_map(|grid| grid.branches.iter_mut())
471 }
472}