use std::collections::BTreeSet;
use crate::graph::formulas::Formula;
use crate::graph::formulas::expr::Expr;
use crate::graph::formulas::fallback::{SourcePreference, aggregate};
use crate::{ComponentGraph, Edge, Error, Node};
pub(crate) fn category_formula<N, E>(
graph: &ComponentGraph<N, E>,
ids: Option<BTreeSet<u64>>,
is_category: impl Fn(&N) -> bool,
category: &str,
prefer_meters: bool,
) -> Result<Formula, Error>
where
N: Node,
E: Edge,
{
let ids = match ids {
Some(ids) => ids,
None => graph.find_all(
graph.root_id,
|node| is_category(node),
petgraph::Direction::Outgoing,
false,
)?,
};
if ids.is_empty() {
return Ok(Formula::new(Expr::number(0.0)));
}
for id in &ids {
if !is_category(graph.component(*id)?) {
return Err(Error::invalid_component(format!(
"Component with id {id} is not {category}."
)));
}
}
aggregate(graph, ids, SourcePreference::prefer_meters(prefer_meters)).map(Formula::new)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeSet;
use crate::{
ComponentGraphConfig, Error, FormulaOverrides, graph::test_utils::ComponentGraphBuilder,
};
#[test]
fn test_chp_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();
let grid_meter = builder.meter();
builder.connect(grid, grid_meter);
let graph = builder.build(None)?;
assert_eq!(graph.chp_formula(None)?.to_string(), "0.0");
let meter_chp_chain = builder.meter_chp_chain(2);
builder.connect(grid_meter, meter_chp_chain);
let graph = builder.build(None)?;
assert_eq!(
graph.chp_formula(None)?.to_string(),
"COALESCE(#4 + #3, #2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph.chp_formula(Some(BTreeSet::from([3])))?.to_string(),
"COALESCE(#3, #2 - #4, 0.0)"
);
let graph = builder.build(Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_chp_formula(true)
.build(),
)
.build(),
))?;
assert_eq!(
graph.chp_formula(None)?.to_string(),
"COALESCE(#2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.chp_formula(Some(BTreeSet::from([1])))
.unwrap_err()
.to_string(),
"InvalidComponent: Component with id 1 is not a CHP."
);
Ok(())
}
#[test]
fn test_ev_charger_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();
let grid_meter = builder.meter();
builder.connect(grid, grid_meter);
let graph = builder.build(None)?;
assert_eq!(graph.ev_charger_formula(None)?.to_string(), "0.0");
let meter_ev_charger_chain = builder.meter_ev_charger_chain(2);
builder.connect(grid_meter, meter_ev_charger_chain);
let graph = builder.build(None)?;
assert_eq!(
graph.ev_charger_formula(None)?.to_string(),
"COALESCE(#4 + #3, #2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.ev_charger_formula(Some(BTreeSet::from([3])))?
.to_string(),
"COALESCE(#3, #2 - #4, 0.0)"
);
let graph = builder.build(Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_ev_charger_formula(true)
.build(),
)
.build(),
))?;
assert_eq!(
graph.ev_charger_formula(None)?.to_string(),
"COALESCE(#2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.ev_charger_formula(Some(BTreeSet::from([1])))
.unwrap_err()
.to_string(),
"InvalidComponent: Component with id 1 is not an EV charger."
);
Ok(())
}
#[test]
fn test_pv_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();
let grid_meter = builder.meter();
builder.connect(grid, grid_meter);
let graph = builder.build(None)?;
assert_eq!(graph.pv_formula(None)?.to_string(), "0.0");
let meter_pv_chain = builder.meter_pv_chain(2);
builder.connect(grid_meter, meter_pv_chain);
let graph = builder.build(None)?;
assert_eq!(
graph.pv_formula(None)?.to_string(),
"COALESCE(#4 + #3, #2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph.pv_formula(Some(BTreeSet::from([3])))?.to_string(),
"COALESCE(#3, #2 - #4, 0.0)"
);
let graph = builder.build(Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_pv_formula(true)
.build(),
)
.build(),
))?;
assert_eq!(
graph.pv_formula(None)?.to_string(),
"COALESCE(#2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.pv_formula(Some(BTreeSet::from([1])))
.unwrap_err()
.to_string(),
"InvalidComponent: Component with id 1 is not a PV inverter."
);
Ok(())
}
#[test]
fn test_steam_boiler_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();
let grid_meter = builder.meter();
builder.connect(grid, grid_meter);
let graph = builder.build(None)?;
assert_eq!(graph.steam_boiler_formula(None)?.to_string(), "0.0");
let meter_steam_boiler_chain = builder.meter_steam_boiler_chain(2);
builder.connect(grid_meter, meter_steam_boiler_chain);
let graph = builder.build(None)?;
assert_eq!(
graph.steam_boiler_formula(None)?.to_string(),
"COALESCE(#4 + #3, #2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.steam_boiler_formula(Some(BTreeSet::from([3])))?
.to_string(),
"COALESCE(#3, #2 - #4, 0.0)"
);
let graph = builder.build(Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_steam_boiler_formula(true)
.build(),
)
.build(),
))?;
assert_eq!(
graph.steam_boiler_formula(None)?.to_string(),
"COALESCE(#2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.steam_boiler_formula(Some(BTreeSet::from([1])))
.unwrap_err()
.to_string(),
"InvalidComponent: Component with id 1 is not a steam boiler."
);
Ok(())
}
#[test]
fn test_wind_turbine_formula() -> Result<(), Error> {
let mut builder = ComponentGraphBuilder::new();
let grid = builder.grid();
let grid_meter = builder.meter();
builder.connect(grid, grid_meter);
let graph = builder.build(None)?;
assert_eq!(graph.wind_turbine_formula(None)?.to_string(), "0.0");
let meter_wind_turbine_chain = builder.meter_wind_turbine_chain(2);
builder.connect(grid_meter, meter_wind_turbine_chain);
let graph = builder.build(None)?;
assert_eq!(
graph.wind_turbine_formula(None)?.to_string(),
"COALESCE(#4 + #3, #2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.wind_turbine_formula(Some(BTreeSet::from([3])))?
.to_string(),
"COALESCE(#3, #2 - #4, 0.0)"
);
let graph = builder.build(Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_wind_turbine_formula(true)
.build(),
)
.build(),
))?;
assert_eq!(
graph.wind_turbine_formula(None)?.to_string(),
"COALESCE(#2, COALESCE(#4, 0.0) + COALESCE(#3, 0.0))"
);
assert_eq!(
graph
.wind_turbine_formula(Some(BTreeSet::from([1])))
.unwrap_err()
.to_string(),
"InvalidComponent: Component with id 1 is not a wind turbine."
);
Ok(())
}
}