use once_cell::sync::Lazy;
use std::collections::HashMap;
use crate::{
error::EpbdError,
types::*,
vecops::{vecvecmin, vecvecsum},
Balance, Components, Factors, UserWF,
};
pub const AREAREF_DEFAULT: f32 = 1.0;
pub const KEXP_DEFAULT: f32 = 0.0;
pub const CTE_LOCS: [&str; 4] = ["PENINSULA", "BALEARES", "CANARIAS", "CEUTAMELILLA"];
pub const CTE_NRBY: [Carrier; 5] = [
Carrier::BIOMASA,
Carrier::BIOMASADENSIFICADA,
Carrier::RED1,
Carrier::RED2,
Carrier::MEDIOAMBIENTE,
];
pub const CTE_USERWF: UserWF<RenNrenCo2> = UserWF {
red1: RenNrenCo2::new(0.0, 1.3, 0.3),
red2: RenNrenCo2::new(0.0, 1.3, 0.3),
cogen_to_grid: RenNrenCo2::new(0.0, 2.5, 0.3),
cogen_to_nepb: RenNrenCo2::new(0.0, 2.5, 0.3),
};
pub static CTE_LOCWF_RITE2014: Lazy<HashMap<&'static str, Factors>> = Lazy::new(|| {
use Carrier::*;
use Dest::*;
use Source::*;
use Step::A;
let wf = Factors {
wmeta: vec![
Meta::new("CTE_FUENTE", "RITE2014"),
Meta::new("CTE_FUENTE_COMENTARIO", "Factores de paso (kWh/kWh_f,kWh/kWh_f,kg_CO2/kWh_f) del documento reconocido del RITE de 20/07/2014")
],
wdata: vec![
Factor::new(MEDIOAMBIENTE, RED, SUMINISTRO, A, (1.000, 0.000, 0.000).into(), "Recursos usados para suministrar energía térmica del medioambiente (red de suministro ficticia)"),
Factor::new(MEDIOAMBIENTE, INSITU, SUMINISTRO, A, (1.000, 0.000, 0.000).into(), "Recursos usados para generar in situ energía térmica del medioambiente (vector renovable)"),
Factor::new(BIOCARBURANTE, RED, SUMINISTRO, A, (1.028, 0.085, 0.018).into(), "Recursos usados para suministrar el vector desde la red (Biocarburante = biomasa densificada (pellets))"),
Factor::new(BIOMASA, RED, SUMINISTRO, A, (1.003, 0.034, 0.018).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(BIOMASADENSIFICADA, RED, SUMINISTRO, A, (1.028, 0.085, 0.018).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(CARBON, RED, SUMINISTRO, A, (0.002, 1.082, 0.472).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(GASNATURAL, RED, SUMINISTRO, A, (0.005, 1.190, 0.252).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(GASOLEO, RED, SUMINISTRO, A, (0.003, 1.179, 0.311).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(GLP, RED, SUMINISTRO, A, (0.003, 1.201, 0.254).into(), "Recursos usados para suministrar el vector desde la red"),
Factor::new(ELECTRICIDAD, INSITU, SUMINISTRO, A, (1.000, 0.000, 0.000).into(), "Recursos usados para producir electricidad in situ"),
Factor::new(ELECTRICIDAD, COGENERACION, SUMINISTRO, A, (0.000, 0.000, 0.000).into(), "Recursos usados para suministrar la energía (0 porque se contabiliza el vector que alimenta el cogenerador)"),
]};
let mut wfpen = wf.clone();
wfpen.set_meta("CTE_LOCALIZACION", "PENINSULA");
wfpen.wdata.push(Factor::new(
ELECTRICIDAD,
RED,
SUMINISTRO,
A,
(0.414, 1.954, 0.331).into(),
"Recursos usados para el suministro desde la red",
));
let mut wfbal = wf.clone();
wfbal.set_meta("CTE_LOCALIZACION", "BALEARES");
wfbal.wdata.push(Factor::new(
ELECTRICIDAD,
RED,
SUMINISTRO,
A,
(0.082, 2.968, 0.932).into(),
"Recursos usados para el suministro desde la red",
));
let mut wfcan = wf.clone();
wfcan.set_meta("CTE_LOCALIZACION", "CANARIAS");
wfcan.wdata.push(Factor::new(
ELECTRICIDAD,
RED,
SUMINISTRO,
A,
(0.070, 2.924, 0.776).into(),
"Recursos usados para el suministro desde la red",
));
let mut wfcym = wf;
wfcym.set_meta("CTE_LOCALIZACION", "CEUTAMELILLA");
#[allow(clippy::approx_constant)]
wfcym.wdata.push(Factor::new(
ELECTRICIDAD,
RED,
SUMINISTRO,
A,
(0.072, 2.718, 0.721).into(),
"Recursos usados para el suministro desde la red",
));
let mut m = HashMap::new();
m.insert("PENINSULA", wfpen);
m.insert("BALEARES", wfbal);
m.insert("CANARIAS", wfcan);
m.insert("CEUTAMELILLA", wfcym);
m
});
pub fn wfactors_from_str(
wfactorsstring: &str,
user: UserWF<Option<RenNrenCo2>>,
userdefaults: UserWF<RenNrenCo2>,
) -> Result<Factors, EpbdError> {
wfactorsstring
.parse::<Factors>()?
.set_user_wfactors(user)
.normalize(&userdefaults)
}
pub fn wfactors_from_loc(
loc: &str,
locmap: &HashMap<&'static str, Factors>,
user: UserWF<Option<RenNrenCo2>>,
userdefaults: UserWF<RenNrenCo2>,
) -> Result<Factors, EpbdError> {
locmap
.get(loc)
.ok_or_else(|| EpbdError::ParseError(format!("Localizacion: {}", loc)))?
.clone()
.set_user_wfactors(user)
.normalize(&userdefaults)
}
pub fn wfactors_to_nearby(wfactors: &Factors) -> Factors {
let wmeta = wfactors.wmeta.clone();
let mut wdata: Vec<Factor> = Vec::new();
for f in wfactors.wdata.iter().cloned() {
if f.source == Source::INSITU
|| f.source == Source::COGENERACION
|| CTE_NRBY.contains(&f.carrier)
{
wdata.push(f)
} else {
wdata.push(Factor::new(
f.carrier,
f.source,
f.dest,
f.step,
RenNrenCo2::new(0.0, f.ren + f.nren, f.co2), format!("Perímetro nearby: {}", f.comment),
))
}
}
let mut factors = Factors { wmeta, wdata };
factors.set_meta("CTE_PERIMETRO", "NEARBY");
factors
}
fn get_fp_ren_fraction(c: Carrier, wfactors: &Factors) -> Result<f32, EpbdError> {
let src = match c {
Carrier::ELECTRICIDAD => Source::INSITU,
_ => Source::RED,
};
wfactors
.wdata
.iter()
.find(|f| f.carrier == c && f.source == src)
.ok_or_else(|| {
EpbdError::WrongInput(format!("No se encuentra el factor de paso para \"{}\"", c))
})
.and_then(|f| Ok(f.ren / (f.ren + f.nren)))
}
#[allow(non_snake_case)]
fn Q_district_and_env_an(
cr_list: &Vec<&Component>,
wfactors: &Factors,
) -> Result<(f32, f32), EpbdError> {
use Carrier::{MEDIOAMBIENTE, RED1, RED2};
let value = cr_list
.iter()
.filter(|c| {
c.ctype == CType::CONSUMO
&& (c.carrier == RED1 || c.carrier == RED2 || c.carrier == MEDIOAMBIENTE)
})
.map(|c| {
let tot = c.values.iter().sum::<f32>();
let ren = tot * get_fp_ren_fraction(c.carrier, wfactors)?;
Ok((tot, ren))
})
.collect::<Result<Vec<(f32, f32)>, EpbdError>>()?
.iter()
.fold((0.0, 0.0), |(ac_tot, ac_ren), &(elem_tot, elem_ren)| {
(ac_tot + elem_tot, ac_ren + elem_ren)
});
Ok(value)
}
fn get_used_carriers(cr_list: &Vec<&Component>) -> Vec<Carrier> {
let mut used_carriers = cr_list
.iter()
.filter(|c| c.ctype == CType::CONSUMO)
.map(|c| c.carrier)
.collect::<Vec<_>>();
used_carriers.sort_unstable();
used_carriers.dedup();
used_carriers
}
#[allow(non_snake_case)]
pub fn fraccion_renovable_acs_nrb(
components: &Components,
wfactors: &Factors,
demanda_anual_acs: f32,
) -> Result<f32, EpbdError> {
use CType::{CONSUMO, PRODUCCION};
use Carrier::{BIOMASA, BIOMASADENSIFICADA, ELECTRICIDAD, MEDIOAMBIENTE, RED1, RED2};
let components = &components.filter_by_epb_service(Service::ACS);
let cr_list: &Vec<&Component> = &components
.cdata
.iter()
.filter(|c| {
!((c.carrier == ELECTRICIDAD && c.comment.contains("CTEEPBD_EXCLUYE_AUX_ACS"))
|| (c.carrier == MEDIOAMBIENTE && c.comment.contains("CTEEPBD_EXCLUYE_SCOP_ACS")))
})
.collect();
if cr_list.is_empty() {
return Ok(0.0);
};
if demanda_anual_acs.abs() < f32::EPSILON {
return Err(EpbdError::WrongInput(
"Demanda anual de ACS nula o casi nula".to_string(),
));
};
let has_el_cgn = cr_list
.iter()
.any(|c| c.ctype == PRODUCCION && c.csubtype == CSubtype::COGENERACION);
if has_el_cgn {
return Err(EpbdError::WrongInput(
"Uso de electricidad cogenerada".to_string(),
));
};
let (Q_district_and_env_an_tot, Q_district_and_env_acs_an_ren) =
Q_district_and_env_an(cr_list, wfactors)?;
let used_carriers = get_used_carriers(cr_list);
let has_biomass = used_carriers.contains(&BIOMASA);
let has_dens_biomass = used_carriers.contains(&BIOMASADENSIFICADA);
let has_any_biomass = has_biomass || has_dens_biomass;
let has_only_one_type_of_biomass =
(has_biomass || has_dens_biomass) && !(has_biomass && has_dens_biomass);
let has_only_biomass_or_onsite_or_district = !used_carriers.iter().any(|c| {
*c != MEDIOAMBIENTE && *c != RED1 && *c != RED2 && *c != BIOMASA && *c != BIOMASADENSIFICADA
});
let Q_biomass_an_ren = if has_only_one_type_of_biomass && has_only_biomass_or_onsite_or_district
{
let Q_any_biomass_acs_an = demanda_anual_acs - Q_district_and_env_an_tot;
let Q_any_biomass_acs_an_ren = if has_biomass {
Q_any_biomass_acs_an * get_fp_ren_fraction(BIOMASA, wfactors)?
} else {
Q_any_biomass_acs_an * get_fp_ren_fraction(BIOMASADENSIFICADA, wfactors)?
};
Q_any_biomass_acs_an_ren
} else if has_any_biomass {
let Q_biomass_an_ren = if has_biomass {
let fp_ren_fraction_biomass = get_fp_ren_fraction(BIOMASA, wfactors)?;
let Q_biomass_an_pct = components
.get_meta_f32("CTE_DEMANDA_ACS_PCT_BIOMASA")
.ok_or_else(|| {
EpbdError::WrongInput(
"No se ha especificado el porcentaje de la demanda de ACS abastecida por BIOMASA en el metadato 'CTE_DEMANDA_ACS_PCT_BIOMASA'"
.to_string(),
)
})?;
demanda_anual_acs * Q_biomass_an_pct / 100.0 * fp_ren_fraction_biomass
} else {
0.0
};
let Q_dens_biomass_an_ren = if has_dens_biomass {
let fp_ren_fraction_dens_biomass = get_fp_ren_fraction(BIOMASADENSIFICADA, wfactors)?;
let Q_dens_biomass_an_pct = components
.get_meta_f32("CTE_DEMANDA_ACS_PCT_BIOMASADENSIFICADA")
.ok_or_else(|| {
EpbdError::WrongInput(
"No se ha especificado el porcentaje de la demanda de ACS abastecida por BIOMASADENSIFICADA en el metadato 'CTE_DEMANDA_ACS_PCT_BIOMASADENSIFICADA'"
.to_string(),
)
})?;
demanda_anual_acs * Q_dens_biomass_an_pct / 100.0 * fp_ren_fraction_dens_biomass
} else {
0.0
};
Q_biomass_an_ren + Q_dens_biomass_an_ren
} else {
0.0
};
let num_steps = cr_list[0].values.len();
let E_EPus_el_t = cr_list
.iter()
.filter(|c| c.carrier == ELECTRICIDAD)
.filter(|c| c.ctype == CONSUMO && c.csubtype == CSubtype::EPB)
.fold(vec![0.0; num_steps], |acc, c| vecvecsum(&acc, &c.values));
let E_pr_el_onsite_t = cr_list
.iter()
.filter(|c| c.carrier == ELECTRICIDAD)
.filter(|c| c.ctype == PRODUCCION && c.csubtype == CSubtype::INSITU)
.fold(vec![0.0; num_steps], |acc, c| vecvecsum(&acc, &c.values));
let Q_el_an_ren: f32 = vecvecmin(&E_EPus_el_t, &E_pr_el_onsite_t).iter().sum();
let Q_an_ren = Q_district_and_env_acs_an_ren + Q_biomass_an_ren + Q_el_an_ren;
Ok(Q_an_ren / demanda_anual_acs)
}
pub fn incorpora_demanda_renovable_acs_nrb(
mut balance: Balance,
demanda_anual_acs: Option<f32>,
) -> Balance {
let mut map = balance.misc.unwrap_or_else(HashMap::<String, String>::new);
match demanda_anual_acs {
Some(demanda_anual_acs) => {
map.insert(
"demanda_anual_acs".to_string(),
format!("{:.1}", demanda_anual_acs),
);
match fraccion_renovable_acs_nrb(
&balance.components,
&balance.wfactors,
demanda_anual_acs,
) {
Ok(fraccion_renovable_acs_nrb) => {
map.insert(
"fraccion_renovable_demanda_acs_nrb".to_string(),
format!("{:.3}", fraccion_renovable_acs_nrb),
);
map.remove("error_acs");
}
Err(e) => {
map.insert(
"error_acs".to_string(),
format!(
"ERROR: no se puede calcular la demanda renovable de ACS \"{}\"",
e
),
);
map.remove("fraccion_renovable_demanda_acs_nrb");
}
}
}
_ => {
map.insert(
"error_acs".to_string(),
"ERROR: demanda anual de ACS no definida".to_string(),
);
}
}
balance.misc = Some(map);
balance
}
pub fn balance_to_plain(balance: &Balance) -> String {
let Balance {
k_exp,
arearef,
balance_m2,
..
} = balance;
let RenNrenCo2 { ren, nren, co2 } = balance_m2.B;
let tot = balance_m2.B.tot();
let rer = balance_m2.B.rer();
let mut use_byuse = balance_m2
.used_EPB_byuse
.iter()
.map(|(k, v)| format!("{}: {:.2}", k, v))
.collect::<Vec<String>>();
use_byuse.sort();
let mut b_byuse = balance_m2
.B_byuse
.iter()
.map(|(k, v)| {
format!(
"{}: ren {:.2}, nren {:.2}, co2: {:.2}",
k, v.ren, v.nren, v.co2
)
})
.collect::<Vec<String>>();
b_byuse.sort();
let out = format!(
"Area_ref = {:.2} [m2]
k_exp = {:.2}
C_ep [kWh/m2.an]: ren = {:.1}, nren = {:.1}, tot = {:.1}, RER = {:.2}
E_CO2 [kg_CO2e/m2.an]: {:.2}
** Energía final (todos los vectores) [kWh/m2.an]:
{}
** Energía primaria (ren, nren) [kWh/m2.an] y emisiones [kg_CO2e/m2.an] por servicios:
{}
",
arearef,
k_exp,
ren,
nren,
tot,
rer,
co2,
use_byuse.join("\n"),
b_byuse.join("\n")
);
if let Some(map) = &balance.misc {
let demanda = map
.get("demanda_anual_acs")
.and_then(|v| v.parse::<f32>().and_then(|r| Ok(format!("{:.1}", r))).ok())
.unwrap_or_else(|| "-".to_string());
let pct_ren = map
.get("fraccion_renovable_demanda_acs_nrb")
.and_then(|v| {
v.parse::<f32>()
.and_then(|r| Ok(format!("{:.1}", r * 100.0)))
.ok()
})
.unwrap_or_else(|| "-".to_string());
format!(
"{}
** Indicadores adicionales
Demanda total de ACS: {} [kWh]
Porcentaje renovable de la demanda de ACS (perímetro próximo): {} [%]
",
out, demanda, pct_ren
)
} else {
out
}
}
pub fn balance_to_xml(balanceobj: &Balance) -> String {
let Balance {
components,
wfactors,
k_exp,
arearef,
balance_m2,
..
} = balanceobj;
let RenNrenCo2 { ren, nren, .. } = balance_m2.B;
let cmeta = &components.cmeta;
let cdata = &components.cdata;
let wmeta = &wfactors.wmeta;
let wdata = &wfactors.wdata;
fn escape_xml(unescaped: &str) -> String {
unescaped
.replace('&', "&")
.replace('<', "<")
.replace('>', ">")
.replace('\\', "'")
.replace('"', """)
}
let wmetastring = wmeta
.iter()
.map(|m| {
format!(
" <Metadato><Clave>{}</Clave><Valor>{}</Valor></Metadato>",
escape_xml(&m.key),
escape_xml(&m.value)
)
})
.collect::<Vec<String>>()
.join("\n");
let wdatastring = wdata
.iter()
.map(|f| {
let Factor {
carrier,
source,
dest,
step,
ren,
nren,
co2,
comment,
} = f;
format!(" <Dato><Vector>{}</Vector><Origen>{}</Origen><Destino>{}</Destino><Paso>{}</Paso><ren>{:.3}</ren><nren>{:.3}</nren><co2>{:.3}</co2><Comentario>{}</Comentario></Dato>",
carrier, source, dest, step, ren, nren, co2, escape_xml(comment))
})
.collect::<Vec<String>>()
.join("\n");
let cmetastring = cmeta
.iter()
.map(|m| {
format!(
" <Metadato><Clave>{}</Clave><Valor>{}</Valor></Metadato>",
escape_xml(&m.key),
escape_xml(&m.value)
)
})
.collect::<Vec<String>>()
.join("\n");
let cdatastring = cdata
.iter()
.map(|c| {
let Component {
carrier,
ctype,
csubtype,
service,
values,
comment,
} = c;
let vals = values
.iter()
.map(|v| format!("{:.2}", v))
.collect::<Vec<String>>()
.join(",");
format!(
" <Dato>
<Vector>{}</Vector><Tipo>{}</Tipo><Subtipo>{}</Subtipo><Servicio>{}</Servicio>
<Valores>{}</Valores>
<Comentario>{}</Comentario>
</Dato>",
carrier,
ctype,
csubtype,
service,
vals,
escape_xml(comment)
)
})
.collect::<Vec<String>>()
.join("\n");
format!(
"<BalanceEPB>
<FactoresDePaso>
<Metadatos>
{}
</Metadatos>
<Datos>
{}
</Datos>
</FactoresDePaso>
<Componentes>
<Metadatos>
{}
</Metadatos>
<Datos>
{}
</Datos>
</Componentes>
<kexp>{:.2}</kexp>
<AreaRef>{:.2}</AreaRef><!-- área de referencia [m2] -->
<Epm2><!-- C_ep [kWh/m2.an] -->
<tot>{:.1}</tot>
<nren>{:.1}</nren>
</Epm2>
</BalanceEPB>",
wmetastring,
wdatastring,
cmetastring,
cdatastring,
k_exp,
arearef,
ren + nren,
nren
)
}