Skip to content
3 changes: 1 addition & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,7 @@ ErrorKind!(
(Internal, internal),
(InvalidComponent, invalid_component),
(InvalidConnection, invalid_connection),
(InvalidGraph, invalid_graph),
(MissingParameters, missing_parameters)
(InvalidGraph, invalid_graph)
);

/// An error that can occur during the creation or traversal of a
Expand Down
2 changes: 1 addition & 1 deletion src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ mod formulas;
pub mod iterators;

use crate::{ComponentGraphConfig, Edge, Node};
pub use formulas::{AggregationFormula, CoalesceFormula};
pub use formulas::{AggregationFormula, CoalesceFormula, Formula};
use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;

Expand Down
26 changes: 16 additions & 10 deletions src/graph/formulas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ mod formula;
mod generators;
mod traversal;

pub use formula::{AggregationFormula, CoalesceFormula};
use expr::Expr;
pub use formula::{AggregationFormula, CoalesceFormula, Formula};

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

/// Returns the coalesce formula for the given component IDs.
///
/// This formula uses the `COALESCE` function to return the first non-null
/// value from the components with the provided IDs.
pub fn coalesce(&self, component_ids: BTreeSet<u64>) -> Result<AggregationFormula, Error> {
generators::generic::CoalesceFormulaBuilder::try_new(self, component_ids)?.build()
}

/// Returns a string representing the EV charger formula for the graph.
/// Returns the EV charger formula for the graph.
pub fn ev_charger_formula(
&self,
ev_charger_ids: Option<BTreeSet<u64>>,
) -> Result<AggregationFormula, Error> {
generators::ev_charger::EVChargerFormulaBuilder::try_new(self, ev_charger_ids)?.build()
}

/// Returns the formula for a specific component by its ID.
pub fn component_formula(&self, component_id: u64) -> Result<AggregationFormula, Error> {
Ok(Expr::component(component_id).into())
}

/// Returns the grid coalesce formula for the graph.
///
/// This formula is used for non-aggregating metrics like AC voltage or
Expand Down Expand Up @@ -129,4 +127,12 @@ where
generators::pv_ac_coalesce::PVAcCoalesceFormulaBuilder::try_new(self, pv_inverter_ids)?
.build()
}

/// Returns the AC coalesce formula for a specific component by its ID.
pub fn component_ac_coalesce_formula(
&self,
component_id: u64,
) -> Result<CoalesceFormula, Error> {
Ok(Expr::component(component_id).into())
}
}
136 changes: 113 additions & 23 deletions src/graph/formulas/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ use crate::Node;

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum Expr {
/// An empty expression, which as a formula would evaluate to None.
None,

/// A negation of an expression.
Neg { param: Box<Expr> },

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

fn add(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::None, other) | (other, Self::None) => other,
// -a + -b = -(a + b)
(Self::Neg { param: lhs }, Self::Neg { param: rhs }) => -(*lhs + *rhs),
// -a + b = b - a
Expand Down Expand Up @@ -68,6 +72,8 @@ impl std::ops::Sub for Expr {

fn sub(self, rhs: Self) -> Self {
match (self, rhs) {
(Self::None, other) => -other,
(other, Self::None) => other,
// (a - b) - -c = a - b + c
(sub @ Self::Sub { .. }, Self::Neg { param }) => sub + *param,
// -a - (b - c) = c - b - a
Expand Down Expand Up @@ -98,6 +104,7 @@ impl std::ops::Neg for Expr {

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

/// Constructors for `FormulaExpression`.
impl Expr {
#[must_use]
pub(crate) fn number(value: f64) -> Self {
Self::Number { value }
}

#[must_use]
pub(crate) fn component(component_id: u64) -> Self {
Self::Component { component_id }
}

pub(crate) fn coalesce(params: Vec<Expr>) -> Self {
if let [param] = params.as_slice() {
param.clone()
} else {
Self::Coalesce { params }
#[must_use]
pub(crate) fn coalesce(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, other) | (other, Expr::None) => other,
(
Expr::Coalesce { mut params },
Expr::Coalesce {
params: other_params,
},
) => {
// If both parameters are coalesce expressions, merge them.
params.extend(other_params);
Self::Coalesce { params }
}
(Expr::Coalesce { mut params }, other) => {
// If the first parameter is a coalesce expression, add the second
// parameter to it.
params.push(other);
Self::Coalesce { params }
}
(
param,
Expr::Coalesce {
params: other_params,
},
) => {
// If the second parameter is a coalesce expression, add the first
// parameter to it.
let mut params = vec![param];
params.extend(other_params);
Self::Coalesce { params }
}
(first, second) => {
// If neither parameter is a coalesce expression, create a new one.
Self::Coalesce {
params: vec![first, second],
}
}
}
}

pub(crate) fn min(params: Vec<Expr>) -> Self {
Self::Min { params }
#[must_use]
pub(crate) fn min(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, expr) | (expr, Expr::None) => expr,
(
Expr::Min { mut params },
Expr::Min {
params: other_params,
},
) => {
// If both parameters are min expressions, merge them.
params.extend(other_params);
Self::Min { params }
}
(Expr::Min { mut params }, other) | (other, Expr::Min { mut params }) => {
// If one parameter is a min expression, add the other parameter
// to it.
params.push(other);
Self::Min { params }
}
(first, second) => {
// If neither parameter is a min expression, create a new one.
Self::Min {
params: vec![first, second],
}
}
}
}

pub(crate) fn max(params: Vec<Expr>) -> Self {
Self::Max { params }
#[must_use]
pub(crate) fn max(self, other: Expr) -> Self {
match (self, other) {
(Expr::None, expr) | (expr, Expr::None) => expr,
(
Expr::Max { mut params },
Expr::Max {
params: other_params,
},
) => {
// If both parameters are max expressions, merge them.
params.extend(other_params);
Self::Max { params }
}
(Expr::Max { mut params }, other) | (other, Expr::Max { mut params }) => {
// If one parameter is a max expression, add the other parameter
// to it.
params.push(other);
Self::Max { params }
}
(first, second) => {
// If neither parameter is a max expression, create a new one.
Self::Max {
params: vec![first, second],
}
}
}
}
}

Expand Down Expand Up @@ -178,6 +270,7 @@ impl Expr {
/// component, the whole expression is enclosed in brackets.
fn generate_string(&self, bracket_whole: bool) -> String {
match self {
Self::None => String::from("None"),
Self::Neg { param } => format!("-{}", param.generate_string(true)),
Self::Number { value } => {
if value.fract() == 0.0 {
Expand Down Expand Up @@ -365,29 +458,26 @@ mod tests {
let comp = Expr::component;
let coalesce = Expr::coalesce;
let number = Expr::number;
let min = Expr::min;
let max = Expr::max;

assert_expr(
&[comp(1)
- (coalesce(vec![comp(5), comp(7) + comp(6)]) + coalesce(vec![comp(2), comp(3)]))
+ coalesce(vec![
max(vec![number(0.0), comp(5)]),
max(vec![number(0.0), comp(7)]) + max(vec![number(0.0), comp(6)]),
])],
&[
comp(1) - (coalesce(comp(5), comp(7) + comp(6)) + coalesce(comp(2), comp(3)))
+ coalesce(
number(0.0).max(comp(5)),
number(0.0).max(comp(7)) + number(0.0).max(comp(6)),
),
],
concat!(
"#1 - (COALESCE(#5, #7 + #6) + COALESCE(#2, #3)) + ",
"COALESCE(MAX(0.0, #5), MAX(0.0, #7) + MAX(0.0, #6))"
),
);

assert_expr(
&[min(vec![number(0.0), comp(5), comp(7) + comp(6)])
- max(vec![
coalesce(vec![comp(5), comp(7) + comp(6)]),
comp(7),
number(22.44),
])],
&[number(0.0).min(comp(5)).min(comp(7) + comp(6))
- coalesce(comp(5), comp(7) + comp(6))
.max(comp(7))
.max(number(22.44))],
"MIN(0.0, #5, #7 + #6) - MAX(COALESCE(#5, #7 + #6), #7, 22.44)",
)
}
Expand Down
23 changes: 11 additions & 12 deletions src/graph/formulas/fallback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ where
.map(|node| {
(
Expr::from(node),
Expr::coalesce(vec![Expr::from(node), Expr::number(0.0)]),
Expr::coalesce(Expr::from(node), Expr::number(0.0)),
)
})
.reduce(|a, b| (a.0 + b.0, a.1 + b.1))
Expand All @@ -91,27 +91,26 @@ where

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

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

if !self.prefer_meters {
to_be_coalesced.push(sum_of_successors.clone());
coalesced = sum_of_successors.clone().coalesce(coalesced);
}
to_be_coalesced.push(Expr::component(component_id));

if self.prefer_meters {
if has_multiple_successors {
to_be_coalesced.push(sum_of_coalesced_successors);
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
} else {
to_be_coalesced.push(sum_of_successors);
to_be_coalesced.push(Expr::number(0.0));
coalesced = coalesced.coalesce(sum_of_successors);
coalesced = coalesced.coalesce(Expr::number(0.0));
}
} else if has_multiple_successors {
to_be_coalesced.push(sum_of_coalesced_successors);
coalesced = coalesced.coalesce(sum_of_coalesced_successors);
} else {
to_be_coalesced.push(Expr::number(0.0));
coalesced = coalesced.coalesce(Expr::number(0.0));
}

Ok(Some(Expr::coalesce(to_be_coalesced)))
Ok(Some(coalesced))
}

/// Returns a fallback expression for components with the following categories:
Expand Down Expand Up @@ -145,10 +144,10 @@ where
.iter()
.all(|sibling| component_ids.contains(&sibling.component_id()))
{
return Ok(Some(Expr::coalesce(vec![
return Ok(Some(Expr::coalesce(
Expr::component(component_id),
Expr::number(0.0),
])));
)));
}

for sibling in siblings {
Expand Down
Loading