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