1use serde::{Deserialize, Serialize};
2
3use powerio::{BusId, IndexedNetwork};
4
5use crate::{Error, Result};
6
7use crate::{ReferenceBuses, Units, limits, nodal};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
14pub struct AcOpfOptions {
15 pub units: Units,
16 pub skip_zero_impedance: bool,
19 pub synthesize_unrated_limits: bool,
24}
25
26impl Default for AcOpfOptions {
27 fn default() -> Self {
28 Self {
29 units: Units::default(),
30 skip_zero_impedance: true,
31 synthesize_unrated_limits: false,
32 }
33 }
34}
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38#[non_exhaustive]
39pub struct AcBusData {
40 pub p_d: Vec<f64>,
42 pub q_d: Vec<f64>,
44 pub g_s: Vec<f64>,
47 pub b_s: Vec<f64>,
50 pub vm_min: Vec<f64>,
52 pub vm_max: Vec<f64>,
54 pub vm: Vec<f64>,
57}
58
59#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
61#[non_exhaustive]
62pub struct AcBranchData {
63 pub from_bus: Vec<usize>,
64 pub to_bus: Vec<usize>,
65 pub g: Vec<f64>,
67 pub b: Vec<f64>,
69 pub g_fr: Vec<f64>,
71 pub b_fr: Vec<f64>,
73 pub g_to: Vec<f64>,
75 pub b_to: Vec<f64>,
77 pub tap: Vec<f64>,
80 pub shift: Vec<f64>,
82 pub s_max: Vec<f64>,
84 pub angle_min: Vec<f64>,
86 pub angle_max: Vec<f64>,
87 pub source_rows: Vec<usize>,
89 pub skipped_zero_impedance: Vec<usize>,
91}
92
93#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
95#[non_exhaustive]
96pub struct AcGeneratorData {
97 pub bus_of_gen: Vec<usize>,
99 pub source_rows: Vec<usize>,
101 pub q: Vec<f64>,
103 pub c: Vec<f64>,
105 pub c0: Vec<f64>,
108 pub pmax: Vec<f64>,
109 pub pmin: Vec<f64>,
110 pub qmax: Vec<f64>,
111 pub qmin: Vec<f64>,
112 pub pg: Vec<f64>,
114 pub qg: Vec<f64>,
116 pub vg: Vec<f64>,
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123#[non_exhaustive]
124pub struct NodalAcGeneratorData {
125 pub q: Vec<f64>,
126 pub c: Vec<f64>,
127 pub c0: Vec<f64>,
128 pub pmax: Vec<f64>,
129 pub pmin: Vec<f64>,
130 pub qmax: Vec<f64>,
131 pub qmin: Vec<f64>,
132 pub has_gen: Vec<bool>,
137}
138
139#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
154#[non_exhaustive]
155pub struct AcOpfInstance {
156 pub name: String,
157 pub n_buses: usize,
158 pub n_source_generators: usize,
159 pub n_source_branches: usize,
160 pub base_mva: f64,
161 pub units: Units,
162 pub skip_zero_impedance: bool,
163 pub bus_ids: Vec<BusId>,
165 pub reference_buses: ReferenceBuses,
166 pub buses: AcBusData,
167 pub generators: AcGeneratorData,
168 pub branches: AcBranchData,
169}
170
171impl AcOpfInstance {
172 #[must_use]
173 pub fn n_generators(&self) -> usize {
174 self.generators.q.len()
175 }
176
177 #[must_use]
178 pub fn n_branches(&self) -> usize {
179 self.branches.g.len()
180 }
181
182 #[must_use]
192 pub fn nodal_generator_data(&self) -> NodalAcGeneratorData {
193 let n = self.n_buses;
194 let generators = &self.generators;
195 let bus_of_gen = &generators.bus_of_gen;
196 let costs =
197 nodal::combine_costs(n, bus_of_gen, &generators.q, &generators.c, &generators.c0);
198 NodalAcGeneratorData {
199 q: costs.q,
200 c: costs.c,
201 c0: costs.c0,
202 pmax: nodal::sum_by_bus(n, bus_of_gen, &generators.pmax),
203 pmin: nodal::sum_by_bus(n, bus_of_gen, &generators.pmin),
204 qmax: nodal::sum_by_bus(n, bus_of_gen, &generators.qmax),
205 qmin: nodal::sum_by_bus(n, bus_of_gen, &generators.qmin),
206 has_gen: nodal::buses_with_generators(n, bus_of_gen),
207 }
208 }
209
210 #[must_use]
217 pub fn vm_setpoints(&self) -> Vec<f64> {
218 let mut vm: Vec<f64> = self
219 .buses
220 .vm
221 .iter()
222 .map(|&value| if value > 0.0 { value } else { 1.0 })
223 .collect();
224 for generator in 0..self.n_generators() {
225 let vg = self.generators.vg[generator];
226 if vg > 0.0 {
227 vm[self.generators.bus_of_gen[generator]] = vg;
228 }
229 }
230 vm
231 }
232}
233
234#[allow(clippy::too_many_lines)]
236pub fn build_ac_opf_instance(
237 case: &IndexedNetwork,
238 options: &AcOpfOptions,
239) -> Result<AcOpfInstance> {
240 case.check_reference_coverage()?;
241 case.network().check_base_mva()?;
242
243 let n_buses = case.n();
244 let base = case.per_unit_base();
245 let (p_scale, y_scale) = options.units.power_scales(base);
246 let thermal = limits::ThermalLimits {
247 synthesize_unrated: options.synthesize_unrated_limits,
248 power_scale: p_scale,
249 admittance_scale: y_scale,
250 };
251 let (q_scale, c_scale) = options.units.cost_scales(base);
252
253 let mut bus_of_gen = Vec::new();
254 let mut generator_rows = Vec::new();
255 let mut cost_q = Vec::new();
256 let mut cost_c = Vec::new();
257 let mut cost_c0 = Vec::new();
258 let mut pmax = Vec::new();
259 let mut pmin = Vec::new();
260 let mut qmax = Vec::new();
261 let mut qmin = Vec::new();
262 let mut pg = Vec::new();
263 let mut qg = Vec::new();
264 let mut vg = Vec::new();
265
266 for (source_row, generator) in case.in_service_gens() {
267 let bus = case
268 .bus_index(generator.bus)
269 .ok_or(powerio::Error::UnknownBus {
270 bus_id: generator.bus,
271 element_index: source_row,
272 })?;
273 let cost = generator
274 .cost
275 .as_ref()
276 .ok_or(powerio::Error::MissingGenCost {
277 gen_index: source_row,
278 })?;
279 let (q_raw, c_raw, c0_raw) = nodal::quadratic_terms(cost, source_row)?;
280 bus_of_gen.push(bus);
281 generator_rows.push(source_row);
282 cost_q.push(q_raw * q_scale);
283 cost_c.push(c_raw * c_scale);
284 cost_c0.push(c0_raw);
285 pmax.push(generator.pmax * p_scale);
286 pmin.push(generator.pmin * p_scale);
287 qmax.push(generator.qmax * p_scale);
288 qmin.push(generator.qmin * p_scale);
289 pg.push(generator.pg * p_scale);
290 qg.push(generator.qg * p_scale);
291 vg.push(generator.vg);
292 }
293 if cost_q.is_empty() {
294 return Err(Error::NoGenerators);
295 }
296
297 let mut g_s: Vec<f64> = case.gs().iter().map(|value| value * p_scale).collect();
298 let mut b_s: Vec<f64> = case.bs().iter().map(|value| value * p_scale).collect();
299
300 let mut from_bus = Vec::new();
301 let mut to_bus = Vec::new();
302 let mut g = Vec::new();
303 let mut b = Vec::new();
304 let mut g_fr = Vec::new();
305 let mut b_fr = Vec::new();
306 let mut g_to = Vec::new();
307 let mut b_to = Vec::new();
308 let mut tap = Vec::new();
309 let mut shift = Vec::new();
310 let mut s_max = Vec::new();
311 let mut angle_min = Vec::new();
312 let mut angle_max = Vec::new();
313 let mut branch_rows = Vec::new();
314 let mut skipped_zero_impedance = Vec::new();
315 let network = case.network();
318
319 for (source_row, branch) in case.in_service_branches() {
320 let from = case
321 .bus_index(branch.from)
322 .ok_or(powerio::Error::UnknownBus {
323 bus_id: branch.from,
324 element_index: source_row,
325 })?;
326 let to = case
327 .bus_index(branch.to)
328 .ok_or(powerio::Error::UnknownBus {
329 bus_id: branch.to,
330 element_index: source_row,
331 })?;
332 let Some((series_g, series_b)) = branch.series_admittance(source_row)? else {
333 if options.skip_zero_impedance {
334 skipped_zero_impedance.push(source_row);
335 continue;
336 }
337 return Err(powerio::Error::ZeroImpedance { row: source_row }.into());
338 };
339 let charging = branch.terminal_charging();
340 if from == to {
341 let tap = branch.divisible_tap(source_row)?;
346 let tap_squared = tap * tap;
347 let cross = 2.0 * case.angle_radians(branch.shift).cos() / tap;
348 g_s[from] += ((series_g + charging.g_fr) / tap_squared + (series_g + charging.g_to)
349 - series_g * cross)
350 * y_scale;
351 b_s[from] += ((series_b + charging.b_fr) / tap_squared + (series_b + charging.b_to)
352 - series_b * cross)
353 * y_scale;
354 continue;
355 }
356 from_bus.push(from);
357 to_bus.push(to);
358 g.push(series_g * y_scale);
359 b.push(series_b * y_scale);
360 g_fr.push(charging.g_fr * y_scale);
361 b_fr.push(charging.b_fr * y_scale);
362 g_to.push(charging.g_to * y_scale);
363 b_to.push(charging.b_to * y_scale);
364 let amin = case.angle_radians(branch.angmin);
365 let amax = case.angle_radians(branch.angmax);
366 tap.push(branch.divisible_tap(source_row)?);
367 shift.push(case.angle_radians(branch.shift));
368 s_max.push(thermal.of(
369 branch,
370 amin,
371 amax,
372 network.buses[from].vmax,
373 network.buses[to].vmax,
374 ));
375 angle_min.push(amin);
376 angle_max.push(amax);
377 branch_rows.push(source_row);
378 }
379
380 let mut vm_min = Vec::with_capacity(n_buses);
381 let mut vm_max = Vec::with_capacity(n_buses);
382 let mut vm = Vec::with_capacity(n_buses);
383 for bus in &network.buses {
384 vm_min.push(bus.vmin);
385 vm_max.push(bus.vmax);
386 vm.push(bus.vm);
387 }
388
389 Ok(AcOpfInstance {
390 name: case.name().to_owned(),
391 n_buses,
392 n_source_generators: case.generators().len(),
393 n_source_branches: case.branches().len(),
394 base_mva: case.base_mva(),
395 units: options.units,
396 skip_zero_impedance: options.skip_zero_impedance,
397 bus_ids: (0..n_buses).map(|index| case.bus_id(index)).collect(),
398 reference_buses: ReferenceBuses::new(case.reference_bus_indices()),
399 buses: AcBusData {
400 p_d: case.pd().iter().map(|value| value * p_scale).collect(),
401 q_d: case.qd().iter().map(|value| value * p_scale).collect(),
402 g_s,
403 b_s,
404 vm_min,
405 vm_max,
406 vm,
407 },
408 generators: AcGeneratorData {
409 bus_of_gen,
410 source_rows: generator_rows,
411 q: cost_q,
412 c: cost_c,
413 c0: cost_c0,
414 pmax,
415 pmin,
416 qmax,
417 qmin,
418 pg,
419 qg,
420 vg,
421 },
422 branches: AcBranchData {
423 from_bus,
424 to_bus,
425 g,
426 b,
427 g_fr,
428 b_fr,
429 g_to,
430 b_to,
431 tap,
432 shift,
433 s_max,
434 angle_min,
435 angle_max,
436 source_rows: branch_rows,
437 skipped_zero_impedance,
438 },
439 })
440}