From 930ad0f38bf73df088af22a29ffd9791c78fac34 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 11:43:30 +0200 Subject: [PATCH 01/13] Add param name to `GraphFormulaProvider` trait macros Signed-off-by: Sahas Subramanian --- .../formula/graph_formula_provider.rs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/logical_meter/formula/graph_formula_provider.rs b/src/logical_meter/formula/graph_formula_provider.rs index ba3d9bf..7513d38 100644 --- a/src/logical_meter/formula/graph_formula_provider.rs +++ b/src/logical_meter/formula/graph_formula_provider.rs @@ -14,7 +14,7 @@ use tokio::sync::mpsc; use super::{AggregationFormula, CoalesceFormula}; macro_rules! graph_formula_provider { - ($(($fnname:ident $(, $idsparam:ident)?)),+ $(,)?) => {$( + ($(($fnname:ident $(, ids:$idsparam:ident)?)),+ $(,)?) => {$( fn $fnname( _graph: &ComponentGraph, @@ -45,15 +45,19 @@ pub trait GraphFormulaProvider: Sized { (grid), (consumer), (producer), - (battery, _battery_ids), - (chp, _chp_ids), - (pv, _pv_inverter_ids), - (ev_charger, _ev_charger_ids), + (battery, ids: _battery_ids), + (chp, ids: _chp_ids), + (pv, ids: _pv_inverter_ids), + (ev_charger, ids: _ev_charger_ids), ); } macro_rules! impl_graph_formula_provider { - ($(($fnname:ident, $graphfnname:ident$(, $idsparam:ident)?)),+ $(,)?) => {$( + ($(( + $fnname:ident, + $graphfnname:ident + $(, ids:$idsparam:ident)? + )),+ $(,)?) => {$( fn $fnname( graph: &ComponentGraph, @@ -76,17 +80,17 @@ impl GraphFormulaProvider for AggregationFormula { (grid, grid_formula), (consumer, consumer_formula), (producer, producer_formula), - (battery, battery_formula, battery_ids), - (chp, chp_formula, chp_ids), - (pv, pv_formula, pv_inverter_ids), - (ev_charger, ev_charger_formula, ev_charger_ids), + (battery, battery_formula, ids: battery_ids), + (chp, chp_formula, ids: chp_ids), + (pv, pv_formula, ids: pv_inverter_ids), + (ev_charger, ev_charger_formula, ids: ev_charger_ids), ); } impl GraphFormulaProvider for CoalesceFormula { impl_graph_formula_provider!( (grid, grid_coalesce_formula), - (battery, battery_ac_coalesce_formula, battery_ids), - (pv, pv_ac_coalesce_formula, pv_inverter_ids), + (battery, battery_ac_coalesce_formula, ids: battery_ids), + (pv, pv_ac_coalesce_formula, ids: pv_inverter_ids), ); } From 152674c0d291cb7cb2ef73b948a6ee0f9cdf4587 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 11:52:59 +0200 Subject: [PATCH 02/13] Update to latest the component graph and formula engine Signed-off-by: Sahas Subramanian --- Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 33b469d..b91727c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ path = "src/lib.rs" [dependencies] chrono = "0.4" -frequenz-microgrid-component-graph = { git = "https://github.com/frequenz-floss/frequenz-microgrid-component-graph-rs.git", rev = "94dc826" } -frequenz-microgrid-formula-engine = { git = "https://github.com/frequenz-floss/frequenz-microgrid-formula-engine-rs.git", rev = "e0567c0" } +frequenz-microgrid-component-graph = { git = "https://github.com/frequenz-floss/frequenz-microgrid-component-graph-rs.git", rev = "b2fd616" } +frequenz-microgrid-formula-engine = { git = "https://github.com/frequenz-floss/frequenz-microgrid-formula-engine-rs.git", rev = "463414a" } frequenz-resampling = { git = "https://github.com/frequenz-floss/frequenz-resampling-rs.git", rev = "ce84d66" } prost = "0.13" prost-types = "0.13" From 9bf9261d350cf00ecbfb3168524507b2c09fcd43 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 11:54:11 +0200 Subject: [PATCH 03/13] Support formula generation for single components Signed-off-by: Sahas Subramanian --- .../formula/graph_formula_provider.rs | 11 +++++++-- src/logical_meter/logical_meter_handle.rs | 23 +++++++++---------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/logical_meter/formula/graph_formula_provider.rs b/src/logical_meter/formula/graph_formula_provider.rs index 7513d38..00eeff3 100644 --- a/src/logical_meter/formula/graph_formula_provider.rs +++ b/src/logical_meter/formula/graph_formula_provider.rs @@ -14,13 +14,14 @@ use tokio::sync::mpsc; use super::{AggregationFormula, CoalesceFormula}; macro_rules! graph_formula_provider { - ($(($fnname:ident $(, ids:$idsparam:ident)?)),+ $(,)?) => {$( + ($(($fnname:ident $(, ids:$idsparam:ident)? $(, id:$idparam:ident)?)),+ $(,)?) => {$( fn $fnname( _graph: &ComponentGraph, _metric: M, _instructions_tx: mpsc::Sender, $($idsparam: Option>,)? + $($idparam: u64,)? ) -> Result { return Err(Error::component_graph_error( format!( @@ -49,6 +50,7 @@ pub trait GraphFormulaProvider: Sized { (chp, ids: _chp_ids), (pv, ids: _pv_inverter_ids), (ev_charger, ids: _ev_charger_ids), + (component, id: _component_id), ); } @@ -57,6 +59,7 @@ macro_rules! impl_graph_formula_provider { $fnname:ident, $graphfnname:ident $(, ids:$idsparam:ident)? + $(, id:$idparam:ident)? )),+ $(,)?) => {$( fn $fnname( @@ -64,14 +67,16 @@ macro_rules! impl_graph_formula_provider { _metric: M, instructions_tx: mpsc::Sender, $($idsparam: Option>,)? + $($idparam: u64,)? ) -> Result { - let formula = graph.$graphfnname($($idsparam)?).map_err(|e| { + let formula = graph.$graphfnname($($idsparam)?$($idparam)?).map_err(|e| { Error::component_graph_error( format!("Could not get {} formula: {e}", stringify!($fnname)) ) })?; Ok(Self::new(formula, M::METRIC, instructions_tx)) } + )+}; } @@ -84,6 +89,7 @@ impl GraphFormulaProvider for AggregationFormula { (chp, chp_formula, ids: chp_ids), (pv, pv_formula, ids: pv_inverter_ids), (ev_charger, ev_charger_formula, ids: ev_charger_ids), + (component, component_formula, id: component_id), ); } @@ -92,5 +98,6 @@ impl GraphFormulaProvider for CoalesceFormula { (grid, grid_coalesce_formula), (battery, battery_ac_coalesce_formula, ids: battery_ids), (pv, pv_ac_coalesce_formula, ids: pv_inverter_ids), + (component, component_ac_coalesce_formula, id: component_id), ); } diff --git a/src/logical_meter/logical_meter_handle.rs b/src/logical_meter/logical_meter_handle.rs index 16208a8..b094606 100644 --- a/src/logical_meter/logical_meter_handle.rs +++ b/src/logical_meter/logical_meter_handle.rs @@ -2,7 +2,6 @@ // Copyright © 2025 Frequenz Energy-as-a-Service GmbH use crate::logical_meter::formula::graph_formula_provider::GraphFormulaProvider; -use crate::proto::common::v1::metrics::Metric; use crate::{ client::MicrogridClientHandle, error::Error, @@ -12,7 +11,7 @@ use frequenz_microgrid_component_graph::{self, ComponentGraph}; use std::collections::BTreeSet; use tokio::sync::mpsc; -use super::{AggregationFormula, LogicalMeterConfig, logical_meter_actor::LogicalMeterActor}; +use super::{LogicalMeterConfig, logical_meter_actor::LogicalMeterActor}; /// This provides an interface stream high-level metrics from a microgrid. #[derive(Clone)] @@ -150,18 +149,18 @@ impl LogicalMeterHandle { M::FormulaType::producer(&self.graph, metric, self.instructions_tx.clone()) } - pub fn coalesce( + /// Returns a receiver that streams samples for the given `metric` for the + /// given component ID. + pub fn component( &mut self, - component_ids: BTreeSet, - metric: Metric, - ) -> Result { - let formula = self.graph.coalesce(component_ids).map_err(|e| { - Error::component_graph_error(format!("Could not derive coalesce formula: {e}")) - })?; - Ok(AggregationFormula::new( - formula, + component_id: u64, + metric: M, + ) -> Result { + M::FormulaType::component( + &self.graph, metric, self.instructions_tx.clone(), - )) + component_id, + ) } } From 7883f782da59e2bb4e94a58b4b4296e0143253e6 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 11:59:00 +0200 Subject: [PATCH 04/13] Some formula doc-comment improvements Signed-off-by: Sahas Subramanian --- src/logical_meter/logical_meter_handle.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/logical_meter/logical_meter_handle.rs b/src/logical_meter/logical_meter_handle.rs index b094606..5fb87d1 100644 --- a/src/logical_meter/logical_meter_handle.rs +++ b/src/logical_meter/logical_meter_handle.rs @@ -131,8 +131,8 @@ impl LogicalMeterHandle { ) } - /// Returns a receiver that streams samples for the given `metric` for all - /// the consumers in the microgrid. + /// Returns a receiver that streams samples for the given `metric` for the + /// logical `consumer` in the microgrid. pub fn consumer( &mut self, metric: M, @@ -140,8 +140,8 @@ impl LogicalMeterHandle { M::FormulaType::consumer(&self.graph, metric, self.instructions_tx.clone()) } - /// Returns a receiver that streams samples for the given `metric` for all - /// producers in the microgrid. + /// Returns a receiver that streams samples for the given `metric` for the + /// logical `producer` in the microgrid. pub fn producer( &mut self, metric: M, From c16cf54e8bdb30b7ec18d32412a18b5ae1251d30 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 13:30:47 +0200 Subject: [PATCH 05/13] =?UTF-8?q?Rename=20`Formula`=20=E2=86=92=20`Formula?= =?UTF-8?q?Subscriber`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And make it internal. There will be a new `Formula` trait that implements additional formula operations that will depend on `FormulaSubscriber`. Signed-off-by: Sahas Subramanian --- src/logical_meter/formula.rs | 2 +- src/logical_meter/formula/aggregation_formula.rs | 4 ++-- src/logical_meter/formula/coalesce_formula.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/logical_meter/formula.rs b/src/logical_meter/formula.rs index 5429df6..c9add32 100644 --- a/src/logical_meter/formula.rs +++ b/src/logical_meter/formula.rs @@ -13,6 +13,6 @@ use crate::{Error, Sample}; use tokio::sync::broadcast; /// Defines a formula that can be subscribed to for receiving samples. -pub trait Formula: std::fmt::Display { +pub(crate) trait FormulaSubscriber: std::fmt::Display { fn subscribe(&self) -> impl Future, Error>> + Send; } diff --git a/src/logical_meter/formula/aggregation_formula.rs b/src/logical_meter/formula/aggregation_formula.rs index f343d5e..7f8f9e4 100644 --- a/src/logical_meter/formula/aggregation_formula.rs +++ b/src/logical_meter/formula/aggregation_formula.rs @@ -3,7 +3,7 @@ //! An formula that supports aggregation operations. -use super::Formula; +use super::FormulaSubscriber; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -36,7 +36,7 @@ impl AggregationFormula { } } -impl Formula for AggregationFormula { +impl FormulaSubscriber for AggregationFormula { async fn subscribe(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); diff --git a/src/logical_meter/formula/coalesce_formula.rs b/src/logical_meter/formula/coalesce_formula.rs index 877c741..7624a04 100644 --- a/src/logical_meter/formula/coalesce_formula.rs +++ b/src/logical_meter/formula/coalesce_formula.rs @@ -3,7 +3,7 @@ //! An coalesce formula. -use super::Formula; +use super::FormulaSubscriber; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -36,7 +36,7 @@ impl CoalesceFormula { } } -impl Formula for CoalesceFormula { +impl FormulaSubscriber for CoalesceFormula { async fn subscribe(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); From 9b6df354d49b5492215076a2c27437cb1d9e26b9 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 13:53:28 +0200 Subject: [PATCH 06/13] Add a trait to connect LM formulas to CG formulas Signed-off-by: Sahas Subramanian --- src/logical_meter/formula.rs | 5 +++++ src/logical_meter/formula/aggregation_formula.rs | 6 +++++- src/logical_meter/formula/coalesce_formula.rs | 6 +++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/logical_meter/formula.rs b/src/logical_meter/formula.rs index c9add32..42763b2 100644 --- a/src/logical_meter/formula.rs +++ b/src/logical_meter/formula.rs @@ -12,6 +12,11 @@ pub use coalesce_formula::CoalesceFormula; use crate::{Error, Sample}; use tokio::sync::broadcast; +/// Connects logical meter formulas to the component graph formulas. +pub(crate) trait GraphFormulaProvider: std::fmt::Display { + type GraphFormulaType: frequenz_microgrid_component_graph::Formula; +} + /// Defines a formula that can be subscribed to for receiving samples. pub(crate) trait FormulaSubscriber: std::fmt::Display { fn subscribe(&self) -> impl Future, Error>> + Send; diff --git a/src/logical_meter/formula/aggregation_formula.rs b/src/logical_meter/formula/aggregation_formula.rs index 7f8f9e4..b2c72c4 100644 --- a/src/logical_meter/formula/aggregation_formula.rs +++ b/src/logical_meter/formula/aggregation_formula.rs @@ -3,7 +3,7 @@ //! An formula that supports aggregation operations. -use super::FormulaSubscriber; +use super::{FormulaSubscriber, GraphFormulaProvider}; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -36,6 +36,10 @@ impl AggregationFormula { } } +impl GraphFormulaProvider for AggregationFormula { + type GraphFormulaType = frequenz_microgrid_component_graph::AggregationFormula; +} + impl FormulaSubscriber for AggregationFormula { async fn subscribe(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); diff --git a/src/logical_meter/formula/coalesce_formula.rs b/src/logical_meter/formula/coalesce_formula.rs index 7624a04..d944189 100644 --- a/src/logical_meter/formula/coalesce_formula.rs +++ b/src/logical_meter/formula/coalesce_formula.rs @@ -3,7 +3,7 @@ //! An coalesce formula. -use super::FormulaSubscriber; +use super::{FormulaSubscriber, GraphFormulaProvider}; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -36,6 +36,10 @@ impl CoalesceFormula { } } +impl GraphFormulaProvider for CoalesceFormula { + type GraphFormulaType = frequenz_microgrid_component_graph::CoalesceFormula; +} + impl FormulaSubscriber for CoalesceFormula { async fn subscribe(&self) -> Result, Error> { let (tx, rx) = oneshot::channel(); From e023ea50a275f1223408696420e98b9f32f7a929 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 13:55:18 +0200 Subject: [PATCH 07/13] Add a `FormulaParams` type to create LM formulas from Signed-off-by: Sahas Subramanian --- src/logical_meter/formula.rs | 27 +++++++++++++++++-- .../formula/aggregation_formula.rs | 20 ++++++++++++++ src/logical_meter/formula/coalesce_formula.rs | 20 ++++++++++++++ 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/src/logical_meter/formula.rs b/src/logical_meter/formula.rs index 42763b2..d9be0df 100644 --- a/src/logical_meter/formula.rs +++ b/src/logical_meter/formula.rs @@ -9,8 +9,10 @@ pub(crate) mod graph_formula_provider; pub use aggregation_formula::AggregationFormula; pub use coalesce_formula::CoalesceFormula; -use crate::{Error, Sample}; -use tokio::sync::broadcast; +use crate::{Error, Sample, proto::common::v1::metrics::Metric}; +use tokio::sync::{broadcast, mpsc}; + +use super::logical_meter_actor; /// Connects logical meter formulas to the component graph formulas. pub(crate) trait GraphFormulaProvider: std::fmt::Display { @@ -21,3 +23,24 @@ pub(crate) trait GraphFormulaProvider: std::fmt::Display { pub(crate) trait FormulaSubscriber: std::fmt::Display { fn subscribe(&self) -> impl Future, Error>> + Send; } + +/// Parameters for creating a logical meter formula. +pub(super) struct FormulaParams { + pub(super) formula: F::GraphFormulaType, + pub(super) metric: Metric, + pub(super) instructions_tx: mpsc::Sender, +} + +impl FormulaParams { + pub(super) fn new( + formula: F::GraphFormulaType, + metric: Metric, + instructions_tx: mpsc::Sender, + ) -> Self { + Self { + formula, + metric, + instructions_tx, + } + } +} diff --git a/src/logical_meter/formula/aggregation_formula.rs b/src/logical_meter/formula/aggregation_formula.rs index b2c72c4..42460ec 100644 --- a/src/logical_meter/formula/aggregation_formula.rs +++ b/src/logical_meter/formula/aggregation_formula.rs @@ -60,6 +60,26 @@ impl FormulaSubscriber for AggregationFormula { } } +impl From> for AggregationFormula { + fn from(params: FormulaParams) -> Self { + Self { + formula: params.formula, + metric: params.metric, + instructions_tx: params.instructions_tx, + } + } +} + +impl From for FormulaParams { + fn from(formula: AggregationFormula) -> Self { + FormulaParams { + formula: formula.formula, + metric: formula.metric, + instructions_tx: formula.instructions_tx, + } + } +} + impl std::ops::Add for AggregationFormula { type Output = Result; diff --git a/src/logical_meter/formula/coalesce_formula.rs b/src/logical_meter/formula/coalesce_formula.rs index d944189..765da35 100644 --- a/src/logical_meter/formula/coalesce_formula.rs +++ b/src/logical_meter/formula/coalesce_formula.rs @@ -59,3 +59,23 @@ impl FormulaSubscriber for CoalesceFormula { Ok(receiver) } } + +impl From> for CoalesceFormula { + fn from(params: FormulaParams) -> Self { + Self { + formula: params.formula, + metric: params.metric, + instructions_tx: params.instructions_tx, + } + } +} + +impl From for FormulaParams { + fn from(formula: CoalesceFormula) -> Self { + FormulaParams { + formula: formula.formula, + metric: formula.metric, + instructions_tx: formula.instructions_tx, + } + } +} From 4bb4729808bf9771bcba970d9440bd389b814407 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 15:16:21 +0200 Subject: [PATCH 08/13] Make `FormulaParams` the only way to create formulas Signed-off-by: Sahas Subramanian --- .../formula/aggregation_formula.rs | 20 +++---------------- src/logical_meter/formula/coalesce_formula.rs | 16 +-------------- .../formula/graph_formula_provider.rs | 3 ++- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/src/logical_meter/formula/aggregation_formula.rs b/src/logical_meter/formula/aggregation_formula.rs index 42460ec..1010878 100644 --- a/src/logical_meter/formula/aggregation_formula.rs +++ b/src/logical_meter/formula/aggregation_formula.rs @@ -3,7 +3,7 @@ //! An formula that supports aggregation operations. -use super::{FormulaSubscriber, GraphFormulaProvider}; +use super::{FormulaParams, FormulaSubscriber, GraphFormulaProvider}; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -22,20 +22,6 @@ impl std::fmt::Display for AggregationFormula { } } -impl AggregationFormula { - pub(crate) fn new( - formula: frequenz_microgrid_component_graph::AggregationFormula, - metric: Metric, - instructions_tx: mpsc::Sender, - ) -> Self { - Self { - formula, - metric, - instructions_tx, - } - } -} - impl GraphFormulaProvider for AggregationFormula { type GraphFormulaType = frequenz_microgrid_component_graph::AggregationFormula; } @@ -91,7 +77,7 @@ impl std::ops::Add for AggregationFormula { ))); } let new_formula = self.formula + other.formula; - Ok(Self::new(new_formula, self.metric, self.instructions_tx)) + Ok(FormulaParams::new(new_formula, self.metric, self.instructions_tx).into()) } } @@ -106,7 +92,7 @@ impl std::ops::Sub for AggregationFormula { ))); } let new_formula = self.formula - other.formula; - Ok(Self::new(new_formula, self.metric, self.instructions_tx)) + Ok(FormulaParams::new(new_formula, self.metric, self.instructions_tx).into()) } } diff --git a/src/logical_meter/formula/coalesce_formula.rs b/src/logical_meter/formula/coalesce_formula.rs index 765da35..551a2dc 100644 --- a/src/logical_meter/formula/coalesce_formula.rs +++ b/src/logical_meter/formula/coalesce_formula.rs @@ -3,7 +3,7 @@ //! An coalesce formula. -use super::{FormulaSubscriber, GraphFormulaProvider}; +use super::{FormulaParams, FormulaSubscriber, GraphFormulaProvider}; use crate::{ Error, Sample, logical_meter::logical_meter_actor, proto::common::v1::metrics::Metric, }; @@ -22,20 +22,6 @@ impl std::fmt::Display for CoalesceFormula { } } -impl CoalesceFormula { - pub(crate) fn new( - formula: frequenz_microgrid_component_graph::CoalesceFormula, - metric: Metric, - instructions_tx: mpsc::Sender, - ) -> Self { - Self { - formula, - metric, - instructions_tx, - } - } -} - impl GraphFormulaProvider for CoalesceFormula { type GraphFormulaType = frequenz_microgrid_component_graph::CoalesceFormula; } diff --git a/src/logical_meter/formula/graph_formula_provider.rs b/src/logical_meter/formula/graph_formula_provider.rs index 00eeff3..44887cd 100644 --- a/src/logical_meter/formula/graph_formula_provider.rs +++ b/src/logical_meter/formula/graph_formula_provider.rs @@ -4,6 +4,7 @@ //! A composable formula type, that can be subscribed to. use crate::Error; +use crate::logical_meter::formula::FormulaParams; use crate::logical_meter::logical_meter_actor; use crate::proto::common::v1::microgrid::components::Component; use crate::proto::common::v1::microgrid::components::ComponentConnection; @@ -74,7 +75,7 @@ macro_rules! impl_graph_formula_provider { format!("Could not get {} formula: {e}", stringify!($fnname)) ) })?; - Ok(Self::new(formula, M::METRIC, instructions_tx)) + Ok(FormulaParams::new(formula, M::METRIC, instructions_tx).into()) } )+}; From 1c4e664e03281c91c95853383176fe106b90415f Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 15:42:14 +0200 Subject: [PATCH 09/13] Add a `Formula` trait exposing generic formula operations This just exposes the same methods from the component graph's Formula trait, but with metric comparison and retaining the ability to subscribe to them. Signed-off-by: Sahas Subramanian --- src/logical_meter/formula.rs | 67 ++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/logical_meter/formula.rs b/src/logical_meter/formula.rs index d9be0df..efd3cbc 100644 --- a/src/logical_meter/formula.rs +++ b/src/logical_meter/formula.rs @@ -3,6 +3,7 @@ //! Formula module for the logical meter. +use frequenz_microgrid_component_graph::Formula as _; mod aggregation_formula; mod coalesce_formula; pub(crate) mod graph_formula_provider; @@ -44,3 +45,69 @@ impl FormulaParams { } } } + +/// A trait that defines generic formula operations. +pub trait Formula: std::fmt::Display + Sized { + fn coalesce(self, other: Self) -> Result; + fn min(self, other: Self) -> Result; + fn max(self, other: Self) -> Result; + fn subscribe(&self) -> impl Future, Error>> + Send; +} + +impl Formula for T +where + T: FormulaSubscriber + + GraphFormulaProvider + + From> + + Into> + + std::fmt::Display, +{ + fn coalesce(self, other: Self) -> Result { + let mut params_self: FormulaParams = self.into(); + let params_other: FormulaParams = other.into(); + + if params_self.metric != params_other.metric { + return Err(Error::invalid_metric(format!( + "Cannot coalesce formulas with different metrics: {} and {}", + params_self.metric.as_str_name(), + params_other.metric.as_str_name() + ))); + } + params_self.formula = params_self.formula.coalesce(params_other.formula); + Ok(params_self.into()) + } + + fn min(self, other: Self) -> Result { + let mut params_self: FormulaParams = self.into(); + let params_other: FormulaParams = other.into(); + + if params_self.metric != params_other.metric { + return Err(Error::invalid_metric(format!( + "Cannot take min of formulas with different metrics: {} and {}", + params_self.metric.as_str_name(), + params_other.metric.as_str_name() + ))); + } + params_self.formula = params_self.formula.min(params_other.formula); + Ok(params_self.into()) + } + + fn max(self, other: Self) -> Result { + let mut params_self: FormulaParams = self.into(); + let params_other: FormulaParams = other.into(); + + if params_self.metric != params_other.metric { + return Err(Error::invalid_metric(format!( + "Cannot take max of formulas with different metrics: {} and {}", + params_self.metric.as_str_name(), + params_other.metric.as_str_name() + ))); + } + params_self.formula = params_self.formula.max(params_other.formula); + Ok(params_self.into()) + } + + fn subscribe(&self) -> impl Future, Error>> + Send { + ::subscribe(self) + } +} From a23314b1c5db98a4e4761f448421d5aee6d2c0b9 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Mon, 28 Jul 2025 16:11:25 +0200 Subject: [PATCH 10/13] Update example to include a voltage coalesce formula Signed-off-by: Sahas Subramanian --- Cargo.toml | 2 ++ examples/logical_meter.rs | 28 +++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b91727c..55e4dda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,8 @@ tonic = "0.13" tracing = { version = "0.1" } tracing-subscriber = { version = "0.3" } +[dev-dependencies] +tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] } [build-dependencies] tonic-build = "0.13" diff --git a/examples/logical_meter.rs b/examples/logical_meter.rs index 6e97f4a..07ebdb5 100644 --- a/examples/logical_meter.rs +++ b/examples/logical_meter.rs @@ -5,12 +5,17 @@ use chrono::TimeDelta; use frequenz_microgrid::{ Error, Formula, LogicalMeterConfig, LogicalMeterHandle, MicrogridClientHandle, metric, }; +use tracing_subscriber::{ + EnvFilter, + fmt::{self}, + prelude::*, +}; #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt::fmt() - .with_file(true) - .with_line_number(true) + tracing_subscriber::registry() + .with(EnvFilter::new("info,frequenz_microgrid=debug")) + .with(fmt::layer().with_file(true).with_line_number(true)) .init(); let client = MicrogridClientHandle::new("http://[::1]:8800"); @@ -36,7 +41,7 @@ async fn main() -> Result<(), Error> { let mut battery_rx = formula_battery.subscribe().await?; let mut consumer_rx = formula_consumer.subscribe().await?; - for _ in 0..10 { + for _ in 0..3 { let sample = rx.recv().await.unwrap(); let grid_sample = grid_rx.recv().await.unwrap(); let battery_sample = battery_rx.recv().await.unwrap(); @@ -49,9 +54,22 @@ async fn main() -> Result<(), Error> { sample.value().unwrap() ); } + let formula_grid_voltage = logical_meter + .battery(None, metric::AcVoltagePhase1N)? + .coalesce(logical_meter.pv(None, metric::AcVoltagePhase1N)?)?; - let formula_grid_voltage = logical_meter.grid(metric::AcVoltagePhase1N)?; + tracing::info!("formula_grid_voltage: {}", formula_grid_voltage); let mut grid_voltage_rx = formula_grid_voltage.subscribe().await?; + for _ in 0..3 { + let sample = grid_voltage_rx.recv().await.unwrap(); + tracing::info!("grid voltage: {}", sample.value().unwrap()); + } + + drop(rx); + drop(grid_rx); + drop(battery_rx); + drop(consumer_rx); + loop { let sample = grid_voltage_rx.recv().await.unwrap(); tracing::info!("grid voltage: {}", sample.value().unwrap()); From 625aa6ec64e2e70a55639b4d59a722a88f31aac7 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 29 Jul 2025 16:01:45 +0200 Subject: [PATCH 11/13] =?UTF-8?q?Rename=20trait=20`AcMetric`=20=E2=86=92?= =?UTF-8?q?=20`Metric`=20and=20make=20it=20publicly=20accessible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sahas Subramanian --- .../formula/graph_formula_provider.rs | 4 ++-- src/logical_meter/logical_meter_handle.rs | 16 ++++++++-------- src/logical_meter/metric.rs | 4 ++-- src/logical_meter/metric/metric_trait.rs | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/logical_meter/formula/graph_formula_provider.rs b/src/logical_meter/formula/graph_formula_provider.rs index 44887cd..8afb605 100644 --- a/src/logical_meter/formula/graph_formula_provider.rs +++ b/src/logical_meter/formula/graph_formula_provider.rs @@ -17,7 +17,7 @@ use super::{AggregationFormula, CoalesceFormula}; macro_rules! graph_formula_provider { ($(($fnname:ident $(, ids:$idsparam:ident)? $(, id:$idparam:ident)?)),+ $(,)?) => {$( - fn $fnname( + fn $fnname( _graph: &ComponentGraph, _metric: M, _instructions_tx: mpsc::Sender, @@ -63,7 +63,7 @@ macro_rules! impl_graph_formula_provider { $(, id:$idparam:ident)? )),+ $(,)?) => {$( - fn $fnname( + fn $fnname( graph: &ComponentGraph, _metric: M, instructions_tx: mpsc::Sender, diff --git a/src/logical_meter/logical_meter_handle.rs b/src/logical_meter/logical_meter_handle.rs index 5fb87d1..bfef271 100644 --- a/src/logical_meter/logical_meter_handle.rs +++ b/src/logical_meter/logical_meter_handle.rs @@ -55,7 +55,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` at the grid /// connection point. - pub fn grid( + pub fn grid( &mut self, metric: M, ) -> Result { @@ -66,7 +66,7 @@ impl LogicalMeterHandle { /// given battery IDs. /// /// When `component_ids` is `None`, all batteries in the microgrid are used. - pub fn battery( + pub fn battery( &mut self, component_ids: Option>, metric: M, @@ -83,7 +83,7 @@ impl LogicalMeterHandle { /// given CHP IDs. /// /// When `component_ids` is `None`, all CHPs in the microgrid are used. - pub fn chp( + pub fn chp( &mut self, component_ids: Option>, metric: M, @@ -100,7 +100,7 @@ impl LogicalMeterHandle { /// given PV IDs. /// /// When `component_ids` is `None`, all PVs in the microgrid are used. - pub fn pv( + pub fn pv( &mut self, component_ids: Option>, metric: M, @@ -118,7 +118,7 @@ impl LogicalMeterHandle { /// /// When `component_ids` is `None`, all EV chargers in the microgrid are /// used. - pub fn ev_charger( + pub fn ev_charger( &mut self, component_ids: Option>, metric: M, @@ -133,7 +133,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// logical `consumer` in the microgrid. - pub fn consumer( + pub fn consumer( &mut self, metric: M, ) -> Result { @@ -142,7 +142,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// logical `producer` in the microgrid. - pub fn producer( + pub fn producer( &mut self, metric: M, ) -> Result { @@ -151,7 +151,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// given component ID. - pub fn component( + pub fn component( &mut self, component_id: u64, metric: M, diff --git a/src/logical_meter/metric.rs b/src/logical_meter/metric.rs index cc65f63..7c09e2a 100644 --- a/src/logical_meter/metric.rs +++ b/src/logical_meter/metric.rs @@ -8,7 +8,7 @@ pub(crate) mod metric_trait; use crate::proto::common::v1::metrics::Metric as MetricPb; use super::formula; -use metric_trait::AcMetric; +pub use metric_trait::Metric; macro_rules! define_metric { ($({name: $metric_name:ident, formula: $formula:ident}),+ $(,)?) => { @@ -18,7 +18,7 @@ macro_rules! define_metric { pub struct $metric_name; // Implement the AcMetric trait for the metric - impl AcMetric for $metric_name { + impl Metric for $metric_name { type FormulaType = formula::$formula; const METRIC: MetricPb = MetricPb::$metric_name; diff --git a/src/logical_meter/metric/metric_trait.rs b/src/logical_meter/metric/metric_trait.rs index cdc5371..fccb650 100644 --- a/src/logical_meter/metric/metric_trait.rs +++ b/src/logical_meter/metric/metric_trait.rs @@ -7,7 +7,7 @@ use super::formula; use crate::proto::common::v1::metrics::Metric as MetricPb; -pub trait AcMetric: std::fmt::Display { +pub trait Metric: std::fmt::Display { type FormulaType: formula::Formula + formula::graph_formula_provider::GraphFormulaProvider; const METRIC: MetricPb; From 75c7a7dd897a6bbbe150fe2e57f4d39738ae6159 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 29 Jul 2025 16:04:45 +0200 Subject: [PATCH 12/13] Move `Metric` trait to `metric` module Because we are exposing it publicly now, it doesn't have to be hidden away in a separate module anymore. Signed-off-by: Sahas Subramanian --- .../formula/graph_formula_provider.rs | 4 ++-- src/logical_meter/logical_meter_handle.rs | 19 ++++++++----------- src/logical_meter/metric.rs | 9 ++++++--- src/logical_meter/metric/metric_trait.rs | 14 -------------- 4 files changed, 16 insertions(+), 30 deletions(-) delete mode 100644 src/logical_meter/metric/metric_trait.rs diff --git a/src/logical_meter/formula/graph_formula_provider.rs b/src/logical_meter/formula/graph_formula_provider.rs index 8afb605..5b0fd94 100644 --- a/src/logical_meter/formula/graph_formula_provider.rs +++ b/src/logical_meter/formula/graph_formula_provider.rs @@ -17,7 +17,7 @@ use super::{AggregationFormula, CoalesceFormula}; macro_rules! graph_formula_provider { ($(($fnname:ident $(, ids:$idsparam:ident)? $(, id:$idparam:ident)?)),+ $(,)?) => {$( - fn $fnname( + fn $fnname( _graph: &ComponentGraph, _metric: M, _instructions_tx: mpsc::Sender, @@ -63,7 +63,7 @@ macro_rules! impl_graph_formula_provider { $(, id:$idparam:ident)? )),+ $(,)?) => {$( - fn $fnname( + fn $fnname( graph: &ComponentGraph, _metric: M, instructions_tx: mpsc::Sender, diff --git a/src/logical_meter/logical_meter_handle.rs b/src/logical_meter/logical_meter_handle.rs index bfef271..55b9634 100644 --- a/src/logical_meter/logical_meter_handle.rs +++ b/src/logical_meter/logical_meter_handle.rs @@ -55,10 +55,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` at the grid /// connection point. - pub fn grid( - &mut self, - metric: M, - ) -> Result { + pub fn grid(&mut self, metric: M) -> Result { M::FormulaType::grid(&self.graph, metric, self.instructions_tx.clone()) } @@ -66,7 +63,7 @@ impl LogicalMeterHandle { /// given battery IDs. /// /// When `component_ids` is `None`, all batteries in the microgrid are used. - pub fn battery( + pub fn battery( &mut self, component_ids: Option>, metric: M, @@ -83,7 +80,7 @@ impl LogicalMeterHandle { /// given CHP IDs. /// /// When `component_ids` is `None`, all CHPs in the microgrid are used. - pub fn chp( + pub fn chp( &mut self, component_ids: Option>, metric: M, @@ -100,7 +97,7 @@ impl LogicalMeterHandle { /// given PV IDs. /// /// When `component_ids` is `None`, all PVs in the microgrid are used. - pub fn pv( + pub fn pv( &mut self, component_ids: Option>, metric: M, @@ -118,7 +115,7 @@ impl LogicalMeterHandle { /// /// When `component_ids` is `None`, all EV chargers in the microgrid are /// used. - pub fn ev_charger( + pub fn ev_charger( &mut self, component_ids: Option>, metric: M, @@ -133,7 +130,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// logical `consumer` in the microgrid. - pub fn consumer( + pub fn consumer( &mut self, metric: M, ) -> Result { @@ -142,7 +139,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// logical `producer` in the microgrid. - pub fn producer( + pub fn producer( &mut self, metric: M, ) -> Result { @@ -151,7 +148,7 @@ impl LogicalMeterHandle { /// Returns a receiver that streams samples for the given `metric` for the /// given component ID. - pub fn component( + pub fn component( &mut self, component_id: u64, metric: M, diff --git a/src/logical_meter/metric.rs b/src/logical_meter/metric.rs index 7c09e2a..f95fe7c 100644 --- a/src/logical_meter/metric.rs +++ b/src/logical_meter/metric.rs @@ -3,12 +3,15 @@ //! Metrics supported by the logical meter. -pub(crate) mod metric_trait; - use crate::proto::common::v1::metrics::Metric as MetricPb; use super::formula; -pub use metric_trait::Metric; + +pub trait Metric: std::fmt::Display { + type FormulaType: formula::Formula + formula::graph_formula_provider::GraphFormulaProvider; + + const METRIC: MetricPb; +} macro_rules! define_metric { ($({name: $metric_name:ident, formula: $formula:ident}),+ $(,)?) => { diff --git a/src/logical_meter/metric/metric_trait.rs b/src/logical_meter/metric/metric_trait.rs deleted file mode 100644 index fccb650..0000000 --- a/src/logical_meter/metric/metric_trait.rs +++ /dev/null @@ -1,14 +0,0 @@ -// License: MIT -// Copyright © 2025 Frequenz Energy-as-a-Service GmbH - -//! A trait specifying the output formula type and the corresponding PB metric, -//! for all metrics supported by the logical meter. - -use super::formula; -use crate::proto::common::v1::metrics::Metric as MetricPb; - -pub trait Metric: std::fmt::Display { - type FormulaType: formula::Formula + formula::graph_formula_provider::GraphFormulaProvider; - - const METRIC: MetricPb; -} From e1d3515c3168708604bbc03b015b4d9d0f26a923 Mon Sep 17 00:00:00 2001 From: Sahas Subramanian Date: Tue, 29 Jul 2025 16:06:37 +0200 Subject: [PATCH 13/13] Add additional bounds to `Metric` Signed-off-by: Sahas Subramanian --- src/logical_meter/metric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/logical_meter/metric.rs b/src/logical_meter/metric.rs index f95fe7c..16ec880 100644 --- a/src/logical_meter/metric.rs +++ b/src/logical_meter/metric.rs @@ -7,7 +7,7 @@ use crate::proto::common::v1::metrics::Metric as MetricPb; use super::formula; -pub trait Metric: std::fmt::Display { +pub trait Metric: std::fmt::Display + std::fmt::Debug + Clone + Copy + PartialEq + Eq { type FormulaType: formula::Formula + formula::graph_formula_provider::GraphFormulaProvider; const METRIC: MetricPb;