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}