Skip to content

Commit b2fd616

Browse files
authored
Support coalesce/min/max on all formulas (#12)
Also return `None` when a coalesce formula can't be generated, instead of an error.
2 parents 94dc826 + edc6dde commit b2fd616

File tree

15 files changed

+333
-272
lines changed

15 files changed

+333
-272
lines changed

src/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,7 @@ ErrorKind!(
5252
(Internal, internal),
5353
(InvalidComponent, invalid_component),
5454
(InvalidConnection, invalid_connection),
55-
(InvalidGraph, invalid_graph),
56-
(MissingParameters, missing_parameters)
55+
(InvalidGraph, invalid_graph)
5756
);
5857

5958
/// An error that can occur during the creation or traversal of a

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::{AggregationFormula, CoalesceFormula};
16+
pub use formulas::{AggregationFormula, CoalesceFormula, Formula};
1717
use petgraph::graph::{DiGraph, NodeIndex};
1818
use std::collections::HashMap;
1919

src/graph/formulas.rs

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

19-
pub use formula::{AggregationFormula, CoalesceFormula};
19+
use expr::Expr;
20+
pub use formula::{AggregationFormula, CoalesceFormula, Formula};
2021

2122
/// Formulas for various microgrid metrics.
2223
impl<N, E> ComponentGraph<N, E>
@@ -63,22 +64,19 @@ where
6364
generators::pv::PVFormulaBuilder::try_new(self, pv_inverter_ids)?.build()
6465
}
6566

66-
/// Returns the coalesce formula for the given component IDs.
67-
///
68-
/// This formula uses the `COALESCE` function to return the first non-null
69-
/// value from the components with the provided IDs.
70-
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<AggregationFormula, Error> {
71-
generators::generic::CoalesceFormulaBuilder::try_new(self, component_ids)?.build()
72-
}
73-
74-
/// Returns a string representing the EV charger formula for the graph.
67+
/// Returns the EV charger formula for the graph.
7568
pub fn ev_charger_formula(
7669
&self,
7770
ev_charger_ids: Option<BTreeSet<u64>>,
7871
) -> Result<AggregationFormula, Error> {
7972
generators::ev_charger::EVChargerFormulaBuilder::try_new(self, ev_charger_ids)?.build()
8073
}
8174

75+
/// Returns the formula for a specific component by its ID.
76+
pub fn component_formula(&self, component_id: u64) -> Result<AggregationFormula, Error> {
77+
Ok(Expr::component(component_id).into())
78+
}
79+
8280
/// Returns the grid coalesce formula for the graph.
8381
///
8482
/// This formula is used for non-aggregating metrics like AC voltage or
@@ -129,4 +127,12 @@ where
129127
generators::pv_ac_coalesce::PVAcCoalesceFormulaBuilder::try_new(self, pv_inverter_ids)?
130128
.build()
131129
}
130+
131+
/// Returns the AC coalesce formula for a specific component by its ID.
132+
pub fn component_ac_coalesce_formula(
133+
&self,
134+
component_id: u64,
135+
) -> Result<CoalesceFormula, Error> {
136+
Ok(Expr::component(component_id).into())
137+
}
132138
}

src/graph/formulas/expr.rs

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ use crate::Node;
55

66
#[derive(Debug, Clone, PartialEq)]
77
pub(crate) enum Expr {
8+
/// An empty expression, which as a formula would evaluate to None.
9+
None,
10+
811
/// A negation of an expression.
912
Neg { param: Box<Expr> },
1013

@@ -35,6 +38,7 @@ impl std::ops::Add for Expr {
3538

3639
fn add(self, rhs: Self) -> Self {
3740
match (self, rhs) {
41+
(Self::None, other) | (other, Self::None) => other,
3842
// -a + -b = -(a + b)
3943
(Self::Neg { param: lhs }, Self::Neg { param: rhs }) => -(*lhs + *rhs),
4044
// -a + b = b - a
@@ -68,6 +72,8 @@ impl std::ops::Sub for Expr {
6872

6973
fn sub(self, rhs: Self) -> Self {
7074
match (self, rhs) {
75+
(Self::None, other) => -other,
76+
(other, Self::None) => other,
7177
// (a - b) - -c = a - b + c
7278
(sub @ Self::Sub { .. }, Self::Neg { param }) => sub + *param,
7379
// -a - (b - c) = c - b - a
@@ -98,6 +104,7 @@ impl std::ops::Neg for Expr {
98104

99105
fn neg(self) -> Self {
100106
match self {
107+
Self::None => Self::None,
101108
// -(-a) = a
102109
Expr::Neg { param: inner } => *inner,
103110
// -(a - b) = b - a
@@ -124,28 +131,113 @@ impl<N: Node> From<&N> for Expr {
124131

125132
/// Constructors for `FormulaExpression`.
126133
impl Expr {
134+
#[must_use]
127135
pub(crate) fn number(value: f64) -> Self {
128136
Self::Number { value }
129137
}
130138

139+
#[must_use]
131140
pub(crate) fn component(component_id: u64) -> Self {
132141
Self::Component { component_id }
133142
}
134143

135-
pub(crate) fn coalesce(params: Vec<Expr>) -> Self {
136-
if let [param] = params.as_slice() {
137-
param.clone()
138-
} else {
139-
Self::Coalesce { params }
144+
#[must_use]
145+
pub(crate) fn coalesce(self, other: Expr) -> Self {
146+
match (self, other) {
147+
(Expr::None, other) | (other, Expr::None) => other,
148+
(
149+
Expr::Coalesce { mut params },
150+
Expr::Coalesce {
151+
params: other_params,
152+
},
153+
) => {
154+
// If both parameters are coalesce expressions, merge them.
155+
params.extend(other_params);
156+
Self::Coalesce { params }
157+
}
158+
(Expr::Coalesce { mut params }, other) => {
159+
// If the first parameter is a coalesce expression, add the second
160+
// parameter to it.
161+
params.push(other);
162+
Self::Coalesce { params }
163+
}
164+
(
165+
param,
166+
Expr::Coalesce {
167+
params: other_params,
168+
},
169+
) => {
170+
// If the second parameter is a coalesce expression, add the first
171+
// parameter to it.
172+
let mut params = vec![param];
173+
params.extend(other_params);
174+
Self::Coalesce { params }
175+
}
176+
(first, second) => {
177+
// If neither parameter is a coalesce expression, create a new one.
178+
Self::Coalesce {
179+
params: vec![first, second],
180+
}
181+
}
140182
}
141183
}
142184

143-
pub(crate) fn min(params: Vec<Expr>) -> Self {
144-
Self::Min { params }
185+
#[must_use]
186+
pub(crate) fn min(self, other: Expr) -> Self {
187+
match (self, other) {
188+
(Expr::None, expr) | (expr, Expr::None) => expr,
189+
(
190+
Expr::Min { mut params },
191+
Expr::Min {
192+
params: other_params,
193+
},
194+
) => {
195+
// If both parameters are min expressions, merge them.
196+
params.extend(other_params);
197+
Self::Min { params }
198+
}
199+
(Expr::Min { mut params }, other) | (other, Expr::Min { mut params }) => {
200+
// If one parameter is a min expression, add the other parameter
201+
// to it.
202+
params.push(other);
203+
Self::Min { params }
204+
}
205+
(first, second) => {
206+
// If neither parameter is a min expression, create a new one.
207+
Self::Min {
208+
params: vec![first, second],
209+
}
210+
}
211+
}
145212
}
146213

147-
pub(crate) fn max(params: Vec<Expr>) -> Self {
148-
Self::Max { params }
214+
#[must_use]
215+
pub(crate) fn max(self, other: Expr) -> Self {
216+
match (self, other) {
217+
(Expr::None, expr) | (expr, Expr::None) => expr,
218+
(
219+
Expr::Max { mut params },
220+
Expr::Max {
221+
params: other_params,
222+
},
223+
) => {
224+
// If both parameters are max expressions, merge them.
225+
params.extend(other_params);
226+
Self::Max { params }
227+
}
228+
(Expr::Max { mut params }, other) | (other, Expr::Max { mut params }) => {
229+
// If one parameter is a max expression, add the other parameter
230+
// to it.
231+
params.push(other);
232+
Self::Max { params }
233+
}
234+
(first, second) => {
235+
// If neither parameter is a max expression, create a new one.
236+
Self::Max {
237+
params: vec![first, second],
238+
}
239+
}
240+
}
149241
}
150242
}
151243

@@ -178,6 +270,7 @@ impl Expr {
178270
/// component, the whole expression is enclosed in brackets.
179271
fn generate_string(&self, bracket_whole: bool) -> String {
180272
match self {
273+
Self::None => String::from("None"),
181274
Self::Neg { param } => format!("-{}", param.generate_string(true)),
182275
Self::Number { value } => {
183276
if value.fract() == 0.0 {
@@ -365,29 +458,26 @@ mod tests {
365458
let comp = Expr::component;
366459
let coalesce = Expr::coalesce;
367460
let number = Expr::number;
368-
let min = Expr::min;
369-
let max = Expr::max;
370461

371462
assert_expr(
372-
&[comp(1)
373-
- (coalesce(vec![comp(5), comp(7) + comp(6)]) + coalesce(vec![comp(2), comp(3)]))
374-
+ coalesce(vec![
375-
max(vec![number(0.0), comp(5)]),
376-
max(vec![number(0.0), comp(7)]) + max(vec![number(0.0), comp(6)]),
377-
])],
463+
&[
464+
comp(1) - (coalesce(comp(5), comp(7) + comp(6)) + coalesce(comp(2), comp(3)))
465+
+ coalesce(
466+
number(0.0).max(comp(5)),
467+
number(0.0).max(comp(7)) + number(0.0).max(comp(6)),
468+
),
469+
],
378470
concat!(
379471
"#1 - (COALESCE(#5, #7 + #6) + COALESCE(#2, #3)) + ",
380472
"COALESCE(MAX(0.0, #5), MAX(0.0, #7) + MAX(0.0, #6))"
381473
),
382474
);
383475

384476
assert_expr(
385-
&[min(vec![number(0.0), comp(5), comp(7) + comp(6)])
386-
- max(vec![
387-
coalesce(vec![comp(5), comp(7) + comp(6)]),
388-
comp(7),
389-
number(22.44),
390-
])],
477+
&[number(0.0).min(comp(5)).min(comp(7) + comp(6))
478+
- coalesce(comp(5), comp(7) + comp(6))
479+
.max(comp(7))
480+
.max(number(22.44))],
391481
"MIN(0.0, #5, #7 + #6) - MAX(COALESCE(#5, #7 + #6), #7, 22.44)",
392482
)
393483
}

src/graph/formulas/fallback.rs

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ where
8181
.map(|node| {
8282
(
8383
Expr::from(node),
84-
Expr::coalesce(vec![Expr::from(node), Expr::number(0.0)]),
84+
Expr::coalesce(Expr::from(node), Expr::number(0.0)),
8585
)
8686
})
8787
.reduce(|a, b| (a.0 + b.0, a.1 + b.1))
@@ -91,27 +91,26 @@ where
9191

9292
let has_multiple_successors = matches!(sum_of_successors, Expr::Add { .. });
9393

94-
let mut to_be_coalesced: Vec<Expr> = vec![];
94+
let mut coalesced = Expr::component(component_id);
9595

9696
if !self.prefer_meters {
97-
to_be_coalesced.push(sum_of_successors.clone());
97+
coalesced = sum_of_successors.clone().coalesce(coalesced);
9898
}
99-
to_be_coalesced.push(Expr::component(component_id));
10099

101100
if self.prefer_meters {
102101
if has_multiple_successors {
103-
to_be_coalesced.push(sum_of_coalesced_successors);
102+
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
104103
} else {
105-
to_be_coalesced.push(sum_of_successors);
106-
to_be_coalesced.push(Expr::number(0.0));
104+
coalesced = coalesced.coalesce(sum_of_successors);
105+
coalesced = coalesced.coalesce(Expr::number(0.0));
107106
}
108107
} else if has_multiple_successors {
109-
to_be_coalesced.push(sum_of_coalesced_successors);
108+
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
110109
} else {
111-
to_be_coalesced.push(Expr::number(0.0));
110+
coalesced = coalesced.coalesce(Expr::number(0.0));
112111
}
113112

114-
Ok(Some(Expr::coalesce(to_be_coalesced)))
113+
Ok(Some(coalesced))
115114
}
116115

117116
/// Returns a fallback expression for components with the following categories:
@@ -145,10 +144,10 @@ where
145144
.iter()
146145
.all(|sibling| component_ids.contains(&sibling.component_id()))
147146
{
148-
return Ok(Some(Expr::coalesce(vec![
147+
return Ok(Some(Expr::coalesce(
149148
Expr::component(component_id),
150149
Expr::number(0.0),
151-
])));
150+
)));
152151
}
153152

154153
for sibling in siblings {

0 commit comments

Comments
 (0)