powerio_dist/model/
voltage_source.rs1use serde::{Deserialize, Serialize, Serializer};
2
3use super::Extras;
4
5#[derive(Clone, Debug, PartialEq, Deserialize)]
9#[serde(try_from = "SourceWire")]
10#[non_exhaustive]
11pub struct VoltageSource {
12 pub name: String,
13 pub bus: String,
14 pub terminal_map: Vec<String>,
15 pub v_magnitude: Vec<f64>,
17 pub v_angle: Vec<f64>,
19 pub reference_terminal: Option<String>,
24 pub energy_cost_rate: Option<Vec<f64>>,
26 pub extras: Extras,
27}
28
29impl VoltageSource {
30 #[must_use]
31 pub fn new(
32 name: impl Into<String>,
33 bus: impl Into<String>,
34 terminal_map: Vec<String>,
35 v_magnitude: Vec<f64>,
36 v_angle: Vec<f64>,
37 ) -> Self {
38 Self {
39 name: name.into(),
40 bus: bus.into(),
41 terminal_map,
42 v_magnitude,
43 v_angle,
44 reference_terminal: None,
45 energy_cost_rate: None,
46 extras: Extras::new(),
47 }
48 }
49
50 #[must_use]
51 pub fn with_reference_terminal(mut self, terminal: impl Into<String>) -> Self {
52 self.reference_terminal = Some(terminal.into());
53 self
54 }
55}
56
57#[derive(Deserialize)]
62#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
63struct SourceFields {
64 name: String,
65 bus: String,
66 terminal_map: Vec<String>,
67 v_magnitude: Vec<f64>,
69 v_angle: Vec<f64>,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
73 energy_cost_rate: Option<Vec<f64>>,
74 extras: Extras,
75 #[serde(flatten)]
76 #[cfg_attr(feature = "schema", schemars(skip))]
77 unknown: std::collections::BTreeMap<String, serde_json::Value>,
78}
79
80#[derive(Deserialize)]
81struct ReferencedSourceFields {
82 #[serde(flatten)]
83 source: SourceFields,
84 reference_terminal: String,
85}
86
87#[cfg(feature = "schema")]
88impl schemars::JsonSchema for ReferencedSourceFields {
89 fn schema_name() -> std::borrow::Cow<'static, str> {
90 "ReferencedSourceFields".into()
91 }
92
93 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
94 let mut value = serde_json::to_value(SourceFields::json_schema(generator))
95 .expect("source schema is serializable");
96 value["properties"]["reference_terminal"] = serde_json::json!({
97 "type": "string", "description": "Declared reference terminal on the source bus; does not imply grounding."
98 });
99 value["properties"]["v_magnitude"]["description"] =
100 "Volts per terminal-to-reference difference, including at grounded phase terminals."
101 .into();
102 value["properties"]["v_angle"]["description"] =
103 "Radians per terminal-to-reference difference.".into();
104 value["required"]
105 .as_array_mut()
106 .expect("source schema has required fields")
107 .push("reference_terminal".into());
108 serde_json::from_value(value).expect("an extended object remains a schema")
109 }
110}
111
112#[derive(Deserialize)]
113#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
114#[serde(tag = "type", content = "value")]
115enum ReferencedSourceWire {
116 #[serde(rename = "powerio.ReferencedVoltageSource")]
117 Referenced(ReferencedSourceFields),
118}
119
120#[derive(Deserialize)]
121#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
122#[serde(untagged)]
123enum SourceWire {
124 Referenced(ReferencedSourceWire),
125 Earth(SourceFields),
126}
127
128impl TryFrom<SourceWire> for VoltageSource {
129 type Error = String;
130
131 fn try_from(wire: SourceWire) -> Result<Self, Self::Error> {
132 let (fields, reference_terminal) = match wire {
133 SourceWire::Earth(fields) => {
134 if ["reference_terminal", "type", "value"]
135 .iter()
136 .any(|key| fields.unknown.contains_key(*key))
137 {
138 return Err("source reference semantics require a powerio.ReferencedVoltageSource record".into());
139 }
140 (fields, None)
141 }
142 SourceWire::Referenced(ReferencedSourceWire::Referenced(fields)) => {
143 (fields.source, Some(fields.reference_terminal))
144 }
145 };
146 Ok(Self {
147 name: fields.name,
148 bus: fields.bus,
149 terminal_map: fields.terminal_map,
150 v_magnitude: fields.v_magnitude,
151 v_angle: fields.v_angle,
152 energy_cost_rate: fields.energy_cost_rate,
153 extras: fields.extras,
154 reference_terminal,
155 })
156 }
157}
158
159struct SourceBody<'a>(&'a VoltageSource);
160
161impl Serialize for SourceBody<'_> {
162 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
163 use serde::ser::SerializeStruct;
164 let s = self.0;
165 let count = 6
166 + usize::from(s.energy_cost_rate.is_some())
167 + usize::from(s.reference_terminal.is_some());
168 let mut body = serializer.serialize_struct("VoltageSource", count)?;
169 body.serialize_field("name", &s.name)?;
170 body.serialize_field("bus", &s.bus)?;
171 body.serialize_field("terminal_map", &s.terminal_map)?;
172 body.serialize_field("v_magnitude", &s.v_magnitude)?;
173 body.serialize_field("v_angle", &s.v_angle)?;
174 if let Some(cost) = &s.energy_cost_rate {
175 body.serialize_field("energy_cost_rate", cost)?;
176 }
177 body.serialize_field("extras", &s.extras)?;
178 if let Some(reference) = &s.reference_terminal {
179 body.serialize_field("reference_terminal", reference)?;
180 }
181 body.end()
182 }
183}
184
185impl Serialize for VoltageSource {
186 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
187 use serde::ser::SerializeStruct;
188 if self.reference_terminal.is_none() {
189 return SourceBody(self).serialize(serializer);
190 }
191 let mut wrapper = serializer.serialize_struct("ReferencedVoltageSource", 2)?;
192 wrapper.serialize_field("type", "powerio.ReferencedVoltageSource")?;
193 wrapper.serialize_field("value", &SourceBody(self))?;
194 wrapper.end()
195 }
196}
197
198#[cfg(feature = "schema")]
199impl schemars::JsonSchema for VoltageSource {
200 fn schema_name() -> std::borrow::Cow<'static, str> {
201 "VoltageSource".into()
202 }
203 fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
204 schemars::json_schema!({"anyOf": [
207 SourceFields::json_schema(generator),
208 generator.subschema_for::<ReferencedSourceWire>(),
209 ]})
210 }
211}