@@ -135,10 +135,11 @@ def _calculate_luts(self):
135135 import numpy as np
136136 import scipy .interpolate as sp_interpolate
137137 from scipy .optimize import minimize
138- SLSQP_MAXITER = 2000
139- SLSQP_FTOL = 1e-12
140- TORQUE_BAND_REL = 1e-3
138+ SLSQP_MAXITER = 300
139+ SLSQP_FTOL = 5e-13
140+ TORQUE_BAND_REL = 5e-4
141141 TORQUE_BAND_ABS = 1e-2
142+ SLSQP_MAXITER_FBK = 600
142143 # ---- pull essentials ----
143144 p = float (self .p )
144145 Ld = float (self .l_d )
@@ -225,30 +226,102 @@ def solve_mtpc_for_T(Tref, x_prev):
225226
226227 bnds = [(- i_s_lim , i_s_lim ), (- i_s_lim , i_s_lim ), (0.0 , i_f_lim )]
227228 #x0 = x_prev if x_prev is not None else _seed_by_hand(Tref)
228- cons = cons_for_T (Tref )
229- x0 = np .asarray (x_prev , float )
230- res = minimize (obj_loss , x0 , method = 'SLSQP' ,
231- bounds = bnds , constraints = cons_for_T (Tref ),
232- options = dict (maxiter = SLSQP_MAXITER , ftol = SLSQP_FTOL , disp = False ))
233- if res .success and feasible (* res .x ):
234- return res .x
235- return x0
236-
229+ cons_eq = cons_for_T (Tref )
230+ x0 = np .asarray (x_prev , float ) if x_prev is not None else _seed_by_hand (Tref )
231+ x_keep = np .asarray (x0 , float )
232+ stats = {
233+ "nit_eq" : 0 , "nfev_eq" : None ,
234+ "nit_fbk1" : 0 , "nfev_fbk1" : None ,
235+ "nit_fbk2" : 0 , "nfev_fbk2" : None
236+ }
237+
238+ # --- equality-constrained loss minimization ---
239+ _ctr_eq = {"k" : 0 }
240+ def _cb_eq (_xk ): _ctr_eq ["k" ] += 1
241+ try :
242+ res = minimize (
243+ obj_loss , x0 , method = 'SLSQP' ,
244+ bounds = bnds , constraints = cons_for_T (Tref ),
245+ options = dict (maxiter = SLSQP_MAXITER , ftol = SLSQP_FTOL , disp = False )
246+ )
247+ stats ["nit_eq" ] = getattr (res , "nit" , None )
248+ stats ["nfev_eq" ] = getattr (res , "nfev" , None )
249+ if res .success and feasible (* res .x ):
250+ return res .x , stats
251+ except Exception :
252+ pass
253+ def obj_torque_err (x ):
254+ return (torque (x [0 ], x [1 ], x [2 ]) - Tref )** 2
255+
256+ cons_soft = [
257+ {'type' : 'ineq' , 'fun' : lambda x : i_s_lim - np .hypot (x [0 ], x [1 ])}, # |is| <=
258+ {'type' : 'ineq' , 'fun' : lambda x : x [2 ]}, # i_f >= 0
259+ {'type' : 'ineq' , 'fun' : lambda x : i_f_lim - x [2 ]}, # i_f <=
260+ {'type' : 'ineq' , 'fun' : lambda x : V_over_omega ** 2
261+ - ((Lq * x [1 ])** 2 + (Lm * x [2 ] + Ld * x [0 ])** 2 )},
262+ ]
263+
264+ # 2a) minimize torque error
265+ try :
266+ res1 = minimize (
267+ obj_torque_err , x0 , method = 'SLSQP' ,
268+ bounds = bnds , constraints = cons_soft ,
269+ options = dict (maxiter = SLSQP_MAXITER_FBK , ftol = SLSQP_FTOL , disp = False )
270+ )
271+ stats ["nit_fbk1" ] = getattr (res , "nit" , None )
272+ stats ["nfev_fbk1" ] = getattr (res1 , "nfev" , None )
273+ if res1 .success and feasible (* res1 .x ):
274+ T_reach = torque (* res1 .x )
275+ band = max (abs (T_reach )* TORQUE_BAND_REL , TORQUE_BAND_ABS )
276+ lo , hi = T_reach - band , T_reach + band
277+
278+ # 2b) keep torque in [lo, hi] while minimizing loss
279+ cons_keep = cons_soft + [
280+ {'type' : 'ineq' , 'fun' : lambda x , lo = lo : torque (x [0 ], x [1 ], x [2 ]) - lo },
281+ {'type' : 'ineq' , 'fun' : lambda x , hi = hi : hi - torque (x [0 ], x [1 ], x [2 ])},
282+ ]
283+
284+
285+ res2 = minimize (
286+ obj_loss , res1 .x , method = 'SLSQP' ,
287+ bounds = bnds , constraints = cons_keep ,
288+ options = dict (maxiter = SLSQP_MAXITER_FBK , ftol = SLSQP_FTOL , disp = False )
289+ )
290+ stats ["nit_fbk2" ] = getattr (res , "nit" , None )
291+ stats ["nfev_fbk2" ] = getattr (res2 , "nfev" , None )
292+ if res2 .success and feasible (* res2 .x ):
293+ T2 = torque (* res2 .x )
294+ if lo <= T2 <= hi :
295+ return res2 .x , stats
296+ # if loss-min step fails the band, keep the torque-closest point
297+ return res1 .x , stats
298+ except Exception :
299+ pass
300+
301+ # as a last resort, keep previous point (keeps curves continuous)
302+ return x_keep , stats
237303 # ---- solve MTPCL, collect optimal points ----
238304 T_curr_cap = 1.5 * p * Lm * i_f_lim * i_s_lim
239305 T_cap = float (min (t_lim , T_curr_cap ))
306+ iters_eq , iters_f1 , iters_f2 = [], [], []
240307 T_vec = np .linspace (0.0 , T_cap , t_count )
241308 x_prev = _seed_by_hand (max (1e-3 , 0.05 * T_cap ))
242309 rows = [] # [T, psi, id, |iq|, if]
243310 for T in T_vec :
244- x_star = solve_mtpc_for_T (T , x_prev )
311+ x_star , st = solve_mtpc_for_T (T , x_prev )
245312 id_opt , iq_opt , if_opt = x_star
246313 x_prev = x_star
314+ iters_eq .append (st ["nit_eq" ] or 0 )
315+ iters_f1 .append (st ["nit_fbk1" ] or 0 )
316+ iters_f2 .append (st ["nit_fbk2" ] or 0 )
247317 psi_d = Lm * if_opt + Ld * id_opt
248318 psi_q = Lq * iq_opt
249319 psi = np .hypot (psi_d , psi_q )
250320 rows .append ([T , psi , id_opt , abs (iq_opt ), if_opt ])
251-
321+ print (
322+ f"SLSQP iters — eq mean={ np .mean (iters_eq ):.1f} max={ np .max (iters_eq )} ; "
323+ f"fbk1 mean={ np .mean (iters_f1 ):.1f} ; fbk2 mean={ np .mean (iters_f2 ):.1f} "
324+ )
252325 bp = np .array (rows , dtype = float )
253326 bp = bp [np .all (np .isfinite (bp ), axis = 1 )]
254327 if bp .shape [0 ] == 0 :
@@ -366,7 +439,7 @@ def _get_t_idx(self, torque):
366439 torque = float (np .clip (torque , 0.0 , t_max ))
367440 rows = int (self .t_grid_count ) - 1
368441 return int (round ((torque / t_max ) * rows ))
369-
442+
370443 def _select_operating_point (self , state , reference ):
371444 import numpy as np
372445
0 commit comments