use std::collections::HashSet;
use std::fmt;
use std::str;
use serde::{Deserialize, Serialize};
use crate::{
error::EpbdError,
types::{CSubtype, CType, Carrier, Component, Meta, MetaVec, Service},
vecops::{veclistsum, vecvecdif, vecvecmin, vecvecmul, vecvecsum},
};
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Components {
pub cmeta: Vec<Meta>,
pub cdata: Vec<Component>,
}
impl MetaVec for Components {
fn get_metavec(&self) -> &Vec<Meta> {
&self.cmeta
}
fn get_mut_metavec(&mut self) -> &mut Vec<Meta> {
&mut self.cmeta
}
}
impl fmt::Display for Components {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let metalines = self
.cmeta
.iter()
.map(|v| format!("{}", v))
.collect::<Vec<_>>()
.join("\n");
let datalines = self
.cdata
.iter()
.map(|v| format!("{}", v))
.collect::<Vec<_>>()
.join("\n");
write!(f, "{}\n{}", metalines, datalines)
}
}
impl str::FromStr for Components {
type Err = EpbdError;
fn from_str(s: &str) -> Result<Components, Self::Err> {
let s_nobom = if s.starts_with("\u{feff}") {
&s[3..]
} else {
s
};
let lines: Vec<&str> = s_nobom.lines().map(str::trim).collect();
let metalines = lines
.iter()
.filter(|l| l.starts_with("#META") || l.starts_with("#CTE_"));
let datalines = lines
.iter()
.filter(|l| !(l.starts_with('#') || l.starts_with("vector,") || l.is_empty()));
let cmeta = metalines
.map(|e| e.parse())
.collect::<Result<Vec<Meta>, _>>()?;
let cdata = datalines
.map(|e| e.parse())
.collect::<Result<Vec<Component>, _>>()?;
{
let cdata_lens: Vec<_> = cdata.iter().map(|e| e.values.len()).collect();
if cdata_lens.iter().max().unwrap() != cdata_lens.iter().min().unwrap() {
return Err(EpbdError::ParseError(s.into()));
}
}
Ok(Components { cmeta, cdata })
}
}
impl Components {
pub fn normalize(mut self) -> Self {
self.force_ndef_use_for_electricity_production();
self.compensate_env_use();
self
}
#[allow(non_snake_case)]
pub fn filter_by_epb_service(&self, service: Service) -> Self {
let num_steps = self.cdata[0].values.len(); let cdata = self.cdata.iter();
let mut cdata_srv: Vec<_> = cdata
.clone()
.filter(|c| {
c.service == service
&& !(c.carrier == Carrier::ELECTRICIDAD && c.ctype == CType::PRODUCCION)
})
.cloned()
.collect();
let E_pr_el_t = cdata
.clone()
.filter(|c| c.carrier == Carrier::ELECTRICIDAD && c.ctype == CType::PRODUCCION);
let E_pr_el_an: f32 = E_pr_el_t.clone().flat_map(|c| c.values.iter()).sum();
let E_EPus_el_t = cdata.clone().filter(|c| {
c.carrier == Carrier::ELECTRICIDAD
&& c.ctype == CType::CONSUMO
&& c.csubtype == CSubtype::EPB
});
let E_srv_el_t = E_EPus_el_t.clone().filter(|c| c.service == service);
let E_srv_el_an: f32 = E_srv_el_t.clone().flat_map(|c| c.values.iter()).sum();
if E_srv_el_an > 0.0 && E_pr_el_an > 0.0 {
let E_EPus_el_t_tot = E_EPus_el_t
.clone()
.fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
let E_srv_el_t_tot = E_srv_el_t
.clone()
.fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
let f_srv_t = E_srv_el_t_tot
.iter()
.zip(&E_EPus_el_t_tot)
.map(|(v, t)| if v.abs() < f32::EPSILON { 0.0 } else { v / t })
.collect::<Vec<_>>();
let f_match_t = vec![1.0; num_steps]; let E_pr_el_t_tot = E_pr_el_t
.clone()
.fold(vec![0.0; num_steps], |acc, e| vecvecsum(&acc, &e.values));
let E_pr_el_used_EPus_t =
vecvecmul(&f_match_t, &vecvecmin(&E_EPus_el_t_tot, &E_pr_el_t_tot));
for mut E_pr_el_i in E_pr_el_t.cloned() {
let f_pr_el_i: f32 = E_pr_el_i.values.iter().sum::<f32>() / E_pr_el_an;
E_pr_el_i.values = (&E_pr_el_used_EPus_t)
.iter()
.zip(&f_srv_t)
.map(|(v, f_srv)| v * f_pr_el_i * f_srv)
.collect();
E_pr_el_i.service = service;
E_pr_el_i.comment = format!(
"{} Producción eléctrica reasignada al servicio",
E_pr_el_i.comment
);
cdata_srv.push(E_pr_el_i);
}
}
let cmeta = self.cmeta.clone();
let mut newcomponents = Self {
cdata: cdata_srv,
cmeta,
};
newcomponents.set_meta("CTE_SERVICIO", &service.to_string());
newcomponents
}
fn force_ndef_use_for_electricity_production(&mut self) {
for component in &mut self.cdata {
if component.carrier == Carrier::ELECTRICIDAD && component.ctype == CType::PRODUCCION {
component.service = Service::NDEF
}
}
}
fn compensate_env_use(&mut self) {
let envcomps: Vec<_> = self
.cdata
.iter()
.cloned()
.filter(|c| c.carrier == Carrier::MEDIOAMBIENTE)
.collect();
let services: HashSet<_> = envcomps.iter().map(|c| c.service).collect();
let mut balancecomps: Vec<Component> = services
.iter()
.map(|&service| {
let ecomps = envcomps.iter().filter(|c| c.service == service);
let consumed: Vec<_> = ecomps
.clone()
.filter(|c| c.ctype == CType::CONSUMO)
.collect();
if consumed.is_empty() {
return None;
};
let mut unbalanced_values = veclistsum(
&consumed
.iter()
.map(|&v| v.values.as_slice())
.collect::<Vec<_>>(),
);
let produced: Vec<_> = ecomps
.clone()
.filter(|c| c.ctype == CType::PRODUCCION)
.collect();
if !produced.is_empty() {
let totproduced = veclistsum(
&produced
.iter()
.map(|&v| v.values.as_slice())
.collect::<Vec<_>>(),
);
unbalanced_values = vecvecdif(&unbalanced_values, &totproduced)
.iter()
.map(|&v| if v > 0.0 { v } else { 0.0 })
.collect();
}
if unbalanced_values.iter().sum::<f32>() == 0.0 {
return None;
};
Some(Component {
carrier: Carrier::MEDIOAMBIENTE,
ctype: CType::PRODUCCION,
csubtype: CSubtype::INSITU,
service,
values: unbalanced_values,
comment: "Equilibrado de consumo sin producción declarada".into(),
})
})
.filter(std::option::Option::is_some)
.collect::<Option<Vec<_>>>()
.unwrap_or_else(Vec::new);
self.cdata.append(&mut balancecomps);
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
const TCOMPS1: &str = "#META CTE_AREAREF: 100.5
ELECTRICIDAD, 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
ELECTRICIDAD, 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
ELECTRICIDAD, 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
MEDIOAMBIENTE, 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";
const TCOMPSRES1: &str = "#META CTE_AREAREF: 100.5
ELECTRICIDAD, 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
ELECTRICIDAD, 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
ELECTRICIDAD, 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
MEDIOAMBIENTE, 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
MEDIOAMBIENTE, 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";
const TCOMPSRES2: &str = "#META CTE_AREAREF: 100.5
#META CTE_SERVICIO: CAL
ELECTRICIDAD, 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
MEDIOAMBIENTE, 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
MEDIOAMBIENTE, 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
ELECTRICIDAD, 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";
const TCOMPS2: &str = "#META CTE_AREAREF: 1.0
ELECTRICIDAD, PRODUCCION, INSITU, NDEF, 2.00, 6.00, 2.00
ELECTRICIDAD, CONSUMO, EPB, REF, 1.00, 1.00, 1.00
ELECTRICIDAD, CONSUMO, EPB, CAL, 1.00, 2.00, 1.00
MEDIOAMBIENTE, CONSUMO, EPB, CAL, 2.00, 2.00, 2.00";
const TCOMPSRES3: &str = "#META CTE_AREAREF: 1.0
#META CTE_SERVICIO: CAL
ELECTRICIDAD, CONSUMO, EPB, CAL, 1.00, 2.00, 1.00
MEDIOAMBIENTE, CONSUMO, EPB, CAL, 2.00, 2.00, 2.00
MEDIOAMBIENTE, PRODUCCION, INSITU, CAL, 2.00, 2.00, 2.00 # Equilibrado de consumo sin producción declarada
ELECTRICIDAD, PRODUCCION, INSITU, CAL, 1.00, 2.00, 1.00 # Producción eléctrica reasignada al servicio";
#[test]
fn tcomponents_parse() {
let tcomps = TCOMPS1.parse::<Components>().unwrap();
assert_eq!(tcomps.to_string(), TCOMPS1);
}
#[test]
fn tcomponents_normalize() {
let tcompsnorm = TCOMPS1.parse::<Components>().unwrap().normalize();
assert_eq!(tcompsnorm.to_string(), TCOMPSRES1);
}
#[test]
fn tcomponents_filter_by_epb_service() {
let tcompsnormfilt = TCOMPS1
.parse::<Components>()
.unwrap()
.normalize()
.filter_by_epb_service(Service::CAL);
assert_eq!(tcompsnormfilt.to_string(), TCOMPSRES2);
}
#[test]
fn tcomponents_filter_by_epb_service_prod_excess() {
let tcompsnormfilt = TCOMPS2
.parse::<Components>()
.unwrap()
.normalize()
.filter_by_epb_service(Service::CAL);
assert_eq!(tcompsnormfilt.to_string(), TCOMPSRES3);
}
}