1use serde::{Deserialize, Serialize};
2
3use powerio::{BusId, Error, IndexedNetwork, Result};
4
5use crate::Units;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
12pub struct AcOpfOptions {
13 pub units: Units,
14 pub skip_zero_impedance: bool,
17}
18
19impl Default for AcOpfOptions {
20 fn default() -> Self {
21 Self {
22 units: Units::default(),
23 skip_zero_impedance: true,
24 }
25 }
26}
27
28#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
30#[non_exhaustive]
31pub struct AcBusData {
32 pub p_d: Vec<f64>,
34 pub q_d: Vec<f64>,
36 pub g_s: Vec<f64>,
39 pub b_s: Vec<f64>,
42 pub vm_min: Vec<f64>,
44 pub vm_max: Vec<f64>,
46 pub vm: Vec<f64>,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53#[non_exhaustive]
54pub struct AcBranchData {
55 pub from_bus: Vec<usize>,
56 pub to_bus: Vec<usize>,
57 pub g: Vec<f64>,
59 pub b: Vec<f64>,
61 pub g_fr: Vec<f64>,
63 pub b_fr: Vec<f64>,
65 pub g_to: Vec<f64>,
67 pub b_to: Vec<f64>,
69 pub tap: Vec<f64>,
72 pub shift: Vec<f64>,
74 pub s_max: Vec<f64>,
76 pub angle_min: Vec<f64>,
78 pub angle_max: Vec<f64>,
79 pub source_rows: Vec<usize>,
81 pub skipped_zero_impedance: Vec<usize>,
83}
84
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
87#[non_exhaustive]
88pub struct AcGeneratorData {
89 pub bus_of_gen: Vec<usize>,
91 pub source_rows: Vec<usize>,
93 pub q: Vec<f64>,
95 pub c: Vec<f64>,
97 pub c0: Vec<f64>,
100 pub pmax: Vec<f64>,
101 pub pmin: Vec<f64>,
102 pub qmax: Vec<f64>,
103 pub qmin: Vec<f64>,
104 pub pg: Vec<f64>,
106 pub qg: Vec<f64>,
108 pub vg: Vec<f64>,
110}
111
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
127#[non_exhaustive]
128pub struct AcOpfInstance {
129 pub name: String,
130 pub n_buses: usize,
131 pub n_source_generators: usize,
132 pub n_source_branches: usize,
133 pub base_mva: f64,
134 pub units: Units,
135 pub skip_zero_impedance: bool,
136 pub bus_ids: Vec<BusId>,
138 pub reference_buses: Vec<usize>,
139 pub buses: AcBusData,
140 pub generators: AcGeneratorData,
141 pub branches: AcBranchData,
142}
143
144impl AcOpfInstance {
145 #[must_use]
146 pub fn n_generators(&self) -> usize {
147 self.generators.q.len()
148 }
149
150 #[must_use]
151 pub fn n_branches(&self) -> usize {
152 self.branches.g.len()
153 }
154
155 #[must_use]
162 pub fn vm_setpoints(&self) -> Vec<f64> {
163 let mut vm: Vec<f64> = self
164 .buses
165 .vm
166 .iter()
167 .map(|&value| if value > 0.0 { value } else { 1.0 })
168 .collect();
169 for generator in 0..self.n_generators() {
170 let vg = self.generators.vg[generator];
171 if vg > 0.0 {
172 vm[self.generators.bus_of_gen[generator]] = vg;
173 }
174 }
175 vm
176 }
177}
178
179#[allow(clippy::too_many_lines)]
181pub fn build_ac_opf_instance(
182 case: &IndexedNetwork,
183 options: &AcOpfOptions,
184) -> Result<AcOpfInstance> {
185 case.check_reference_coverage()?;
186 case.network().check_base_mva()?;
187
188 let n_buses = case.n();
189 let base = case.per_unit_base();
190 let (p_scale, y_scale) = options.units.power_scales(base);
191 let (q_scale, c_scale) = options.units.cost_scales(base);
192
193 let mut bus_of_gen = Vec::new();
194 let mut generator_rows = Vec::new();
195 let mut cost_q = Vec::new();
196 let mut cost_c = Vec::new();
197 let mut cost_c0 = Vec::new();
198 let mut pmax = Vec::new();
199 let mut pmin = Vec::new();
200 let mut qmax = Vec::new();
201 let mut qmin = Vec::new();
202 let mut pg = Vec::new();
203 let mut qg = Vec::new();
204 let mut vg = Vec::new();
205
206 for (source_row, generator) in case.in_service_gens() {
207 let bus = case.bus_index(generator.bus).ok_or(Error::UnknownBus {
208 bus_id: generator.bus,
209 element_index: source_row,
210 })?;
211 let cost = generator.cost.as_ref().ok_or(Error::MissingGenCost {
212 gen_index: source_row,
213 })?;
214 let (q_raw, c_raw, c0_raw) =
215 cost.quadratic_with_constant()
216 .ok_or(Error::UnsupportedCostModel {
217 gen_index: source_row,
218 model: cost.model,
219 ncost: cost.ncost,
220 })?;
221 bus_of_gen.push(bus);
222 generator_rows.push(source_row);
223 cost_q.push(q_raw * q_scale);
224 cost_c.push(c_raw * c_scale);
225 cost_c0.push(c0_raw);
226 pmax.push(generator.pmax * p_scale);
227 pmin.push(generator.pmin * p_scale);
228 qmax.push(generator.qmax * p_scale);
229 qmin.push(generator.qmin * p_scale);
230 pg.push(generator.pg * p_scale);
231 qg.push(generator.qg * p_scale);
232 vg.push(generator.vg);
233 }
234 if cost_q.is_empty() {
235 return Err(Error::NoGenerators);
236 }
237
238 let mut g_s: Vec<f64> = case.gs().iter().map(|value| value * p_scale).collect();
239 let mut b_s: Vec<f64> = case.bs().iter().map(|value| value * p_scale).collect();
240
241 let mut from_bus = Vec::new();
242 let mut to_bus = Vec::new();
243 let mut g = Vec::new();
244 let mut b = Vec::new();
245 let mut g_fr = Vec::new();
246 let mut b_fr = Vec::new();
247 let mut g_to = Vec::new();
248 let mut b_to = Vec::new();
249 let mut tap = Vec::new();
250 let mut shift = Vec::new();
251 let mut s_max = Vec::new();
252 let mut angle_min = Vec::new();
253 let mut angle_max = Vec::new();
254 let mut branch_rows = Vec::new();
255 let mut skipped_zero_impedance = Vec::new();
256
257 for (source_row, branch) in case.in_service_branches() {
258 let from = case.bus_index(branch.from).ok_or(Error::UnknownBus {
259 bus_id: branch.from,
260 element_index: source_row,
261 })?;
262 let to = case.bus_index(branch.to).ok_or(Error::UnknownBus {
263 bus_id: branch.to,
264 element_index: source_row,
265 })?;
266 let Some((series_g, series_b)) = branch.series_admittance(source_row)? else {
267 if options.skip_zero_impedance {
268 skipped_zero_impedance.push(source_row);
269 continue;
270 }
271 return Err(Error::ZeroImpedance { row: source_row });
272 };
273 let charging = branch.terminal_charging();
274 if from == to {
275 let tap = branch.effective_tap();
280 let tap_squared = tap * tap;
281 let cross = 2.0 * case.angle_radians(branch.shift).cos() / tap;
282 g_s[from] += ((series_g + charging.g_fr) / tap_squared + (series_g + charging.g_to)
283 - series_g * cross)
284 * y_scale;
285 b_s[from] += ((series_b + charging.b_fr) / tap_squared + (series_b + charging.b_to)
286 - series_b * cross)
287 * y_scale;
288 continue;
289 }
290 from_bus.push(from);
291 to_bus.push(to);
292 g.push(series_g * y_scale);
293 b.push(series_b * y_scale);
294 g_fr.push(charging.g_fr * y_scale);
295 b_fr.push(charging.b_fr * y_scale);
296 g_to.push(charging.g_to * y_scale);
297 b_to.push(charging.b_to * y_scale);
298 tap.push(branch.effective_tap());
299 shift.push(case.angle_radians(branch.shift));
300 s_max.push(branch.rate_a * p_scale);
301 angle_min.push(case.angle_radians(branch.angmin));
302 angle_max.push(case.angle_radians(branch.angmax));
303 branch_rows.push(source_row);
304 }
305
306 let network = case.network();
309 let mut vm_min = Vec::with_capacity(n_buses);
310 let mut vm_max = Vec::with_capacity(n_buses);
311 let mut vm = Vec::with_capacity(n_buses);
312 for bus in &network.buses {
313 vm_min.push(bus.vmin);
314 vm_max.push(bus.vmax);
315 vm.push(bus.vm);
316 }
317
318 Ok(AcOpfInstance {
319 name: case.name().to_owned(),
320 n_buses,
321 n_source_generators: case.generators().len(),
322 n_source_branches: case.branches().len(),
323 base_mva: case.base_mva(),
324 units: options.units,
325 skip_zero_impedance: options.skip_zero_impedance,
326 bus_ids: (0..n_buses).map(|index| case.bus_id(index)).collect(),
327 reference_buses: case.reference_bus_indices(),
328 buses: AcBusData {
329 p_d: case.pd().iter().map(|value| value * p_scale).collect(),
330 q_d: case.qd().iter().map(|value| value * p_scale).collect(),
331 g_s,
332 b_s,
333 vm_min,
334 vm_max,
335 vm,
336 },
337 generators: AcGeneratorData {
338 bus_of_gen,
339 source_rows: generator_rows,
340 q: cost_q,
341 c: cost_c,
342 c0: cost_c0,
343 pmax,
344 pmin,
345 qmax,
346 qmin,
347 pg,
348 qg,
349 vg,
350 },
351 branches: AcBranchData {
352 from_bus,
353 to_bus,
354 g,
355 b,
356 g_fr,
357 b_fr,
358 g_to,
359 b_to,
360 tap,
361 shift,
362 s_max,
363 angle_min,
364 angle_max,
365 source_rows: branch_rows,
366 skipped_zero_impedance,
367 },
368 })
369}