diff --git a/Cargo.lock b/Cargo.lock index aa971be..ae03cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,8 +17,7 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "frequenz-microgrid-component-graph" version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2ae40700292e149155d32a2267f93c48fdf1749a2176bbd7f4b0b0fa643396c" +source = "git+https://github.com/shsms/frequenz-microgrid-component-graph-rs?branch=meter-subtraction#c2d7d1848e95364746b559fcc0f718a988d781c4" dependencies = [ "petgraph", "tracing", @@ -34,9 +33,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -46,9 +45,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" -version = "2.12.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown", @@ -56,15 +55,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.177" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "petgraph" @@ -78,21 +77,21 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -156,18 +155,18 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.41" +version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" dependencies = [ "proc-macro2", ] [[package]] name = "syn" -version = "2.0.108" +version = "2.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" dependencies = [ "proc-macro2", "quote", @@ -176,15 +175,15 @@ dependencies = [ [[package]] name = "target-lexicon" -version = "0.13.3" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df7f62577c25e07834649fc3b39fafdc597c0a3527dc1c60129201ccfcbaa50c" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-attributes", @@ -193,9 +192,9 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", @@ -204,15 +203,15 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" diff --git a/Cargo.toml b/Cargo.toml index d96ca99..2cdcc5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,4 @@ crate-type = ["cdylib"] [dependencies] pyo3 = "0.29.0" -frequenz-microgrid-component-graph = "0.5" +frequenz-microgrid-component-graph = { git = "https://github.com/shsms/frequenz-microgrid-component-graph-rs", branch = "meter-subtraction" } diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fe1e234..7a1df34 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,8 @@ - Bumped PyO3 to 0.29 to pull in the fix for RUSTSEC out-of-bounds read advisory GHSA-36hh-v3qg-5jq4 (`nth`/`nth_back` on list/tuple iterators). +- `prefer_meters_in_component_formulas` now defaults to `False`: per-category formulas use the component reading as the primary source and the meter as the fallback. Set it to `True` to restore the previous meter-first behavior. + ## New Features diff --git a/python/frequenz/microgrid_component_graph/__init__.pyi b/python/frequenz/microgrid_component_graph/__init__.pyi index e3d17d0..354b654 100644 --- a/python/frequenz/microgrid_component_graph/__init__.pyi +++ b/python/frequenz/microgrid_component_graph/__init__.pyi @@ -25,7 +25,7 @@ class ComponentGraphConfig: allow_unspecified_inverters: bool = False, disable_fallback_components: bool = False, include_phantom_loads_in_consumer_formula: bool = False, - prefer_meters_in_component_formulas: bool = True, + prefer_meters_in_component_formulas: bool = False, formula_overrides: FormulaOverrides | None = None, ) -> None: """Initialize this instance. @@ -50,25 +50,25 @@ class ComponentGraphConfig: predecessor meters. When `false`, consumer formula is generated by excluding production and battery components from the grid measurements. prefer_meters_in_component_formulas: Default policy for the per-category - formulas. When `True` (the default), the meter measurement is the - primary source and the device measurement is the fallback for + formulas. When `False` (the default), the component measurement is the + primary source and the meter measurement is the fallback for `battery_formula`, `chp_formula`, `pv_formula`, `wind_turbine_formula`, - `ev_charger_formula`, and `steam_boiler_formula`. When `False`, the - device is primary and the meter is the fallback. Has no effect on + `ev_charger_formula`, and `steam_boiler_formula`. When `True`, the + meter is primary and the component is the fallback. Has no effect on `grid_formula`, `consumer_formula`, `producer_formula`, or any of the coalesce formulas. - formula_overrides: Per-formula overrides for the meter/device preference; + formula_overrides: Per-formula overrides for the meter/component preference; see `FormulaOverrides`. Each entry, when set, takes precedence over `prefer_meters_in_component_formulas` for that formula. """ class FormulaOverrides: - """Per-formula overrides for the meter/device preference. + """Per-formula overrides for the meter/component preference. Each parameter is `None` by default, meaning the corresponding formula follows the global `prefer_meters_in_component_formulas` setting on `ComponentGraphConfig`. Setting `True` forces the meter as primary - for that formula; `False` forces the device. + for that formula; `False` forces the component. """ def __init__( diff --git a/src/graph.rs b/src/graph.rs index 6685eb1..aba7d25 100644 --- a/src/graph.rs +++ b/src/graph.rs @@ -32,7 +32,7 @@ impl ComponentGraphConfig { allow_unspecified_inverters = false, disable_fallback_components = false, include_phantom_loads_in_consumer_formula = false, - prefer_meters_in_component_formulas = true, + prefer_meters_in_component_formulas = false, formula_overrides = None, ))] fn new( diff --git a/tests/test_formula_preferences.py b/tests/test_formula_preferences.py index bc699f6..ad19354 100644 --- a/tests/test_formula_preferences.py +++ b/tests/test_formula_preferences.py @@ -3,14 +3,14 @@ """Behavioral tests for `ComponentGraphConfig` formula preferences. -These tests build a small, controllable graph (Grid -> Meter -> Device) +These tests build a small, controllable graph (Grid -> Meter -> Component) for each per-category formula method and assert the actual formula -output for the four meter/device-preference combinations: +output for the four meter/component-preference combinations: - * default config -> meter primary - * global False -> device primary + * default config -> component primary + * global True -> meter primary * per-formula override = True -> meter primary (override wins) - * per-formula override = False -> device primary (override wins) + * per-formula override = False -> component primary (override wins) The drift test in `test_stub_drift.py` only checks signatures; this file catches actual mis-wiring -- a swapped override, an inverted @@ -46,9 +46,9 @@ _MGRID = MicrogridId(1) # In every per-category topology built below, component #2 is the meter -# and #3 is the device that appears in the formula. +# and #3 is the component that appears in the formula. _METER_PRIMARY = "COALESCE(#2, #3, 0.0)" -_DEVICE_PRIMARY = "COALESCE(#3, #2, 0.0)" +_COMPONENT_PRIMARY = "COALESCE(#3, #2, 0.0)" # Each per-category graph builder takes an optional `config` and returns a # graph rooted at the same Grid -> Meter pair, so the assertion strings @@ -104,7 +104,7 @@ def _battery_graph( config: ComponentGraphConfig | None = None, ) -> ComponentGraph[Any, Any, Any]: # Grid -> Meter -> BatteryInverter -> Battery; the formula references - # the inverter (#3) as the device, the battery (#4) doesn't appear. + # the inverter (#3) as the component, the battery (#4) doesn't appear. return ComponentGraph( components={ _grid(), @@ -190,26 +190,26 @@ def _steam_boiler_graph( @pytest.mark.parametrize("build_graph,method,override_field", _CATEGORIES) -def test_default_config_prefers_meter( +def test_default_config_prefers_component( build_graph: GraphBuilder, method: str, override_field: str, # pylint: disable=unused-argument ) -> None: - """Default config selects the meter as the primary source.""" + """Default config selects the component as the primary source.""" formula = getattr(build_graph(None), method)(None) - assert formula == _METER_PRIMARY + assert formula == _COMPONENT_PRIMARY @pytest.mark.parametrize("build_graph,method,override_field", _CATEGORIES) -def test_global_false_prefers_device( +def test_global_true_prefers_meter( build_graph: GraphBuilder, method: str, override_field: str, # pylint: disable=unused-argument ) -> None: - """Setting `prefer_meters_in_component_formulas=False` selects the device.""" - config = ComponentGraphConfig(prefer_meters_in_component_formulas=False) + """Setting `prefer_meters_in_component_formulas=True` selects the meter.""" + config = ComponentGraphConfig(prefer_meters_in_component_formulas=True) formula = getattr(build_graph(config), method)(None) - assert formula == _DEVICE_PRIMARY + assert formula == _METER_PRIMARY @pytest.mark.parametrize("build_graph,method,override_field", _CATEGORIES) @@ -229,13 +229,13 @@ def test_override_true_wins_over_global_false( def test_override_false_wins_over_global_true( build_graph: GraphBuilder, method: str, override_field: str ) -> None: - """A `False` per-formula override flips to device despite a `True` global.""" + """A `False` per-formula override flips to component despite a `True` global.""" config = ComponentGraphConfig( prefer_meters_in_component_formulas=True, formula_overrides=FormulaOverrides(**{override_field: False}), ) formula = getattr(build_graph(config), method)(None) - assert formula == _DEVICE_PRIMARY + assert formula == _COMPONENT_PRIMARY def _empty_graph() -> ComponentGraph[Any, Any, Any]: diff --git a/tests/test_microgrid_component_graph.py b/tests/test_microgrid_component_graph.py index 7d6c301..dae7903 100644 --- a/tests/test_microgrid_component_graph.py +++ b/tests/test_microgrid_component_graph.py @@ -141,10 +141,11 @@ def test_wind_turbine_graph() -> None: } # 3. Test Formula Generation - # References the Meter (ID 2) measuring the Turbine (ID 3). + # Component-first by default: the turbine (ID 3) is the primary source, + # the meter (ID 2) the fallback. assert ( graph.wind_turbine_formula(wind_turbine_ids={ComponentId(3)}) - == "COALESCE(#2, #3, 0.0)" + == "COALESCE(#3, #2, 0.0)" ) # 4. Test Topology (Successors/Predecessors) @@ -179,7 +180,7 @@ def test_steam_boiler_graph() -> None: } assert ( graph.steam_boiler_formula(steam_boiler_ids={ComponentId(3)}) - == "COALESCE(#2, #3, 0.0)" + == "COALESCE(#3, #2, 0.0)" )