Source code for pydynamicestimator.tests.test_inverter_strategy_selectors

# Created: 2026-06-05
# (c) Copyright 2025 ETH Zurich
#
# Licensed under the GNU General Public License v3.0;
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
#     https://www.gnu.org/licenses/gpl-3.0.en.html
#
# This software is distributed "AS IS", WITHOUT WARRANTY OF ANY KIND,
# express or implied. See the License for specific language governing
# permissions and limitations under the License.

"""Phase 3 gate: the data-file strategy selectors (filter=/angle=/voltage=/inner=
/pll=) are threaded correctly through the data loader.

Copies the inverter fixture and appends *explicit, default-matching* strategy
selectors to every GridForming/GridFollowing row, then checks the simulated
trajectory is byte-identical to the no-selector sim_ld baseline. This proves the
threading both (a) routes each selector to the right strategy and (b) reproduces
the hardcoded defaults exactly -- a regression guard for the registry plumbing.
"""

import pickle
import shutil

import numpy as np

from pydynamicestimator.run import run
from pydynamicestimator.tests.baselines.inverter_baseline import (
    BASELINES,
    FIXTURE_ROOT,
    SIM_LD_STRIDE,
    make_inverter_baseline_config,
)


[docs] def _add_default_selectors(line: str) -> str: stripped = line.strip() if stripped.startswith("GridForming,"): return ( line.rstrip() + ', filter = "LCL", angle = "Droop", voltage = "QVDroop",' + ' inner = "Cascaded"' ) if stripped.startswith("GridFollowing,"): return ( line.rstrip() + ', filter = "LCL", angle = "PLL", voltage = "QVDroop",' + ' inner = "Cascaded", pll = "SRF_PLL"' ) return line
[docs] def test_explicit_default_selectors_match_baseline(tmp_path): # Copy the fixture and inject explicit (default-matching) selectors. dst = tmp_path / "IEEE39_bus_inverter" shutil.copytree(FIXTURE_ROOT / "IEEE39_bus_inverter", dst) sim_param = dst / "sim_param.txt" patched = "\n".join( _add_default_selectors(line) for line in sim_param.read_text().splitlines() ) sim_param.write_text(patched) # Same run config as the sim_ld baseline, pointed at the patched fixture. cfg = make_inverter_baseline_config("sim_ld").updated(system_root=tmp_path) _, sim = run(cfg) sim_x = np.asarray(sim.x_full)[:, ::SIM_LD_STRIDE] with open(BASELINES["sim_ld"], "rb") as file: base = pickle.load(file) assert np.allclose(sim_x, base, atol=1e-6), ( "Explicit default-matching strategy selectors did not reproduce the " "no-selector baseline -- data-loader threading is wrong" )