Skip to content

Commit 94dc826

Browse files
authored
Implement coalesce formula generators (#11)
These formulas can be used for voltage or frequency metrics.
2 parents ab3b998 + f40fbbb commit 94dc826

21 files changed

+717
-105
lines changed

src/component_category.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,10 +96,10 @@ impl Display for ComponentCategory {
9696
ComponentCategory::Unspecified => write!(f, "Unspecified"),
9797
ComponentCategory::Grid => write!(f, "Grid"),
9898
ComponentCategory::Meter => write!(f, "Meter"),
99-
ComponentCategory::Battery(battery_type) => write!(f, "Battery({})", battery_type),
100-
ComponentCategory::Inverter(inverter_type) => write!(f, "{}Inverter", inverter_type),
99+
ComponentCategory::Battery(battery_type) => write!(f, "Battery({battery_type})"),
100+
ComponentCategory::Inverter(inverter_type) => write!(f, "{inverter_type}Inverter"),
101101
ComponentCategory::EvCharger(ev_charger_type) => {
102-
write!(f, "EVCharger({})", ev_charger_type)
102+
write!(f, "EVCharger({ev_charger_type})")
103103
}
104104
ComponentCategory::Converter => write!(f, "Converter"),
105105
ComponentCategory::CryptoMiner => write!(f, "CryptoMiner"),

src/graph.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ mod formulas;
1313
pub mod iterators;
1414

1515
use crate::{ComponentGraphConfig, Edge, Node};
16-
pub use formulas::Formula;
16+
pub use formulas::{AggregationFormula, CoalesceFormula};
1717
use petgraph::graph::{DiGraph, NodeIndex};
1818
use std::collections::HashMap;
1919

src/graph/formulas.rs

Lines changed: 76 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,57 +16,117 @@ mod formula;
1616
mod generators;
1717
mod traversal;
1818

19-
pub use formula::Formula;
19+
pub use formula::{AggregationFormula, CoalesceFormula};
2020

2121
/// Formulas for various microgrid metrics.
2222
impl<N, E> ComponentGraph<N, E>
2323
where
2424
N: Node,
2525
E: Edge,
2626
{
27-
/// Returns a string representing the consumer formula for the graph.
28-
pub fn consumer_formula(&self) -> Result<Formula, Error> {
27+
/// Returns the consumer formula for the graph.
28+
pub fn consumer_formula(&self) -> Result<AggregationFormula, Error> {
2929
generators::consumer::ConsumerFormulaBuilder::try_new(self)?.build()
3030
}
3131

32-
/// Returns a string representing the grid formula for the graph.
33-
pub fn grid_formula(&self) -> Result<Formula, Error> {
32+
/// Returns the grid formula for the graph.
33+
pub fn grid_formula(&self) -> Result<AggregationFormula, Error> {
3434
generators::grid::GridFormulaBuilder::try_new(self)?.build()
3535
}
3636

37-
/// Returns a string representing the producer formula for the graph.
38-
pub fn producer_formula(&self) -> Result<Formula, Error> {
37+
/// Returns the producer formula for the graph.
38+
pub fn producer_formula(&self) -> Result<AggregationFormula, Error> {
3939
generators::producer::ProducerFormulaBuilder::try_new(self)?.build()
4040
}
4141

42-
/// Returns a string representing the battery formula for the graph.
43-
pub fn battery_formula(&self, battery_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
42+
/// Returns the battery formula with the given battery IDs.
43+
///
44+
/// If `battery_ids` is `None`, the formula will contain all batteries in
45+
/// the graph.
46+
pub fn battery_formula(
47+
&self,
48+
battery_ids: Option<BTreeSet<u64>>,
49+
) -> Result<AggregationFormula, Error> {
4450
generators::battery::BatteryFormulaBuilder::try_new(self, battery_ids)?.build()
4551
}
4652

47-
/// Returns a string representing the CHP formula for the graph.
48-
pub fn chp_formula(&self, chp_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
53+
/// Returns the CHP formula for the graph.
54+
pub fn chp_formula(&self, chp_ids: Option<BTreeSet<u64>>) -> Result<AggregationFormula, Error> {
4955
generators::chp::CHPFormulaBuilder::try_new(self, chp_ids)?.build()
5056
}
5157

52-
/// Returns a string representing the PV formula for the graph.
53-
pub fn pv_formula(&self, pv_inverter_ids: Option<BTreeSet<u64>>) -> Result<Formula, Error> {
58+
/// Returns the PV formula for the graph.
59+
pub fn pv_formula(
60+
&self,
61+
pv_inverter_ids: Option<BTreeSet<u64>>,
62+
) -> Result<AggregationFormula, Error> {
5463
generators::pv::PVFormulaBuilder::try_new(self, pv_inverter_ids)?.build()
5564
}
5665

57-
/// Returns a string with the coalesce formula for the given component IDs.
66+
/// Returns the coalesce formula for the given component IDs.
5867
///
5968
/// This formula uses the `COALESCE` function to return the first non-null
6069
/// value from the components with the provided IDs.
61-
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<Formula, Error> {
70+
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<AggregationFormula, Error> {
6271
generators::generic::CoalesceFormulaBuilder::try_new(self, component_ids)?.build()
6372
}
6473

6574
/// Returns a string representing the EV charger formula for the graph.
6675
pub fn ev_charger_formula(
6776
&self,
6877
ev_charger_ids: Option<BTreeSet<u64>>,
69-
) -> Result<Formula, Error> {
78+
) -> Result<AggregationFormula, Error> {
7079
generators::ev_charger::EVChargerFormulaBuilder::try_new(self, ev_charger_ids)?.build()
7180
}
81+
82+
/// Returns the grid coalesce formula for the graph.
83+
///
84+
/// This formula is used for non-aggregating metrics like AC voltage or
85+
/// frequency.
86+
///
87+
/// The formula is a `COALESCE` expression that includes all meters,
88+
/// PV inverters, and battery inverters that are directly connected to the
89+
/// grid.
90+
pub fn grid_coalesce_formula(&self) -> Result<CoalesceFormula, Error> {
91+
generators::grid_coalesce::GridCoalesceFormulaBuilder::try_new(self)?.build()
92+
}
93+
94+
/// Returns the battery AC coalesce formula for the given components.
95+
///
96+
/// This formula is used for non-aggregating metrics like AC voltage or
97+
/// frequency.
98+
///
99+
/// The formula is a `COALESCE` expression that includes all the specified
100+
/// battery meters and corresponding inverters.
101+
///
102+
/// When the `battery_ids` parameter is `None`, it will include all the
103+
/// battery meters and inverters in the graph.
104+
pub fn battery_ac_coalesce_formula(
105+
&self,
106+
battery_ids: Option<BTreeSet<u64>>,
107+
) -> Result<CoalesceFormula, Error> {
108+
generators::battery_ac_coalesce::BatteryAcCoalesceFormulaBuilder::try_new(
109+
self,
110+
battery_ids,
111+
)?
112+
.build()
113+
}
114+
115+
/// Returns the PV AC coalesce formula for the given components.
116+
///
117+
/// This formula is used for non-aggregating metrics like AC voltage or
118+
/// frequency.
119+
///
120+
/// The formula is a `COALESCE` expression that includes all the specified
121+
/// PV meters and corresponding inverters.
122+
///
123+
/// When the `pv_inverter_ids` parameter is `None`, it will include all the
124+
/// PV meters and inverters in the graph.
125+
pub fn pv_ac_coalesce_formula(
126+
&self,
127+
pv_inverter_ids: Option<BTreeSet<u64>>,
128+
) -> Result<CoalesceFormula, Error> {
129+
generators::pv_ac_coalesce::PVAcCoalesceFormulaBuilder::try_new(self, pv_inverter_ids)?
130+
.build()
131+
}
72132
}

src/graph/formulas/expr.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
use crate::Node;
55

6-
#[derive(Debug, Clone)]
6+
#[derive(Debug, Clone, PartialEq)]
77
pub(crate) enum Expr {
88
/// A negation of an expression.
99
Neg { param: Box<Expr> },
@@ -133,7 +133,11 @@ impl Expr {
133133
}
134134

135135
pub(crate) fn coalesce(params: Vec<Expr>) -> Self {
136-
Self::Coalesce { params }
136+
if let [param] = params.as_slice() {
137+
param.clone()
138+
} else {
139+
Self::Coalesce { params }
140+
}
137141
}
138142

139143
pub(crate) fn min(params: Vec<Expr>) -> Self {
@@ -178,13 +182,13 @@ impl Expr {
178182
Self::Number { value } => {
179183
if value.fract() == 0.0 {
180184
// For whole numbers, format with one decimal place.
181-
format!("{:.1}", value)
185+
format!("{value:.1}")
182186
} else {
183187
// else format normally.
184-
format!("{}", value)
188+
format!("{value}")
185189
}
186190
}
187-
Self::Component { component_id } => format!("#{}", component_id),
191+
Self::Component { component_id } => format!("#{component_id}"),
188192
Self::Add { params } => {
189193
Self::join_params(params, " + ", None, BracketComponents::None, bracket_whole)
190194
}
@@ -219,7 +223,7 @@ impl Expr {
219223
bracket_whole: bool,
220224
) -> String {
221225
let (mut result, suffix) = match prefix {
222-
Some(prefix) => (format!("{}(", prefix), String::from(")")),
226+
Some(prefix) => (format!("{prefix}("), String::from(")")),
223227
None => (String::new(), String::new()),
224228
};
225229
let mut num_components = 0;

src/graph/formulas/formula.rs

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,47 +8,78 @@ use super::expr::Expr;
88
// This struct represents the microgrid metric formulas that are generated by
99
// traversing the component graph.
1010
//
11-
// `Formula` objects can be added or subtracted from each other, and they can
12-
// converted to a string representation, before they are passed to an evaluator.
13-
#[derive(Debug, Clone)]
14-
pub struct Formula {
11+
// They are used to represent quantities that are aggregated over multiple
12+
// components in the microgrid, such as power or current.
13+
//
14+
// `AggregationFormula` objects can be added or subtracted from each other, and
15+
// they can converted to a string representation, before they are passed to an
16+
// evaluator.
17+
#[derive(Debug, Clone, PartialEq)]
18+
pub struct AggregationFormula {
1519
expr: Expr,
1620
}
1721

18-
impl Formula {
22+
impl AggregationFormula {
1923
pub(crate) fn new(expr: Expr) -> Self {
20-
Formula { expr }
24+
AggregationFormula { expr }
2125
}
2226
}
2327

24-
impl std::ops::Add for Formula {
28+
impl std::ops::Add for AggregationFormula {
2529
type Output = Self;
2630

2731
fn add(self, rhs: Self) -> Self::Output {
28-
Formula {
32+
AggregationFormula {
2933
expr: self.expr + rhs.expr,
3034
}
3135
}
3236
}
3337

34-
impl std::ops::Sub for Formula {
38+
impl std::ops::Sub for AggregationFormula {
3539
type Output = Self;
3640

3741
fn sub(self, rhs: Self) -> Self::Output {
38-
Formula {
42+
AggregationFormula {
3943
expr: self.expr - rhs.expr,
4044
}
4145
}
4246
}
4347

44-
impl std::fmt::Display for Formula {
48+
impl std::fmt::Display for AggregationFormula {
49+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50+
self.expr.fmt(f)
51+
}
52+
}
53+
54+
impl From<AggregationFormula> for String {
55+
fn from(formula: AggregationFormula) -> Self {
56+
formula.expr.to_string()
57+
}
58+
}
59+
60+
/// Represents a formula that coalesces metrics from multiple components.
61+
///
62+
/// This is typically used for non-aggregating metrics like AC voltage or
63+
/// frequency.
64+
#[derive(Debug, Clone, PartialEq)]
65+
pub struct CoalesceFormula {
66+
expr: Expr,
67+
}
68+
69+
impl CoalesceFormula {
70+
pub(crate) fn new(expr: Expr) -> Self {
71+
CoalesceFormula { expr }
72+
}
73+
}
74+
75+
impl std::fmt::Display for CoalesceFormula {
4576
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4677
self.expr.fmt(f)
4778
}
4879
}
4980

50-
impl From<Formula> for String {
51-
fn from(formula: Formula) -> Self {
81+
impl From<CoalesceFormula> for String {
82+
fn from(formula: CoalesceFormula) -> Self {
5283
formula.expr.to_string()
5384
}
5485
}

src/graph/formulas/generators.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
//! Formula generators for standard metrics.
55
66
pub(super) mod battery;
7+
pub(super) mod battery_ac_coalesce;
78
pub(super) mod chp;
89
pub(super) mod consumer;
910
pub(super) mod ev_charger;
1011
pub(super) mod generic;
1112
pub(super) mod grid;
13+
pub(super) mod grid_coalesce;
1214
pub(super) mod producer;
1315
pub(super) mod pv;
16+
pub(super) mod pv_ac_coalesce;

src/graph/formulas/generators/battery.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::collections::BTreeSet;
77

88
use crate::component_category::CategoryPredicates;
99
use crate::graph::formulas::expr::Expr;
10-
use crate::graph::formulas::Formula;
10+
use crate::graph::formulas::AggregationFormula;
1111
use crate::{ComponentGraph, Edge, Error, Node};
1212

1313
pub(crate) struct BatteryFormulaBuilder<'a, N, E>
@@ -49,26 +49,25 @@ where
4949
/// This is the sum of all battery_inverters in the graph. If the
5050
/// battery_ids are provided, only the batteries with the given ids are
5151
/// included in the formula.
52-
pub fn build(self) -> Result<Formula, Error> {
52+
pub fn build(self) -> Result<AggregationFormula, Error> {
5353
if self.inverter_ids.is_empty() {
54-
return Ok(Formula::new(Expr::number(0.0)));
54+
return Ok(AggregationFormula::new(Expr::number(0.0)));
5555
}
5656

5757
self.graph
5858
.fallback_expr(self.inverter_ids, false)
59-
.map(Formula::new)
59+
.map(AggregationFormula::new)
6060
}
6161

62-
fn find_inverter_ids(
62+
pub(super) fn find_inverter_ids(
6363
graph: &ComponentGraph<N, E>,
6464
battery_ids: &BTreeSet<u64>,
6565
) -> Result<BTreeSet<u64>, Error> {
6666
let mut inverter_ids = BTreeSet::new();
6767
for battery_id in battery_ids {
6868
if !graph.component(*battery_id)?.is_battery() {
6969
return Err(Error::invalid_component(format!(
70-
"Component with id {} is not a battery.",
71-
battery_id
70+
"Component with id {battery_id} is not a battery."
7271
)));
7372
}
7473
for sibling in graph.siblings_from_predecessors(*battery_id)? {

0 commit comments

Comments
 (0)