Source code for pydynamicestimator.tests.test_inverter_init_composition

# Created: 2026-06-07
# (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.

"""The strategy-composed sequential init: each strategy owns its stage, the host
orchestrates by name. The byte-identity that this reproduces the old monolithic
init is covered by the recur/selector/init-method gates; here we lock the two
*new* guarantees of the composition:

1. **Compatibility warning** -- if the inner controller needs a plant capability
   the filter doesn't provide, the host warns (but continues).
2. **Init guard** -- a strategy that provides no ``finit_sequential`` raises a
   clear error instead of silently mis-initializing.
"""

import logging

import pytest

from pydynamicestimator.devices.inverter import GridForming
from pydynamicestimator.devices.inverter_inner import Cascaded
from pydynamicestimator.devices.inverter_voltage import QVDroop, VoltageControl


[docs] def test_default_combo_no_warning(caplog): """The default LCL + Cascaded combo is compatible (capacitor provided).""" with caplog.at_level(logging.WARNING): GridForming() assert not any( "strategy mismatch" in r.getMessage().lower() for r in caplog.records )
[docs] def test_capability_mismatch_warns(caplog, monkeypatch): """An inner controller requiring a plant feature the filter lacks warns.""" monkeypatch.setattr(Cascaded, "requires", lambda self: {"flux_capacitor"}) with caplog.at_level(logging.WARNING): GridForming() msgs = [r.getMessage() for r in caplog.records] assert any("flux_capacitor" in m and "Cascaded" in m for m in msgs), msgs
[docs] def test_sequential_init_guard(monkeypatch): """A strategy with no sequential init raises a clear error (rather than silently solving the wrong equations).""" # Make QVDroop fall back to the abstract base (no finit_sequential). monkeypatch.setattr(QVDroop, "finit_sequential", VoltageControl.finit_sequential) with pytest.raises(NotImplementedError, match="sequential voltage init"): QVDroop().finit_sequential(None, None, None, None)