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}