use std::collections::BTreeSet;
use crate::component_category::CategoryPredicates;
use crate::graph::formulas::AggregationFormula;
use crate::graph::formulas::expr::Expr;
use crate::graph::formulas::fallback::FallbackExpr;
use crate::{ComponentGraph, Edge, Error, Node};
pub(crate) struct CHPFormulaBuilder<'a, N, E>
where
N: Node,
E: Edge,
{
graph: &'a ComponentGraph<N, E>,
chp_ids: BTreeSet<u64>,
}
impl<'a, N, E> CHPFormulaBuilder<'a, N, E>
where
N: Node,
E: Edge,
{
pub fn try_new(
graph: &'a ComponentGraph<N, E>,
chp_ids: Option<BTreeSet<u64>>,
) -> Result<Self, Error> {
let chp_ids = if let Some(chp_ids) = chp_ids {
chp_ids
} else {
graph.find_all(
graph.root_id,
|node| node.is_chp(),
petgraph::Direction::Outgoing,
false,
)?
};
Ok(Self { graph, chp_ids })
}
pub fn build(self) -> Result<AggregationFormula, Error> {
if self.chp_ids.is_empty() {
return Ok(AggregationFormula::new(Expr::number(0.0)));
}
for id in &self.chp_ids {
if !self.graph.component(*id)?.is_chp() {
return Err(Error::invalid_component(format!(
"Component with id {id} is not a CHP."
)));
}
}
FallbackExpr::new()
.prefer_meters(self.graph.config.prefer_meters_in_chp_formula())
.generate(self.graph, self.chp_ids.clone())
.map(AggregationFormula::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 prefer_chp_config = Some(
ComponentGraphConfig::builder()
.formula_overrides(
FormulaOverrides::builder()
.prefer_meters_in_chp_formula(false)
.build(),
)
.build(),
);
let graph = builder.build(prefer_chp_config.clone())?;
let formula = graph.chp_formula(None)?.to_string();
assert_eq!(formula, "0.0");
let meter_chp_chain = builder.meter_chp_chain(1);
builder.connect(grid_meter, meter_chp_chain);
assert_eq!(grid_meter.component_id(), 1);
assert_eq!(meter_chp_chain.component_id(), 2);
let graph = builder.build(prefer_chp_config.clone())?;
let formula = graph.chp_formula(None)?.to_string();
assert_eq!(formula, "COALESCE(#3, #2, 0.0)");
let meter_bat_chain = builder.meter_bat_chain(1, 2);
builder.connect(grid_meter, meter_bat_chain);
assert_eq!(meter_bat_chain.component_id(), 4);
let graph = builder.build(prefer_chp_config.clone())?;
let formula = graph.chp_formula(None)?.to_string();
assert_eq!(formula, "COALESCE(#3, #2, 0.0)");
let meter_chp_chain = builder.meter_chp_chain(2);
builder.connect(grid_meter, meter_chp_chain);
assert_eq!(meter_chp_chain.component_id(), 8);
let graph = builder.build(prefer_chp_config.clone())?;
let formula = graph.chp_formula(None)?.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#3, #2, 0.0) + ",
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0))"
)
);
let formula = graph
.chp_formula(Some(BTreeSet::from([10, 3])))?
.to_string();
assert_eq!(formula, "COALESCE(#3, #2, 0.0) + COALESCE(#10, 0.0)");
let meter_chp_chain = builder.meter_chp_chain(3);
builder.connect(grid, meter_chp_chain);
assert_eq!(meter_chp_chain.component_id(), 11);
let graph = builder.build(prefer_chp_config)?;
let graph_prefer_meters = builder.build(None)?;
let formula = graph.chp_formula(None)?.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#3, #2, 0.0) + ",
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(",
"#14 + #13 + #12, ",
"#11, ",
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
")"
),
);
let formula = graph_prefer_meters.chp_formula(None)?.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#2, #3, 0.0) + ",
"COALESCE(#8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(",
"#11, ",
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
")"
),
);
let formula = graph
.chp_formula(Some(BTreeSet::from([3, 9, 10, 12, 13])))?
.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#3, #2, 0.0) + ",
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(#12, 0.0) + COALESCE(#13, 0.0)"
)
);
let formula = graph_prefer_meters
.chp_formula(Some(BTreeSet::from([3, 9, 10, 12, 13])))?
.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#2, #3, 0.0) + ",
"COALESCE(#8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(#12, 0.0) + COALESCE(#13, 0.0)"
)
);
let formula = graph
.chp_formula(Some(BTreeSet::from([3, 9, 10, 12, 13, 14])))?
.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#3, #2, 0.0) + ",
"COALESCE(#10 + #9, #8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(",
"#14 + #13 + #12, ",
"#11, ",
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
")"
),
);
let formula = graph_prefer_meters
.chp_formula(Some(BTreeSet::from([3, 9, 10, 12, 13, 14])))?
.to_string();
assert_eq!(
formula,
concat!(
"COALESCE(#2, #3, 0.0) + ",
"COALESCE(#8, COALESCE(#10, 0.0) + COALESCE(#9, 0.0)) + ",
"COALESCE(",
"#11, ",
"COALESCE(#14, 0.0) + COALESCE(#13, 0.0) + COALESCE(#12, 0.0)",
")"
),
);
let formula = graph
.chp_formula(Some(BTreeSet::from([10, 14])))?
.to_string();
assert_eq!(formula, "COALESCE(#10, 0.0) + COALESCE(#14, 0.0)");
let formula = graph_prefer_meters
.chp_formula(Some(BTreeSet::from([10, 14])))?
.to_string();
assert_eq!(formula, "COALESCE(#10, 0.0) + COALESCE(#14, 0.0)");
let formula = graph.chp_formula(Some(BTreeSet::from([8])));
assert_eq!(
formula.unwrap_err().to_string(),
"InvalidComponent: Component with id 8 is not a CHP."
);
let formula = graph_prefer_meters.chp_formula(Some(BTreeSet::from([8])));
assert_eq!(
formula.unwrap_err().to_string(),
"InvalidComponent: Component with id 8 is not a CHP."
);
Ok(())
}
}