From aabcaf0e4373c6d961e87e4e41b9fd0871e4e11b Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Thu, 1 Oct 2026 13:18:33 +0200 Subject: [PATCH 1/6] parameterize phase count, "ABC" and "ABCDE" in each machine --- motors/synrm_3f_36s.py | 24 +++++++++++++----------- motors/synrm_5f_40s.py | 38 ++++++++++++++++++++------------------ 2 files changed, 33 insertions(+), 29 deletions(-) diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index c054e41..78672da 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -190,13 +190,14 @@ def set_oper_params(self): self.set_initpos() def set_solution_expressions(self): + phases = "ABCDE"[: self.geometry.n_phases] self.solution_expressions = [ "Moving1.Position", "Moving1.Torque", - *[f"FluxLinkage(Phase{p})" for p in "ABC"], - *[f"InducedVoltage(Phase{p})" for p in "ABC"], - *[f"InputCurrent(Phase{p})" for p in "ABC"], - *[f"L(Phase{x},Phase{y})" for x in "ABC" for y in "ABC"], + *[f"FluxLinkage(Phase{p})" for p in phases], + *[f"InducedVoltage(Phase{p})" for p in phases], + *[f"InputCurrent(Phase{p})" for p in phases], + *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], ] def set_output_vars(self): @@ -259,20 +260,21 @@ def extract_results(self, solutions): torque = self.extract_expression(solutions, "Moving1.Torque") theta_el = (position - np.deg2rad(self.InitPos)) * self.geometry.PolePairs - flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in "ABC"], axis=-1) - vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in "ABC"], axis=-1) - current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in "ABC"], axis=-1) + phases = "ABCDE"[: self.geometry.n_phases] + flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) + vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) + current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) Flux_d, Flux_q = to_dq(flux_phases, theta_el, harmonic=1) Ui_d, Ui_q = to_dq(vind_phases, theta_el, harmonic=1) I_d, I_q = to_dq(current_phases, theta_el, harmonic=1) - L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in "ABC"], axis=-1) for x in "ABC"] + L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] - L_d_row = np.zeros((len(position), 3)) - L_q_row = np.zeros((len(position), 3)) + L_d_row = np.zeros((len(position), self.geometry.n_phases)) + L_q_row = np.zeros((len(position), self.geometry.n_phases)) for i, L_row in enumerate(L_raw): - L_d_row[:, i], L_q_row[:, i] = (v * m for v, m in zip(to_dq(L_row, theta_el, harmonic=1), (3 / 2, -3 / 2))) + L_d_row[:, i], L_q_row[:, i] = (v * m for v, m in zip(to_dq(L_row, theta_el, harmonic=1), (self.geometry.n_phases / 2, -self.geometry.n_phases / 2))) L_d, _ = to_dq(L_d_row, theta_el, harmonic=1) _, L_q_raw = to_dq(L_q_row, theta_el, harmonic=1) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 99256a7..9e4c441 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -60,13 +60,14 @@ def set_oper_params(self): self.set_initpos() def set_solution_expressions(self): + phases = "ABCDE"[: self.geometry.n_phases] self.solution_expressions = [ "Moving1.Position", "Moving1.Torque", - *[f"FluxLinkage(Phase{p})" for p in "ABCDE"], - *[f"InducedVoltage(Phase{p})" for p in "ABCDE"], - *[f"InputCurrent(Phase{p})" for p in "ABCDE"], - *[f"L(Phase{x},Phase{y})" for x in "ABCDE" for y in "ABCDE"], + *[f"FluxLinkage(Phase{p})" for p in phases], + *[f"InducedVoltage(Phase{p})" for p in phases], + *[f"InputCurrent(Phase{p})" for p in phases], + *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], ] def set_output_vars(self): @@ -231,9 +232,10 @@ def extract_results(self, solutions): torque = self.extract_expression(solutions, "Moving1.Torque") theta_el = electrical_angle(position, np.deg2rad(self.InitPos), self.geometry.PolePairs, self.RotSign, degrees=False) - flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in "ABCDE"], axis=-1) - vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in "ABCDE"], axis=-1) - current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in "ABCDE"], axis=-1) + phases = "ABCDE"[: self.geometry.n_phases] + flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) + vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) + current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) Flux_d1, Flux_q1 = to_dq(flux_phases, theta_el, harmonic=1) Flux_d3, Flux_q3 = to_dq(flux_phases, theta_el, harmonic=3) @@ -248,9 +250,9 @@ def extract_results(self, solutions): epsI3 = np.atan2(self.Iq3, self.Id3) time = np.array(solutions.primary_sweep_values) - dI_dt_phases = np.zeros((len(time), 5)) - for k in range(5): - phase_offset = -2 * np.pi * k / 5 # 5 angles, 0deg, -72deg, -144deg, -216deg, -288deg + dI_dt_phases = np.zeros((len(time), self.geometry.n_phases)) + for k in range(self.geometry.n_phases): + phase_offset = -2 * np.pi * k / self.geometry.n_phases dI_dt_phases[:, k] = -Im1 * self.w * np.sin(self.w * time + phase_offset + epsI1 - np.pi) - Im3 * 3 * self.w * np.sin(3 * (self.w * time + phase_offset) + epsI3 - np.pi) V_phases = vind_phases + self.Rstat * current_phases + self.Lew * dI_dt_phases @@ -258,15 +260,15 @@ def extract_results(self, solutions): V_d1, V_q1 = to_dq(V_phases, theta_el, harmonic=1) V_d3, V_q3 = to_dq(V_phases, theta_el, harmonic=3) - L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in "ABCDE"], axis=-1) for x in "ABCDE"] + L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] - L_d1_row = np.zeros((len(time), 5)) - L_q1_row = np.zeros((len(time), 5)) - L_d3_row = np.zeros((len(time), 5)) - L_q3_row = np.zeros((len(time), 5)) + L_d1_row = np.zeros((len(time), self.geometry.n_phases)) + L_q1_row = np.zeros((len(time), self.geometry.n_phases)) + L_d3_row = np.zeros((len(time), self.geometry.n_phases)) + L_q3_row = np.zeros((len(time), self.geometry.n_phases)) for i, L_row in enumerate(L_raw): - L_d1_row[:, i], L_q1_row[:, i] = (v * 5 / 2 for v in to_dq(L_row, theta_el, harmonic=1)) - L_d3_row[:, i], L_q3_row[:, i] = (v * 5 / 2 for v in to_dq(L_row, theta_el, harmonic=3)) + L_d1_row[:, i], L_q1_row[:, i] = (v * self.geometry.n_phases / 2 for v in to_dq(L_row, theta_el, harmonic=1)) + L_d3_row[:, i], L_q3_row[:, i] = (v * self.geometry.n_phases / 2 for v in to_dq(L_row, theta_el, harmonic=3)) Ld1, Ld1q1 = to_dq(L_d1_row, theta_el, harmonic=1) Lq1d1, Lq1 = to_dq(L_q1_row, theta_el, harmonic=1) @@ -282,7 +284,7 @@ def extract_results(self, solutions): Flux_e_d3 = Flux_d3 - (Ld3d1 * I_d1 + Ld3q1 * I_q1 + Ld3 * I_d3 + Ld3q3 * I_q3) Flux_e_q3 = Flux_q3 - (Lq3d1 * I_d1 + Lq3q1 * I_q1 + Lq3d3 * I_d3 + Lq3 * I_q3) - # Torque_dq = 5 / 2 * self.geometry.PolePairs * ((Flux_d1 * I_q1 - Flux_q1 * I_d1) + 3 * (Flux_d3 * I_q3 - Flux_q3 * I_d3)) + # Torque_dq = self.geometry.n_phases / 2 * self.geometry.PolePairs * ((Flux_d1 * I_q1 - Flux_q1 * I_d1) + 3 * (Flux_d3 * I_q3 - Flux_q3 * I_d3)) out = { "V_d1": V_d1, From c9e9982a9eca015e73a0288cf5fb29cee29094e6 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Thu, 1 Oct 2026 13:36:01 +0200 Subject: [PATCH 2/6] move solution_expressions() to abstract parents class --- machine_design/designs/computation.py | 11 +++++++++++ motors/synrm_3f_36s.py | 10 +--------- motors/synrm_5f_40s.py | 10 +--------- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index 1b76a3a..ba5a462 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -60,6 +60,17 @@ def compute_phase_groups(self): Q = int(g.geom_params["SlotNumber"]) return phase_groups(Q, g.PolePairs, g.n_phases, belt_offset=g.belt_offset) + def compute_solution_expressions(self): + phases = "ABCDE"[: self.geometry.n_phases] + return [ + "Moving1.Position", + "Moving1.Torque", + *[f"FluxLinkage(Phase{p})" for p in phases], + *[f"InducedVoltage(Phase{p})" for p in phases], + *[f"InputCurrent(Phase{p})" for p in phases], + *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], + ] + def push_variables(self, m2d: Maxwell2d) -> None: for k, v in self.oper_params.items(): m2d[k] = v diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index 78672da..e3a2530 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -190,15 +190,7 @@ def set_oper_params(self): self.set_initpos() def set_solution_expressions(self): - phases = "ABCDE"[: self.geometry.n_phases] - self.solution_expressions = [ - "Moving1.Position", - "Moving1.Torque", - *[f"FluxLinkage(Phase{p})" for p in phases], - *[f"InducedVoltage(Phase{p})" for p in phases], - *[f"InputCurrent(Phase{p})" for p in phases], - *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], - ] + self.solution_expressions = self.compute_solution_expressions() def set_output_vars(self): self.output_vars = {} diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 9e4c441..13335f6 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -60,15 +60,7 @@ def set_oper_params(self): self.set_initpos() def set_solution_expressions(self): - phases = "ABCDE"[: self.geometry.n_phases] - self.solution_expressions = [ - "Moving1.Position", - "Moving1.Torque", - *[f"FluxLinkage(Phase{p})" for p in phases], - *[f"InducedVoltage(Phase{p})" for p in phases], - *[f"InputCurrent(Phase{p})" for p in phases], - *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], - ] + self.solution_expressions = self.compute_solution_expressions() def set_output_vars(self): self.output_vars = {} From ba5f342736d9e196477e9fb7887b605f30cb7bc2 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Thu, 1 Oct 2026 13:51:54 +0200 Subject: [PATCH 3/6] extract general function into ComputationBase --- machine_design/designs/computation.py | 9 +++++++++ motors/synrm_3f_36s.py | 8 ++------ motors/synrm_5f_40s.py | 8 ++------ 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index ba5a462..a4f9f11 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -71,6 +71,15 @@ def compute_solution_expressions(self): *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], ] + def extract_phase_results(self, solutions): + # SI units + phases = "ABCDE"[: self.geometry.n_phases] + flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) + vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) + current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) + L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] + return flux_phases, vind_phases, current_phases, L_raw + def push_variables(self, m2d: Maxwell2d) -> None: for k, v in self.oper_params.items(): m2d[k] = v diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index e3a2530..2d68dac 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -252,17 +252,13 @@ def extract_results(self, solutions): torque = self.extract_expression(solutions, "Moving1.Torque") theta_el = (position - np.deg2rad(self.InitPos)) * self.geometry.PolePairs - phases = "ABCDE"[: self.geometry.n_phases] - flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) - vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) - current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) + + flux_phases, vind_phases, current_phases, L_raw = self.extract_phase_results(solutions) Flux_d, Flux_q = to_dq(flux_phases, theta_el, harmonic=1) Ui_d, Ui_q = to_dq(vind_phases, theta_el, harmonic=1) I_d, I_q = to_dq(current_phases, theta_el, harmonic=1) - L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] - L_d_row = np.zeros((len(position), self.geometry.n_phases)) L_q_row = np.zeros((len(position), self.geometry.n_phases)) for i, L_row in enumerate(L_raw): diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 13335f6..4d38e62 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -224,10 +224,8 @@ def extract_results(self, solutions): torque = self.extract_expression(solutions, "Moving1.Torque") theta_el = electrical_angle(position, np.deg2rad(self.InitPos), self.geometry.PolePairs, self.RotSign, degrees=False) - phases = "ABCDE"[: self.geometry.n_phases] - flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) - vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) - current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) + + flux_phases, vind_phases, current_phases, L_raw = self.extract_phase_results(solutions) Flux_d1, Flux_q1 = to_dq(flux_phases, theta_el, harmonic=1) Flux_d3, Flux_q3 = to_dq(flux_phases, theta_el, harmonic=3) @@ -252,8 +250,6 @@ def extract_results(self, solutions): V_d1, V_q1 = to_dq(V_phases, theta_el, harmonic=1) V_d3, V_q3 = to_dq(V_phases, theta_el, harmonic=3) - L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] - L_d1_row = np.zeros((len(time), self.geometry.n_phases)) L_q1_row = np.zeros((len(time), self.geometry.n_phases)) L_d3_row = np.zeros((len(time), self.geometry.n_phases)) From e240c7d15a5d58b952f28f94f75924737c382bed Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Fri, 2 Oct 2026 07:37:40 +0200 Subject: [PATCH 4/6] move phase to init and add to 11 phases --- machine_design/designs/computation.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index a4f9f11..22dcf1b 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -13,6 +13,7 @@ class ComputationBase(ABC): def __init__(self, geometry: GeometryBase) -> None: self.geometry = geometry + self.phases="ABCDEFGHIJK"[: self.geometry.n_phases] self.setup_name = "Setup1" self.rotor_mesh = None self.set_oper_params() @@ -61,23 +62,21 @@ def compute_phase_groups(self): return phase_groups(Q, g.PolePairs, g.n_phases, belt_offset=g.belt_offset) def compute_solution_expressions(self): - phases = "ABCDE"[: self.geometry.n_phases] return [ "Moving1.Position", "Moving1.Torque", - *[f"FluxLinkage(Phase{p})" for p in phases], - *[f"InducedVoltage(Phase{p})" for p in phases], - *[f"InputCurrent(Phase{p})" for p in phases], - *[f"L(Phase{x},Phase{y})" for x in phases for y in phases], + *[f"FluxLinkage(Phase{p})" for p in self.phases], + *[f"InducedVoltage(Phase{p})" for p in self.phases], + *[f"InputCurrent(Phase{p})" for p in self.phases], + *[f"L(Phase{x},Phase{y})" for x in self.phases for y in self.phases], ] def extract_phase_results(self, solutions): # SI units - phases = "ABCDE"[: self.geometry.n_phases] - flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in phases], axis=-1) - vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in phases], axis=-1) - current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in phases], axis=-1) - L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in phases], axis=-1) for x in phases] + flux_phases = np.stack([self.extract_expression(solutions, f"FluxLinkage(Phase{p})") for p in self.phases], axis=-1) + vind_phases = np.stack([self.extract_expression(solutions, f"InducedVoltage(Phase{p})") for p in self.phases], axis=-1) + current_phases = np.stack([self.extract_expression(solutions, f"InputCurrent(Phase{p})") for p in self.phases], axis=-1) + L_raw = [np.stack([self.extract_expression(solutions, f"L(Phase{x},Phase{y})") for y in self.phases], axis=-1) for x in self.phases] return flux_phases, vind_phases, current_phases, L_raw def push_variables(self, m2d: Maxwell2d) -> None: From 5d73cc2e25624d3f14224d52b435e9fc0053604f Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Fri, 2 Oct 2026 07:38:32 +0200 Subject: [PATCH 5/6] ruff --- machine_design/designs/computation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index 22dcf1b..425101d 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -13,7 +13,7 @@ class ComputationBase(ABC): def __init__(self, geometry: GeometryBase) -> None: self.geometry = geometry - self.phases="ABCDEFGHIJK"[: self.geometry.n_phases] + self.phases = "ABCDEFGHIJK"[: self.geometry.n_phases] self.setup_name = "Setup1" self.rotor_mesh = None self.set_oper_params() From 91204fb04b789baf9ccb370f7175c3bc4aa84950 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Fri, 2 Oct 2026 07:48:13 +0200 Subject: [PATCH 6/6] update --- tests/test_computation.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/test_computation.py b/tests/test_computation.py index f675f33..37c34b1 100644 --- a/tests/test_computation.py +++ b/tests/test_computation.py @@ -1,3 +1,5 @@ +from types import SimpleNamespace + import pytest from machine_design.designs.computation import ComputationBase @@ -35,19 +37,19 @@ def extract_results(self, solutions): def test_no_reserved_names_does_not_raise(): - _DummyComputation(object(), {"Im": "1A"}, {"pos": "Moving1.Position"}) + _DummyComputation(SimpleNamespace(n_phases=3), {"Im": "1A"}, {"pos": "Moving1.Position"}) def test_reserved_name_in_oper_params_raises(): with pytest.raises(ValueError, match="F"): - _DummyComputation(object(), {"F": "50Hz"}, {}) + _DummyComputation(SimpleNamespace(n_phases=3), {"F": "50Hz"}, {}) def test_reserved_name_in_output_vars_raises(): with pytest.raises(ValueError, match="Time"): - _DummyComputation(object(), {}, {"Time": "0s"}) + _DummyComputation(SimpleNamespace(n_phases=3), {}, {"Time": "0s"}) def test_reserved_name_check_is_case_insensitive(): with pytest.raises(ValueError, match="f"): - _DummyComputation(object(), {"f": "50Hz"}, {}) + _DummyComputation(SimpleNamespace(n_phases=3), {"f": "50Hz"}, {})