1use std::collections::HashMap;
23use std::path::Path;
24
25use serde_json::Value;
26use surge_network::Network;
27use surge_network::network::AreaSchedule;
28use surge_network::network::facts::FactsType;
29use surge_network::network::{Branch, BranchType, Bus, BusType, Generator};
30use surge_network::network::{FactsDevice, FactsMode};
31use surge_network::network::{LccConverterTerminal, LccHvdcControlMode, LccHvdcLink};
32use surge_network::network::{
33 VscConverterAcControlMode, VscConverterTerminal, VscHvdcControlMode, VscHvdcLink,
34};
35use thiserror::Error;
36
37use super::reader::{apply_cz_conversion, compute_winding_tap_pu, make_xfmr_branch};
38
39#[derive(Error, Debug)]
40pub enum RawxError {
41 #[error("I/O error: {0}")]
42 Io(#[from] std::io::Error),
43
44 #[error("JSON parse error: {0}")]
45 Json(#[from] serde_json::Error),
46
47 #[error("missing section: {0}")]
48 MissingSection(String),
49
50 #[error("missing field '{field}' in section '{section}'")]
51 MissingField { section: String, field: String },
52
53 #[error("invalid value in section '{section}', field '{field}': {message}")]
54 InvalidValue {
55 section: String,
56 field: String,
57 message: String,
58 },
59}
60
61pub fn parse_file(path: &Path) -> Result<Network, RawxError> {
63 let content = std::fs::read_to_string(path)?;
64 let name = path
65 .file_stem()
66 .and_then(|s| s.to_str())
67 .unwrap_or("unknown")
68 .to_string();
69 parse_string_with_name(&content, &name)
70}
71
72pub fn parse_str(content: &str) -> Result<Network, RawxError> {
74 parse_string_with_name(content, "unknown")
75}
76
77fn parse_string_with_name(content: &str, name: &str) -> Result<Network, RawxError> {
78 let root: Value = serde_json::from_str(content)?;
79 let net_obj = root
80 .get("network")
81 .ok_or_else(|| RawxError::MissingSection("network".into()))?;
82
83 let (sbase, _version, freq_hz) = parse_caseid(net_obj)?;
85
86 let mut network = Network::new(name);
87 network.base_mva = sbase;
88 network.freq_hz = freq_hz;
89
90 let (buses, bus_idx, bus_kv) = parse_buses(net_obj)?;
92 network.buses = buses;
93
94 crate::parse_utils::sanitize_voltage_limits(&mut network);
96
97 if let Some(section) = get_section(net_obj, "load") {
99 parse_loads(§ion, &bus_idx, &mut network)?;
100 }
101
102 if let Some(section) = get_section(net_obj, "fixshunt") {
104 parse_fixshunts(§ion, &bus_idx, &mut network)?;
105 }
106
107 if let Some(section) = get_section(net_obj, "generator") {
109 network.generators = parse_generators(§ion)?;
110 }
111
112 if let Some(section) = get_section(net_obj, "acline") {
114 network.branches = parse_aclines(§ion)?;
115 }
116
117 if let Some(section) = get_section(net_obj, "transformer") {
119 let (xfmrs, star_buses) = parse_transformers(§ion, sbase, &bus_kv)?;
120 network.buses.extend(star_buses);
121 network.branches.extend(xfmrs);
122 }
123
124 if let Some(section) = get_section(net_obj, "area") {
126 network.area_schedules = parse_areas(§ion)?;
127 }
128
129 if let Some(section) = get_section(net_obj, "twotermdc") {
131 network.hvdc.links = parse_twotermdc(§ion)?
132 .into_iter()
133 .map(surge_network::network::HvdcLink::Lcc)
134 .collect();
135 }
136
137 if let Some(section) = get_section(net_obj, "vscdc") {
139 network.hvdc.links.extend(
140 parse_vscdc(§ion)?
141 .into_iter()
142 .map(surge_network::network::HvdcLink::Vsc),
143 );
144 }
145
146 if let Some(section) = get_section(net_obj, "facts") {
148 network.facts_devices = parse_facts(§ion)?;
149 }
150
151 if let Some(section) = get_section(net_obj, "swshunt") {
153 parse_switched_shunts(§ion, &bus_idx, &mut network)?;
154 }
155 Ok(network)
156}
157
158struct RawxSection<'a> {
164 fields: HashMap<String, usize>,
165 data: &'a Vec<Value>,
166}
167
168impl<'a> RawxSection<'a> {
169 fn get_f64(&self, row: &Value, field: &str) -> Option<f64> {
170 let idx = *self.fields.get(field)?;
171 let arr = row.as_array()?;
172 arr.get(idx)?.as_f64()
173 }
174
175 fn get_f64_or(&self, row: &Value, field: &str, default: f64) -> f64 {
176 self.get_f64(row, field).unwrap_or(default)
177 }
178
179 fn get_i64(&self, row: &Value, field: &str) -> Option<i64> {
180 let idx = *self.fields.get(field)?;
181 let arr = row.as_array()?;
182 let val = arr.get(idx)?;
183 val.as_i64().or_else(|| val.as_f64().map(|f| f as i64))
184 }
185
186 fn get_i64_or(&self, row: &Value, field: &str, default: i64) -> i64 {
187 self.get_i64(row, field).unwrap_or(default)
188 }
189
190 fn get_str<'b>(&self, row: &'b Value, field: &str) -> Option<&'b str> {
191 let idx = *self.fields.get(field)?;
192 let arr = row.as_array()?;
193 arr.get(idx)?.as_str()
194 }
195
196 fn get_str_or<'b>(&self, row: &'b Value, field: &str, default: &'b str) -> &'b str {
197 self.get_str(row, field).unwrap_or(default)
198 }
199}
200
201fn get_section<'a>(net_obj: &'a Value, name: &str) -> Option<RawxSection<'a>> {
202 let section = net_obj.get(name)?;
203 let fields_arr = section.get("fields")?.as_array()?;
204 let data = section.get("data")?.as_array()?;
205 if data.is_empty() {
206 return None;
207 }
208 let mut fields = HashMap::new();
209 for (i, f) in fields_arr.iter().enumerate() {
210 if let Some(s) = f.as_str() {
211 fields.insert(s.to_lowercase(), i);
212 }
213 }
214 Some(RawxSection { fields, data })
215}
216
217fn parse_caseid(net_obj: &Value) -> Result<(f64, u32, f64), RawxError> {
222 let caseid = net_obj
223 .get("caseid")
224 .ok_or_else(|| RawxError::MissingSection("caseid".into()))?;
225 let fields_arr = caseid
226 .get("fields")
227 .and_then(|v| v.as_array())
228 .ok_or_else(|| RawxError::MissingField {
229 section: "caseid".into(),
230 field: "fields".into(),
231 })?;
232 let data = caseid
233 .get("data")
234 .and_then(|v| v.as_array())
235 .ok_or_else(|| RawxError::MissingField {
236 section: "caseid".into(),
237 field: "data".into(),
238 })?;
239
240 let mut field_map: HashMap<String, usize> = HashMap::new();
241 for (i, f) in fields_arr.iter().enumerate() {
242 if let Some(s) = f.as_str() {
243 field_map.insert(s.to_lowercase(), i);
244 }
245 }
246
247 let get = |name: &str| -> Option<f64> {
248 let idx = *field_map.get(name)?;
249 data.get(idx)?.as_f64()
250 };
251
252 let sbase = get("sbase").unwrap_or(100.0);
253 let rev = get("rev").unwrap_or(35.0) as u32;
254 let freq = get("basfrq").unwrap_or(60.0);
255 let freq_hz = if freq > 0.0 { freq } else { 60.0 };
256
257 Ok((sbase, rev, freq_hz))
258}
259
260#[allow(clippy::type_complexity)]
265fn parse_buses(
266 net_obj: &Value,
267) -> Result<(Vec<Bus>, HashMap<u32, usize>, HashMap<u32, f64>), RawxError> {
268 let section =
269 get_section(net_obj, "bus").ok_or_else(|| RawxError::MissingSection("bus".into()))?;
270
271 let mut buses = Vec::new();
272 let mut bus_idx: HashMap<u32, usize> = HashMap::new();
273 let mut bus_kv: HashMap<u32, f64> = HashMap::new();
274
275 for row in section.data {
276 let number = section.get_i64_or(row, "ibus", 0) as u32;
277 if number == 0 {
278 continue;
279 }
280 let name = section.get_str_or(row, "name", "").trim().to_string();
281 let base_kv = section.get_f64_or(row, "baskv", 1.0);
282 let ide = section.get_i64_or(row, "ide", 1);
283 let bus_type = match ide {
284 2 => BusType::PV,
285 3 => BusType::Slack,
286 4 => BusType::Isolated,
287 _ => BusType::PQ,
288 };
289 let area = section.get_i64_or(row, "area", 1) as u32;
290 let zone = section.get_i64_or(row, "zone", 1) as u32;
291 let vm = section.get_f64_or(row, "vm", 1.0);
292 let va_deg = section.get_f64_or(row, "va", 0.0);
293 let vmax = section.get_f64_or(row, "nvhi", 1.1);
294 let vmin = section.get_f64_or(row, "nvlo", 0.9);
295
296 let idx = buses.len();
297 bus_idx.insert(number, idx);
298 bus_kv.insert(number, base_kv);
299
300 buses.push(Bus {
301 number,
302 name,
303 bus_type,
304 shunt_conductance_mw: 0.0,
305 shunt_susceptance_mvar: 0.0,
306 area,
307 voltage_magnitude_pu: vm,
308 voltage_angle_rad: va_deg.to_radians(),
309 base_kv,
310 zone,
311 voltage_max_pu: vmax,
312 voltage_min_pu: vmin,
313 island_id: 0,
314 latitude: None,
315 longitude: None,
316 ..Bus::new(0, BusType::PQ, 0.0)
317 });
318 }
319
320 Ok((buses, bus_idx, bus_kv))
321}
322
323fn parse_loads(
328 section: &RawxSection,
329 _bus_idx: &HashMap<u32, usize>,
330 network: &mut Network,
331) -> Result<(), RawxError> {
332 for row in section.data {
333 let bus = section.get_i64_or(row, "ibus", 0) as u32;
334 let stat = section.get_i64_or(row, "stat", 1);
335 if stat == 0 || bus == 0 {
336 continue;
337 }
338 let pl = section.get_f64_or(row, "pl", 0.0);
339 let ql = section.get_f64_or(row, "ql", 0.0);
340
341 use surge_network::network::Load;
342 network.loads.push(Load::new(bus, pl, ql));
343 }
344 Ok(())
345}
346
347fn parse_fixshunts(
352 section: &RawxSection,
353 bus_idx: &HashMap<u32, usize>,
354 network: &mut Network,
355) -> Result<(), RawxError> {
356 for row in section.data {
357 let bus = section.get_i64_or(row, "ibus", 0) as u32;
358 let stat = section.get_i64_or(row, "stat", 1);
359 if stat == 0 || bus == 0 {
360 continue;
361 }
362 let gl = section.get_f64_or(row, "gl", 0.0);
363 let bl = section.get_f64_or(row, "bl", 0.0);
364
365 if let Some(&idx) = bus_idx.get(&bus) {
366 network.buses[idx].shunt_conductance_mw += gl;
367 network.buses[idx].shunt_susceptance_mvar += bl;
368 }
369 }
370 Ok(())
371}
372
373fn parse_generators(section: &RawxSection) -> Result<Vec<Generator>, RawxError> {
378 let mut generators = Vec::new();
379
380 for row in section.data {
381 let bus = section.get_i64_or(row, "ibus", 0) as u32;
382 if bus == 0 {
383 continue;
384 }
385 let machid = section
386 .get_str(row, "machid")
387 .map(|s| s.trim().trim_matches('\'').to_string());
388 let stat = section.get_i64_or(row, "stat", 1);
389 let pg = section.get_f64_or(row, "pg", 0.0);
390 let qg = section.get_f64_or(row, "qg", 0.0);
391 let qt = section.get_f64_or(row, "qt", 9999.0); let qb = section.get_f64_or(row, "qb", -9999.0); let vs = section.get_f64_or(row, "vs", 1.0);
394 let mbase = section.get_f64_or(row, "mbase", 100.0);
395 let pt = section.get_f64_or(row, "pt", 9999.0); let pb = section.get_f64_or(row, "pb", 0.0); let zx = section.get_f64_or(row, "zx", 0.0); let pc1 = section.get_f64(row, "pc1");
401 let pc2 = section.get_f64(row, "pc2");
402 let qc1min = section.get_f64(row, "qc1min");
403 let qc1max = section.get_f64(row, "qc1max");
404 let qc2min = section.get_f64(row, "qc2min");
405 let qc2max = section.get_f64(row, "qc2max");
406
407 let mut g = Generator::new(bus, pg, vs);
408 g.machine_id = machid;
409 g.q = qg;
410 g.qmax = qt;
411 g.qmin = qb;
412 g.machine_base_mva = mbase;
413 g.pmax = pt;
414 g.pmin = pb;
415 g.in_service = stat != 0;
416 if zx != 0.0 {
417 g.fault_data.get_or_insert_with(Default::default).xs = Some(zx);
418 }
419 if pc1.is_some()
420 || pc2.is_some()
421 || qc1min.is_some()
422 || qc1max.is_some()
423 || qc2min.is_some()
424 || qc2max.is_some()
425 {
426 let rc = g.reactive_capability.get_or_insert_with(Default::default);
427 rc.pc1 = pc1;
428 rc.pc2 = pc2;
429 rc.qc1min = qc1min;
430 rc.qc1max = qc1max;
431 rc.qc2min = qc2min;
432 rc.qc2max = qc2max;
433 }
434
435 generators.push(g);
436 }
437
438 Ok(generators)
439}
440
441fn parse_aclines(section: &RawxSection) -> Result<Vec<Branch>, RawxError> {
446 let mut branches = Vec::new();
447
448 for row in section.data {
449 let from_bus = section.get_i64_or(row, "ibus", 0) as u32;
450 let to_bus = section.get_i64_or(row, "jbus", 0) as u32;
451 if from_bus == 0 || to_bus == 0 {
452 continue;
453 }
454 let ckt_str = section.get_str_or(row, "ckt", "1").trim().to_string();
455 let circuit = ckt_str.trim_matches('\'').trim().to_string();
456 let r = section.get_f64_or(row, "rpu", 0.0);
457 let x = section.get_f64_or(row, "xpu", 0.01);
458 let b = section.get_f64_or(row, "bpu", 0.0);
459 let stat = section.get_i64_or(row, "stat", 1);
460
461 let rate_a = section.get_f64_or(row, "rate1", 0.0);
462 let rate_b = section.get_f64_or(row, "rate2", 0.0);
463 let rate_c = section.get_f64_or(row, "rate3", 0.0);
464
465 let _gi = section.get_f64_or(row, "gi", 0.0);
468 let _bi = section.get_f64_or(row, "bi", 0.0);
469 let _gj = section.get_f64_or(row, "gj", 0.0);
470 let _bj = section.get_f64_or(row, "bj", 0.0);
471
472 let mut branch = Branch::new_line(from_bus, to_bus, r, x, b);
473 branch.circuit = circuit;
474 branch.rating_a_mva = rate_a;
475 branch.rating_b_mva = rate_b;
476 branch.rating_c_mva = rate_c;
477 branch.in_service = stat != 0;
478
479 branches.push(branch);
480 }
481
482 Ok(branches)
483}
484
485fn parse_transformers(
490 section: &RawxSection,
491 sbase: f64,
492 bus_kv: &HashMap<u32, f64>,
493) -> Result<(Vec<Branch>, Vec<Bus>), RawxError> {
494 let mut transformers = Vec::new();
495 let mut star_buses = Vec::new();
496 let mut max_bus_num: u32 = bus_kv.keys().copied().max().unwrap_or(0);
497
498 for row in section.data {
499 let from_bus = section.get_i64_or(row, "ibus", 0) as u32;
500 let to_bus = section.get_i64_or(row, "jbus", 0).unsigned_abs() as u32;
501 let k = section.get_i64_or(row, "kbus", 0);
502 if from_bus == 0 || to_bus == 0 {
503 continue;
504 }
505
506 let ckt_str = section.get_str_or(row, "ckt", "1").trim().to_string();
507 let circuit = ckt_str.trim_matches('\'').trim().to_string();
508
509 let cw = section.get_i64_or(row, "cw", 1) as u32;
510 let cz = section.get_i64_or(row, "cz", 1) as u32;
511 let mag1 = section.get_f64_or(row, "mag1", 0.0);
512 let mag2 = section.get_f64_or(row, "mag2", 0.0);
513 let stat = section.get_i64_or(row, "stat", 1);
514
515 let r12_raw = section.get_f64_or(row, "r1-2", 0.0);
517 let x12_raw = section.get_f64_or(row, "x1-2", 0.01);
518 let sbase12 = section.get_f64_or(row, "sbase1-2", sbase);
519
520 let (r12, x12) = apply_cz_conversion(r12_raw, x12_raw, sbase12, sbase, cz);
521
522 let is_3winding = k != 0;
523 let k_bus = k.unsigned_abs() as u32;
524
525 if is_3winding {
526 let r23_raw = section.get_f64_or(row, "r2-3", 0.0);
528 let x23_raw = section.get_f64_or(row, "x2-3", 0.01);
529 let sbase23 = section.get_f64_or(row, "sbase2-3", sbase);
530 let r31_raw = section.get_f64_or(row, "r3-1", 0.0);
531 let x31_raw = section.get_f64_or(row, "x3-1", 0.01);
532 let sbase31 = section.get_f64_or(row, "sbase3-1", sbase);
533 let vmstar = section.get_f64_or(row, "vmstar", 1.0);
534 let anstar_deg = section.get_f64_or(row, "anstar", 0.0);
535
536 let (r23, x23) = apply_cz_conversion(r23_raw, x23_raw, sbase23, sbase, cz);
537 let (r31, x31) = apply_cz_conversion(r31_raw, x31_raw, sbase31, sbase, cz);
538
539 let r1 = (r12 + r31 - r23) / 2.0;
541 let x1 = (x12 + x31 - x23) / 2.0;
542 let r2 = (r12 + r23 - r31) / 2.0;
543 let x2 = (x12 + x23 - x31) / 2.0;
544 let r3 = (r23 + r31 - r12) / 2.0;
545 let x3 = (x23 + x31 - x12) / 2.0;
546
547 let windv1 = section.get_f64_or(row, "windv1", 1.0);
549 let nomv1 = section.get_f64_or(row, "nomv1", 0.0);
550 let ang1 = section.get_f64_or(row, "ang1", 0.0);
551 let rata1 = section.get_f64_or(row, "wdg1rate1", 0.0);
552 let ratb1 = section.get_f64_or(row, "wdg1rate2", 0.0);
553 let ratc1 = section.get_f64_or(row, "wdg1rate3", 0.0);
554
555 let windv2 = section.get_f64_or(row, "windv2", 1.0);
556 let nomv2 = section.get_f64_or(row, "nomv2", 0.0);
557 let ang2 = section.get_f64_or(row, "ang2", 0.0);
558 let rata2 = section.get_f64_or(row, "wdg2rate1", 0.0);
559 let ratb2 = section.get_f64_or(row, "wdg2rate2", 0.0);
560 let ratc2 = section.get_f64_or(row, "wdg2rate3", 0.0);
561
562 let windv3 = section.get_f64_or(row, "windv3", 1.0);
563 let nomv3 = section.get_f64_or(row, "nomv3", 0.0);
564 let ang3 = section.get_f64_or(row, "ang3", 0.0);
565 let rata3 = section.get_f64_or(row, "wdg3rate1", 0.0);
566 let ratb3 = section.get_f64_or(row, "wdg3rate2", 0.0);
567 let ratc3 = section.get_f64_or(row, "wdg3rate3", 0.0);
568
569 let bkv1 = bus_kv.get(&from_bus).copied().unwrap_or(1.0);
570 let bkv2 = bus_kv.get(&to_bus).copied().unwrap_or(1.0);
571 let bkv3 = bus_kv.get(&k_bus).copied().unwrap_or(1.0);
572 let tap1 = compute_winding_tap_pu(windv1, nomv1, bkv1, cw);
573 let tap2 = compute_winding_tap_pu(windv2, nomv2, bkv2, cw);
574 let tap3 = compute_winding_tap_pu(windv3, nomv3, bkv3, cw);
575
576 max_bus_num += 1;
578 let star_bus_num = max_bus_num;
579 star_buses.push(Bus {
580 number: star_bus_num,
581 name: format!("STAR_{from_bus}_{to_bus}_{k_bus}"),
582 bus_type: BusType::PQ,
583 shunt_conductance_mw: 0.0,
584 shunt_susceptance_mvar: 0.0,
585 area: 1,
586 voltage_magnitude_pu: vmstar,
587 voltage_angle_rad: anstar_deg.to_radians(),
588 base_kv: bkv1.max(bkv2).max(bkv3).max(1.0),
589 zone: 1,
590 voltage_max_pu: 1.1,
591 voltage_min_pu: 0.9,
592 island_id: 0,
593 latitude: None,
594 longitude: None,
595 ..Bus::new(0, BusType::PQ, 0.0)
596 });
597
598 let in_service = stat > 0;
599
600 let mut w1 = make_xfmr_branch(
602 from_bus,
603 star_bus_num,
604 circuit.clone(),
605 r1,
606 x1,
607 rata1,
608 ratb1,
609 ratc1,
610 tap1,
611 ang1,
612 in_service,
613 mag1,
614 mag2,
615 );
616 w1.branch_type = BranchType::Transformer3W;
617 transformers.push(w1);
618 let mut w2 = make_xfmr_branch(
620 to_bus,
621 star_bus_num,
622 circuit.clone(),
623 r2,
624 x2,
625 rata2,
626 ratb2,
627 ratc2,
628 tap2,
629 ang2,
630 in_service,
631 0.0,
632 0.0,
633 );
634 w2.branch_type = BranchType::Transformer3W;
635 transformers.push(w2);
636 let mut w3 = make_xfmr_branch(
638 k_bus,
639 star_bus_num,
640 circuit,
641 r3,
642 x3,
643 rata3,
644 ratb3,
645 ratc3,
646 tap3,
647 ang3,
648 in_service,
649 0.0,
650 0.0,
651 );
652 w3.branch_type = BranchType::Transformer3W;
653 transformers.push(w3);
654 } else {
655 let windv1 = section.get_f64_or(row, "windv1", 1.0);
657 let nomv1 = section.get_f64_or(row, "nomv1", 0.0);
658 let ang1 = section.get_f64_or(row, "ang1", 0.0);
659 let rata1 = section.get_f64_or(row, "wdg1rate1", 0.0);
660 let ratb1 = section.get_f64_or(row, "wdg1rate2", 0.0);
661 let ratc1 = section.get_f64_or(row, "wdg1rate3", 0.0);
662
663 let windv2 = section.get_f64_or(row, "windv2", 1.0);
664 let nomv2 = section.get_f64_or(row, "nomv2", 0.0);
665
666 let tap = match cw {
668 1 => {
669 if windv2 != 0.0 {
670 windv1 / windv2
671 } else {
672 windv1
673 }
674 }
675 2 => {
676 let bkv1 = bus_kv.get(&from_bus).copied().unwrap_or(1.0);
677 let bkv2 = bus_kv.get(&to_bus).copied().unwrap_or(1.0);
678 let t1 = if bkv1 > 0.0 { windv1 / bkv1 } else { windv1 };
679 let t2 = if bkv2 > 0.0 { windv2 / bkv2 } else { windv2 };
680 if t2 != 0.0 { t1 / t2 } else { t1 }
681 }
682 3 => {
683 let bkv1 = bus_kv.get(&from_bus).copied().unwrap_or(1.0);
684 let bkv2 = bus_kv.get(&to_bus).copied().unwrap_or(1.0);
685 let n1 = if nomv1 > 0.0 { nomv1 } else { bkv1 };
686 let n2 = if nomv2 > 0.0 { nomv2 } else { bkv2 };
687 let t1 = if bkv1 > 0.0 {
688 windv1 * n1 / bkv1
689 } else {
690 windv1
691 };
692 let t2 = if bkv2 > 0.0 {
693 windv2 * n2 / bkv2
694 } else {
695 windv2
696 };
697 if t2 != 0.0 { t1 / t2 } else { t1 }
698 }
699 _ => windv1,
700 };
701
702 transformers.push(make_xfmr_branch(
703 from_bus,
704 to_bus,
705 circuit,
706 r12,
707 x12,
708 rata1,
709 ratb1,
710 ratc1,
711 tap,
712 ang1,
713 stat > 0,
714 mag1,
715 mag2,
716 ));
717 }
718 }
719
720 Ok((transformers, star_buses))
721}
722
723fn parse_areas(section: &RawxSection) -> Result<Vec<AreaSchedule>, RawxError> {
728 let mut areas = Vec::new();
729 for row in section.data {
730 let number = section.get_i64_or(row, "iarea", 0) as u32;
731 if number == 0 {
732 continue;
733 }
734 let slack_bus = section.get_i64_or(row, "isw", 0) as u32;
735 let pdes = section.get_f64_or(row, "pdes", 0.0);
736 let ptol = section.get_f64_or(row, "ptol", 10.0);
737 let name = section
738 .get_str(row, "arnam")
739 .unwrap_or("")
740 .trim()
741 .trim_matches('\'')
742 .to_string();
743
744 areas.push(AreaSchedule {
745 number,
746 slack_bus,
747 p_desired_mw: pdes,
748 p_tolerance_mw: ptol,
749 name,
750 });
751 }
752 Ok(areas)
753}
754
755fn parse_switched_shunts(
760 section: &RawxSection,
761 _bus_idx: &HashMap<u32, usize>,
762 network: &mut Network,
763) -> Result<(), RawxError> {
764 use crate::parse_utils::{RawSwitchedShunt, apply_switched_shunts};
765
766 let mut raw_shunts: Vec<RawSwitchedShunt> = Vec::new();
767
768 for row in section.data {
769 let bus = section.get_i64_or(row, "ibus", 0) as u32;
770 if bus == 0 {
771 continue;
772 }
773 let modsw = section.get_i64_or(row, "modsw", 0) as i32;
774 let stat = section.get_i64_or(row, "stat", 1) as i32;
775 let vswhi = section.get_f64_or(row, "vswhi", 1.1);
776 let vswlo = section.get_f64_or(row, "vswlo", 0.9);
777 let swrem = section.get_i64_or(row, "swrem", 0) as u32;
778 let binit = section.get_f64_or(row, "binit", 0.0);
779
780 let mut blocks = Vec::new();
782 for i in 1u32..=8 {
783 let nk = section.get_i64_or(row, &format!("n{i}"), 0) as i32;
784 let bk = section.get_f64_or(row, &format!("b{i}"), 0.0);
785 blocks.push((nk, bk));
786 }
787
788 raw_shunts.push(RawSwitchedShunt {
789 bus,
790 modsw,
791 stat,
792 vswhi,
793 vswlo,
794 swrem,
795 binit,
796 blocks,
797 });
798 }
799
800 let base_mva = network.base_mva;
801 apply_switched_shunts(network, &raw_shunts, base_mva);
802 Ok(())
803}
804
805fn parse_twotermdc(section: &RawxSection) -> Result<Vec<LccHvdcLink>, RawxError> {
810 let mut lcc_links = Vec::new();
811 for row in section.data {
812 let name = section
813 .get_str(row, "name")
814 .unwrap_or("")
815 .trim()
816 .trim_matches('\'')
817 .to_string();
818 let mdc = section.get_i64_or(row, "mdc", 0) as u32;
819 let resistance_ohm = section.get_f64_or(row, "resistance_ohm", 0.0);
820 let setvl = section.get_f64_or(row, "setvl", 0.0);
821 let vschd = section.get_f64_or(row, "vschd", 0.0);
822 let vcmod = section.get_f64_or(row, "vcmod", 0.0);
823 let rcomp = section.get_f64_or(row, "rcomp", 0.0);
824 let delti = section.get_f64_or(row, "delti", 0.0);
825
826 let ipr = section.get_i64_or(row, "ipr", 0) as u32;
828 let nbr = section.get_i64_or(row, "nbr", 6) as u32;
829 let anmxr = section.get_f64_or(row, "anmxr", 90.0);
830 let anmnr = section.get_f64_or(row, "anmnr", 0.0);
831 let rcr = section.get_f64_or(row, "rcr", 0.0);
832 let xcr = section.get_f64_or(row, "xcr", 0.0);
833 let ebasr = section.get_f64_or(row, "ebasr", 0.0);
834 let trr = section.get_f64_or(row, "trr", 1.0);
835 let tapr = section.get_f64_or(row, "tapr", 1.0);
836 let tmxr = section.get_f64_or(row, "tmxr", 1.5);
837 let tmnr = section.get_f64_or(row, "tmnr", 0.51);
838 let stpr = section.get_f64_or(row, "stpr", 0.00625);
839
840 let ipi = section.get_i64_or(row, "ipi", 0) as u32;
842 let nbi = section.get_i64_or(row, "nbi", 6) as u32;
843 let anmxi = section.get_f64_or(row, "anmxi", 90.0);
844 let anmni = section.get_f64_or(row, "anmni", 0.0);
845 let rci = section.get_f64_or(row, "rci", 0.0);
846 let xci = section.get_f64_or(row, "xci", 0.0);
847 let ebasi = section.get_f64_or(row, "ebasi", 0.0);
848 let tri = section.get_f64_or(row, "tri", 1.0);
849 let tapi = section.get_f64_or(row, "tapi", 1.0);
850 let tmxi = section.get_f64_or(row, "tmxi", 1.5);
851 let tmni = section.get_f64_or(row, "tmni", 0.51);
852 let stpi = section.get_f64_or(row, "stpi", 0.00625);
853
854 lcc_links.push(LccHvdcLink {
855 name,
856 mode: LccHvdcControlMode::from_u32(mdc),
857 resistance_ohm,
858 scheduled_setpoint: setvl,
859 scheduled_voltage_kv: vschd,
860 voltage_mode_switch_kv: vcmod,
861 compounding_resistance_ohm: rcomp,
862 current_margin_ka: delti,
863 meter: 'I',
864 voltage_min_kv: 0.0,
865 ac_dc_iteration_max: 20,
866 ac_dc_iteration_acceleration: 1.0,
867 rectifier: LccConverterTerminal {
868 bus: ipr,
869 n_bridges: nbr,
870 alpha_max: anmxr,
871 alpha_min: anmnr,
872 commutation_resistance_ohm: rcr,
873 commutation_reactance_ohm: xcr,
874 base_voltage_kv: ebasr,
875 turns_ratio: trr,
876 tap: tapr,
877 tap_max: tmxr,
878 tap_min: tmnr,
879 tap_step: stpr,
880 in_service: true,
881 },
882 inverter: LccConverterTerminal {
883 bus: ipi,
884 n_bridges: nbi,
885 alpha_max: anmxi,
886 alpha_min: anmni,
887 commutation_resistance_ohm: rci,
888 commutation_reactance_ohm: xci,
889 base_voltage_kv: ebasi,
890 turns_ratio: tri,
891 tap: tapi,
892 tap_max: tmxi,
893 tap_min: tmni,
894 tap_step: stpi,
895 in_service: true,
896 },
897 p_dc_min_mw: 0.0,
901 p_dc_max_mw: 0.0,
902 });
903 }
904 Ok(lcc_links)
905}
906
907fn parse_vscdc(section: &RawxSection) -> Result<Vec<VscHvdcLink>, RawxError> {
912 let mut vsc_lines = Vec::new();
913 for row in section.data {
914 let name = section
915 .get_str(row, "name")
916 .unwrap_or("")
917 .trim()
918 .trim_matches('\'')
919 .to_string();
920 let mdc = section.get_i64_or(row, "mdc", 0) as u32;
921 let resistance_ohm = section.get_f64_or(row, "resistance_ohm", 0.0);
922
923 let ibus1 = section.get_i64_or(row, "ibus1", 0) as u32;
924 let mode1 = section.get_i64_or(row, "mode1", 1) as u32;
925 let acset1 = section.get_f64_or(row, "acset1", 1.0);
926 let dcset1 = section.get_f64_or(row, "dcset1", 0.0);
927 let aloss1 = section.get_f64_or(row, "aloss1", 0.0);
928 let bloss1 = section.get_f64_or(row, "bloss1", 0.0);
929
930 let ibus2 = section.get_i64_or(row, "ibus2", 0) as u32;
931 let mode2 = section.get_i64_or(row, "mode2", 1) as u32;
932 let acset2 = section.get_f64_or(row, "acset2", 1.0);
933 let dcset2 = section.get_f64_or(row, "dcset2", 0.0);
934 let aloss2 = section.get_f64_or(row, "aloss2", 0.0);
935 let bloss2 = section.get_f64_or(row, "bloss2", 0.0);
936
937 vsc_lines.push(VscHvdcLink {
938 name,
939 mode: VscHvdcControlMode::from_u32(mdc),
940 resistance_ohm,
941 converter1: VscConverterTerminal {
942 bus: ibus1,
943 control_mode: VscConverterAcControlMode::from_u32(mode1),
944 ac_setpoint: acset1,
945 dc_setpoint: dcset1,
946 loss_constant_mw: aloss1,
947 loss_linear: bloss1,
948 ..VscConverterTerminal::default()
949 },
950 converter2: VscConverterTerminal {
951 bus: ibus2,
952 control_mode: VscConverterAcControlMode::from_u32(mode2),
953 ac_setpoint: acset2,
954 dc_setpoint: dcset2,
955 loss_constant_mw: aloss2,
956 loss_linear: bloss2,
957 ..VscConverterTerminal::default()
958 },
959 });
960 }
961 Ok(vsc_lines)
962}
963
964fn parse_facts(section: &RawxSection) -> Result<Vec<FactsDevice>, RawxError> {
969 let mut facts = Vec::new();
970 for row in section.data {
971 let name = section
972 .get_str(row, "name")
973 .unwrap_or("")
974 .trim()
975 .trim_matches('\'')
976 .to_string();
977 let bus_i = section.get_i64_or(row, "ibus", 0) as u32;
978 let bus_j = section.get_i64_or(row, "jbus", 0) as u32;
979 let mode = section.get_i64_or(row, "mode", 1) as u32;
980 let pdes = section.get_f64_or(row, "pdes", 0.0);
981 let qdes = section.get_f64_or(row, "qdes", 0.0);
982 let vset = section.get_f64_or(row, "vset", 1.0);
983 let shmx = section.get_f64_or(row, "shmx", 9999.0);
984 let linx = section.get_f64_or(row, "linx", 0.05);
985 let stat = section.get_i64_or(row, "stat", 1);
986
987 let facts_mode = FactsMode::from_u32(mode);
988
989 let facts_type = match facts_mode {
991 FactsMode::ShuntOnly => FactsType::Svc,
992 FactsMode::SeriesOnly | FactsMode::ImpedanceModulation => FactsType::Tcsc,
993 FactsMode::ShuntSeries | FactsMode::SeriesPowerControl => FactsType::Upfc,
994 FactsMode::OutOfService => FactsType::Svc,
995 };
996
997 facts.push(FactsDevice {
998 name,
999 bus_from: bus_i,
1000 bus_to: bus_j,
1001 mode: facts_mode,
1002 p_setpoint_mw: pdes,
1003 q_setpoint_mvar: qdes,
1004 voltage_setpoint_pu: vset,
1005 q_max: shmx,
1006 series_reactance_pu: linx,
1007 in_service: stat != 0,
1008 facts_type,
1009 ..FactsDevice::default()
1010 });
1011 }
1012 Ok(facts)
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::*;
1018
1019 #[test]
1020 fn test_parse_minimal_rawx() {
1021 let json = r#"{
1022 "network": {
1023 "caseid": {
1024 "fields": ["ic", "sbase", "rev", "xfrrat", "nxfrat", "basfrq", "title1", "title2"],
1025 "data": [0, 100.0, 35, 0, 0, 60.0, "test", ""]
1026 },
1027 "bus": {
1028 "fields": ["ibus", "name", "baskv", "ide", "area", "zone", "owner", "vm", "va"],
1029 "data": [
1030 [1, "Bus 1", 138.0, 3, 1, 1, 1, 1.06, 0.0],
1031 [2, "Bus 2", 138.0, 1, 1, 1, 1, 1.0, -5.0]
1032 ]
1033 },
1034 "load": {
1035 "fields": ["ibus", "loadid", "stat", "area", "zone", "pl", "ql"],
1036 "data": [
1037 [2, "1", 1, 1, 1, 100.0, 35.0]
1038 ]
1039 },
1040 "generator": {
1041 "fields": ["ibus", "machid", "pg", "qg", "qt", "qb", "vs", "ireg", "mbase", "zr", "zx", "rt", "xt", "gtap", "stat", "rmpct", "pt", "pb"],
1042 "data": [
1043 [1, "1", 80.0, 30.0, 200.0, -200.0, 1.06, 0, 100.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1, 100.0, 200.0, 0.0]
1044 ]
1045 },
1046 "acline": {
1047 "fields": ["ibus", "jbus", "ckt", "rpu", "xpu", "bpu", "name", "rate1", "rate2", "rate3", "gi", "bi", "gj", "bj", "stat", "met", "len"],
1048 "data": [
1049 [1, 2, "1", 0.02, 0.06, 0.03, "Line 1-2", 100.0, 100.0, 100.0, 0.0, 0.0, 0.0, 0.0, 1, 1, 0.0]
1050 ]
1051 }
1052 }
1053 }"#;
1054
1055 let net = parse_str(json).unwrap();
1056 assert_eq!(net.n_buses(), 2);
1057 assert_eq!(net.branches.len(), 1);
1058 assert_eq!(net.generators.len(), 1);
1059 assert_eq!(net.base_mva, 100.0);
1060 assert_eq!(net.freq_hz, 60.0);
1061
1062 assert_eq!(net.buses[0].number, 1);
1064 assert_eq!(net.buses[0].bus_type, BusType::Slack);
1065 assert_eq!(net.buses[0].base_kv, 138.0);
1066 assert!((net.buses[0].voltage_magnitude_pu - 1.06).abs() < 1e-10);
1067
1068 assert_eq!(net.buses[1].number, 2);
1069 assert_eq!(net.buses[1].bus_type, BusType::PQ);
1070 let bus_pd = net.bus_load_p_mw();
1071 let bus_qd = net.bus_load_q_mvar();
1072 assert!((bus_pd[1] - 100.0).abs() < 1e-10);
1073 assert!((bus_qd[1] - 35.0).abs() < 1e-10);
1074
1075 assert_eq!(net.generators[0].bus, 1);
1077 assert!((net.generators[0].p - 80.0).abs() < 1e-10);
1078 assert!((net.generators[0].voltage_setpoint_pu - 1.06).abs() < 1e-10);
1079 assert!((net.generators[0].pmax - 200.0).abs() < 1e-10);
1080
1081 assert_eq!(net.branches[0].from_bus, 1);
1083 assert_eq!(net.branches[0].to_bus, 2);
1084 assert!((net.branches[0].r - 0.02).abs() < 1e-10);
1085 assert!((net.branches[0].x - 0.06).abs() < 1e-10);
1086 }
1087
1088 #[test]
1089 fn test_parse_rawx_with_transformer() {
1090 let json = r#"{
1091 "network": {
1092 "caseid": {
1093 "fields": ["ic", "sbase", "rev", "xfrrat", "nxfrat", "basfrq"],
1094 "data": [0, 100.0, 35, 0, 0, 60.0]
1095 },
1096 "bus": {
1097 "fields": ["ibus", "name", "baskv", "ide", "area", "zone", "owner", "vm", "va"],
1098 "data": [
1099 [1, "HV Bus", 345.0, 3, 1, 1, 1, 1.04, 0.0],
1100 [2, "LV Bus", 138.0, 1, 1, 1, 1, 1.0, -3.0]
1101 ]
1102 },
1103 "transformer": {
1104 "fields": ["ibus", "jbus", "kbus", "ckt", "cw", "cz", "cm", "mag1", "mag2", "nmet", "name", "stat", "o1", "f1", "o2", "f2", "o3", "f3", "o4", "f4", "vecgrp", "zcod", "r1-2", "x1-2", "sbase1-2", "windv1", "nomv1", "ang1", "wdg1rate1", "wdg1rate2", "wdg1rate3", "cod1", "cont1", "node1", "rma1", "rmi1", "vma1", "vmi1", "ntp1", "tab1", "cr1", "cx1", "cnxa1", "windv2", "nomv2", "ang2"],
1105 "data": [
1106 [1, 2, 0, "1", 1, 1, 1, 0.0, 0.0, 2, "Xfmr 1-2", 1, 1, 1.0, 0, 1.0, 0, 1.0, 0, 1.0, "", 0, 0.005, 0.1, 100.0, 1.0, 0.0, 0.0, 200.0, 200.0, 200.0, 0, 0, 0, 1.1, 0.9, 1.1, 0.9, 33, 0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0]
1107 ]
1108 }
1109 }
1110 }"#;
1111
1112 let net = parse_str(json).unwrap();
1113 assert_eq!(net.n_buses(), 2);
1114 assert_eq!(net.branches.len(), 1);
1116 let xfmr = &net.branches[0];
1117 assert_eq!(xfmr.from_bus, 1);
1118 assert_eq!(xfmr.to_bus, 2);
1119 assert!((xfmr.r - 0.005).abs() < 1e-10);
1120 assert!((xfmr.x - 0.1).abs() < 1e-10);
1121 assert!((xfmr.tap - 1.0).abs() < 1e-10);
1123 assert!((xfmr.rating_a_mva - 200.0).abs() < 1e-10);
1124 }
1125
1126 #[test]
1127 fn test_parse_rawx_empty_sections() {
1128 let json = r#"{
1130 "network": {
1131 "caseid": {
1132 "fields": ["ic", "sbase"],
1133 "data": [0, 100.0]
1134 },
1135 "bus": {
1136 "fields": ["ibus", "name", "baskv", "ide"],
1137 "data": [
1138 [1, "Solo Bus", 69.0, 3]
1139 ]
1140 }
1141 }
1142 }"#;
1143
1144 let net = parse_str(json).unwrap();
1145 assert_eq!(net.n_buses(), 1);
1146 assert_eq!(net.branches.len(), 0);
1147 assert_eq!(net.generators.len(), 0);
1148 assert_eq!(net.base_mva, 100.0);
1149 }
1150
1151 #[test]
1152 fn test_parse_rawx_fixshunt_accumulation() {
1153 let json = r#"{
1154 "network": {
1155 "caseid": {
1156 "fields": ["ic", "sbase"],
1157 "data": [0, 100.0]
1158 },
1159 "bus": {
1160 "fields": ["ibus", "name", "baskv", "ide"],
1161 "data": [
1162 [1, "Bus 1", 138.0, 3]
1163 ]
1164 },
1165 "fixshunt": {
1166 "fields": ["ibus", "shntid", "stat", "gl", "bl"],
1167 "data": [
1168 [1, "1", 1, 0.0, 25.0],
1169 [1, "2", 1, 0.0, 10.0]
1170 ]
1171 }
1172 }
1173 }"#;
1174
1175 let net = parse_str(json).unwrap();
1176 assert!((net.buses[0].shunt_susceptance_mvar - 35.0).abs() < 1e-10);
1177 }
1178}