Coverage for tests/cim_converter/unit/test_reg_transformer_small.py: 98%
88 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
« prev ^ index » next coverage.py v7.10.6, created at 2025-11-13 17:34 -0800
1# tests/unit/test_reg_transformer_small.py
2import math
3import pytest
4from cimgraph.data_profile import cimhub_2023 as cim
5from distopf.cim_importer.processors.regulator_processor import RegulatorProcessor
6from distopf.cim_importer.processors.transformer_processor import TransformerProcessor
7from distopf.cim_importer.utils.phase_utils import PhaseUtils
10def make_terminal(name: str, mrid: str | None = None):
11 node = cim.ConnectivityNode(mRID=(mrid or f"mrid_{name}"), name=name)
12 terminal = cim.Terminal(ConnectivityNode=node)
13 # Add terminal backref into node.Terminals list
14 node.Terminals = [terminal]
15 return terminal
18def test_regulator_is_regulator_and_extract_tap(monkeypatch):
19 # Build a TransformerTankEnd with a RatioTapChanger and attach to TransformerTank
20 t_from = make_terminal("frombus")
21 t_to = make_terminal("tobus")
23 # Create a RatioTapChanger in the profile if available; otherwise fall back to simple object
24 try:
25 rtc = cim.RatioTapChanger(step=10.0, stepVoltageIncrement=2.0)
26 except AttributeError:
27 # Fallback if RatioTapChanger dataclass isn't present in the profile
28 rtc = type("RTC", (), {"step": 10.0, "stepVoltageIncrement": 2.0})()
30 tank_end = cim.TransformerTankEnd(
31 orderedPhases=None, Terminal=t_from, RatioTapChanger=rtc
32 )
33 tank = cim.TransformerTank(name="tank1", TransformerTankEnds=[tank_end])
34 xfmr = cim.PowerTransformer(
35 name="Reg1",
36 TransformerTanks=[tank],
37 PowerTransformerEnd=[],
38 Terminals=[t_from, t_to],
39 )
41 # Make PhaseUtils deterministic for orderedPhases handling
42 monkeypatch.setattr(PhaseUtils, "get_phase_str", staticmethod(lambda v: "a"))
43 reg_proc = RegulatorProcessor(1e6)
45 assert reg_proc.is_regulator(xfmr) is True
47 reg_data = reg_proc._extract_regulator_data(xfmr)
48 # Expected: tap_a set to step (10.0) and ratio_a computed as 1 + step * (stepVoltageIncrement/100)
49 assert "tap_a" in reg_data
50 assert pytest.approx(reg_data["tap_a"], rel=1e-12) == 10.0
51 # stepVoltageIncrement is given in percent in the implementation; divide by 100
52 expected_ratio = 1.0 + (reg_data["tap_a"] * (rtc.stepVoltageIncrement / 100.0))
53 assert pytest.approx(reg_data["ratio_a"], rel=1e-12) == expected_ratio
54 assert reg_data["phases"] == "a"
55 assert reg_data["from_name"] == t_from.ConnectivityNode.name
56 assert reg_data["to_name"] == t_to.ConnectivityNode.name
59def _make_power_transformer_end(
60 end_number: int, ratedU: float = 480.0, mesh_r=None, mesh_x=None, to_end_number=None
61):
62 """
63 Create a cim.PowerTransformerEnd with optional FromMeshImpedance entries.
64 """
65 pte = cim.PowerTransformerEnd(endNumber=end_number, ratedU=float(ratedU))
66 if mesh_r is not None or mesh_x is not None:
67 # Create a TransformerMeshImpedance instance
68 mesh_imp = cim.TransformerMeshImpedance(
69 r=float(mesh_r) if mesh_r is not None else None,
70 x=float(mesh_x) if mesh_x is not None else None,
71 )
72 if to_end_number is not None:
73 # create a TransformerEnd placeholder with endNumber to serve as ToTransformerEnd
74 to_end = cim.TransformerEnd(endNumber=int(to_end_number))
75 mesh_imp.ToTransformerEnd = [to_end]
76 pte.FromMeshImpedance = [mesh_imp]
77 else:
78 pte.FromMeshImpedance = []
79 return pte
82def test_transformer_2winding_from_mesh_impedance():
83 t0 = make_terminal("busA")
84 t1 = make_terminal("busB")
85 primary_end = _make_power_transformer_end(1, ratedU=480.0, mesh_r=0.1, mesh_x=0.2)
86 xfmr = cim.PowerTransformer(
87 name="Xfmr2W",
88 PowerTransformerEnd=[primary_end],
89 TransformerTanks=[],
90 Terminals=[t0, t1],
91 )
92 tp = TransformerProcessor(1e6)
93 data = tp._process_2winding_transformer(xfmr, ["busA", "busB"])
94 v_ln_expected = float(primary_end.ratedU) / math.sqrt(3)
95 assert pytest.approx(data["v_ln_base"], rel=1e-12) == v_ln_expected
96 z_base = v_ln_expected**2 / tp.s_base
97 expected_r = float(primary_end.FromMeshImpedance[0].r) / z_base
98 expected_x = float(primary_end.FromMeshImpedance[0].x) / z_base
99 assert pytest.approx(data["raa"], rel=1e-9) == expected_r
100 assert pytest.approx(data["xaa"], rel=1e-9) == expected_x
101 assert data["type"] == "transformer"
102 assert data["from_name"] == "busA"
103 assert data["to_name"] == "busB"
106def test_transformer_3winding_mesh_impedance_pairing():
107 t0 = make_terminal("bus1")
108 t1 = make_terminal("bus2")
109 t2 = make_terminal("bus3")
110 # Primary end with mesh impedance that references transformer end 2
111 primary_end = _make_power_transformer_end(
112 1, ratedU=480.0, mesh_r=0.3, mesh_x=0.4, to_end_number=2
113 )
114 end2 = _make_power_transformer_end(2, ratedU=480.0)
115 end3 = _make_power_transformer_end(3, ratedU=480.0)
116 xfmr = cim.PowerTransformer(
117 name="Xfmr3W",
118 PowerTransformerEnd=[primary_end, end2, end3],
119 TransformerTanks=[],
120 Terminals=[t0, t1, t2],
121 )
122 tp = TransformerProcessor(1e6)
123 results = tp._process_3winding_transformer(xfmr, ["bus1", "bus2", "bus3"])
124 assert isinstance(results, list) and len(results) == 2
125 data12 = results[0]
126 v_ln_expected = float(primary_end.ratedU) / math.sqrt(3)
127 z_base = v_ln_expected**2 / tp.s_base
128 expected_r = float(primary_end.FromMeshImpedance[0].r) / z_base
129 expected_x = float(primary_end.FromMeshImpedance[0].x) / z_base
130 assert pytest.approx(data12["raa"], rel=1e-9) == expected_r
131 assert pytest.approx(data12["xaa"], rel=1e-9) == expected_x
132 assert data12["from_name"] == "bus1"
133 assert data12["to_name"] == "bus2"
136def test_transformer_default_impedance_when_none_found():
137 t0 = make_terminal("b1")
138 t1 = make_terminal("b2")
139 primary_end = cim.PowerTransformerEnd(
140 endNumber=1, ratedU=float(480.0), FromMeshImpedance=[]
141 )
142 xfmr = cim.PowerTransformer(
143 name="Xfmr_default",
144 PowerTransformerEnd=[primary_end],
145 TransformerTanks=[],
146 Terminals=[t0, t1],
147 )
148 tp = TransformerProcessor(1e6)
149 data = tp._process_2winding_transformer(xfmr, ["b1", "b2"])
150 # default values expected
151 assert pytest.approx(data["raa"], rel=1e-12) == 0.01
152 assert pytest.approx(data["xaa"], rel=1e-12) == 0.05