Skip to main content

surge_network/network/
cgmes_roundtrip.rs

1// SPDX-License-Identifier: LicenseRef-PolyForm-Noncommercial-1.0.0
2//! Typed CGMES source-object state preserved for faithful round-trip export.
3
4use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8/// Source CGMES objects that must survive lowering into the native solver model.
9///
10/// These records preserve the original class identity and any class-specific
11/// fields the writer cannot recover from the lowered network alone.
12#[derive(Debug, Clone, Default, Serialize, Deserialize)]
13pub struct CgmesRoundtripData {
14    /// Original `EquivalentInjection` objects keyed by source mRID.
15    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
16    pub equivalent_injections: HashMap<String, CgmesEquivalentInjectionSource>,
17    /// Original `ExternalNetworkInjection` objects keyed by source mRID.
18    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
19    pub external_network_injections: HashMap<String, CgmesExternalNetworkInjectionSource>,
20    /// Original `DanglingLine` objects keyed by source mRID.
21    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
22    pub dangling_lines: HashMap<String, CgmesDanglingLineSource>,
23}
24
25impl CgmesRoundtripData {
26    /// Returns `true` when no CGMES source-object state is present.
27    pub fn is_empty(&self) -> bool {
28        self.equivalent_injections.is_empty()
29            && self.external_network_injections.is_empty()
30            && self.dangling_lines.is_empty()
31    }
32}
33
34/// Original CGMES `EquivalentInjection` preserved across import/export.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct CgmesEquivalentInjectionSource {
37    /// Source mRID.
38    pub mrid: String,
39    /// Source `IdentifiedObject.name`, when present.
40    #[serde(default, skip_serializing_if = "Option::is_none")]
41    pub name: Option<String>,
42    /// Connected bus number.
43    pub bus: u32,
44    /// Imported operating-point active power (MW).
45    pub p_mw: f64,
46    /// Imported operating-point reactive power (MVAr).
47    pub q_mvar: f64,
48    /// Imported in-service status.
49    pub in_service: bool,
50    /// `RegulatingCondEq.controlEnabled`.
51    pub control_enabled: bool,
52    /// `EquivalentInjection.regulationStatus`.
53    pub regulation_status: bool,
54    /// `RegulatingControl.targetValue` in kV, when present.
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub target_voltage_kv: Option<f64>,
57    /// Imported minimum reactive limit in MVAr, when present.
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub min_q_mvar: Option<f64>,
60    /// Imported maximum reactive limit in MVAr, when present.
61    #[serde(default, skip_serializing_if = "Option::is_none")]
62    pub max_q_mvar: Option<f64>,
63}
64
65/// Original CGMES `ExternalNetworkInjection` preserved across import/export.
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct CgmesExternalNetworkInjectionSource {
68    /// Source mRID.
69    pub mrid: String,
70    /// Source `IdentifiedObject.name`, when present.
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub name: Option<String>,
73    /// Connected bus number.
74    pub bus: u32,
75    /// Imported operating-point active power (MW).
76    pub p_mw: f64,
77    /// Imported operating-point reactive power (MVAr).
78    pub q_mvar: f64,
79    /// Imported in-service status.
80    pub in_service: bool,
81    /// Imported slack-selection priority.
82    #[serde(default, skip_serializing_if = "Option::is_none")]
83    pub reference_priority: Option<u32>,
84    /// `RegulatingCondEq.controlEnabled`.
85    pub control_enabled: bool,
86    /// `ExternalNetworkInjection.regulationStatus`.
87    pub regulation_status: bool,
88    /// `RegulatingControl.targetValue` in kV, when present.
89    #[serde(default, skip_serializing_if = "Option::is_none")]
90    pub target_voltage_kv: Option<f64>,
91    /// Imported minimum reactive limit in MVAr, when present.
92    #[serde(default, skip_serializing_if = "Option::is_none")]
93    pub min_q_mvar: Option<f64>,
94    /// Imported maximum reactive limit in MVAr, when present.
95    #[serde(default, skip_serializing_if = "Option::is_none")]
96    pub max_q_mvar: Option<f64>,
97}
98
99/// Original CGMES `DanglingLine` preserved across import/export.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct CgmesDanglingLineSource {
102    /// Source mRID.
103    pub mrid: String,
104    /// Source `IdentifiedObject.name`, when present.
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub name: Option<String>,
107    /// Connected bus number.
108    pub bus: u32,
109    /// Imported operating-point active power (MW).
110    pub p_mw: f64,
111    /// Imported operating-point reactive power (MVAr).
112    pub q_mvar: f64,
113    /// Imported in-service status.
114    pub in_service: bool,
115    /// Series resistance in ohms, when present.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub r_ohm: Option<f64>,
118    /// Series reactance in ohms, when present.
119    #[serde(default, skip_serializing_if = "Option::is_none")]
120    pub x_ohm: Option<f64>,
121    /// Shunt conductance in Siemens.
122    pub g_s: f64,
123    /// Shunt susceptance in Siemens.
124    pub b_s: f64,
125}