From c936467dcdb56ce969755c559c37305e7eda4718 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 13:48:14 +0200 Subject: [PATCH 01/41] copy-paste winding.py from vs_5f and create synrm_3f_60s.py --- machine_design/winding.py | 96 +++++++++++++++++++++++++++++++++++++++ motors/synrm_3f_60s.py | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 machine_design/winding.py create mode 100644 motors/synrm_3f_60s.py diff --git a/machine_design/winding.py b/machine_design/winding.py new file mode 100644 index 0000000..70bb828 --- /dev/null +++ b/machine_design/winding.py @@ -0,0 +1,96 @@ +"""Parametric stator winding-layout generator (pure algorithm, no AEDT). + +Produces the coil -> (phase, polarity) map for the one-pole, anti-periodic FEA +sector our machine models, for an arbitrary balanced integer-slot distributed +winding given (Q slots, p pole-pairs, m phases). It replaces the hardcoded +per-coil assignment in Design.assign_stator_coils / Design2.assign_stator_coils. + +Convention (reverse-engineered from, and validated to exactly reproduce, the +project's 40-slot/5-phase and 36-slot/3-phase windings): + - the FEA sector spans ONE pole, n = Q/(2p) slots = coils, with anti-periodic BC; + - q = Q/(2*p*m) slots per phase belt (must be a positive integer); + - coils are created in slot order: coil 0 = "Coil", coil k = "Coil_{k}"; + - belt index of coil k: b = (k + 1) // q (raw, half-slot-offset origin) + - phase index: ((m+1)//2 * b) mod m (standard (m+1)/2 progression) + - polarity: +1 if b even else -1 (alternating belts; the wrap + coil flips via anti-periodicity) +""" + + +def coil_name(k: int) -> str: + """AEDT object name for the k-th duplicated coil (0-based).""" + return "Coil" if k == 0 else f"Coil_{k}" + + +def sector_winding(Q: int, p: int, m: int, belt_offset: int = 1): + """Return a list (length n = Q/(2p)) of (phase_index 0..m-1, sign +/-1), + one entry per sector coil in slot/creation order. + + belt_offset shifts the belt origin (a pure spatial rotation of the winding, + immaterial to performance, absorbed by the rotor-alignment / current angle): + - belt_offset=1 -> PhaseA centred on the d-axis with an anti-periodic wrap; + reproduces the project's 5-phase (40-slot) winding. Default / preferred. + - belt_offset=0 -> belts start at slot 0; reproduces the legacy 3-phase + (36-slot) base winding. + New machines should use the default (1) and align the dq reference to it.""" + if Q % (2 * p * m) != 0: + raise ValueError(f"non-integer slots/pole/phase: Q={Q}, 2*p*m={2 * p * m} (q=Q/(2pm) must be a positive integer for this winding)") + n = Q // (2 * p) # coils in the one-pole sector + q = Q // (2 * p * m) # slots per belt + step = (m + 1) // 2 + out = [] + for k in range(n): + b = (k + belt_offset) // q + out.append(((step * b) % m, 1 if b % 2 == 0 else -1)) + return out + + +def phase_groups(Q: int, p: int, m: int, belt_offset: int = 1): + """Return m lists; phase j -> [(coil_name, polarity_str), ...] for driving + assign_coil / add_winding_coils generically.""" + groups = [[] for _ in range(m)] + for k, (ph, sg) in enumerate(sector_winding(Q, p, m, belt_offset)): + groups[ph].append((coil_name(k), "Positive" if sg > 0 else "Negative")) + return groups + + +if __name__ == "__main__": + PHASE = "ABCDEFG" + + # --- validation: must reproduce the existing hardcoded windings exactly --- + def asmap(Q, p, m, off): + g = {} + for k, (ph, sg) in enumerate(sector_winding(Q, p, m, off)): + g.setdefault(PHASE[ph], set()).add((k, sg)) + return g + + EXPECT_5 = { # design2.assign_stator_coils (CS_i -> Coil_{i-1} -> slot i-1) + "A": {(0, 1), (9, -1)}, + "B": {(3, 1), (4, 1)}, + "C": {(7, 1), (8, 1)}, + "D": {(1, -1), (2, -1)}, + "E": {(5, -1), (6, -1)}, + } + EXPECT_3 = { # design.assign_stator_coils (PhaseA=CS1-3, B=CS7-9, C=CS4-6) + "A": {(0, 1), (1, 1), (2, 1)}, + "B": {(6, 1), (7, 1), (8, 1)}, + "C": {(3, -1), (4, -1), (5, -1)}, + } + assert asmap(40, 2, 5, 1) == EXPECT_5, "5-phase (offset 1) mismatch" + assert asmap(36, 2, 3, 0) == EXPECT_3, "3-phase (offset 0) mismatch" + print("OK reproduces BOTH hardcoded windings exactly: 5-phase 40-slot (offset 1), 3-phase 36-slot (offset 0)") + + def show(Q, p, m): + lay = sector_winding(Q, p, m) + bins = {} + for k, (ph, sg) in enumerate(lay): + bins.setdefault(PHASE[ph], []).append(f"{coil_name(k)}{'+' if sg > 0 else '-'}") + q = Q // (2 * p * m) + print(f"\n(Q={Q}, p={p}, m={m}) sector coils={len(lay)} q={q}") + for ph in sorted(bins): + print(f" Phase {ph}: {bins[ph]}") + + show(40, 2, 5) # current 5-phase + show(36, 2, 3) # current 3-phase + show(60, 2, 3) # common-stator target, 3-phase + show(60, 2, 5) # common-stator target, 5-phase diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py new file mode 100644 index 0000000..baf9550 --- /dev/null +++ b/motors/synrm_3f_60s.py @@ -0,0 +1,88 @@ +"""Anchor `synrm_3f_60s`: 3-phase synchronous reluctance machine, 60 stator slots.""" + +import numpy as np +from ansys.aedt.core import Maxwell2d +from ansys.aedt.core.modeler.modeler_2d import Modeler2D + +from machine_design.designs.computation import ComputationBase +from machine_design.winding import phase_groups +from .synrm_3f_36s import Geometry as BaseGeometry + + +class Geometry(BaseGeometry): + def set_geom_params(self): + super().set_geom_params() + self.geom_params["SlotNumber"] = "60" + + def set_slot_params(self): + super().set_slot_params() + self.slot_params["Bs0"] = "1.5mm" + self.slot_params["Bs1"] = "2.0mm" + self.slot_params["Bs2"] = "2.85mm" + self.slot_params["Rs"] = "1.0mm" + self.slot_params["SetAngle"] = "6deg" + + def set_winds_params(self): + super().set_winds_params() + self.wind_params["CoilPitch"] = "15" # full pitch over one pole (60/4) + + +class Computation(ComputationBase): + def set_oper_params(self): + f = 50 # [Hz] + RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] + w = 2 * np.pi * f + self.oper_params = { + "Id": "0.0A", + "Iq": "0.0A", + "epsI": "atan2(Iq,Id)", # current angle + "Im": "sqrt(Id^2+Iq^2)", + "InitPos": "-30deg", + "w": f"{w}Hz", + "RotSpeed": f"{RotSpeed}rpm", + "Nper": "1/6", # number of included periods + "PointPer": "101", # number of time points per period + } + + def set_solution_expressions(self): + self.solution_expressions = ["Moving1.Torque"] + + def set_output_vars(self): + self.output_vars = {} + + def assign_stator_coils(self, m2d: Maxwell2d) -> None: + # Excitations + I_A = "Im * cos(w*Time+epsI)" + I_B = "Im * cos(w*Time-120deg+epsI)" + I_C = "Im * cos(w*Time-240deg+epsI)" + + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + # belt_offset=0 reproduces the legacy 3-phase(36-slot) base winding. + groups = phase_groups(Q, p, 3, belt_offset=0) + + for group in groups: + for coil_name, polarity in group: + m2d.assign_coil(assignment=[coil_name], conductors_number="Nc", polarity=polarity, name=f"CS_{coil_name}") + + for phase_name, current, group in zip("ABC", [I_A, I_B, I_C], groups): + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=current, + parallel_branches="ParallelPaths", + name=f"Phase{phase_name}", + ) + m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) + + def inductance_computation(self, m2d: Maxwell2d) -> None: + m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=False) + + def set_variables(self, m2d: Maxwell2d, Id, Iq): + self.Id, self.Iq = Id, Iq + m2d.variable_manager["Id"] = f"{Id}A" + m2d.variable_manager["Iq"] = f"{Iq}A" + + def extract_results(self, solutions): + return {"Moving1.Torque": np.array(solutions.data_real("Moving1.Torque"))} From 04a9bd8611a755f0ca049c9fca2633a38190ac77 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 14:12:37 +0200 Subject: [PATCH 02/41] adjust import order --- motors/synrm_3f_60s.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index baf9550..f6a560d 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -2,10 +2,10 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from ansys.aedt.core.modeler.modeler_2d import Modeler2D from machine_design.designs.computation import ComputationBase from machine_design.winding import phase_groups + from .synrm_3f_36s import Geometry as BaseGeometry From 81d193c09e1df8505c8e7ad446793cdb2331f7b1 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 14:44:23 +0200 Subject: [PATCH 03/41] create 5f_60s --- motors/synrm_5f_60s.py | 223 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) create mode 100644 motors/synrm_5f_60s.py diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py new file mode 100644 index 0000000..b6f90af --- /dev/null +++ b/motors/synrm_5f_60s.py @@ -0,0 +1,223 @@ +"""Anchor `synrm_5f_60s`: 5-phase synchronous reluctance machine, 60 stator slots. + +Derives from `synrm_3f_60s`, overriding only the slotting and winding it needs for five +phases. Excitation is dq1 + dq3. +""" + +import numpy as np +from ansys.aedt.core import Maxwell2d + +from machine_design.designs.computation import ComputationBase +from machine_design.winding import phase_groups + +from .synrm_5f_40s import Geometry as BaseGeometry + + +class Geometry(BaseGeometry): + def set_geom_params(self): + super().set_geom_params() + self.geom_params["SlotNumber"] = "60" + + def set_slot_params(self): + super().set_slot_params() + self.slot_params["Bs0"] = "1.5mm" + self.slot_params["Bs1"] = "2.0mm" + self.slot_params["Bs2"] = "2.85mm" + self.slot_params["Rs"] = "1.0mm" + self.slot_params["SetAngle"] = "6deg" + + def set_winds_params(self): + super().set_winds_params() + self.wind_params["CoilPitch"] = "15" + + +class Computation(ComputationBase): + def set_oper_params(self): + f = 50 # [Hz] + RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] + w = 2 * np.pi * f + self.oper_params = { + "Id1": "0.0A", + "Iq1": "0.0A", + "Id3": "0.0A", + "Iq3": "0.0A", + "epsI1": "atan2(Iq1,Id1)", # current angle, 1st harmonic + "epsI3": "atan2(Iq3,Id3)", # current angle, 1st harmonic + "Im1": "sqrt(Id1^2+Iq1^2)", + "Im3": "sqrt(Id3^2+Iq3^2)", + "InitPos": "-45deg", + "w": f"{w}Hz", + "RotSpeed": f"{RotSpeed}rpm", + "Nper": "1/10", # number of included periods + "PointPer": "101", # number of time points per period + } + + def set_solution_expressions(self): + self.solution_expressions = ["Moving1.Torque"] + + def set_output_vars(self): + self.output_vars = {} + + def assign_stator_coils(self, m2d: Maxwell2d) -> None: + # Excitations + I_A = "Im1*cos(w*Time+epsI1-pi) + Im3*cos(3*(w*Time)+epsI3-pi)" + I_B = "Im1*cos(w*Time-72deg+epsI1-pi) + Im3*cos(3*(w*Time-72deg)+epsI3-pi)" + I_C = "Im1*cos(w*Time-144deg+epsI1-pi) + Im3*cos(3*(w*Time-144deg)+epsI3-pi)" + I_D = "Im1*cos(w*Time-216deg+epsI1-pi) + Im3*cos(3*(w*Time-216deg)+epsI3-pi)" + I_E = "Im1*cos(w*Time-288deg+epsI1-pi) + Im3*cos(3*(w*Time-288deg)+epsI3-pi)" + + Q=int(self.geometry.geom_params["SlotNumber"]) + p=self.geometry.PolePairs + # belt_offset=1 reproduces the 5-phase(40-slot) base winding. + groups = phase_groups(Q, p, 5, belt_offset=1) + + for group in groups: + for coil_name, polarity in group: + m2d.assign_coil(assignment=[coil_name], conductors_number="Nc", polarity=polarity, name=f"CS_{coil_name}") + + for phase_name, current, group in zip("ABCDE", [I_A, I_B, I_C, ], groups): + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=current, + parallel_branches="ParallelPaths", + name=f"Phase{phase_name}", + ) + m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) + + + + + m2d.assign_coil + # Define phase windings + m2d.assign_coil( + assignment=["Coil"], + conductors_number="Nc", + polarity="Positive", + name="CS1", + ) + m2d.assign_coil( + assignment=["Coil_1"], + conductors_number="Nc", + polarity="Negative", + name="CS2", + ) + m2d.assign_coil( + assignment=["Coil_2"], + conductors_number="Nc", + polarity="Negative", + name="CS3", + ) + m2d.assign_coil( + assignment=["Coil_3"], + conductors_number="Nc", + polarity="Positive", + name="CS4", + ) + m2d.assign_coil( + assignment=["Coil_4"], + conductors_number="Nc", + polarity="Positive", + name="CS5", + ) + m2d.assign_coil( + assignment=["Coil_5"], + conductors_number="Nc", + polarity="Negative", + name="CS6", + ) + m2d.assign_coil( + assignment=["Coil_6"], + conductors_number="Nc", + polarity="Negative", + name="CS7", + ) + m2d.assign_coil( + assignment=["Coil_7"], + conductors_number="Nc", + polarity="Positive", + name="CS8", + ) + m2d.assign_coil( + assignment=["Coil_8"], + conductors_number="Nc", + polarity="Positive", + name="CS9", + ) + m2d.assign_coil( + assignment=["Coil_9"], + conductors_number="Nc", + polarity="Negative", + name="CS10", + ) + + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=I_A, + parallel_branches="ParallelPaths", + name="PhaseA", + ) + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=I_B, + parallel_branches="ParallelPaths", + name="PhaseB", + ) + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=I_C, + parallel_branches="ParallelPaths", + name="PhaseC", + ) + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=I_D, + parallel_branches="ParallelPaths", + name="PhaseD", + ) + m2d.assign_winding( + assignment=None, + winding_type="Current", + is_solid=False, + current=I_E, + parallel_branches="ParallelPaths", + name="PhaseE", + ) + + m2d.add_winding_coils(assignment="PhaseA", coils=["CS1", "CS10"]) + m2d.add_winding_coils(assignment="PhaseB", coils=["CS4", "CS5"]) + m2d.add_winding_coils(assignment="PhaseC", coils=["CS8", "CS9"]) + m2d.add_winding_coils(assignment="PhaseD", coils=["CS2", "CS3"]) + m2d.add_winding_coils(assignment="PhaseE", coils=["CS6", "CS7"]) + + def inductance_computation(self, m2d: Maxwell2d) -> None: + m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=True) + + def set_variables(self, m2d: Maxwell2d, Id1, Iq1, Id3, Iq3): + m2d.variable_manager["Id1"] = f"{Id1}A" + m2d.variable_manager["Iq1"] = f"{Iq1}A" + m2d.variable_manager["Id3"] = f"{Id3}A" + m2d.variable_manager["Iq3"] = f"{Iq3}A" + + def extract_results(self, solutions): + out = {} + for expr in self.solution_expressions: + val = solutions.data_real(expr) + + if expr.startswith("Ld") or expr.startswith("Lq"): + val = np.array(val) / 1e9 + elif expr.startswith("I_"): + val = np.array(val) / 1e3 + + # TODO: possibly assign val[:-1]. check whether values are identical + out[expr] = val + return out From 6f831cec63a49b99cf2f8b8ccb77a8be21988d4e Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 15:12:28 +0200 Subject: [PATCH 04/41] update 5f_60s --- motors/synrm_5f_60s.py | 137 +++-------------------------------------- 1 file changed, 7 insertions(+), 130 deletions(-) diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index b6f90af..793d382 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -1,6 +1,6 @@ """Anchor `synrm_5f_60s`: 5-phase synchronous reluctance machine, 60 stator slots. -Derives from `synrm_3f_60s`, overriding only the slotting and winding it needs for five +Derives from `synrm_5f_40s`, overriding only the slotting and winding it needs for five phases. Excitation is dq1 + dq3. """ @@ -42,7 +42,7 @@ def set_oper_params(self): "Id3": "0.0A", "Iq3": "0.0A", "epsI1": "atan2(Iq1,Id1)", # current angle, 1st harmonic - "epsI3": "atan2(Iq3,Id3)", # current angle, 1st harmonic + "epsI3": "atan2(Iq3,Id3)", # current angle, 3rd harmonic "Im1": "sqrt(Id1^2+Iq1^2)", "Im3": "sqrt(Id3^2+Iq3^2)", "InitPos": "-45deg", @@ -66,8 +66,8 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: I_D = "Im1*cos(w*Time-216deg+epsI1-pi) + Im3*cos(3*(w*Time-216deg)+epsI3-pi)" I_E = "Im1*cos(w*Time-288deg+epsI1-pi) + Im3*cos(3*(w*Time-288deg)+epsI3-pi)" - Q=int(self.geometry.geom_params["SlotNumber"]) - p=self.geometry.PolePairs + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs # belt_offset=1 reproduces the 5-phase(40-slot) base winding. groups = phase_groups(Q, p, 5, belt_offset=1) @@ -75,7 +75,7 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: for coil_name, polarity in group: m2d.assign_coil(assignment=[coil_name], conductors_number="Nc", polarity=polarity, name=f"CS_{coil_name}") - for phase_name, current, group in zip("ABCDE", [I_A, I_B, I_C, ], groups): + for phase_name, current, group in zip("ABCDE", [I_A, I_B, I_C, I_D, I_E], groups): m2d.assign_winding( assignment=None, winding_type="Current", @@ -86,138 +86,15 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - - - - m2d.assign_coil - # Define phase windings - m2d.assign_coil( - assignment=["Coil"], - conductors_number="Nc", - polarity="Positive", - name="CS1", - ) - m2d.assign_coil( - assignment=["Coil_1"], - conductors_number="Nc", - polarity="Negative", - name="CS2", - ) - m2d.assign_coil( - assignment=["Coil_2"], - conductors_number="Nc", - polarity="Negative", - name="CS3", - ) - m2d.assign_coil( - assignment=["Coil_3"], - conductors_number="Nc", - polarity="Positive", - name="CS4", - ) - m2d.assign_coil( - assignment=["Coil_4"], - conductors_number="Nc", - polarity="Positive", - name="CS5", - ) - m2d.assign_coil( - assignment=["Coil_5"], - conductors_number="Nc", - polarity="Negative", - name="CS6", - ) - m2d.assign_coil( - assignment=["Coil_6"], - conductors_number="Nc", - polarity="Negative", - name="CS7", - ) - m2d.assign_coil( - assignment=["Coil_7"], - conductors_number="Nc", - polarity="Positive", - name="CS8", - ) - m2d.assign_coil( - assignment=["Coil_8"], - conductors_number="Nc", - polarity="Positive", - name="CS9", - ) - m2d.assign_coil( - assignment=["Coil_9"], - conductors_number="Nc", - polarity="Negative", - name="CS10", - ) - - m2d.assign_winding( - assignment=None, - winding_type="Current", - is_solid=False, - current=I_A, - parallel_branches="ParallelPaths", - name="PhaseA", - ) - m2d.assign_winding( - assignment=None, - winding_type="Current", - is_solid=False, - current=I_B, - parallel_branches="ParallelPaths", - name="PhaseB", - ) - m2d.assign_winding( - assignment=None, - winding_type="Current", - is_solid=False, - current=I_C, - parallel_branches="ParallelPaths", - name="PhaseC", - ) - m2d.assign_winding( - assignment=None, - winding_type="Current", - is_solid=False, - current=I_D, - parallel_branches="ParallelPaths", - name="PhaseD", - ) - m2d.assign_winding( - assignment=None, - winding_type="Current", - is_solid=False, - current=I_E, - parallel_branches="ParallelPaths", - name="PhaseE", - ) - - m2d.add_winding_coils(assignment="PhaseA", coils=["CS1", "CS10"]) - m2d.add_winding_coils(assignment="PhaseB", coils=["CS4", "CS5"]) - m2d.add_winding_coils(assignment="PhaseC", coils=["CS8", "CS9"]) - m2d.add_winding_coils(assignment="PhaseD", coils=["CS2", "CS3"]) - m2d.add_winding_coils(assignment="PhaseE", coils=["CS6", "CS7"]) - def inductance_computation(self, m2d: Maxwell2d) -> None: m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=True) def set_variables(self, m2d: Maxwell2d, Id1, Iq1, Id3, Iq3): + self.Id1, self.Iq1, self.Id3, self.Iq3 = Id1, Iq1, Id3, Iq3 m2d.variable_manager["Id1"] = f"{Id1}A" m2d.variable_manager["Iq1"] = f"{Iq1}A" m2d.variable_manager["Id3"] = f"{Id3}A" m2d.variable_manager["Iq3"] = f"{Iq3}A" def extract_results(self, solutions): - out = {} - for expr in self.solution_expressions: - val = solutions.data_real(expr) - - if expr.startswith("Ld") or expr.startswith("Lq"): - val = np.array(val) / 1e9 - elif expr.startswith("I_"): - val = np.array(val) / 1e3 - - # TODO: possibly assign val[:-1]. check whether values are identical - out[expr] = val - return out + return {"Moving1.Torque": np.array(solutions.data_real("Moving1.Torque"))} From fa24cae41f085a1566bec1f0db7cc8ce863acac9 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 15:43:56 +0200 Subject: [PATCH 05/41] update set_post_params() --- motors/synrm_3f_60s.py | 12 ++++++++++++ motors/synrm_5f_60s.py | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index f6a560d..bf44c91 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -50,6 +50,18 @@ def set_solution_expressions(self): def set_output_vars(self): self.output_vars = {} + def set_post_params(self): + self.post_params = { # reports + ("InducedVoltage(PhaseA)", "InducedVoltage(PhaseB)", "InducedVoltage(PhaseC)"): "InducedVoltage", + ("Moving1.Torque"): "Torque", + ("InputCurrent(PhaseA)", "InputCurrent(PhaseB)", "InputCurrent(PhaseC)"): "Current", + ( + "FluxLinkage(PhaseA)", + "FluxLinkage(PhaseB)", + "FluxLinkage(PhaseC)", + ): "FluxLinkage", + } + def assign_stator_coils(self, m2d: Maxwell2d) -> None: # Excitations I_A = "Im * cos(w*Time+epsI)" diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index 793d382..615e6f0 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -58,6 +58,32 @@ def set_solution_expressions(self): def set_output_vars(self): self.output_vars = {} + def set_post_params(self): + self.post_params = { # reports + ( + "InducedVoltage(PhaseA)", + "InducedVoltage(PhaseB)", + "InducedVoltage(PhaseC)", + "InducedVoltage(PhaseD)", + "InducedVoltage(PhaseE)", + ): "InducedVoltage", + ("Moving1.Torque"): "Torque", + ( + "InputCurrent(PhaseA)", + "InputCurrent(PhaseB)", + "InputCurrent(PhaseC)", + "InputCurrent(PhaseD)", + "InputCurrent(PhaseE)", + ): "Current", + ( + "FluxLinkage(PhaseA)", + "FluxLinkage(PhaseB)", + "FluxLinkage(PhaseC)", + "FluxLinkage(PhaseD)", + "FluxLinkage(PhaseE)", + ): "FluxLinkage", + } + def assign_stator_coils(self, m2d: Maxwell2d) -> None: # Excitations I_A = "Im1*cos(w*Time+epsI1-pi) + Im3*cos(3*(w*Time)+epsI3-pi)" From 1df0ab8ff6766faade4ae84c21b8a7e1339a56b5 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 16:33:47 +0200 Subject: [PATCH 06/41] create test file with ansys --- tests/test_ansys_synrm_60s_solve.py | 76 +++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/test_ansys_synrm_60s_solve.py diff --git a/tests/test_ansys_synrm_60s_solve.py b/tests/test_ansys_synrm_60s_solve.py new file mode 100644 index 0000000..82664f6 --- /dev/null +++ b/tests/test_ansys_synrm_60s_solve.py @@ -0,0 +1,76 @@ +"""Goal: test both synrm_3f_60s.py and synrm_5f_60s.py can accept the same rotor and solve. + +Run explicitly with: + + pytest -m ansys tests/test_ansys_synrm_60s_solve.py + +Requires a running Ansys Electronics Desktop session and a free license seat. +""" + +import numpy as np +import pytest + +from machine_design.config import load_config +from machine_design.designs.design import Design as LiveDesign +from machine_design.optimization.generators import HacklGenerator_OneLambda +from motors.synrm_3f_60s import Computation as Computation3f +from motors.synrm_3f_60s import Geometry as Geometry3f +from motors.synrm_5f_60s import Computation as Computation5f +from motors.synrm_5f_60s import Geometry as Geometry5f + +pytestmark = pytest.mark.ansys + +AEDT_VERSION = load_config()["aedt_version"] +R_STATOR_END = 0.7 +OFFSET = 0.35 +SEEDS = (42, 43) +NUM_CORES = 1 +Machines=[ + ("synrm_3f_60s", Geometry3f, Computation3f, (1.5, 1.5)), + ("synrm_5f_60s", Geometry5f, Computation5f, (1.5, 1.5, 0.0, 0.0)), +] + + +def _generate_one_lambda_barriers(seed): + # rotor_r_min/rotor_r_max are pure-Python derived params, so a throwaway + # instance (no Ansys) is enough to size the generator. + geometry = Geometry3f() + dummy = LiveDesign(m2d=None, geometry=geometry, computation=Computation3f(geometry)) + generator = HacklGenerator_OneLambda(dummy, R_STATOR_END, offset=OFFSET) + + np.random.seed(seed) + while True: + params = generator.random_parameters() + generator.set_parameters(params) + barriers = generator.generate_barriers() + barriers = generator.split_barriers(barriers) + if generator.feasible_barriers(barriers): + return barriers + + +def test_synrm_3f_60s_and_5f_60s_solve_with_same_rotor(tmp_path): + for seed in SEEDS: + barriers=_generate_one_lambda_barriers(seed) + + for name, geometry_cls, computation_cls, current_setpoint in Machines: + geometry=geometry_cls() + computation=computation_cls(geometry) + design=LiveDesign.create( + f"Check60_{name}_{seed}", + "Design01", + str(tmp_path/f"{name}_{seed}.aedt"), + geometry, + computation, + version=AEDT_VERSION, + non_graphical=True, + new_desktop=False, + close_on_exit=False, + ) + try: + design.add_rotor() + for barrier in barriers: + design.add_rotor_barrier(barrier) + torque=design.compute(*current_setpoint, NUM_CORES=NUM_CORES) + assert torque is not None, f"{name} seed {seed}: torque is None" + finally: + design.close_project() From f57f69ebdd680fa118f1eb58cc7b60fd2fa92c09 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 16:51:13 +0200 Subject: [PATCH 07/41] update currents --- tests/test_ansys_synrm_60s_solve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_ansys_synrm_60s_solve.py b/tests/test_ansys_synrm_60s_solve.py index 82664f6..fc64553 100644 --- a/tests/test_ansys_synrm_60s_solve.py +++ b/tests/test_ansys_synrm_60s_solve.py @@ -27,7 +27,7 @@ NUM_CORES = 1 Machines=[ ("synrm_3f_60s", Geometry3f, Computation3f, (1.5, 1.5)), - ("synrm_5f_60s", Geometry5f, Computation5f, (1.5, 1.5, 0.0, 0.0)), + ("synrm_5f_60s", Geometry5f, Computation5f, (7.0711, 7.0711, 0.0, 0.0)), ] From 4dc9913cd919a9916a1e29a402e7a5884d9afea4 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Mon, 21 Sep 2026 17:09:05 +0200 Subject: [PATCH 08/41] ruff format --- tests/test_ansys_synrm_60s_solve.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/test_ansys_synrm_60s_solve.py b/tests/test_ansys_synrm_60s_solve.py index fc64553..7504fa4 100644 --- a/tests/test_ansys_synrm_60s_solve.py +++ b/tests/test_ansys_synrm_60s_solve.py @@ -25,7 +25,7 @@ OFFSET = 0.35 SEEDS = (42, 43) NUM_CORES = 1 -Machines=[ +Machines = [ ("synrm_3f_60s", Geometry3f, Computation3f, (1.5, 1.5)), ("synrm_5f_60s", Geometry5f, Computation5f, (7.0711, 7.0711, 0.0, 0.0)), ] @@ -50,15 +50,15 @@ def _generate_one_lambda_barriers(seed): def test_synrm_3f_60s_and_5f_60s_solve_with_same_rotor(tmp_path): for seed in SEEDS: - barriers=_generate_one_lambda_barriers(seed) + barriers = _generate_one_lambda_barriers(seed) for name, geometry_cls, computation_cls, current_setpoint in Machines: - geometry=geometry_cls() - computation=computation_cls(geometry) - design=LiveDesign.create( + geometry = geometry_cls() + computation = computation_cls(geometry) + design = LiveDesign.create( f"Check60_{name}_{seed}", "Design01", - str(tmp_path/f"{name}_{seed}.aedt"), + str(tmp_path / f"{name}_{seed}.aedt"), geometry, computation, version=AEDT_VERSION, @@ -69,8 +69,8 @@ def test_synrm_3f_60s_and_5f_60s_solve_with_same_rotor(tmp_path): try: design.add_rotor() for barrier in barriers: - design.add_rotor_barrier(barrier) - torque=design.compute(*current_setpoint, NUM_CORES=NUM_CORES) + design.add_rotor_barrier(barrier) + torque = design.compute(*current_setpoint, NUM_CORES=NUM_CORES) assert torque is not None, f"{name} seed {seed}: torque is None" finally: design.close_project() From 77696cca1a2071d06a272fa0896f39e9e6803f04 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 22 Sep 2026 12:35:57 +0200 Subject: [PATCH 09/41] import computation from 26s and 40s, delete inductance_computation() --- motors/synrm_3f_60s.py | 6 ++---- motors/synrm_5f_60s.py | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index bf44c91..e591300 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -3,9 +3,9 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.designs.computation import ComputationBase from machine_design.winding import phase_groups +from .synrm_3f_36s import Computation as BaseComputation from .synrm_3f_36s import Geometry as BaseGeometry @@ -27,7 +27,7 @@ def set_winds_params(self): self.wind_params["CoilPitch"] = "15" # full pitch over one pole (60/4) -class Computation(ComputationBase): +class Computation(BaseComputation): def set_oper_params(self): f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] @@ -88,8 +88,6 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def inductance_computation(self, m2d: Maxwell2d) -> None: - m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=False) def set_variables(self, m2d: Maxwell2d, Id, Iq): self.Id, self.Iq = Id, Iq diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index 615e6f0..f69f015 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -7,9 +7,9 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.designs.computation import ComputationBase from machine_design.winding import phase_groups +from .synrm_5f_40s import Computation as BaseComputation from .synrm_5f_40s import Geometry as BaseGeometry @@ -31,7 +31,7 @@ def set_winds_params(self): self.wind_params["CoilPitch"] = "15" -class Computation(ComputationBase): +class Computation(BaseComputation): def set_oper_params(self): f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] @@ -112,8 +112,6 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def inductance_computation(self, m2d: Maxwell2d) -> None: - m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=True) def set_variables(self, m2d: Maxwell2d, Id1, Iq1, Id3, Iq3): self.Id1, self.Iq1, self.Id3, self.Iq3 = Id1, Iq1, Id3, Iq3 From c39ff4257033b8b7ca893e955d72a5a2d4ca8812 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 22 Sep 2026 12:46:45 +0200 Subject: [PATCH 10/41] ruff format --- motors/synrm_3f_60s.py | 1 - motors/synrm_5f_60s.py | 1 - 2 files changed, 2 deletions(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index e591300..fccf12e 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -88,7 +88,6 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def set_variables(self, m2d: Maxwell2d, Id, Iq): self.Id, self.Iq = Id, Iq m2d.variable_manager["Id"] = f"{Id}A" diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index f69f015..a8514c4 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -112,7 +112,6 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def set_variables(self, m2d: Maxwell2d, Id1, Iq1, Id3, Iq3): self.Id1, self.Iq1, self.Id3, self.Iq3 = Id1, Iq1, Id3, Iq3 m2d.variable_manager["Id1"] = f"{Id1}A" From 0bdfd0972101c29a8ec24b4511a776834feb231e Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 22 Sep 2026 16:00:18 +0200 Subject: [PATCH 11/41] move winding.py to generic/ --- machine_design/generic/__init__.py | 1 + machine_design/{ => generic}/winding.py | 0 2 files changed, 1 insertion(+) rename machine_design/{ => generic}/winding.py (100%) diff --git a/machine_design/generic/__init__.py b/machine_design/generic/__init__.py index bd6633f..b1bd079 100644 --- a/machine_design/generic/__init__.py +++ b/machine_design/generic/__init__.py @@ -1 +1,2 @@ from .transforms import electrical_angle, to_dq, to_phases +from .winding import coil_name, phase_groups, sector_winding diff --git a/machine_design/winding.py b/machine_design/generic/winding.py similarity index 100% rename from machine_design/winding.py rename to machine_design/generic/winding.py From c689da4b10e8ca88b3315d1562d367ffb1f5f882 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 22 Sep 2026 16:08:29 +0200 Subject: [PATCH 12/41] simpilfy computation part --- motors/synrm_3f_60s.py | 24 ++---------------------- motors/synrm_5f_60s.py | 32 +------------------------------- 2 files changed, 3 insertions(+), 53 deletions(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index fccf12e..c911616 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -3,7 +3,7 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.winding import phase_groups +from machine_design.generic.winding import phase_groups from .synrm_3f_36s import Computation as BaseComputation from .synrm_3f_36s import Geometry as BaseGeometry @@ -28,25 +28,6 @@ def set_winds_params(self): class Computation(BaseComputation): - def set_oper_params(self): - f = 50 # [Hz] - RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] - w = 2 * np.pi * f - self.oper_params = { - "Id": "0.0A", - "Iq": "0.0A", - "epsI": "atan2(Iq,Id)", # current angle - "Im": "sqrt(Id^2+Iq^2)", - "InitPos": "-30deg", - "w": f"{w}Hz", - "RotSpeed": f"{RotSpeed}rpm", - "Nper": "1/6", # number of included periods - "PointPer": "101", # number of time points per period - } - - def set_solution_expressions(self): - self.solution_expressions = ["Moving1.Torque"] - def set_output_vars(self): self.output_vars = {} @@ -89,9 +70,8 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) def set_variables(self, m2d: Maxwell2d, Id, Iq): + super().set_variables(m2d, Id, Iq) self.Id, self.Iq = Id, Iq - m2d.variable_manager["Id"] = f"{Id}A" - m2d.variable_manager["Iq"] = f"{Iq}A" def extract_results(self, solutions): return {"Moving1.Torque": np.array(solutions.data_real("Moving1.Torque"))} diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index a8514c4..ee715ba 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -7,7 +7,7 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.winding import phase_groups +from machine_design.generic.winding import phase_groups from .synrm_5f_40s import Computation as BaseComputation from .synrm_5f_40s import Geometry as BaseGeometry @@ -32,32 +32,9 @@ def set_winds_params(self): class Computation(BaseComputation): - def set_oper_params(self): - f = 50 # [Hz] - RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] - w = 2 * np.pi * f - self.oper_params = { - "Id1": "0.0A", - "Iq1": "0.0A", - "Id3": "0.0A", - "Iq3": "0.0A", - "epsI1": "atan2(Iq1,Id1)", # current angle, 1st harmonic - "epsI3": "atan2(Iq3,Id3)", # current angle, 3rd harmonic - "Im1": "sqrt(Id1^2+Iq1^2)", - "Im3": "sqrt(Id3^2+Iq3^2)", - "InitPos": "-45deg", - "w": f"{w}Hz", - "RotSpeed": f"{RotSpeed}rpm", - "Nper": "1/10", # number of included periods - "PointPer": "101", # number of time points per period - } - def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] - def set_output_vars(self): - self.output_vars = {} - def set_post_params(self): self.post_params = { # reports ( @@ -112,12 +89,5 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def set_variables(self, m2d: Maxwell2d, Id1, Iq1, Id3, Iq3): - self.Id1, self.Iq1, self.Id3, self.Iq3 = Id1, Iq1, Id3, Iq3 - m2d.variable_manager["Id1"] = f"{Id1}A" - m2d.variable_manager["Iq1"] = f"{Iq1}A" - m2d.variable_manager["Id3"] = f"{Id3}A" - m2d.variable_manager["Iq3"] = f"{Iq3}A" - def extract_results(self, solutions): return {"Moving1.Torque": np.array(solutions.data_real("Moving1.Torque"))} From 75ff35e4ab54ed5275b19a8be441907ef5533bf2 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 09:46:43 +0200 Subject: [PATCH 13/41] update 3f-60s, 5f-60s, and test file --- motors/synrm_3f_60s.py | 8 +- motors/synrm_5f_60s.py | 5 ++ tests/test_synrm_60s_torque_i3_zero.py | 102 +++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 6 deletions(-) create mode 100644 tests/test_synrm_60s_torque_i3_zero.py diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index c911616..1eb1075 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -28,8 +28,8 @@ def set_winds_params(self): class Computation(BaseComputation): - def set_output_vars(self): - self.output_vars = {} + def set_solution_expressions(self): + self.solution_expressions = ["Moving1.Torque"] def set_post_params(self): self.post_params = { # reports @@ -69,9 +69,5 @@ def assign_stator_coils(self, m2d: Maxwell2d) -> None: ) m2d.add_winding_coils(assignment=f"Phase{phase_name}", coils=[f"CS_{coil_name}" for coil_name, _ in group]) - def set_variables(self, m2d: Maxwell2d, Id, Iq): - super().set_variables(m2d, Id, Iq) - self.Id, self.Iq = Id, Iq - def extract_results(self, solutions): return {"Moving1.Torque": np.array(solutions.data_real("Moving1.Torque"))} diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index ee715ba..17871be 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -32,6 +32,11 @@ def set_winds_params(self): class Computation(BaseComputation): + def set_oper_params(self): + super().set_oper_params() + self.InitPos = -42 # it should be computed first for different machines + self.oper_params["InitPos"] = f"{self.InitPos}deg" + def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] diff --git a/tests/test_synrm_60s_torque_i3_zero.py b/tests/test_synrm_60s_torque_i3_zero.py new file mode 100644 index 0000000..a185790 --- /dev/null +++ b/tests/test_synrm_60s_torque_i3_zero.py @@ -0,0 +1,102 @@ +"""Goal: test 5f_60s performs the same with 3f_60s when Id3=Iq3=0 and the same rotor. + +Run explicitly with: + + pytest -m ansys tests/test_synrm_60s_torque_i3_zero.py + +Requires a running Ansys Electronics Desktop session and a free license seat. +""" + +import numpy as np +import pytest + +from machine_design.config import load_config +from machine_design.designs.design import Design as LiveDesign +from machine_design.optimization.generators import HacklGenerator_OneLambda +from machine_design.optimization.geometry import analyze_results +from motors.synrm_3f_60s import Computation as Computation3f +from motors.synrm_3f_60s import Geometry as Geometry3f +from motors.synrm_5f_60s import Computation as Computation5f +from motors.synrm_5f_60s import Geometry as Geometry5f + +pytestmark = pytest.mark.ansys + +AEDT_VERSION = load_config()["aedt_version"] +R_STATOR_END = 0.7 +OFFSET = 0.35 +SEEDS = (42, 43) +NUM_CORES = 1 +current_setpoints = (7.0711, 7.0711) + + +class Geometry3fSameNc(Geometry3f): + def set_winds_params(self): + super().set_winds_params() + self.wind_params["Nc"] = "113" + + +class Geometry5fSameNc(Geometry5f): + def set_winds_params(self): + super().set_winds_params() + self.wind_params["Nc"] = "113" + + +Machines = [ + ("synrm_3f_60s", Geometry3fSameNc, Computation3f, current_setpoints), + ("synrm_5f_60s", Geometry5fSameNc, Computation5f, (*current_setpoints, 0.0, 0.0)), +] + + +def _generate_one_lambda_barriers(seed): + # rotor_r_min/rotor_r_max are pure-Python derived params, so a throwaway + # instance (no Ansys) is enough to size the generator. + geometry = Geometry3f() + dummy = LiveDesign(m2d=None, geometry=geometry, computation=Computation3f(geometry)) + generator = HacklGenerator_OneLambda(dummy, R_STATOR_END, offset=OFFSET) + + np.random.seed(seed) + while True: + params = generator.random_parameters() + generator.set_parameters(params) + barriers = generator.generate_barriers() + barriers = generator.split_barriers(barriers) + if generator.feasible_barriers(barriers): + return barriers + + +def test_synrm_5f_60s_i3_zero_performs_same_with_3f_60s(tmp_path): + for seed in SEEDS: + barriers = _generate_one_lambda_barriers(seed) + + results = {} + for name, geometry_cls, computation_cls, current_setpoint in Machines: + geometry = geometry_cls() + computation = computation_cls(geometry) + design = LiveDesign.create( + f"CheckI3zero_{name}_{seed}", + "Design01", + str(tmp_path / f"{name}_{seed}.aedt"), + geometry, + computation, + version=AEDT_VERSION, + non_graphical=True, + new_desktop=False, + close_on_exit=False, + ) + try: + design.add_rotor() + for barrier in barriers: + design.add_rotor_barrier(barrier) + + torque = design.compute(*current_setpoint, NUM_CORES=NUM_CORES) + assert torque is not None, f"{name} seed {seed}: torque is None" + TorAvg, _, TorRippleRms = analyze_results(torque["Moving1.Torque"]) + results[name] = TorAvg + finally: + design.close_project() + + torque_3f = results["synrm_3f_60s"] + torque_5f = results["synrm_5f_60s"] + diff_pct = abs(torque_3f - torque_5f) / abs(torque_3f) * 100 + print(f"seed {seed}: 3f TorAvg={torque_3f:.4f} Nm, 5f(i3=0) TorAvg={torque_5f:.4f} Nm, diff={diff_pct:.2f}%") + assert diff_pct < 5, f"seed {seed}: 3f and 5f(i3=0) torque differ by {diff_pct:.2f}%, expected roughly comparable" From c3ac7a376ae7b8f3ff9ad406cd56e032a0c1e140 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 10:43:39 +0200 Subject: [PATCH 14/41] computr InitPos function --- machine_design/generic/compute_initpos.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 machine_design/generic/compute_initpos.py diff --git a/machine_design/generic/compute_initpos.py b/machine_design/generic/compute_initpos.py new file mode 100644 index 0000000..06e2cc2 --- /dev/null +++ b/machine_design/generic/compute_initpos.py @@ -0,0 +1,20 @@ +import math + +from machine_design.generic.winding import sector_winding + + +def compute_initpos(Q: int, p: int, m: int, belt_offset: int): + out1 = sector_winding(Q, p, m, belt_offset) + xa = ya = 0.0 + for k, (phase_index, sign) in enumerate(out1): + if phase_index != 0: # only need phase A + continue + theta_e_deg = (0.5 + k) * (360.0 * p / Q) + if sign > 0: + eff_deg = theta_e_deg + else: + eff_deg = theta_e_deg + 180.0 + xa += math.cos(math.radians(eff_deg)) + ya += math.sin(math.radians(eff_deg)) + gamma_e_deg = math.degrees(math.atan2(ya, xa)) % 360 + return (gamma_e_deg - 90.0) / p From 7a57cfca2c7a65f1946bd27fc843c5e78031a4d7 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 12:06:12 +0200 Subject: [PATCH 15/41] add description --- machine_design/generic/compute_initpos.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/machine_design/generic/compute_initpos.py b/machine_design/generic/compute_initpos.py index 06e2cc2..0cd93c8 100644 --- a/machine_design/generic/compute_initpos.py +++ b/machine_design/generic/compute_initpos.py @@ -4,9 +4,13 @@ def compute_initpos(Q: int, p: int, m: int, belt_offset: int): - out1 = sector_winding(Q, p, m, belt_offset) + """This function aims to compute the value of InitPos for those machines with d-axis + at 45deg. + Override this method if a different rotor generator places the d-axis elsewhere.""" + + out = sector_winding(Q, p, m, belt_offset) xa = ya = 0.0 - for k, (phase_index, sign) in enumerate(out1): + for k, (phase_index, sign) in enumerate(out): if phase_index != 0: # only need phase A continue theta_e_deg = (0.5 + k) * (360.0 * p / Q) From 424ad40c7575d648a90f0f751c8821b07f7d609d Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 12:14:10 +0200 Subject: [PATCH 16/41] update gamma_e_deg --- machine_design/generic/compute_initpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_design/generic/compute_initpos.py b/machine_design/generic/compute_initpos.py index 0cd93c8..5255907 100644 --- a/machine_design/generic/compute_initpos.py +++ b/machine_design/generic/compute_initpos.py @@ -20,5 +20,5 @@ def compute_initpos(Q: int, p: int, m: int, belt_offset: int): eff_deg = theta_e_deg + 180.0 xa += math.cos(math.radians(eff_deg)) ya += math.sin(math.radians(eff_deg)) - gamma_e_deg = math.degrees(math.atan2(ya, xa)) % 360 + gamma_e_deg = math.degrees(math.atan2(ya, xa)) return (gamma_e_deg - 90.0) / p From eca62e06c080eaa7d609941a07c31bc760ac19fe Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 12:28:59 +0200 Subject: [PATCH 17/41] use compute initpos() to calculate InitPos for 3f_60s machine --- motors/synrm_3f_60s.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index 1eb1075..2347db8 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -3,6 +3,7 @@ import numpy as np from ansys.aedt.core import Maxwell2d +from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.winding import phase_groups from .synrm_3f_36s import Computation as BaseComputation @@ -28,6 +29,13 @@ def set_winds_params(self): class Computation(BaseComputation): + def set_oper_params(self): + super().set_oper_params() + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + self.InitPos = compute_initpos(Q, p, 3, belt_offset=0) + self.oper_params["InitPos"] = f"{self.InitPos}deg" + def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] From 5cf21eaac6b0bf436562462e66ec6479bc0b7907 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 12:30:34 +0200 Subject: [PATCH 18/41] use compute initpos() to calculate InitPos for 5f_60s machine --- motors/synrm_5f_60s.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index 17871be..2112293 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -7,6 +7,7 @@ import numpy as np from ansys.aedt.core import Maxwell2d +from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.winding import phase_groups from .synrm_5f_40s import Computation as BaseComputation @@ -34,7 +35,9 @@ def set_winds_params(self): class Computation(BaseComputation): def set_oper_params(self): super().set_oper_params() - self.InitPos = -42 # it should be computed first for different machines + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + self.InitPos = compute_initpos(Q, p, 5, belt_offset=1) self.oper_params["InitPos"] = f"{self.InitPos}deg" def set_solution_expressions(self): From ddab2f1155528baaa9bc1fc86a108ed0bb555058 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 13:02:51 +0200 Subject: [PATCH 19/41] add compute initpos --- machine_design/generic/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/machine_design/generic/__init__.py b/machine_design/generic/__init__.py index b1bd079..c59ec55 100644 --- a/machine_design/generic/__init__.py +++ b/machine_design/generic/__init__.py @@ -1,2 +1,3 @@ from .transforms import electrical_angle, to_dq, to_phases from .winding import coil_name, phase_groups, sector_winding +from .compute_initpos import compute_initpos From dc9e2b1c344ead5c06c36cdc9b26a1e92ec23f2a Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 13:24:31 +0200 Subject: [PATCH 20/41] ruff format --- machine_design/generic/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_design/generic/__init__.py b/machine_design/generic/__init__.py index c59ec55..a63bd58 100644 --- a/machine_design/generic/__init__.py +++ b/machine_design/generic/__init__.py @@ -1,3 +1,3 @@ +from .compute_initpos import compute_initpos from .transforms import electrical_angle, to_dq, to_phases from .winding import coil_name, phase_groups, sector_winding -from .compute_initpos import compute_initpos From 5098274991d080482db80a753ee78e4179da0674 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 14:30:22 +0200 Subject: [PATCH 21/41] add set_initpos() for 3f_36s --- motors/synrm_3f_36s.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index 93af0c0..e803a37 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -6,6 +6,7 @@ from machine_design.designs.computation import ComputationBase from machine_design.designs.geometry import GeometryBase +from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.transforms import to_dq @@ -175,18 +176,23 @@ def set_oper_params(self): f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f - self.InitPos = -30 # deg self.oper_params = { "Id": "0.0A", "Iq": "0.0A", "epsI": "atan2(Iq,Id)", # current angle "Im": "sqrt(Id^2+Iq^2)", - "InitPos": f"{self.InitPos}deg", "w": f"{w}Hz", "RotSpeed": f"{RotSpeed}rpm", "Nper": "1/6", # number of included periods "PointPer": "101", # number of time points per period } + self.set_initpos() + + def set_initpos(self): + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + self.InitPos = compute_initpos(Q, p, 3, belt_offset=0) + self.oper_params["InitPos"] = f"{self.InitPos}deg" def set_solution_expressions(self): self.solution_expressions = [ From 22eeb3204023cccef0aabd778168702efecbe4e1 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 14:30:41 +0200 Subject: [PATCH 22/41] add set_initpos() for 5f_40s --- motors/synrm_5f_40s.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index de271c9..03be211 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -8,6 +8,7 @@ from ansys.aedt.core import Maxwell2d from machine_design.designs.computation import ComputationBase +from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.transforms import electrical_angle, to_dq from .synrm_3f_36s import Geometry as BaseGeometry @@ -34,7 +35,6 @@ def set_oper_params(self): f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f - self.InitPos = -45 # deg self.RotSign = 1 self.Rstat = 19.0 self.Lew = 0.0 @@ -48,12 +48,18 @@ def set_oper_params(self): "epsI3": "atan2(Iq3,Id3)", # current angle, 1st harmonic "Im1": "sqrt(Id1^2+Iq1^2)", "Im3": "sqrt(Id3^2+Iq3^2)", - "InitPos": f"{self.InitPos}deg", "w": f"{w}Hz", "RotSpeed": f"{RotSpeed}rpm", "Nper": "1/10", # number of included periods "PointPer": "101", # number of time points per period } + self.set_initpos() + + def set_initpos(self): + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + self.InitPos = compute_initpos(Q, p, 5, belt_offset=1) + self.oper_params["InitPos"] = f"{self.InitPos}deg" def set_solution_expressions(self): self.solution_expressions = [ From d49d709edd556bc0d877058b0132aa80820d8c64 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 14:31:13 +0200 Subject: [PATCH 23/41] delete set_oper_params() --- motors/synrm_3f_60s.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index 2347db8..49eaa50 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -29,13 +29,6 @@ def set_winds_params(self): class Computation(BaseComputation): - def set_oper_params(self): - super().set_oper_params() - Q = int(self.geometry.geom_params["SlotNumber"]) - p = self.geometry.PolePairs - self.InitPos = compute_initpos(Q, p, 3, belt_offset=0) - self.oper_params["InitPos"] = f"{self.InitPos}deg" - def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] From e8987faedc74f4396819fcc6b9f9d14cf1445233 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 14:31:54 +0200 Subject: [PATCH 24/41] delete set_oper_params() --- motors/synrm_5f_60s.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index 2112293..79d9a75 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -33,13 +33,6 @@ def set_winds_params(self): class Computation(BaseComputation): - def set_oper_params(self): - super().set_oper_params() - Q = int(self.geometry.geom_params["SlotNumber"]) - p = self.geometry.PolePairs - self.InitPos = compute_initpos(Q, p, 5, belt_offset=1) - self.oper_params["InitPos"] = f"{self.InitPos}deg" - def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] From d166e591750bc7c587d94ee260c1052c6e28f9db Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 16:19:30 +0200 Subject: [PATCH 25/41] units consistency --- motors/synrm_5f_40s.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 03be211..ff665c0 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -245,8 +245,8 @@ def extract_results(self, 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) - Vind_d1, Vind_q1 = to_dq(vind_phases, theta_el, harmonic=1) - Vind_d3, Vind_q3 = to_dq(vind_phases, theta_el, harmonic=3) + Vind_d1, Vind_q1 = (v/1e3 for v in to_dq(vind_phases, theta_el, harmonic=1)) + Vind_d3, Vind_q3 = (v/1e3 for v in to_dq(vind_phases, theta_el, harmonic=3)) I_d1, I_q1 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=1)) I_d3, I_q3 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=3)) # mA to A @@ -262,7 +262,7 @@ def extract_results(self, solutions): phase_offset = -2 * np.pi * k / 5 # 5 angles, 0deg, -72deg, -144deg, -216deg, -288deg 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 + V_phases = vind_phases/1e3 + self.Rstat * current_phases / 1e3 + self.Lew * dI_dt_phases V_d1, V_q1 = to_dq(V_phases, theta_el, harmonic=1) V_d3, V_q3 = to_dq(V_phases, theta_el, harmonic=3) From 202407515d83292bf4c5adf3b0286136b5f63039 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 16:22:18 +0200 Subject: [PATCH 26/41] delete unnecessay import --- motors/synrm_3f_60s.py | 1 - 1 file changed, 1 deletion(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index 49eaa50..1eb1075 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -3,7 +3,6 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.winding import phase_groups from .synrm_3f_36s import Computation as BaseComputation From a52497533d60d147ad20450516788b4f3de772e3 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 16:22:58 +0200 Subject: [PATCH 27/41] udate corresponding test file --- tests/test_synrm_3f_36s.py | 2 +- tests/test_synrm_5f_40s.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_synrm_3f_36s.py b/tests/test_synrm_3f_36s.py index 3f5fa68..69d9ad5 100644 --- a/tests/test_synrm_3f_36s.py +++ b/tests/test_synrm_3f_36s.py @@ -77,7 +77,7 @@ def test_oper_params(computation): "Iq": "0.0A", "epsI": "atan2(Iq,Id)", "Im": "sqrt(Id^2+Iq^2)", - "InitPos": "-30deg", + "InitPos": "-30.0deg", "w": f"{2 * np.pi * 50}Hz", "RotSpeed": "1500.0rpm", "Nper": "1/6", diff --git a/tests/test_synrm_5f_40s.py b/tests/test_synrm_5f_40s.py index bd5a5dc..ed7953e 100644 --- a/tests/test_synrm_5f_40s.py +++ b/tests/test_synrm_5f_40s.py @@ -53,7 +53,7 @@ def test_oper_params_fully_replaced(computation2): "epsI3": "atan2(Iq3,Id3)", "Im1": "sqrt(Id1^2+Iq1^2)", "Im3": "sqrt(Id3^2+Iq3^2)", - "InitPos": "-45deg", + "InitPos": "-45.0deg", "w": f"{2 * np.pi * 50}Hz", "RotSpeed": "1500.0rpm", "Nper": "1/10", From b243076e037d4bd9af35473f56ec16ead4c6f184 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 16:40:15 +0200 Subject: [PATCH 28/41] print each vars to check units --- motors/synrm_5f_40s.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index ff665c0..56b1092 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -328,4 +328,7 @@ def extract_results(self, solutions): "Moving1.Torque": torque, } + for key, value in out.items(): + print(f"{key}: {np.asarray(value).flat[0]:.4g}") + return out From aa8c5dd94c677e9b8918c5d88243c88f510b4f4c Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 17:05:52 +0200 Subject: [PATCH 29/41] ruff format --- motors/synrm_5f_40s.py | 9 +++------ motors/synrm_5f_60s.py | 1 - 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 56b1092..eda247e 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -245,8 +245,8 @@ def extract_results(self, 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) - Vind_d1, Vind_q1 = (v/1e3 for v in to_dq(vind_phases, theta_el, harmonic=1)) - Vind_d3, Vind_q3 = (v/1e3 for v in to_dq(vind_phases, theta_el, harmonic=3)) + Vind_d1, Vind_q1 = (v / 1e3 for v in to_dq(vind_phases, theta_el, harmonic=1)) + Vind_d3, Vind_q3 = (v / 1e3 for v in to_dq(vind_phases, theta_el, harmonic=3)) I_d1, I_q1 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=1)) I_d3, I_q3 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=3)) # mA to A @@ -262,7 +262,7 @@ def extract_results(self, solutions): phase_offset = -2 * np.pi * k / 5 # 5 angles, 0deg, -72deg, -144deg, -216deg, -288deg 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/1e3 + self.Rstat * current_phases / 1e3 + self.Lew * dI_dt_phases + V_phases = vind_phases / 1e3 + self.Rstat * current_phases / 1e3 + self.Lew * dI_dt_phases V_d1, V_q1 = to_dq(V_phases, theta_el, harmonic=1) V_d3, V_q3 = to_dq(V_phases, theta_el, harmonic=3) @@ -328,7 +328,4 @@ def extract_results(self, solutions): "Moving1.Torque": torque, } - for key, value in out.items(): - print(f"{key}: {np.asarray(value).flat[0]:.4g}") - return out diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index 79d9a75..ee715ba 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -7,7 +7,6 @@ import numpy as np from ansys.aedt.core import Maxwell2d -from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.winding import phase_groups from .synrm_5f_40s import Computation as BaseComputation From d528d6ae6be19d4d6b1e80575705347cb2f1713b Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 17:07:14 +0200 Subject: [PATCH 30/41] simplify set_post_params() --- motors/synrm_3f_36s.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index e803a37..eab089f 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -217,10 +217,6 @@ def set_post_params(self): "FluxLinkage(PhaseB)", "FluxLinkage(PhaseC)", ): "FluxLinkage", - ("I_d", "I_q"): "Current_dq", - ("Flux_d", "Flux_q"): "FluxLinkage_dq", - ("Ui_d", "Ui_q"): "InducedVoltage_dq", - ("L_d", "L_q"): "Inductance_dq", } def assign_stator_coils(self, m2d: Maxwell2d) -> None: From e19243647888aa3d7ad7d712bd7554f708d64ac6 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 17:08:11 +0200 Subject: [PATCH 31/41] simplify set_post_params() --- motors/synrm_5f_40s.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index eda247e..496fc05 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -98,13 +98,6 @@ def set_post_params(self): "FluxLinkage(PhaseD)", "FluxLinkage(PhaseE)", ): "FluxLinkage", - ("I_d1", "I_q1", "I_d3", "I_q3"): "Current_dq", - ("Flux_d1", "Flux_q1", "Flux_d3", "Flux_q3"): "FluxLinkage_dq", - ("Flux_e_d1", "Flux_e_q1", "Flux_e_d3", "Flux_e_q3"): "FluxLinkage excitation_dq", - ("Vind_d1", "Vind_q1", "Vind_d3", "Vind_q3"): "InducedVoltage_dq", - ("V_d1", "V_q1", "V_d3", "V_q3"): "TerminalVoltage_dq", - ("Ld1", "Lq1", "Ld3", "Lq3"): "Inductance_dq main", - ("Ld1q1", "Ld1d3", "Ld1q3", "Lq1d3", "Lq1q3", "Ld3q3"): "Inductance_dq cross-coupling", } def assign_stator_coils(self, m2d: Maxwell2d) -> None: From 0d8f8d1a87f351df96c75bd383fc76ae1ab6874f Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 17:18:04 +0200 Subject: [PATCH 32/41] delete set_post_params() --- motors/synrm_3f_60s.py | 12 ------------ motors/synrm_5f_60s.py | 26 -------------------------- 2 files changed, 38 deletions(-) diff --git a/motors/synrm_3f_60s.py b/motors/synrm_3f_60s.py index 1eb1075..65c0189 100644 --- a/motors/synrm_3f_60s.py +++ b/motors/synrm_3f_60s.py @@ -31,18 +31,6 @@ class Computation(BaseComputation): def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] - def set_post_params(self): - self.post_params = { # reports - ("InducedVoltage(PhaseA)", "InducedVoltage(PhaseB)", "InducedVoltage(PhaseC)"): "InducedVoltage", - ("Moving1.Torque"): "Torque", - ("InputCurrent(PhaseA)", "InputCurrent(PhaseB)", "InputCurrent(PhaseC)"): "Current", - ( - "FluxLinkage(PhaseA)", - "FluxLinkage(PhaseB)", - "FluxLinkage(PhaseC)", - ): "FluxLinkage", - } - def assign_stator_coils(self, m2d: Maxwell2d) -> None: # Excitations I_A = "Im * cos(w*Time+epsI)" diff --git a/motors/synrm_5f_60s.py b/motors/synrm_5f_60s.py index ee715ba..8e6fe46 100644 --- a/motors/synrm_5f_60s.py +++ b/motors/synrm_5f_60s.py @@ -35,32 +35,6 @@ class Computation(BaseComputation): def set_solution_expressions(self): self.solution_expressions = ["Moving1.Torque"] - def set_post_params(self): - self.post_params = { # reports - ( - "InducedVoltage(PhaseA)", - "InducedVoltage(PhaseB)", - "InducedVoltage(PhaseC)", - "InducedVoltage(PhaseD)", - "InducedVoltage(PhaseE)", - ): "InducedVoltage", - ("Moving1.Torque"): "Torque", - ( - "InputCurrent(PhaseA)", - "InputCurrent(PhaseB)", - "InputCurrent(PhaseC)", - "InputCurrent(PhaseD)", - "InputCurrent(PhaseE)", - ): "Current", - ( - "FluxLinkage(PhaseA)", - "FluxLinkage(PhaseB)", - "FluxLinkage(PhaseC)", - "FluxLinkage(PhaseD)", - "FluxLinkage(PhaseE)", - ): "FluxLinkage", - } - def assign_stator_coils(self, m2d: Maxwell2d) -> None: # Excitations I_A = "Im1*cos(w*Time+epsI1-pi) + Im3*cos(3*(w*Time)+epsI3-pi)" From 1380f20a6e30f6ab219ebcde915ed348dabce9fc Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Tue, 29 Sep 2026 17:28:54 +0200 Subject: [PATCH 33/41] convergence --- machine_design/generic/compute_initpos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/machine_design/generic/compute_initpos.py b/machine_design/generic/compute_initpos.py index 5255907..c7e4209 100644 --- a/machine_design/generic/compute_initpos.py +++ b/machine_design/generic/compute_initpos.py @@ -21,4 +21,4 @@ def compute_initpos(Q: int, p: int, m: int, belt_offset: int): xa += math.cos(math.radians(eff_deg)) ya += math.sin(math.radians(eff_deg)) gamma_e_deg = math.degrees(math.atan2(ya, xa)) - return (gamma_e_deg - 90.0) / p + return round((gamma_e_deg - 90.0) / p, 6) From a61e9d9f92424ec1c39aea06abfaefd66eeed709 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 08:54:03 +0200 Subject: [PATCH 34/41] revert units fix --- motors/synrm_5f_40s.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 496fc05..3d33014 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -238,8 +238,8 @@ def extract_results(self, 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) - Vind_d1, Vind_q1 = (v / 1e3 for v in to_dq(vind_phases, theta_el, harmonic=1)) - Vind_d3, Vind_q3 = (v / 1e3 for v in to_dq(vind_phases, theta_el, harmonic=3)) + Vind_d1, Vind_q1 = to_dq(vind_phases, theta_el, harmonic=1) + Vind_d3, Vind_q3 = to_dq(vind_phases, theta_el, harmonic=3) I_d1, I_q1 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=1)) I_d3, I_q3 = (v / 1e3 for v in to_dq(current_phases, theta_el, harmonic=3)) # mA to A @@ -255,7 +255,7 @@ def extract_results(self, solutions): phase_offset = -2 * np.pi * k / 5 # 5 angles, 0deg, -72deg, -144deg, -216deg, -288deg 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 / 1e3 + self.Rstat * current_phases / 1e3 + self.Lew * dI_dt_phases + V_phases = vind_phases + self.Rstat * current_phases + self.Lew * dI_dt_phases V_d1, V_q1 = to_dq(V_phases, theta_el, harmonic=1) V_d3, V_q3 = to_dq(V_phases, theta_el, harmonic=3) From 76e8397c14f2aa8e1bb63d726ef891fc3acd3359 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 09:35:18 +0200 Subject: [PATCH 35/41] delete the function to force Nc be equal and update currents ratio to be 5:3 --- tests/test_synrm_60s_torque_i3_zero.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/tests/test_synrm_60s_torque_i3_zero.py b/tests/test_synrm_60s_torque_i3_zero.py index a185790..3a1664e 100644 --- a/tests/test_synrm_60s_torque_i3_zero.py +++ b/tests/test_synrm_60s_torque_i3_zero.py @@ -26,24 +26,12 @@ OFFSET = 0.35 SEEDS = (42, 43) NUM_CORES = 1 -current_setpoints = (7.0711, 7.0711) - - -class Geometry3fSameNc(Geometry3f): - def set_winds_params(self): - super().set_winds_params() - self.wind_params["Nc"] = "113" - - -class Geometry5fSameNc(Geometry5f): - def set_winds_params(self): - super().set_winds_params() - self.wind_params["Nc"] = "113" - +current_setpoints_3f = (5.0, 5.0) +current_setpoints_5f = (3.0, 3.0, 0.0, 0.0) Machines = [ - ("synrm_3f_60s", Geometry3fSameNc, Computation3f, current_setpoints), - ("synrm_5f_60s", Geometry5fSameNc, Computation5f, (*current_setpoints, 0.0, 0.0)), + ("synrm_3f_60s", Geometry3f, Computation3f, current_setpoints_3f), + ("synrm_5f_60s", Geometry5f, Computation5f, current_setpoints_5f), ] @@ -99,4 +87,4 @@ def test_synrm_5f_60s_i3_zero_performs_same_with_3f_60s(tmp_path): torque_5f = results["synrm_5f_60s"] diff_pct = abs(torque_3f - torque_5f) / abs(torque_3f) * 100 print(f"seed {seed}: 3f TorAvg={torque_3f:.4f} Nm, 5f(i3=0) TorAvg={torque_5f:.4f} Nm, diff={diff_pct:.2f}%") - assert diff_pct < 5, f"seed {seed}: 3f and 5f(i3=0) torque differ by {diff_pct:.2f}%, expected roughly comparable" + assert diff_pct < 50, f"seed {seed}: 3f and 5f(i3=0) torque differ by {diff_pct:.2f}%, expected roughly comparable" From 8a691ad0126396c0a0716e655025e681ce61c404 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 10:15:14 +0200 Subject: [PATCH 36/41] update convergence range to 5% --- tests/test_synrm_60s_torque_i3_zero.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_synrm_60s_torque_i3_zero.py b/tests/test_synrm_60s_torque_i3_zero.py index 3a1664e..406ff50 100644 --- a/tests/test_synrm_60s_torque_i3_zero.py +++ b/tests/test_synrm_60s_torque_i3_zero.py @@ -26,8 +26,8 @@ OFFSET = 0.35 SEEDS = (42, 43) NUM_CORES = 1 -current_setpoints_3f = (5.0, 5.0) -current_setpoints_5f = (3.0, 3.0, 0.0, 0.0) +current_setpoints_3f = (2.5, 2.5) +current_setpoints_5f = (1.5, 1.5, 0.0, 0.0) Machines = [ ("synrm_3f_60s", Geometry3f, Computation3f, current_setpoints_3f), @@ -87,4 +87,4 @@ def test_synrm_5f_60s_i3_zero_performs_same_with_3f_60s(tmp_path): torque_5f = results["synrm_5f_60s"] diff_pct = abs(torque_3f - torque_5f) / abs(torque_3f) * 100 print(f"seed {seed}: 3f TorAvg={torque_3f:.4f} Nm, 5f(i3=0) TorAvg={torque_5f:.4f} Nm, diff={diff_pct:.2f}%") - assert diff_pct < 50, f"seed {seed}: 3f and 5f(i3=0) torque differ by {diff_pct:.2f}%, expected roughly comparable" + assert diff_pct < 5, f"seed {seed}: 3f and 5f(i3=0) torque differ by {diff_pct:.2f}%, expected roughly comparable" From 573674de5d7d64c4fc4ae3813f24352c8dafbab0 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 12:26:34 +0200 Subject: [PATCH 37/41] move set_initpos to abstract parent class --- machine_design/designs/computation.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index 179f643..19be11a 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -5,6 +5,7 @@ from .geometry import GeometryBase from .reserved_variables import check_no_reserved_variable_names +from machine_design.generic.compute_initpos import compute_initpos class ComputationBase(ABC): def __init__(self, geometry: GeometryBase) -> None: @@ -41,6 +42,12 @@ def set_variables(self, m2d: Maxwell2d, *args): ... @abstractmethod def extract_results(self, solutions): ... + def set_initpos(self) -> None: + Q=int(self.geometry.geom_params["SlotNumber"]) + p=self.geometry.PolePairs + self.InitPos=compute_initpos(Q, p, self.phases, belt_offset=self.belt_offset) + self.oper_params["InitPos"] = f"{self.InitPos}deg" + def push_variables(self, m2d: Maxwell2d) -> None: for k, v in self.oper_params.items(): m2d[k] = v From abdd4e34671c8e1e64f6fdf44b22ef5d15508d7d Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 12:30:02 +0200 Subject: [PATCH 38/41] set phases and belt for 3f --- motors/synrm_3f_36s.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index eab089f..d5f7011 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -6,7 +6,6 @@ from machine_design.designs.computation import ComputationBase from machine_design.designs.geometry import GeometryBase -from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.transforms import to_dq @@ -173,6 +172,8 @@ def _assign_stator_mesh(self, m2d: Maxwell2d, stator_id, id_coils) -> None: class Computation(ComputationBase): def set_oper_params(self): + self.phases=3 + self.belt_offset=0 f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f @@ -188,12 +189,6 @@ def set_oper_params(self): } self.set_initpos() - def set_initpos(self): - Q = int(self.geometry.geom_params["SlotNumber"]) - p = self.geometry.PolePairs - self.InitPos = compute_initpos(Q, p, 3, belt_offset=0) - self.oper_params["InitPos"] = f"{self.InitPos}deg" - def set_solution_expressions(self): self.solution_expressions = [ "Moving1.Position", From 9edca4d1b04f3c08266c89de087cb7bb9bcc6852 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 12:32:03 +0200 Subject: [PATCH 39/41] set phases and belt_offset for 5f and delete compute_initpos() --- motors/synrm_5f_40s.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index 3d33014..a9ac7f5 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -8,7 +8,6 @@ from ansys.aedt.core import Maxwell2d from machine_design.designs.computation import ComputationBase -from machine_design.generic.compute_initpos import compute_initpos from machine_design.generic.transforms import electrical_angle, to_dq from .synrm_3f_36s import Geometry as BaseGeometry @@ -32,6 +31,8 @@ def set_winds_params(self): class Computation(ComputationBase): def set_oper_params(self): + self.phases=5 + self.belt_offset=1 f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f @@ -55,12 +56,6 @@ def set_oper_params(self): } self.set_initpos() - def set_initpos(self): - Q = int(self.geometry.geom_params["SlotNumber"]) - p = self.geometry.PolePairs - self.InitPos = compute_initpos(Q, p, 5, belt_offset=1) - self.oper_params["InitPos"] = f"{self.InitPos}deg" - def set_solution_expressions(self): self.solution_expressions = [ "Moving1.Position", From 287646d3357641b6572a12cee05b36311245d5be Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 12:33:54 +0200 Subject: [PATCH 40/41] ruff format --- machine_design/designs/computation.py | 7 ++++--- motors/synrm_3f_36s.py | 4 ++-- motors/synrm_5f_40s.py | 4 ++-- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index 19be11a..c21a94e 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -7,6 +7,7 @@ from machine_design.generic.compute_initpos import compute_initpos + class ComputationBase(ABC): def __init__(self, geometry: GeometryBase) -> None: self.geometry = geometry @@ -43,9 +44,9 @@ def set_variables(self, m2d: Maxwell2d, *args): ... def extract_results(self, solutions): ... def set_initpos(self) -> None: - Q=int(self.geometry.geom_params["SlotNumber"]) - p=self.geometry.PolePairs - self.InitPos=compute_initpos(Q, p, self.phases, belt_offset=self.belt_offset) + Q = int(self.geometry.geom_params["SlotNumber"]) + p = self.geometry.PolePairs + self.InitPos = compute_initpos(Q, p, self.phases, belt_offset=self.belt_offset) self.oper_params["InitPos"] = f"{self.InitPos}deg" def push_variables(self, m2d: Maxwell2d) -> None: diff --git a/motors/synrm_3f_36s.py b/motors/synrm_3f_36s.py index d5f7011..812a051 100644 --- a/motors/synrm_3f_36s.py +++ b/motors/synrm_3f_36s.py @@ -172,8 +172,8 @@ def _assign_stator_mesh(self, m2d: Maxwell2d, stator_id, id_coils) -> None: class Computation(ComputationBase): def set_oper_params(self): - self.phases=3 - self.belt_offset=0 + self.phases = 3 + self.belt_offset = 0 f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f diff --git a/motors/synrm_5f_40s.py b/motors/synrm_5f_40s.py index a9ac7f5..d22601a 100644 --- a/motors/synrm_5f_40s.py +++ b/motors/synrm_5f_40s.py @@ -31,8 +31,8 @@ def set_winds_params(self): class Computation(ComputationBase): def set_oper_params(self): - self.phases=5 - self.belt_offset=1 + self.phases = 5 + self.belt_offset = 1 f = 50 # [Hz] RotSpeed = 60 * f / self.geometry.PolePairs # [rpm] w = 2 * np.pi * f From 0931cc1019ed529bdb9cc928a900fdd695217164 Mon Sep 17 00:00:00 2001 From: haitao-ZCU Date: Wed, 30 Sep 2026 12:40:32 +0200 Subject: [PATCH 41/41] update import order --- machine_design/designs/computation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/machine_design/designs/computation.py b/machine_design/designs/computation.py index c21a94e..4e57fbe 100644 --- a/machine_design/designs/computation.py +++ b/machine_design/designs/computation.py @@ -2,11 +2,11 @@ from ansys.aedt.core import Maxwell2d +from machine_design.generic.compute_initpos import compute_initpos + from .geometry import GeometryBase from .reserved_variables import check_no_reserved_variable_names -from machine_design.generic.compute_initpos import compute_initpos - class ComputationBase(ABC): def __init__(self, geometry: GeometryBase) -> None: