1use crate::{HashSet, Segment, disaggregate};
2use fts_core::models::{DemandCurve, DemandGroup, Map, ProductGroup};
3use std::fmt::Display;
4use std::hash::Hash;
5use std::io::Write;
6
7pub fn export_mps<
10 DemandId: Display + Eq + Hash + Clone,
11 PortfolioId: Display + Eq + Hash + Clone,
12 ProductId: Display + Eq + Hash + Clone + Ord,
13>(
14 demand_curves: Map<DemandId, DemandCurve>,
15 portfolios: Map<PortfolioId, (DemandGroup<DemandId>, ProductGroup<ProductId>)>,
16 buffer: &mut impl Write,
17) -> Result<(), std::io::Error> {
18 let (demand_curves, portfolios, _, products) = super::prepare(demand_curves, portfolios);
24
25 writeln!(buffer, "NAME flow_trade_qp")?;
26 writeln!(buffer, "ROWS")?;
27
28 writeln!(buffer, " N gft")?;
30
31 for product_id in products.keys() {
33 writeln!(buffer, " E p_{product_id}")?;
35 }
36
37 for demand_id in demand_curves.keys() {
39 writeln!(buffer, " E d_{demand_id}")?;
40 }
41
42 writeln!(buffer, "COLUMNS")?;
46
47 let mut zeroed = HashSet::default();
48
49 for (portfolio_id, (demand_weights, product_weights)) in portfolios.iter() {
51 for (product_id, weight) in product_weights.iter() {
52 writeln!(buffer, " x_{portfolio_id} p_{product_id} {weight}",)?;
53 }
54 for (demand_id, weight) in demand_weights.iter() {
55 writeln!(buffer, " x_{portfolio_id} d_{demand_id} {weight}",)?;
56 }
57 if product_weights.len() == 0 || demand_weights.len() == 0 {
58 zeroed.insert(portfolio_id);
59 }
60 }
61
62 let mut all_segments = Map::<(DemandId, usize), Segment>::default();
64 for (demand_id, demand_curve) in demand_curves.into_iter() {
65 let (min, max) = demand_curve.domain();
66 let points = demand_curve.points();
67
68 let segments = disaggregate(points.into_iter(), min, max).expect("empty demand curve");
69 for (idx, segment) in segments.enumerate() {
70 let segment = segment.unwrap();
72
73 let (_, b) = segment.slope_intercept();
76 writeln!(
77 buffer,
78 " y_{demand_id}_{idx} gft {term} d_{demand_id} -1",
79 term = -b
80 )?;
81
82 all_segments.insert((demand_id.clone(), idx), segment);
84 }
85 }
86
87 writeln!(buffer, "BOUNDS")?;
89 for portfolio_id in portfolios.keys() {
90 if zeroed.contains(portfolio_id) {
91 writeln!(buffer, " FX BND x_{portfolio_id} 0")?;
92 } else {
93 writeln!(buffer, " FR BND x_{portfolio_id}")?;
95 }
96 }
97
98 for ((demand_id, idx), segment) in all_segments.iter() {
99 let min = segment.q0;
100 let max = segment.q1;
101
102 if min.is_finite() {
104 writeln!(buffer, " LO BND y_{demand_id}_{idx} {min}",)?;
105 } else {
106 writeln!(buffer, " MI BND y_{demand_id}_{idx}",)?;
107 }
108 if max.is_finite() {
109 writeln!(buffer, " UP BND y_{demand_id}_{idx} {max}",)?;
110 } else {
111 writeln!(buffer, " PL BND y_{demand_id}_{idx}",)?;
112 }
113 }
114
115 writeln!(buffer, "QUADOBJ")?;
117 for ((demand_id, idx), segment) in all_segments {
118 let (m, _) = segment.slope_intercept();
119 writeln!(
120 buffer,
121 " y_{demand_id}_{idx} y_{demand_id}_{idx} {term}",
122 term = -m
123 )?;
124 }
125
126 writeln!(buffer, "ENDATA")?;
127 Ok(())
128}
129
130pub fn export_lp<
133 DemandId: Display + Eq + Hash + Clone,
134 PortfolioId: Display + Eq + Hash + Clone,
135 ProductId: Display + Eq + Hash + Clone + Ord,
136>(
137 demand_curves: Map<DemandId, DemandCurve>,
138 portfolios: Map<PortfolioId, (DemandGroup<DemandId>, ProductGroup<ProductId>)>,
139 buffer: &mut impl Write,
140) -> Result<(), std::io::Error> {
141 let (demand_curves, portfolios, _, products) = super::prepare(demand_curves, portfolios);
143 let mut all_segments = Map::<(DemandId, usize), Segment>::default();
144
145 writeln!(buffer, "Maximize")?;
147
148 write!(buffer, " gft: ")?;
150
151 let mut first_term = true;
153 let mut has_quadratic_terms = false;
154
155 for (demand_id, demand_curve) in demand_curves.iter() {
157 let (min, max) = demand_curve.domain();
158 let points = demand_curve.clone().points();
159
160 let segments = disaggregate(points.into_iter(), min, max).expect("empty demand curve");
161 for (idx, segment) in segments.enumerate() {
162 let segment = segment.unwrap();
164
165 let (m, b) = segment.slope_intercept();
166 has_quadratic_terms = has_quadratic_terms || m != 0.0;
167 if first_term {
168 write!(buffer, "{b} y_{demand_id}_{idx}")?;
169 first_term = false;
170 } else {
171 write!(buffer, " + {b} y_{demand_id}_{idx}")?;
172 }
173
174 all_segments.insert((demand_id.clone(), idx), segment);
176 }
177 }
178
179 if first_term {
181 write!(buffer, "0")?;
182 }
183
184 if has_quadratic_terms {
185 write!(buffer, " + [ ")?;
186
187 first_term = true;
189
190 for ((demand_id, idx), segment) in all_segments.iter() {
191 let (m, _) = segment.slope_intercept();
192 if first_term {
193 write!(buffer, "{m} y_{demand_id}_{idx} ^ 2")?;
194 first_term = false;
195 } else {
196 write!(buffer, " + {m} y_{demand_id}_{idx} ^ 2",)?;
197 }
198 }
199
200 write!(buffer, " ] / 2")?;
201 }
202
203 writeln!(buffer)?;
205
206 writeln!(buffer, "Subject To")?;
208
209 for product_id in products.keys() {
211 write!(buffer, " p_{product_id}: ")?;
213
214 let mut first_term = true;
216
217 for (portfolio_id, (_, product_weights)) in portfolios.iter() {
219 if let Some(&weight) = product_weights.get(product_id) {
220 if first_term {
221 write!(buffer, "{weight} x_{portfolio_id}")?;
222 first_term = false;
223 } else {
224 write!(buffer, " + {weight} x_{portfolio_id}",)?;
225 }
226 }
227 }
228
229 if first_term {
231 write!(buffer, "0")?;
232 }
233
234 writeln!(buffer, " = 0")?;
236 }
237
238 for demand_id in demand_curves.keys() {
240 write!(buffer, " d_{demand_id}: ")?;
242
243 let mut first_term = true;
245
246 for (portfolio_id, (demand_weights, _)) in portfolios.iter() {
247 if let Some(&weight) = demand_weights.get(demand_id) {
248 if first_term {
249 write!(buffer, "{weight} x_{portfolio_id}")?;
250 first_term = false;
251 } else {
252 write!(buffer, " + {weight} x_{portfolio_id}",)?;
253 }
254 }
255 }
256
257 let mut key = (demand_id.clone(), 0);
260 while all_segments.contains_key(&key) {
261 write!(buffer, " - y_{demand_id}_{idx}", idx = key.1)?;
262 key.1 += 1;
263 }
264
265 writeln!(buffer, " = 0")?;
267 }
268
269 writeln!(buffer, "Bounds")?;
271
272 for (portfolio_id, (a, b)) in portfolios.iter() {
274 if a.len() == 0 || b.len() == 0 {
275 writeln!(buffer, " x_{portfolio_id} = 0")?;
276 } else {
277 writeln!(buffer, " x_{portfolio_id} free")?;
278 }
279 }
280
281 for ((demand_id, idx), segment) in all_segments {
283 match (segment.q0.is_finite(), segment.q1.is_finite()) {
284 (true, true) => {
285 writeln!(
286 buffer,
287 " {min} <= y_{demand_id}_{idx} <= {max}",
288 min = segment.q0,
289 max = segment.q1
290 )?;
291 }
292 (true, false) => {
293 writeln!(buffer, " y_{demand_id}_{idx} >= {min}", min = segment.q0)?;
294 }
295 (false, true) => {
296 writeln!(buffer, " y_{demand_id}_{idx} <= {max}", max = segment.q1)?;
297 }
298 (false, false) => {
299 writeln!(buffer, " y_{demand_id}_{idx} free")?;
300 }
301 }
302 }
303
304 writeln!(buffer, "End")?;
306
307 Ok(())
308}