diff --git a/tests/legacy/__init__.py b/tests/legacy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/legacy/design2.py b/tests/legacy/design2.py new file mode 100644 index 0000000..84b6753 --- /dev/null +++ b/tests/legacy/design2.py @@ -0,0 +1,332 @@ +import numpy as np + +from .design import Design + + +class Design2(Design): + def set_geom_params(self): + super().set_geom_params() + self.geom_params["SlotNumber"] = "40" + + def set_slot_params(self): + super().set_slot_params() + self.slot_params["Bs1"] = "3.0mm" + self.slot_params["Bs2"] = "4.3mm" + self.slot_params["SetAngle"] = "9deg" + + def set_winds_params(self): + super().set_winds_params() + self.wind_params["Nc"] = "113" + + def set_oper_params(self): + fr = 50 # [Hz] + RotSpeed = 60 * fr / self.PolePairs # [rpm] + 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", + "fr": f"{fr}Hz", + "f": "fr", # replace f in design.py with fr (line 355-356 in design.py) + "RotSpeed": f"{RotSpeed}rpm", + "Nper": "1/10", # number of included periods + "PointPer": "101", # number of time points per period + } + + def set_derived_params(self): + super().set_derived_params() + # pass + + def set_solution_expressions(self): + self.solution_expressions = [ + "V_d1", + "V_q1", + "V_d3", + "V_q3", + "Flux_e_d1", + "Flux_e_q1", + "Flux_e_d3", + "Flux_e_q3", + "I_d1", + "I_q1", + "I_d3", + "I_q3", + "Ld1", + "Ld1q1", + "Ld1d3", + "Ld1q3", + "Lq1", + "Lq1d3", + "Lq1q3", + "Ld3", + "Ld3q3", + "Lq3", + "Moving1.Torque", + ] + + def set_output_vars(self): + self.output_vars = { + "PolePairs": "2", + "RotSign": "1", + "Rstat": "19", + "Lew": "0", + "theta_el": "RotSign*(Moving1.Position - InitPos) * PolePairs - pi", + "cos0_1": "cos(1*(theta_el - 2*PI*0/5))", + "sin0_1": "sin(-1*(theta_el - 2*PI*0/5))", + "cos1_1": "cos(1*(theta_el - 2*PI*1/5))", + "sin1_1": "sin(-1*(theta_el - 2*PI*1/5))", + "cos2_1": "cos(1*(theta_el - 2*PI*2/5))", + "sin2_1": "sin(-1*(theta_el - 2*PI*2/5))", + "cos3_1": "cos(1*(theta_el - 2*PI*3/5))", + "sin3_1": "sin(-1*(theta_el - 2*PI*3/5))", + "cos4_1": "cos(1*(theta_el - 2*PI*4/5))", + "sin4_1": "sin(-1*(theta_el - 2*PI*4/5))", + "cos0_3": "cos(3*(theta_el - 2*PI*0/5))", + "sin0_3": "sin(-3*(theta_el - 2*PI*0/5))", + "cos1_3": "cos(3*(theta_el - 2*PI*1/5))", + "sin1_3": "sin(-3*(theta_el - 2*PI*1/5))", + "cos2_3": "cos(3*(theta_el - 2*PI*2/5))", + "sin2_3": "sin(-3*(theta_el - 2*PI*2/5))", + "cos3_3": "cos(3*(theta_el - 2*PI*3/5))", + "sin3_3": "sin(-3*(theta_el - 2*PI*3/5))", + "cos4_3": "cos(3*(theta_el - 2*PI*4/5))", + "sin4_3": "sin(-3*(theta_el - 2*PI*4/5))", + "Flux_d1": "(FluxLinkage(PhaseA)*cos0_1 + FluxLinkage(PhaseB)*cos1_1 + FluxLinkage(PhaseC)*cos2_1 + FluxLinkage(PhaseD)*cos3_1 + FluxLinkage(PhaseE)*cos4_1) * 2/5", + "Flux_q1": "(FluxLinkage(PhaseA)*sin0_1 + FluxLinkage(PhaseB)*sin1_1 + FluxLinkage(PhaseC)*sin2_1 + FluxLinkage(PhaseD)*sin3_1 + FluxLinkage(PhaseE)*sin4_1) * 2/5", + "Flux_d3": "(FluxLinkage(PhaseA)*cos0_3 + FluxLinkage(PhaseB)*cos1_3 + FluxLinkage(PhaseC)*cos2_3 + FluxLinkage(PhaseD)*cos3_3 + FluxLinkage(PhaseE)*cos4_3) * 2/5", + "Flux_q3": "(FluxLinkage(PhaseA)*sin0_3 + FluxLinkage(PhaseB)*sin1_3 + FluxLinkage(PhaseC)*sin2_3 + FluxLinkage(PhaseD)*sin3_3 + FluxLinkage(PhaseE)*sin4_3) * 2/5", + "Vind_d1": "(InducedVoltage(PhaseA)*cos0_1 + InducedVoltage(PhaseB)*cos1_1 + InducedVoltage(PhaseC)*cos2_1 + InducedVoltage(PhaseD)*cos3_1 + InducedVoltage(PhaseE)*cos4_1) * 2/5", + "Vind_q1": "(InducedVoltage(PhaseA)*sin0_1 + InducedVoltage(PhaseB)*sin1_1 + InducedVoltage(PhaseC)*sin2_1 + InducedVoltage(PhaseD)*sin3_1 + InducedVoltage(PhaseE)*sin4_1) * 2/5", + "Vind_d3": "(InducedVoltage(PhaseA)*cos0_3 + InducedVoltage(PhaseB)*cos1_3 + InducedVoltage(PhaseC)*cos2_3 + InducedVoltage(PhaseD)*cos3_3 + InducedVoltage(PhaseE)*cos4_3) * 2/5", + "Vind_q3": "(InducedVoltage(PhaseA)*sin0_3 + InducedVoltage(PhaseB)*sin1_3 + InducedVoltage(PhaseC)*sin2_3 + InducedVoltage(PhaseD)*sin3_3 + InducedVoltage(PhaseE)*sin4_3) * 2/5", + "dIA_dt": "-Im1*2*pi*fr*sin(2*pi*fr*Time+epsI1-pi) - Im3*6*pi*fr*sin(3*(2*pi*fr*Time)+epsI3-pi)", + "dIB_dt": "-Im1*2*pi*fr*sin(2*pi*fr*Time-72deg+epsI1-pi) - Im3*6*pi*fr*sin(3*(2*pi*fr*Time-72deg)+epsI3-pi)", + "dIC_dt": "-Im1*2*pi*fr*sin(2*pi*fr*Time-144deg+epsI1-pi) - Im3*6*pi*fr*sin(3*(2*pi*fr*Time-144deg)+epsI3-pi)", + "dID_dt": "-Im1*2*pi*fr*sin(2*pi*fr*Time-216deg+epsI1-pi) - Im3*6*pi*fr*sin(3*(2*pi*fr*Time-216deg)+epsI3-pi)", + "dIE_dt": "-Im1*2*pi*fr*sin(2*pi*fr*Time-288deg+epsI1-pi) - Im3*6*pi*fr*sin(3*(2*pi*fr*Time-288deg)+epsI3-pi)", + "V_A": "InducedVoltage(PhaseA) + Rstat*InputCurrent(PhaseA) + Lew*dIA_dt", + "V_B": "InducedVoltage(PhaseB) + Rstat*InputCurrent(PhaseB) + Lew*dIB_dt", + "V_C": "InducedVoltage(PhaseC) + Rstat*InputCurrent(PhaseC) + Lew*dIC_dt", + "V_D": "InducedVoltage(PhaseD) + Rstat*InputCurrent(PhaseD) + Lew*dID_dt", + "V_E": "InducedVoltage(PhaseE) + Rstat*InputCurrent(PhaseE) + Lew*dIE_dt", + "V_AC": "V_A - V_C", + "V_BD": "V_B - V_D", + "V_CE": "V_C - V_E", + "V_DA": "V_D - V_A", + "V_EB": "V_E - V_B", + "Vterm_A": "1/5*(2*V_AC + -1*V_BD + 1*V_CE + -2*V_DA)", + "Vterm_B": "1/5*(2*V_AC + 4*V_BD + 1*V_CE + 3*V_DA)", + "Vterm_C": "1/5*(-3*V_AC + -1*V_BD + 1*V_CE + -2*V_DA)", + "Vterm_D": "1/5*(2*V_AC + -1*V_BD + 1*V_CE + 3*V_DA)", + "Vterm_E": "1/5*(-3*V_AC + -1*V_BD + -4*V_CE + -2*V_DA)", + "I_d1": "(InputCurrent(PhaseA)*cos0_1 + InputCurrent(PhaseB)*cos1_1 + InputCurrent(PhaseC)*cos2_1 + InputCurrent(PhaseD)*cos3_1 + InputCurrent(PhaseE)*cos4_1) * 2/5", + "I_q1": "(InputCurrent(PhaseA)*sin0_1 + InputCurrent(PhaseB)*sin1_1 + InputCurrent(PhaseC)*sin2_1 + InputCurrent(PhaseD)*sin3_1 + InputCurrent(PhaseE)*sin4_1) * 2/5", + "I_d3": "(InputCurrent(PhaseA)*cos0_3 + InputCurrent(PhaseB)*cos1_3 + InputCurrent(PhaseC)*cos2_3 + InputCurrent(PhaseD)*cos3_3 + InputCurrent(PhaseE)*cos4_3) * 2/5", + "I_q3": "(InputCurrent(PhaseA)*sin0_3 + InputCurrent(PhaseB)*sin1_3 + InputCurrent(PhaseC)*sin2_3 + InputCurrent(PhaseD)*sin3_3 + InputCurrent(PhaseE)*sin4_3) * 2/5", + "V_d1": "(V_A*cos0_1 + V_B*cos1_1 + V_C*cos2_1 + V_D*cos3_1 + V_E*cos4_1) * 2/5", + "V_q1": "(V_A*sin0_1 + V_B*sin1_1 + V_C*sin2_1 + V_D*sin3_1 + V_E*sin4_1) * 2/5", + "V_d3": "(V_A*cos0_3 + V_B*cos1_3 + V_C*cos2_3 + V_D*cos3_3 + V_E*cos4_3) * 2/5", + "V_q3": "(V_A*sin0_3 + V_B*sin1_3 + V_C*sin2_3 + V_D*sin3_3 + V_E*sin4_3) * 2/5", + "L0d_1": "L(PhaseA,PhaseA)*cos0_1 + L(PhaseA,PhaseB)*cos1_1 + L(PhaseA,PhaseC)*cos2_1 + L(PhaseA,PhaseD)*cos3_1 + L(PhaseA,PhaseE)*cos4_1", + "L0q_1": "L(PhaseA,PhaseA)*sin0_1 + L(PhaseA,PhaseB)*sin1_1 + L(PhaseA,PhaseC)*sin2_1 + L(PhaseA,PhaseD)*sin3_1 + L(PhaseA,PhaseE)*sin4_1", + "L1d_1": "L(PhaseB,PhaseA)*cos0_1 + L(PhaseB,PhaseB)*cos1_1 + L(PhaseB,PhaseC)*cos2_1 + L(PhaseB,PhaseD)*cos3_1 + L(PhaseB,PhaseE)*cos4_1", + "L1q_1": "L(PhaseB,PhaseA)*sin0_1 + L(PhaseB,PhaseB)*sin1_1 + L(PhaseB,PhaseC)*sin2_1 + L(PhaseB,PhaseD)*sin3_1 + L(PhaseB,PhaseE)*sin4_1", + "L2d_1": "L(PhaseC,PhaseA)*cos0_1 + L(PhaseC,PhaseB)*cos1_1 + L(PhaseC,PhaseC)*cos2_1 + L(PhaseC,PhaseD)*cos3_1 + L(PhaseC,PhaseE)*cos4_1", + "L2q_1": "L(PhaseC,PhaseA)*sin0_1 + L(PhaseC,PhaseB)*sin1_1 + L(PhaseC,PhaseC)*sin2_1 + L(PhaseC,PhaseD)*sin3_1 + L(PhaseC,PhaseE)*sin4_1", + "L3d_1": "L(PhaseD,PhaseA)*cos0_1 + L(PhaseD,PhaseB)*cos1_1 + L(PhaseD,PhaseC)*cos2_1 + L(PhaseD,PhaseD)*cos3_1 + L(PhaseD,PhaseE)*cos4_1", + "L3q_1": "L(PhaseD,PhaseA)*sin0_1 + L(PhaseD,PhaseB)*sin1_1 + L(PhaseD,PhaseC)*sin2_1 + L(PhaseD,PhaseD)*sin3_1 + L(PhaseD,PhaseE)*sin4_1", + "L4d_1": "L(PhaseE,PhaseA)*cos0_1 + L(PhaseE,PhaseB)*cos1_1 + L(PhaseE,PhaseC)*cos2_1 + L(PhaseE,PhaseD)*cos3_1 + L(PhaseE,PhaseE)*cos4_1", + "L4q_1": "L(PhaseE,PhaseA)*sin0_1 + L(PhaseE,PhaseB)*sin1_1 + L(PhaseE,PhaseC)*sin2_1 + L(PhaseE,PhaseD)*sin3_1 + L(PhaseE,PhaseE)*sin4_1", + "L0d_3": "L(PhaseA,PhaseA)*cos0_3 + L(PhaseA,PhaseB)*cos1_3 + L(PhaseA,PhaseC)*cos2_3 + L(PhaseA,PhaseD)*cos3_3 + L(PhaseA,PhaseE)*cos4_3", + "L0q_3": "L(PhaseA,PhaseA)*sin0_3 + L(PhaseA,PhaseB)*sin1_3 + L(PhaseA,PhaseC)*sin2_3 + L(PhaseA,PhaseD)*sin3_3 + L(PhaseA,PhaseE)*sin4_3", + "L1d_3": "L(PhaseB,PhaseA)*cos0_3 + L(PhaseB,PhaseB)*cos1_3 + L(PhaseB,PhaseC)*cos2_3 + L(PhaseB,PhaseD)*cos3_3 + L(PhaseB,PhaseE)*cos4_3", + "L1q_3": "L(PhaseB,PhaseA)*sin0_3 + L(PhaseB,PhaseB)*sin1_3 + L(PhaseB,PhaseC)*sin2_3 + L(PhaseB,PhaseD)*sin3_3 + L(PhaseB,PhaseE)*sin4_3", + "L2d_3": "L(PhaseC,PhaseA)*cos0_3 + L(PhaseC,PhaseB)*cos1_3 + L(PhaseC,PhaseC)*cos2_3 + L(PhaseC,PhaseD)*cos3_3 + L(PhaseC,PhaseE)*cos4_3", + "L2q_3": "L(PhaseC,PhaseA)*sin0_3 + L(PhaseC,PhaseB)*sin1_3 + L(PhaseC,PhaseC)*sin2_3 + L(PhaseC,PhaseD)*sin3_3 + L(PhaseC,PhaseE)*sin4_3", + "L3d_3": "L(PhaseD,PhaseA)*cos0_3 + L(PhaseD,PhaseB)*cos1_3 + L(PhaseD,PhaseC)*cos2_3 + L(PhaseD,PhaseD)*cos3_3 + L(PhaseD,PhaseE)*cos4_3", + "L3q_3": "L(PhaseD,PhaseA)*sin0_3 + L(PhaseD,PhaseB)*sin1_3 + L(PhaseD,PhaseC)*sin2_3 + L(PhaseD,PhaseD)*sin3_3 + L(PhaseD,PhaseE)*sin4_3", + "L4d_3": "L(PhaseE,PhaseA)*cos0_3 + L(PhaseE,PhaseB)*cos1_3 + L(PhaseE,PhaseC)*cos2_3 + L(PhaseE,PhaseD)*cos3_3 + L(PhaseE,PhaseE)*cos4_3", + "L4q_3": "L(PhaseE,PhaseA)*sin0_3 + L(PhaseE,PhaseB)*sin1_3 + L(PhaseE,PhaseC)*sin2_3 + L(PhaseE,PhaseD)*sin3_3 + L(PhaseE,PhaseE)*sin4_3", + "Ld1": "(L0d_1*cos0_1 + L1d_1*cos1_1 + L2d_1*cos2_1 + L3d_1*cos3_1 + L4d_1*cos4_1) * 2/5", + "Ld1q1": "(L0d_1*sin0_1 + L1d_1*sin1_1 + L2d_1*sin2_1 + L3d_1*sin3_1 + L4d_1*sin4_1) * 2/5", + "Lq1d1": "(L0q_1*cos0_1 + L1q_1*cos1_1 + L2q_1*cos2_1 + L3q_1*cos3_1 + L4q_1*cos4_1) * 2/5", + "Lq1": "(L0q_1*sin0_1 + L1q_1*sin1_1 + L2q_1*sin2_1 + L3q_1*sin3_1 + L4q_1*sin4_1) * 2/5", + "Ld1d3": "(L0d_1*cos0_3 + L1d_1*cos1_3 + L2d_1*cos2_3 + L3d_1*cos3_3 + L4d_1*cos4_3) * 2/5", + "Ld1q3": "(L0d_1*sin0_3 + L1d_1*sin1_3 + L2d_1*sin2_3 + L3d_1*sin3_3 + L4d_1*sin4_3) * 2/5", + "Lq1d3": "(L0q_1*cos0_3 + L1q_1*cos1_3 + L2q_1*cos2_3 + L3q_1*cos3_3 + L4q_1*cos4_3) * 2/5", + "Lq1q3": "(L0q_1*sin0_3 + L1q_1*sin1_3 + L2q_1*sin2_3 + L3q_1*sin3_3 + L4q_1*sin4_3) * 2/5", + "Ld3d1": "(L0d_3*cos0_1 + L1d_3*cos1_1 + L2d_3*cos2_1 + L3d_3*cos3_1 + L4d_3*cos4_1) * 2/5", + "Ld3q1": "(L0d_3*sin0_1 + L1d_3*sin1_1 + L2d_3*sin2_1 + L3d_3*sin3_1 + L4d_3*sin4_1) * 2/5", + "Lq3d1": "(L0q_3*cos0_1 + L1q_3*cos1_1 + L2q_3*cos2_1 + L3q_3*cos3_1 + L4q_3*cos4_1) * 2/5", + "Lq3q1": "(L0q_3*sin0_1 + L1q_3*sin1_1 + L2q_3*sin2_1 + L3q_3*sin3_1 + L4q_3*sin4_1) * 2/5", + "Ld3": "(L0d_3*cos0_3 + L1d_3*cos1_3 + L2d_3*cos2_3 + L3d_3*cos3_3 + L4d_3*cos4_3) * 2/5", + "Ld3q3": "(L0d_3*sin0_3 + L1d_3*sin1_3 + L2d_3*sin2_3 + L3d_3*sin3_3 + L4d_3*sin4_3) * 2/5", + "Lq3d3": "(L0q_3*cos0_3 + L1q_3*cos1_3 + L2q_3*cos2_3 + L3q_3*cos3_3 + L4q_3*cos4_3) * 2/5", + "Lq3": "(L0q_3*sin0_3 + L1q_3*sin1_3 + L2q_3*sin2_3 + L3q_3*sin3_3 + L4q_3*sin4_3) * 2/5", + "Flux_e_d1": "Flux_d1 - (Ld1*I_d1 + Ld1q1*I_q1 + Ld1d3*I_d3 + Ld1q3*I_q3)", + "Flux_e_q1": "Flux_q1 - (Lq1d1*I_d1 + Lq1*I_q1 + Lq1d3*I_d3 + Lq1q3*I_q3)", + "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*PolePairs*(1*(Flux_d1*I_q1 - Flux_q1*I_d1) + 3*(Flux_d3*I_q3 - Flux_q3*I_d3))", + } + + 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", + ("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 = self.m2d + + # Excitations + I_A = "Im1*cos(2*pi*fr*time+epsI1-pi) + Im3*cos(3*(2*pi*fr*time)+epsI3-pi)" + I_B = "Im1*cos(2*pi*fr*time-72deg+epsI1-pi) + Im3*cos(3*(2*pi*fr*time-72deg)+epsI3-pi)" + I_C = "Im1*cos(2*pi*fr*time-144deg+epsI1-pi) + Im3*cos(3*(2*pi*fr*time-144deg)+epsI3-pi)" + I_D = "Im1*cos(2*pi*fr*time-216deg+epsI1-pi) + Im3*cos(3*(2*pi*fr*time-216deg)+epsI3-pi)" + I_E = "Im1*cos(2*pi*fr*time-288deg+epsI1-pi) + Im3*cos(3*(2*pi*fr*time-288deg)+epsI3-pi)" + 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): + self.m2d.change_inductance_computation(compute_transient_inductance=True, incremental_matrix=True) + + def set_variables(self, Id1, Iq1, Id3, Iq3): + self.m2d.variable_manager["Id1"] = f"{Id1}A" + self.m2d.variable_manager["Iq1"] = f"{Iq1}A" + self.m2d.variable_manager["Id3"] = f"{Id3}A" + self.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 + + out[expr] = val + return out diff --git a/tests/test_ansys_regression.py b/tests/test_ansys_regression.py index 158f597..8c7b89e 100644 --- a/tests/test_ansys_regression.py +++ b/tests/test_ansys_regression.py @@ -1,4 +1,4 @@ -"""Golden-master check: tests/legacy/design.py vs. the live machine_design/design.py. +"""Golden-master check: tests/legacy/design.py vs. the live machine_design/designs/design.py. Generates two feasible rotor designs (different seeds) with HacklGenerator_OneLambda and runs each through add_rotor -> compute -> delete_rotor on the SAME Design instance, both diff --git a/tests/test_ansys_regression_synrm_5f_40s.py b/tests/test_ansys_regression_synrm_5f_40s.py new file mode 100644 index 0000000..fd48d7c --- /dev/null +++ b/tests/test_ansys_regression_synrm_5f_40s.py @@ -0,0 +1,108 @@ +"""Golden-master check: tests/legacy/design2.py vs. the live machine_design/designs/design.py. + +Generates two feasible rotor designs (different seeds) with HacklGenerator_OneLambda and +runs each through add_rotor -> compute -> delete_rotor on the SAME Design instance, both +with the frozen reference implementation and the current one - mirroring how an +optimization loop reuses one Design across many candidate rotors. Asserts the resulting +torque waveforms match at each step. Run explicitly with: + + pytest -m ansys tests/test_ansys_regression_synrm_5f_40s.py + +Requires a running Ansys Electronics Desktop session and a free license seat. +""" + +import numpy as np +import pytest +from legacy.design2 import Design2 + +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_5f_40s import Computation, Geometry + +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_setpoint = (7.0711, 7.0711, 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 = Geometry() + dummy = LiveDesign(m2d=None, geometry=geometry, computation=Computation(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 _add_rotor(design, barriers): + design.add_rotor() + for barrier in barriers: + design.add_rotor_barrier(barrier) + + +def _create_design(design_cls, project_name, file_name, extra_args=()): + return design_cls.create( + project_name, + "Design01", + file_name, + *extra_args, + version=AEDT_VERSION, + non_graphical=True, + new_desktop=False, + close_on_exit=False, + ) + + +def _compute_torque(design, barriers, add_rotor, setpoint=current_setpoint): + add_rotor(design, barriers) + torque = design.compute(*setpoint, NUM_CORES=NUM_CORES) + design.delete_rotor() + return torque + + +def test_legacy_and_live_design_match_across_rotor_rebuilds(tmp_path): + barrier_sets = [_generate_one_lambda_barriers(seed) for seed in SEEDS] + + legacy_design, live_design = None, None + try: + legacy_design = _create_design(Design2, "RegressionTest_legacy", str(tmp_path / "legacy.aedt")) + + live_geometry = Geometry() + live_computation = Computation(live_geometry) + live_design = _create_design( + LiveDesign, + "RegressionTest_live", + str(tmp_path / "live.aedt"), + extra_args=(live_geometry, live_computation), + ) + + for seed, barriers in zip(SEEDS, barrier_sets): + torque_legacy = _compute_torque(legacy_design, barriers, _add_rotor) + torque_live = _compute_torque(live_design, barriers, _add_rotor) + + assert torque_legacy is not None, f"legacy torque is None for seed {seed}" + assert torque_live is not None, f"live torque is None for seed {seed}" + + for expr in live_computation.solution_expressions: + np.testing.assert_allclose(torque_live[expr], torque_legacy[expr], rtol=1e-3, atol=2e-4, err_msg=f"mismatch for {expr}, seed {seed}") + # rtol is a relative tolerance, but when current_setpoint = (7.0711, 7.0711, 0.0, 0.0) is used, + # I_d3 is 1e-15 and 2.2e-16 for legacy and live, respectively, rtol is not meaningful when comparing such small numbers, + # so atol (absolute tolerance) is used to avoid false failures. + finally: + if live_design is not None: + live_design.close_project() + elif legacy_design is not None: + legacy_design.close_project()