powerio_matrix/matrix/
opf.rs1use sprs::CsMat;
11
12use crate::indexed::IndexedNetwork;
13use crate::matrix::incidence::{IncidenceParts, diagonal};
14use crate::matrix::triplet::CooBuilder;
15use crate::{Error, Result};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
19#[non_exhaustive]
20pub enum Units {
21 #[default]
27 PerUnit,
28 Native,
30}
31
32#[derive(Debug, Clone)]
35#[non_exhaustive]
36pub struct BusCosts {
37 pub q: Vec<f64>,
39 pub c: Vec<f64>,
41 pub pmax: Vec<f64>,
43 pub pmin: Vec<f64>,
45 pub p_d: Vec<f64>,
47}
48
49#[derive(Debug, Clone)]
51#[non_exhaustive]
52pub struct GenCosts {
53 pub q: Vec<f64>,
54 pub c: Vec<f64>,
55 pub pmax: Vec<f64>,
56 pub pmin: Vec<f64>,
57 pub gen_of_col: Vec<usize>,
59}
60
61#[derive(Debug, Clone)]
63#[non_exhaustive]
64pub struct OpfInstance {
65 pub n: usize,
66 pub m: usize,
67 pub bus: BusCosts,
69 pub gen_costs: GenCosts,
71 pub f_max: Vec<f64>,
73 pub c_g: CsMat<f64>,
75}
76
77impl OpfInstance {
78 #[must_use]
80 pub fn n_gen(&self) -> usize {
81 self.gen_costs.q.len()
82 }
83}
84
85pub fn build_opf_instance(
90 case: &IndexedNetwork,
91 incidence: &IncidenceParts,
92 units: Units,
93) -> Result<OpfInstance> {
94 let n = case.n();
95 let m = incidence.m();
96 let base = case.per_unit_base();
99
100 let p_scale = match units {
101 Units::PerUnit => 1.0 / base,
102 Units::Native => 1.0,
103 };
104 let (q_scale, c_scale) = match units {
107 Units::PerUnit => (base * base, base),
108 Units::Native => (1.0, 1.0),
109 };
110
111 let in_service: Vec<(usize, &crate::network::Generator)> = case.in_service_gens().collect();
112 let n_gen = in_service.len();
113 if n_gen == 0 {
114 return Err(Error::NoGenerators);
115 }
116
117 let mut q_gen = Vec::with_capacity(n_gen);
118 let mut c_gen = Vec::with_capacity(n_gen);
119 let mut pmax_gen = Vec::with_capacity(n_gen);
120 let mut pmin_gen = Vec::with_capacity(n_gen);
121 let mut gen_of_col = Vec::with_capacity(n_gen);
122 let mut cg = CooBuilder::with_capacity_rect(n, n_gen, n_gen);
123
124 for (col, &(gidx, generator)) in in_service.iter().enumerate() {
125 let bus = case.bus_index(generator.bus).ok_or(Error::UnknownBus {
126 bus_id: generator.bus,
127 element_index: gidx,
128 })?;
129 let cost = generator
133 .cost
134 .as_ref()
135 .ok_or(Error::MissingGenCost { gen_index: gidx })?;
136 let (q_raw, c_raw) = cost.quadratic().ok_or(Error::UnsupportedCostModel {
137 gen_index: gidx,
138 model: cost.model,
139 ncost: cost.ncost,
140 })?;
141 q_gen.push(q_raw * q_scale);
142 c_gen.push(c_raw * c_scale);
143 pmax_gen.push(generator.pmax * p_scale);
144 pmin_gen.push(generator.pmin * p_scale);
145 gen_of_col.push(gidx);
146 cg.add(bus, col, 1.0);
147 }
148 let c_g = cg.finish_csr();
149
150 let q_bus = project_gen_to_bus(&c_g, &q_gen);
151 let c_bus = project_gen_to_bus(&c_g, &c_gen);
152 let pmax_bus = project_gen_to_bus(&c_g, &pmax_gen);
153 let pmin_bus = project_gen_to_bus(&c_g, &pmin_gen);
154
155 let p_d: Vec<f64> = case.pd().iter().map(|&p| p * p_scale).collect();
156
157 let f_max: Vec<f64> = incidence
158 .branch_of_col
159 .iter()
160 .map(|&k| case.branches()[k].rate_a * p_scale)
161 .collect();
162
163 Ok(OpfInstance {
164 n,
165 m,
166 bus: BusCosts {
167 q: q_bus,
168 c: c_bus,
169 pmax: pmax_bus,
170 pmin: pmin_bus,
171 p_d,
172 },
173 gen_costs: GenCosts {
174 q: q_gen,
175 c: c_gen,
176 pmax: pmax_gen,
177 pmin: pmin_gen,
178 gen_of_col,
179 },
180 f_max,
181 c_g,
182 })
183}
184
185pub fn project_gen_to_bus(c_g: &CsMat<f64>, v: &[f64]) -> Vec<f64> {
188 let mut out = vec![0.0; c_g.rows()];
189 for (&val, (bus, g)) in c_g {
190 out[bus] += val * v[g];
191 }
192 out
193}
194
195pub fn cost_quadratic_diag(q: &[f64]) -> CsMat<f64> {
199 diagonal(q)
200}