Skip to main content

frequenz_microgrid_component_graph/
config.rs

1// License: MIT
2// Copyright © 2024 Frequenz Energy-as-a-Service GmbH
3
4//! This module contains the configuration options for the `ComponentGraph`.
5
6/// Configuration options for the `ComponentGraph`.
7#[derive(Clone, Debug, Default)]
8pub struct ComponentGraphConfig {
9    /// Whether to allow validation errors on components.  When this is `true`,
10    /// the graph will be built even if there are validation errors on
11    /// components.
12    pub(crate) allow_component_validation_failures: bool,
13
14    /// Whether to allow unconnected components in the graph, that are not
15    /// reachable from the root.
16    pub(crate) allow_unconnected_components: bool,
17
18    /// Whether to allow untyped inverters in the graph.  When this is `true`,
19    /// inverters that have `InverterType::Unspecified` will be assumed to be
20    /// Battery inverters.
21    pub(crate) allow_unspecified_inverters: bool,
22
23    /// Whether to disable fallback components in generated formulas.  When this
24    /// is `true`, the formulas will not include fallback components.
25    pub(crate) disable_fallback_components: bool,
26
27    /// Meters with successors can still have loads not represented in the
28    /// component graph.  These are called phantom loads.
29    ///
30    /// When this is `true`, phantom loads are included in formulas by excluding
31    /// the measurements of successor meters from the measurements of their
32    /// predecessor meters.
33    ///
34    /// When `false`, consumer formula is generated by excluding production
35    /// and battery components from the grid measurements.
36    pub(crate) include_phantom_loads_in_consumer_formula: bool,
37
38    /// Default policy for the per-category "component" formulas.
39    ///
40    /// When `false` (the default), the component measurement is the primary
41    /// source and the meter measurement is the fallback for the per-
42    /// category formulas (`battery_formula`, `chp_formula`, `pv_formula`,
43    /// `wind_turbine_formula`, `ev_charger_formula`, `steam_boiler_formula`).
44    /// When `true`, the meter is primary and the component is the fallback.
45    ///
46    /// Per-formula overrides live in [`formula_overrides`][Self::formula_overrides].
47    ///
48    /// Has no effect on `grid_formula`, `consumer_formula`,
49    /// `producer_formula`, or any of the coalesce formulas.
50    pub(crate) prefer_meters_in_component_formulas: bool,
51
52    /// Per-formula overrides for the meter/component preference; see
53    /// [`FormulaOverrides`].
54    pub(crate) formula_overrides: FormulaOverrides,
55}
56
57impl ComponentGraphConfig {
58    /// Effective "prefer meters" setting for [`ComponentGraph::pv_formula`][cg].
59    ///
60    /// [cg]: crate::ComponentGraph::pv_formula
61    pub(crate) fn prefer_meters_in_pv_formula(&self) -> bool {
62        self.formula_overrides
63            .prefer_meters_in_pv_formula
64            .unwrap_or(self.prefer_meters_in_component_formulas)
65    }
66
67    /// Effective "prefer meters" setting for [`ComponentGraph::battery_formula`][cg].
68    ///
69    /// [cg]: crate::ComponentGraph::battery_formula
70    pub(crate) fn prefer_meters_in_battery_formula(&self) -> bool {
71        self.formula_overrides
72            .prefer_meters_in_battery_formula
73            .unwrap_or(self.prefer_meters_in_component_formulas)
74    }
75
76    /// Effective "prefer meters" setting for [`ComponentGraph::chp_formula`][cg].
77    ///
78    /// [cg]: crate::ComponentGraph::chp_formula
79    pub(crate) fn prefer_meters_in_chp_formula(&self) -> bool {
80        self.formula_overrides
81            .prefer_meters_in_chp_formula
82            .unwrap_or(self.prefer_meters_in_component_formulas)
83    }
84
85    /// Effective "prefer meters" setting for [`ComponentGraph::ev_charger_formula`][cg].
86    ///
87    /// [cg]: crate::ComponentGraph::ev_charger_formula
88    pub(crate) fn prefer_meters_in_ev_charger_formula(&self) -> bool {
89        self.formula_overrides
90            .prefer_meters_in_ev_charger_formula
91            .unwrap_or(self.prefer_meters_in_component_formulas)
92    }
93
94    /// Effective "prefer meters" setting for [`ComponentGraph::wind_turbine_formula`][cg].
95    ///
96    /// [cg]: crate::ComponentGraph::wind_turbine_formula
97    pub(crate) fn prefer_meters_in_wind_turbine_formula(&self) -> bool {
98        self.formula_overrides
99            .prefer_meters_in_wind_turbine_formula
100            .unwrap_or(self.prefer_meters_in_component_formulas)
101    }
102
103    /// Effective "prefer meters" setting for [`ComponentGraph::steam_boiler_formula`][cg].
104    ///
105    /// [cg]: crate::ComponentGraph::steam_boiler_formula
106    pub(crate) fn prefer_meters_in_steam_boiler_formula(&self) -> bool {
107        self.formula_overrides
108            .prefer_meters_in_steam_boiler_formula
109            .unwrap_or(self.prefer_meters_in_component_formulas)
110    }
111
112    /// Returns a [`ComponentGraphConfigBuilder`] initialised with all
113    /// options set to their default values.
114    pub fn builder() -> ComponentGraphConfigBuilder {
115        ComponentGraphConfigBuilder::new()
116    }
117}
118
119/// Builder for [`ComponentGraphConfig`].
120///
121/// Each method sets the corresponding option and returns `self`, so calls
122/// can be chained. Call [`build`][Self::build] to obtain the final
123/// `ComponentGraphConfig`.
124#[derive(Clone, Debug)]
125pub struct ComponentGraphConfigBuilder {
126    inner: ComponentGraphConfig,
127}
128
129impl ComponentGraphConfigBuilder {
130    /// Creates a new builder with all options set to their default values.
131    #[allow(clippy::new_without_default)]
132    pub fn new() -> Self {
133        Self {
134            inner: ComponentGraphConfig::default(),
135        }
136    }
137
138    /// When `true`, the graph is built even if per-component validation
139    /// rules fail; failures are reported as `tracing::warn!` instead of
140    /// returning an error.
141    pub fn allow_component_validation_failures(mut self, value: bool) -> Self {
142        self.inner.allow_component_validation_failures = value;
143        self
144    }
145
146    /// When `true`, components that are not reachable from the root are
147    /// permitted; otherwise the graph fails to build.
148    pub fn allow_unconnected_components(mut self, value: bool) -> Self {
149        self.inner.allow_unconnected_components = value;
150        self
151    }
152
153    /// When `true`, inverters with `InverterType::Unspecified` are
154    /// treated as battery inverters instead of being rejected.
155    pub fn allow_unspecified_inverters(mut self, value: bool) -> Self {
156        self.inner.allow_unspecified_inverters = value;
157        self
158    }
159
160    /// When `true`, generated formulas omit fallback components.
161    pub fn disable_fallback_components(mut self, value: bool) -> Self {
162        self.inner.disable_fallback_components = value;
163        self
164    }
165
166    /// Controls how the consumer formula handles meters with successors,
167    /// which can carry loads not represented in the graph (phantom loads).
168    ///
169    /// When `true`, phantom loads are included by subtracting successor
170    /// meter measurements from their predecessor meter's measurements.
171    /// When `false`, the consumer formula instead excludes production and
172    /// battery components from the grid measurements.
173    pub fn include_phantom_loads_in_consumer_formula(mut self, value: bool) -> Self {
174        self.inner.include_phantom_loads_in_consumer_formula = value;
175        self
176    }
177
178    /// Sets the global meter-vs-component source preference for the
179    /// per-category formulas. See the field-level docs on
180    /// [`ComponentGraphConfig`] for the exact list of affected formulas.
181    pub fn prefer_meters_in_component_formulas(mut self, value: bool) -> Self {
182        self.inner.prefer_meters_in_component_formulas = value;
183        self
184    }
185
186    /// Sets the per-formula overrides for the meter/component preference.
187    /// Each override, when `Some(_)`, takes precedence over
188    /// [`prefer_meters_in_component_formulas`][Self::prefer_meters_in_component_formulas]
189    /// for that formula.
190    pub fn formula_overrides(mut self, overrides: FormulaOverrides) -> Self {
191        self.inner.formula_overrides = overrides;
192        self
193    }
194
195    /// Consumes the builder and returns the resulting [`ComponentGraphConfig`].
196    pub fn build(self) -> ComponentGraphConfig {
197        self.inner
198    }
199}
200
201/// Per-formula overrides for the meter/component preference in the
202/// per-category formulas.
203///
204/// Each field is `None` by default, meaning the corresponding formula
205/// follows the global `prefer_meters_in_component_formulas` setting on
206/// [`ComponentGraphConfig`]. Setting an entry to `Some(true)` forces
207/// the meter as primary for that formula; `Some(false)` forces the
208/// component.
209///
210/// Construct via [`FormulaOverrides::builder`] or
211/// [`FormulaOverrides::default`].
212#[derive(Clone, Default, Debug)]
213pub struct FormulaOverrides {
214    pub(crate) prefer_meters_in_pv_formula: Option<bool>,
215    pub(crate) prefer_meters_in_battery_formula: Option<bool>,
216    pub(crate) prefer_meters_in_chp_formula: Option<bool>,
217    pub(crate) prefer_meters_in_ev_charger_formula: Option<bool>,
218    pub(crate) prefer_meters_in_wind_turbine_formula: Option<bool>,
219    pub(crate) prefer_meters_in_steam_boiler_formula: Option<bool>,
220}
221
222impl FormulaOverrides {
223    /// Returns a [`FormulaOverridesBuilder`] with no overrides set.
224    pub fn builder() -> FormulaOverridesBuilder {
225        FormulaOverridesBuilder::new()
226    }
227}
228
229/// Builder for [`FormulaOverrides`].
230#[derive(Clone, Debug)]
231pub struct FormulaOverridesBuilder {
232    inner: FormulaOverrides,
233}
234
235impl FormulaOverridesBuilder {
236    /// Creates a new builder with no overrides set.
237    #[allow(clippy::new_without_default)]
238    pub fn new() -> Self {
239        Self {
240            inner: FormulaOverrides::default(),
241        }
242    }
243
244    /// Override the meter/component preference for
245    /// [`ComponentGraph::pv_formula`][cg].
246    ///
247    /// [cg]: crate::ComponentGraph::pv_formula
248    pub fn prefer_meters_in_pv_formula(mut self, value: bool) -> Self {
249        self.inner.prefer_meters_in_pv_formula = Some(value);
250        self
251    }
252
253    /// Override the meter/component preference for
254    /// [`ComponentGraph::battery_formula`][cg].
255    ///
256    /// [cg]: crate::ComponentGraph::battery_formula
257    pub fn prefer_meters_in_battery_formula(mut self, value: bool) -> Self {
258        self.inner.prefer_meters_in_battery_formula = Some(value);
259        self
260    }
261
262    /// Override the meter/component preference for
263    /// [`ComponentGraph::chp_formula`][cg].
264    ///
265    /// [cg]: crate::ComponentGraph::chp_formula
266    pub fn prefer_meters_in_chp_formula(mut self, value: bool) -> Self {
267        self.inner.prefer_meters_in_chp_formula = Some(value);
268        self
269    }
270
271    /// Override the meter/component preference for
272    /// [`ComponentGraph::ev_charger_formula`][cg].
273    ///
274    /// [cg]: crate::ComponentGraph::ev_charger_formula
275    pub fn prefer_meters_in_ev_charger_formula(mut self, value: bool) -> Self {
276        self.inner.prefer_meters_in_ev_charger_formula = Some(value);
277        self
278    }
279
280    /// Override the meter/component preference for
281    /// [`ComponentGraph::wind_turbine_formula`][cg].
282    ///
283    /// [cg]: crate::ComponentGraph::wind_turbine_formula
284    pub fn prefer_meters_in_wind_turbine_formula(mut self, value: bool) -> Self {
285        self.inner.prefer_meters_in_wind_turbine_formula = Some(value);
286        self
287    }
288
289    /// Override the meter/component preference for
290    /// [`ComponentGraph::steam_boiler_formula`][cg].
291    ///
292    /// [cg]: crate::ComponentGraph::steam_boiler_formula
293    pub fn prefer_meters_in_steam_boiler_formula(mut self, value: bool) -> Self {
294        self.inner.prefer_meters_in_steam_boiler_formula = Some(value);
295        self
296    }
297
298    /// Consumes the builder and returns the resulting [`FormulaOverrides`].
299    pub fn build(self) -> FormulaOverrides {
300        self.inner
301    }
302}