frequenz-microgrid-component-graph 0.6.0

A library for representing the components of a microgrid and the connections between them as a Directed Acyclic Graph (DAG).
Documentation
// License: MIT
// Copyright © 2025 Frequenz Energy-as-a-Service GmbH

//! This module defines the `Formula` type and its operations.

use super::expr::Expr;

/// A microgrid metric formula generated by traversing the component graph.
///
/// Formulas represent quantities derived from multiple components — both
/// aggregated metrics like power or current (which can be added and
/// subtracted), and coalesced metrics like AC voltage or frequency. They can be
/// combined with `+`, `-`, [`coalesce`][Self::coalesce], [`min`][Self::min] and
/// [`max`][Self::max], and converted to a string before being passed to an
/// evaluator.
#[derive(Debug, Clone, PartialEq)]
pub struct Formula {
    pub(crate) expr: Expr,
}

impl Formula {
    pub(crate) fn new(expr: Expr) -> Self {
        Formula { expr }
    }

    /// Returns a formula that evaluates to the first of `self` and `other` that
    /// has a value.
    #[must_use]
    pub fn coalesce(self, other: Self) -> Self {
        Formula::new(self.expr.coalesce(other.expr))
    }

    /// Returns a formula that evaluates to the minimum of `self` and `other`.
    #[must_use]
    pub fn min(self, other: Self) -> Self {
        Formula::new(self.expr.min(other.expr))
    }

    /// Returns a formula that evaluates to the maximum of `self` and `other`.
    #[must_use]
    pub fn max(self, other: Self) -> Self {
        Formula::new(self.expr.max(other.expr))
    }
}

impl From<Expr> for Formula {
    fn from(expr: Expr) -> Self {
        Formula { expr }
    }
}

impl From<Formula> for Expr {
    fn from(formula: Formula) -> Self {
        formula.expr
    }
}

impl std::ops::Add for Formula {
    type Output = Self;

    fn add(self, rhs: Self) -> Self::Output {
        Formula::new(self.expr + rhs.expr)
    }
}

impl std::ops::Sub for Formula {
    type Output = Self;

    fn sub(self, rhs: Self) -> Self::Output {
        Formula::new(self.expr - rhs.expr)
    }
}

impl std::fmt::Display for Formula {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.expr.fmt(f)
    }
}

impl From<Formula> for String {
    fn from(formula: Formula) -> Self {
        formula.expr.to_string()
    }
}

#[cfg(test)]
mod tests {
    use super::Formula;
    use crate::graph::formulas::expr::Expr;

    #[test]
    fn test_formula_arith() {
        let a = Formula::new(Expr::component(1));
        let b = Formula::new(Expr::component(2));

        assert_eq!((a.clone() + b.clone()).to_string(), "#1 + #2");
        assert_eq!((a - b).to_string(), "#1 - #2");
    }

    #[test]
    fn test_formula_combinators() {
        let a = Formula::new(Expr::component(1));
        let b = Formula::new(Expr::component(2));

        assert_eq!(
            a.clone().coalesce(b.clone()).to_string(),
            "COALESCE(#1, #2)"
        );
        assert_eq!(a.clone().min(b.clone()).to_string(), "MIN(#1, #2)");
        assert_eq!(a.max(b).to_string(), "MAX(#1, #2)");
    }
}