1use std::collections::HashSet;
41use std::fmt;
42use std::str;
43
44use serde::{Deserialize, Serialize};
45
46use crate::{
47 error::EpbdError,
48 types::{CSubtype, CType, Carrier, Component, Meta, MetaVec, Service},
49 vecops::{veclistsum, vecvecdif, vecvecmin, vecvecmul, vecvecsum},
50};
51
52#[derive(Debug, Default, Clone, Serialize, Deserialize)]
60pub struct Components {
61 pub cmeta: Vec<Meta>,
63 pub cdata: Vec<Component>,
65}
66
67impl MetaVec for Components {
68 fn get_metavec(&self) -> &Vec<Meta> {
69 &self.cmeta
70 }
71 fn get_mut_metavec(&mut self) -> &mut Vec<Meta> {
72 &mut self.cmeta
73 }
74}
75
76impl fmt::Display for Components {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 let metalines = self
79 .cmeta
80 .iter()
81 .map(|v| format!("{}", v))
82 .collect::<Vec<_>>()
83 .join("\n");
84 let datalines = self
85 .cdata
86 .iter()
87 .map(|v| format!("{}", v))
88 .collect::<Vec<_>>()
89 .join("\n");
90 write!(f, "{}\n{}", metalines, datalines)
91 }
92}
93
94impl str::FromStr for Components {
95 type Err = EpbdError;
96
97 fn from_str(s: &str) -> Result<Components, Self::Err> {
98 let s_nobom = if s.starts_with("\u{feff}") {
99 &s[3..]
100 } else {
101 s
102 };
103 let lines: Vec<&str> = s_nobom.lines().map(str::trim).collect();
104 let metalines = lines
105 .iter()
106 .filter(|l| l.starts_with("#META") || l.starts_with("#CTE_"));
107 let datalines = lines
108 .iter()
109 .filter(|l| !(l.starts_with('#') || l.starts_with("vector,") || l.is_empty()));
110 let cmeta = metalines
111 .map(|e| e.parse())
112 .collect::<Result<Vec<Meta>, _>>()?;
113 let cdata = datalines
114 .map(|e| e.parse())
115 .collect::<Result<Vec<Component>, _>>()?;
116 {
117 let cdata_lens: Vec<_> = cdata.iter().map(|e| e.values.len()).collect();
118 if cdata_lens.iter().max().unwrap() != cdata_lens.iter().min().unwrap() {
119 return Err(EpbdError::ParseError(s.into()));
120 }
121 }
122 Ok(Components { cmeta, cdata })
123 }
124}
125
126impl Components {
127 pub fn normalize(mut self) -> Self {
134 self.force_ndef_use_for_electricity_production();
135 self.compensate_env_use();
136 self
137 }
138
139 #[allow(non_snake_case)]
149 pub fn filter_by_epb_service(&self, service: Service) -> Self {
150 let num_steps = self.cdata[0].values.len(); let cdata = self.cdata.iter(); let mut cdata_srv: Vec<_> = cdata
155 .clone()
156 .filter(|c| {
157 c.service == service
158 && !(c.carrier == Carrier::ELECTRICIDAD && c.ctype == CType::PRODUCCION)
159 })
160 .cloned()
161 .collect();
162
163 let E_pr_el_t = cdata
165 .clone()
166 .filter(|c| c.carrier == Carrier::ELECTRICIDAD && c.ctype == CType::PRODUCCION);
167 let E_pr_el_an: f32 = E_pr_el_t.clone().flat_map(|c| c.values.iter()).sum();
168
169 let E_EPus_el_t = cdata.clone().filter(|c| {
172 c.carrier == Carrier::ELECTRICIDAD
173 && c.ctype == CType::CONSUMO
174 && c.csubtype == CSubtype::EPB
175 });
176
177 let E_srv_el_t = E_EPus_el_t.clone().filter(|c| c.service == service);
179 let E_srv_el_an: f32 = E_srv_el_t.clone().flat_map(|c| c.values.iter()).sum();
180
181 if E_srv_el_an > 0.0 && E_pr_el_an > 0.0 {
183 let E_EPus_el_t_tot = E_EPus_el_t
185 .clone()
186 .fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
187
188 let E_srv_el_t_tot = E_srv_el_t
190 .clone()
191 .fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
192 let f_srv_t = E_srv_el_t_tot
193 .iter()
194 .zip(&E_EPus_el_t_tot)
195 .map(|(v, t)| if v.abs() < f32::EPSILON { 0.0 } else { v / t })
196 .collect::<Vec<_>>();
197
198 let f_match_t = vec![1.0; num_steps]; let E_pr_el_t_tot = E_pr_el_t
203 .clone()
204 .fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
205 let E_pr_el_used_EPus_t =
206 vecvecmul(&f_match_t, &vecvecmin(&E_EPus_el_t_tot, &E_pr_el_t_tot));
207
208 for mut E_pr_el_i in E_pr_el_t.cloned() {
210 let f_pr_el_i: f32 = E_pr_el_i.values.iter().sum::<f32>() / E_pr_el_an;
212
213 E_pr_el_i.values = (&E_pr_el_used_EPus_t)
215 .iter()
216 .zip(&f_srv_t)
217 .map(|(v, f_srv)| v * f_pr_el_i * f_srv)
218 .collect();
219 E_pr_el_i.service = service;
220 E_pr_el_i.comment = format!(
221 "{} Producción eléctrica reasignada al servicio",
222 E_pr_el_i.comment
223 );
224 cdata_srv.push(E_pr_el_i);
225 }
226 }
227
228 let cmeta = self.cmeta.clone();
229 let mut newcomponents = Self {
230 cdata: cdata_srv,
231 cmeta,
232 };
233 newcomponents.set_meta("CTE_SERVICIO", &service.to_string());
234
235 newcomponents
236 }
237
238 fn force_ndef_use_for_electricity_production(&mut self) {
244 for component in &mut self.cdata {
246 if component.carrier == Carrier::ELECTRICIDAD && component.ctype == CType::PRODUCCION {
247 component.service = Service::NDEF
248 }
249 }
250 }
251
252 fn compensate_env_use(&mut self) {
258 let envcomps: Vec<_> = self
260 .cdata
261 .iter()
262 .cloned()
263 .filter(|c| c.carrier == Carrier::MEDIOAMBIENTE)
264 .collect();
265 let services: HashSet<_> = envcomps.iter().map(|c| c.service).collect();
267
268 let mut balancecomps: Vec<Component> = services
272 .iter()
273 .map(|&service| {
274 let ecomps = envcomps.iter().filter(|c| c.service == service);
276 let consumed: Vec<_> = ecomps
278 .clone()
279 .filter(|c| c.ctype == CType::CONSUMO)
280 .collect();
281 if consumed.is_empty() {
283 return None;
284 };
285 let mut unbalanced_values = veclistsum(
287 &consumed
288 .iter()
289 .map(|&v| v.values.as_slice())
290 .collect::<Vec<_>>(),
291 );
292 let produced: Vec<_> = ecomps
294 .clone()
295 .filter(|c| c.ctype == CType::PRODUCCION)
296 .collect();
297 if !produced.is_empty() {
299 let totproduced = veclistsum(
300 &produced
301 .iter()
302 .map(|&v| v.values.as_slice())
303 .collect::<Vec<_>>(),
304 );
305 unbalanced_values = vecvecdif(&unbalanced_values, &totproduced)
306 .iter()
307 .map(|&v| if v > 0.0 { v } else { 0.0 })
308 .collect();
309 }
310 if unbalanced_values.iter().sum::<f32>() == 0.0 {
312 return None;
313 };
314
315 Some(Component {
317 carrier: Carrier::MEDIOAMBIENTE,
318 ctype: CType::PRODUCCION,
319 csubtype: CSubtype::INSITU,
320 service,
321 values: unbalanced_values,
322 comment: "Equilibrado de consumo sin producción declarada".into(),
323 })
324 })
325 .filter(std::option::Option::is_some)
326 .collect::<Option<Vec<_>>>()
327 .unwrap_or_else(Vec::new);
328 self.cdata.append(&mut balancecomps);
330 }
331}
332
333#[cfg(test)]
334mod tests {
335 use super::*;
336 use pretty_assertions::assert_eq;
337
338 const TCOMPS1: &str = "#META CTE_AREAREF: 100.5
339ELECTRICIDAD, PRODUCCION, INSITU, CAL, 8.20, 6.56, 4.10, 3.69, 2.05, 2.46, 3.28, 2.87, 2.05, 3.28, 4.92, 6.56
340ELECTRICIDAD, CONSUMO, EPB, REF, 16.39, 13.11, 8.20, 7.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 13.11
341ELECTRICIDAD, CONSUMO, EPB, CAL, 16.39, 13.11, 8.20, 7.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 13.11
342MEDIOAMBIENTE, CONSUMO, EPB, CAL, 6.39, 3.11, 8.20, 17.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 3.11";
343
344 const TCOMPSRES1: &str = "#META CTE_AREAREF: 100.5
346ELECTRICIDAD, PRODUCCION, INSITU, NDEF, 8.20, 6.56, 4.10, 3.69, 2.05, 2.46, 3.28, 2.87, 2.05, 3.28, 4.92, 6.56
347ELECTRICIDAD, CONSUMO, EPB, REF, 16.39, 13.11, 8.20, 7.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 13.11
348ELECTRICIDAD, CONSUMO, EPB, CAL, 16.39, 13.11, 8.20, 7.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 13.11
349MEDIOAMBIENTE, CONSUMO, EPB, CAL, 6.39, 3.11, 8.20, 17.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 3.11
350MEDIOAMBIENTE, PRODUCCION, INSITU, CAL, 6.39, 3.11, 8.20, 17.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 3.11 # Equilibrado de consumo sin producción declarada";
351
352 const TCOMPSRES2: &str = "#META CTE_AREAREF: 100.5
354#META CTE_SERVICIO: CAL
355ELECTRICIDAD, CONSUMO, EPB, CAL, 16.39, 13.11, 8.20, 7.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 13.11
356MEDIOAMBIENTE, CONSUMO, EPB, CAL, 6.39, 3.11, 8.20, 17.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 3.11
357MEDIOAMBIENTE, PRODUCCION, INSITU, CAL, 6.39, 3.11, 8.20, 17.38, 4.10, 4.92, 6.56, 5.74, 4.10, 6.56, 9.84, 3.11 # Equilibrado de consumo sin producción declarada
358ELECTRICIDAD, PRODUCCION, INSITU, CAL, 4.10, 3.28, 2.05, 1.85, 1.02, 1.23, 1.64, 1.43, 1.02, 1.64, 2.46, 3.28 # Producción eléctrica reasignada al servicio";
359
360 const TCOMPS2: &str = "#META CTE_AREAREF: 1.0
362ELECTRICIDAD, PRODUCCION, INSITU, NDEF, 2.00, 6.00, 2.00
363ELECTRICIDAD, CONSUMO, EPB, REF, 1.00, 1.00, 1.00
364ELECTRICIDAD, CONSUMO, EPB, CAL, 1.00, 2.00, 1.00
365MEDIOAMBIENTE, CONSUMO, EPB, CAL, 2.00, 2.00, 2.00";
366
367 const TCOMPSRES3: &str = "#META CTE_AREAREF: 1.0
368#META CTE_SERVICIO: CAL
369ELECTRICIDAD, CONSUMO, EPB, CAL, 1.00, 2.00, 1.00
370MEDIOAMBIENTE, CONSUMO, EPB, CAL, 2.00, 2.00, 2.00
371MEDIOAMBIENTE, PRODUCCION, INSITU, CAL, 2.00, 2.00, 2.00 # Equilibrado de consumo sin producción declarada
372ELECTRICIDAD, PRODUCCION, INSITU, CAL, 1.00, 2.00, 1.00 # Producción eléctrica reasignada al servicio";
373
374 #[test]
375 fn tcomponents_parse() {
376 let tcomps = TCOMPS1.parse::<Components>().unwrap();
377 assert_eq!(tcomps.to_string(), TCOMPS1);
379 }
380
381 #[test]
382 fn tcomponents_normalize() {
383 let tcompsnorm = TCOMPS1.parse::<Components>().unwrap().normalize();
384 assert_eq!(tcompsnorm.to_string(), TCOMPSRES1);
385 }
386
387 #[test]
388 fn tcomponents_filter_by_epb_service() {
389 let tcompsnormfilt = TCOMPS1
390 .parse::<Components>()
391 .unwrap()
392 .normalize()
393 .filter_by_epb_service(Service::CAL);
394 assert_eq!(tcompsnormfilt.to_string(), TCOMPSRES2);
395 }
396
397 #[test]
398 fn tcomponents_filter_by_epb_service_prod_excess() {
399 let tcompsnormfilt = TCOMPS2
400 .parse::<Components>()
401 .unwrap()
402 .normalize()
403 .filter_by_epb_service(Service::CAL);
404 assert_eq!(tcompsnormfilt.to_string(), TCOMPSRES3);
405 }
406}