1use serde::Serialize;
13use serde_json::{Map, Value};
14
15use super::error::ScopfError;
16use super::types::{
17 ScopfAcContingencySurvivors, ScopfAcLineRow, ScopfAcLineSurvivorRow, ScopfActiveReserveRow,
18 ScopfActiveReserveSetRow, ScopfBusRow, ScopfDcContingencyFlowRow, ScopfDcLineRow,
19 ScopfDeviceRow, ScopfEnergyWindowMaxCsRow, ScopfEnergyWindowMaxPrRow,
20 ScopfEnergyWindowMinCsRow, ScopfEnergyWindowMinPrRow, ScopfEnergyWindowPeriodMaxCsRow,
21 ScopfEnergyWindowPeriodMaxPrRow, ScopfEnergyWindowPeriodMinCsRow,
22 ScopfEnergyWindowPeriodMinPrRow, ScopfEnergyWindows, ScopfFixedPhaseRow, ScopfFixedRatioRow,
23 ScopfLengths, ScopfPriceBlockRow, ScopfPriceBlocks, ScopfReactiveReserveRow,
24 ScopfReactiveReserveSetRow, ScopfShuntRow, ScopfStaticData, ScopfTransformerRow,
25 ScopfTransformerSurvivorRow, ScopfVariablePhaseRow, ScopfVariableRatioRow, ScopfViolationCost,
26};
27use super::{ScopfInstance, ScopfResult};
28
29pub const SCOPF_SCHEMA: &str = "powerio.scopf.julia";
30
31#[derive(Serialize)]
32struct Envelope {
33 schema: &'static str,
34 #[serde(rename = "powerio_version")]
36 powerio_version: &'static str,
37 index_base: usize,
38 instance: Value,
39}
40
41trait SerializedFields: Serialize {
44 const INDEX_FIELDS: &'static [&'static str] = &[];
47 const RENAMED_FIELDS: &'static [(&'static str, &'static str)] = &[];
49}
50
51macro_rules! serialized_fields {
55 ($row:ident {
56 index: [$($index:ident),* $(,)?],
57 values: [$($value:ident),* $(,)?]
58 $(, renamed: [$($from:ident => $to:literal),+ $(,)?])? $(,)?
59 }) => {
60 impl SerializedFields for $row {
61 const INDEX_FIELDS: &'static [&'static str] = &[$(stringify!($index)),*];
62 $(const RENAMED_FIELDS: &'static [(&'static str, &'static str)] =
63 &[$((stringify!($from), $to)),+];)?
64 }
65 const _: () = {
66 #[allow(dead_code)]
67 fn classified(row: $row) {
68 let $row { $($index: _,)* $($value: _,)* $($($from: _,)+)? } = row;
69 }
70 };
71 };
72}
73
74serialized_fields!(ScopfBusRow {
75 index: [],
76 values: [i, uid, v_min, v_max],
77});
78serialized_fields!(ScopfShuntRow {
79 index: [j_sh],
80 values: [uid, bus, g_sh, b_sh],
81});
82serialized_fields!(ScopfAcLineRow {
83 index: [j_ln],
84 values: [
85 uid, to_bus, fr_bus, c_su, c_sd, s_max, g_sr, b_sr, b_ch, g_fr, g_to, b_fr, b_to
86 ],
87});
88serialized_fields!(ScopfTransformerRow {
89 index: [j_xf],
90 values: [
91 uid, to_bus, fr_bus, c_su, c_sd, s_max, g_sr, b_sr, b_ch, g_fr, g_to, b_fr, b_to
92 ],
93});
94serialized_fields!(ScopfDcLineRow {
95 index: [j_dc],
96 values: [
97 uid, pdc_max, qdc_fr_min, qdc_to_min, qdc_fr_max, qdc_to_max, to_bus, fr_bus
98 ],
99});
100serialized_fields!(ScopfVariablePhaseRow {
101 index: [j_xf],
102 values: [phi_min, phi_max],
103});
104serialized_fields!(ScopfFixedPhaseRow {
105 index: [j_xf],
106 values: [phi_o],
107});
108serialized_fields!(ScopfVariableRatioRow {
109 index: [j_xf],
110 values: [tau_min, tau_max],
111});
112serialized_fields!(ScopfFixedRatioRow {
113 index: [j_xf],
114 values: [tau_o],
115});
116serialized_fields!(ScopfDeviceRow {
117 index: [],
118 values: [
119 bus,
120 uid,
121 c_on,
122 c_su,
123 c_sd,
124 p_ru,
125 p_rd,
126 p_ru_su,
127 p_rd_sd,
128 c_rgu,
129 c_rgd,
130 c_scr,
131 c_nsc,
132 c_rru_on,
133 c_rru_off,
134 c_rrd_on,
135 c_rrd_off,
136 c_qru,
137 c_qrd,
138 p_rgu_max,
139 p_rgd_max,
140 p_scr_max,
141 p_nsc_max,
142 p_rru_on_max,
143 p_rru_off_max,
144 p_rrd_on_max,
145 p_rrd_off_max,
146 p_0,
147 q_0,
148 p_max,
149 p_min,
150 q_max,
151 q_min,
152 sus,
153 q_bound_cap,
154 q_linear_cap,
155 beta_ub,
156 beta_lb,
157 q_0_ub,
158 q_0_lb,
159 beta,
160 q_p0
161 ],
162});
163serialized_fields!(ScopfActiveReserveRow {
164 index: [n_p],
165 values: [uid, c_rgu, c_rgd, c_scr, c_nsc, c_rru, c_rrd, p_rru_min, p_rrd_min],
166 renamed: [
167 sigma_rgu => "σ_rgu",
168 sigma_rgd => "σ_rgd",
169 sigma_scr => "σ_scr",
170 sigma_nsc => "σ_nsc",
171 ],
172});
173serialized_fields!(ScopfReactiveReserveRow {
174 index: [n_q],
175 values: [uid, c_qru, c_qrd, q_qru_min, q_qrd_min],
176});
177serialized_fields!(ScopfActiveReserveSetRow {
178 index: [n_p],
179 values: [i, uid],
180});
181serialized_fields!(ScopfReactiveReserveSetRow {
182 index: [n_q],
183 values: [i, uid],
184});
185serialized_fields!(ScopfLengths {
186 index: [],
187 values: [],
188 renamed: [
189 l_j_xf => "L_J_xf",
190 l_j_ln => "L_J_ln",
191 l_j_ac => "L_J_ac",
192 l_j_dc => "L_J_dc",
193 l_j_br => "L_J_br",
194 l_j_cs => "L_J_cs",
195 l_j_pr => "L_J_pr",
196 l_j_cspr => "L_J_cspr",
197 l_j_sh => "L_J_sh",
198 i => "I",
199 l_t => "L_T",
200 l_n_p => "L_N_p",
201 l_n_q => "L_N_q",
202 k => "K",
203 ],
204});
205serialized_fields!(ScopfViolationCost {
206 index: [],
207 values: [p_bus, q_bus, s, e],
208});
209serialized_fields!(ScopfEnergyWindowMaxPrRow {
210 index: [w_en_max_pr_ind],
211 values: [uid, a_en_max_start, a_en_max_end, e_max],
212});
213serialized_fields!(ScopfEnergyWindowMaxCsRow {
214 index: [w_en_max_cs_ind],
215 values: [uid, a_en_max_start, a_en_max_end, e_max],
216});
217serialized_fields!(ScopfEnergyWindowMinPrRow {
218 index: [w_en_min_pr_ind],
219 values: [uid, a_en_min_start, a_en_min_end, e_min],
220});
221serialized_fields!(ScopfEnergyWindowMinCsRow {
222 index: [w_en_min_cs_ind],
223 values: [uid, a_en_min_start, a_en_min_end, e_min],
224});
225serialized_fields!(ScopfEnergyWindowPeriodMaxPrRow {
226 index: [w_en_max_pr_ind, t],
227 values: [uid, dt],
228});
229serialized_fields!(ScopfEnergyWindowPeriodMaxCsRow {
230 index: [w_en_max_cs_ind, t],
231 values: [uid, dt],
232});
233serialized_fields!(ScopfEnergyWindowPeriodMinPrRow {
234 index: [w_en_min_pr_ind, t],
235 values: [uid, dt],
236});
237serialized_fields!(ScopfEnergyWindowPeriodMinCsRow {
238 index: [w_en_min_cs_ind, t],
239 values: [uid, dt],
240});
241serialized_fields!(ScopfPriceBlockRow {
242 index: [flat_k, t, m],
243 values: [uid, c_en, p_max],
244});
245serialized_fields!(ScopfAcLineSurvivorRow {
246 index: [ctg, j_ln],
247 values: [uid, to_bus, fr_bus, b_sr, s_max_ctg],
248});
249serialized_fields!(ScopfTransformerSurvivorRow {
250 index: [ctg, j_xf],
251 values: [uid, to_bus, fr_bus, b_sr, s_max_ctg],
252});
253serialized_fields!(ScopfDcContingencyFlowRow {
254 index: [flat_jtk_dc, ctg, j_dc, t],
255 values: [to_bus, fr_bus, dt],
256});
257
258pub fn to_json_value(instance: &ScopfInstance) -> ScopfResult<Value> {
260 let ScopfInstance {
261 static_data,
262 lengths,
263 energy_windows,
264 price_blocks,
265 ac_contingency_survivors,
266 dc_contingency_flows,
267 violation_cost,
268 device_class_layout,
269 } = instance;
270 let mut fields = Map::new();
271 fields.insert("static".to_owned(), serialize_static(static_data)?);
272 fields.insert("lengths".to_owned(), serialize_object(lengths)?);
273 fields.insert(
274 "energy_windows".to_owned(),
275 serialize_energy_windows(energy_windows)?,
276 );
277 fields.insert(
278 "price_blocks".to_owned(),
279 serialize_price_blocks(price_blocks)?,
280 );
281 fields.insert(
282 "ac_contingency_survivors".to_owned(),
283 serialize_survivors(ac_contingency_survivors)?,
284 );
285 fields.insert(
286 "dc_contingency_flows".to_owned(),
287 serialize_rows(dc_contingency_flows)?,
288 );
289 fields.insert(
290 "violation_cost".to_owned(),
291 serialize_object(violation_cost)?,
292 );
293 fields.insert(
294 "device_class_layout".to_owned(),
295 serde_json::to_value(device_class_layout)?,
296 );
297 Ok(serde_json::to_value(Envelope {
298 schema: SCOPF_SCHEMA,
299 powerio_version: powerio::VERSION,
300 index_base: 1,
301 instance: Value::Object(fields),
302 })?)
303}
304
305pub fn to_json(instance: &ScopfInstance) -> ScopfResult<String> {
307 Ok(serde_json::to_string(&to_json_value(instance)?)?)
308}
309
310fn serialize_static(data: &ScopfStaticData) -> ScopfResult<Value> {
311 let ScopfStaticData {
312 bus,
313 shunt,
314 acl_branch,
315 acx_branch,
316 vpd,
317 fpd,
318 vwr,
319 fwr,
320 dc_branch,
321 prod,
322 cons,
323 active_reserve,
324 reactive_reserve,
325 active_reserve_set_pr,
326 active_reserve_set_cs,
327 reactive_reserve_set_pr,
328 reactive_reserve_set_cs,
329 } = data;
330 let mut object = Map::new();
331 object.insert("bus".to_owned(), serialize_rows(bus)?);
332 object.insert("shunt".to_owned(), serialize_rows(shunt)?);
333 object.insert("acl_branch".to_owned(), serialize_rows(acl_branch)?);
334 object.insert("acx_branch".to_owned(), serialize_rows(acx_branch)?);
335 object.insert("vpd".to_owned(), serialize_rows(vpd)?);
336 object.insert("fpd".to_owned(), serialize_rows(fpd)?);
337 object.insert("vwr".to_owned(), serialize_rows(vwr)?);
338 object.insert("fwr".to_owned(), serialize_rows(fwr)?);
339 object.insert("dc_branch".to_owned(), serialize_rows(dc_branch)?);
340 object.insert("prod".to_owned(), serialize_rows(prod)?);
341 object.insert("cons".to_owned(), serialize_rows(cons)?);
342 object.insert("active_reserve".to_owned(), serialize_rows(active_reserve)?);
343 object.insert(
344 "reactive_reserve".to_owned(),
345 serialize_rows(reactive_reserve)?,
346 );
347 object.insert(
348 "active_reserve_set_pr".to_owned(),
349 serialize_rows(active_reserve_set_pr)?,
350 );
351 object.insert(
352 "active_reserve_set_cs".to_owned(),
353 serialize_rows(active_reserve_set_cs)?,
354 );
355 object.insert(
356 "reactive_reserve_set_pr".to_owned(),
357 serialize_rows(reactive_reserve_set_pr)?,
358 );
359 object.insert(
360 "reactive_reserve_set_cs".to_owned(),
361 serialize_rows(reactive_reserve_set_cs)?,
362 );
363 Ok(Value::Object(object))
364}
365
366fn serialize_energy_windows(windows: &ScopfEnergyWindows) -> ScopfResult<Value> {
367 let ScopfEnergyWindows {
368 w_en_max_pr,
369 w_en_max_cs,
370 w_en_min_pr,
371 w_en_min_cs,
372 t_w_en_max_pr,
373 t_w_en_max_cs,
374 t_w_en_min_pr,
375 t_w_en_min_cs,
376 } = windows;
377 let mut object = Map::new();
378 object.insert("W_en_max_pr".to_owned(), serialize_rows(w_en_max_pr)?);
379 object.insert("W_en_max_cs".to_owned(), serialize_rows(w_en_max_cs)?);
380 object.insert("W_en_min_pr".to_owned(), serialize_rows(w_en_min_pr)?);
381 object.insert("W_en_min_cs".to_owned(), serialize_rows(w_en_min_cs)?);
382 object.insert("T_w_en_max_pr".to_owned(), serialize_rows(t_w_en_max_pr)?);
383 object.insert("T_w_en_max_cs".to_owned(), serialize_rows(t_w_en_max_cs)?);
384 object.insert("T_w_en_min_pr".to_owned(), serialize_rows(t_w_en_min_pr)?);
385 object.insert("T_w_en_min_cs".to_owned(), serialize_rows(t_w_en_min_cs)?);
386 Ok(Value::Object(object))
387}
388
389fn serialize_price_blocks(blocks: &ScopfPriceBlocks) -> ScopfResult<Value> {
390 let ScopfPriceBlocks { producer, consumer } = blocks;
391 let mut object = Map::new();
392 object.insert("producer".to_owned(), serialize_rows(producer)?);
393 object.insert("consumer".to_owned(), serialize_rows(consumer)?);
394 Ok(Value::Object(object))
395}
396
397fn serialize_survivors(survivors: &ScopfAcContingencySurvivors) -> ScopfResult<Value> {
398 let ScopfAcContingencySurvivors { ln, xf } = survivors;
399 let mut object = Map::new();
400 object.insert("ln".to_owned(), serialize_nested_rows(ln)?);
401 object.insert("xf".to_owned(), serialize_nested_rows(xf)?);
402 Ok(Value::Object(object))
403}
404
405fn serialize_rows<R: SerializedFields>(rows: &[R]) -> ScopfResult<Value> {
406 rows.iter()
407 .map(serialize_object)
408 .collect::<ScopfResult<Vec<_>>>()
409 .map(Value::from)
410}
411
412fn serialize_nested_rows<R: SerializedFields>(groups: &[Vec<R>]) -> ScopfResult<Value> {
413 groups
414 .iter()
415 .map(|group| serialize_rows(group))
416 .collect::<ScopfResult<Vec<_>>>()
417 .map(Value::from)
418}
419
420fn serialize_object<R: SerializedFields>(row: &R) -> ScopfResult<Value> {
425 let mut value = serde_json::to_value(row)?;
426 let Some(object) = value.as_object_mut() else {
427 return Err(ScopfError::invalid(
428 "struct did not serialize to a JSON object",
429 ));
430 };
431 for &field in R::INDEX_FIELDS {
432 let index = object
433 .get_mut(field)
434 .ok_or_else(|| ScopfError::invalid(format!("index field `{field}` not serialized")))?;
435 let Some(zero_based) = index.as_u64() else {
436 return Err(ScopfError::invalid(format!(
437 "index field `{field}` is not an unsigned integer"
438 )));
439 };
440 *index = Value::from(zero_based + 1);
441 }
442 for &(from, to) in R::RENAMED_FIELDS {
443 let renamed = object
444 .remove(from)
445 .ok_or_else(|| ScopfError::invalid(format!("renamed field `{from}` not serialized")))?;
446 object.insert(to.to_owned(), renamed);
447 }
448 Ok(value)
449}