@@ -427,3 +427,179 @@ def _get_bezier_curve(self, phi_deg, is_inner, i):
427427
428428 # Stack segments and drop duplicate joint points
429429 return np .vstack ((seg1 , seg2 [1 :], seg3 [1 :]))
430+
431+
432+ class MagnetGenerator (BarrierGenerator ):
433+ def __init__ (self , design , r_stator_end , der1 = 1.0 , der2 = 1.0 , symmetric = True , ** kwargs ):
434+ self .n_designs = kwargs .get ("n_designs" , 1 )
435+ self .der1 = der1
436+ self .der2 = der2
437+ self .symmetric = symmetric
438+ self .n_barriers = kwargs .get ("n_barriers" , 1 )
439+ super ().__init__ (design , r_stator_end , ** kwargs )
440+
441+ def _create_barrier (
442+ self ,
443+ y_min ,
444+ w_min ,
445+ y_mid ,
446+ w_mid ,
447+ theta ,
448+ w_max ,
449+ ):
450+
451+ theta1 = (theta + 45 ) / 180 * np .pi
452+ x_max1 = self .R * np .cos (theta1 )
453+ y_max1 = self .R * np .sin (theta1 )
454+
455+ x1 = [0 , x_max1 / 2 , x_max1 ]
456+ y1 = [y_min , y_mid , y_max1 ]
457+ f1 = CubicSpline (x1 , y1 , bc_type = ((1 , 0 ), (1 , self .der1 )))
458+
459+ theta2 = theta1 + w_max / self .r_max
460+ x_max2 = self .R * np .cos (theta2 )
461+ y_max2 = self .R * np .sin (theta2 )
462+
463+ s = w_mid / np .sqrt (1 + self .der1 ** 2 / 4 )
464+ x2 = [0 , x1 [1 ] - s * self .der1 / 2 , x_max2 ]
465+ y2 = [y_min + w_min , y1 [1 ] + s , y_max2 ]
466+ f2 = CubicSpline (x2 , y2 , bc_type = ((1 , 0 ), (1 , self .der2 )))
467+
468+ x_interp1 = np .linspace (x1 [0 ], x1 [- 1 ], self .n_curve )
469+ x_interp2 = np .linspace (x2 [0 ], x2 [- 1 ], self .n_curve )
470+ # TODO: use more points for connecting?
471+ x_all = np .concatenate ((x_interp1 , x_interp2 [::- 1 ]))
472+ y_all = np .concatenate ((f1 (x_interp1 ), f2 (x_interp2 )[::- 1 ]))
473+ if self .symmetric :
474+ x_all = np .concatenate ((x_all , - x_all [::- 1 ][1 :]))
475+ y_all = np .concatenate ((y_all , y_all [::- 1 ][1 :]))
476+ x_all , y_all = rotate (x_all , y_all , - 45 )
477+ return x_all , y_all
478+
479+ @property
480+ def bounds (self ) -> tuple [np .ndarray , np .ndarray ]:
481+ n = self .n_barriers
482+
483+ lb = np .concatenate (
484+ [
485+ np .full (n , 0.7 ), # rand1
486+ np .array ([12 ]), # rand2
487+ np .full (n - 1 , 1 ), # spacings
488+ ]
489+ )
490+
491+ ub = np .concatenate ([np .full (n , 1.0 ), np .array ([20 ]), np .full (n - 1 , 5 )])
492+
493+ return lb , ub
494+
495+ def generate_w_mins_base (self , rotor_r_min , rotor_r_max ):
496+ n_barriers = self .n_barriers
497+ available_height = rotor_r_max - rotor_r_min
498+ gap_ratio = 0.5
499+ total_dap = available_height * gap_ratio
500+
501+ total_height_barrier = available_height - total_dap
502+
503+ if n_barriers == 1 :
504+ weights = np .array ([1.0 ])
505+ else :
506+ weights = np .array ([0.99 ** i for i in range (n_barriers )])
507+
508+ weights = weights / np .sum (weights )
509+
510+ return total_height_barrier * weights
511+
512+ def random_parameters (self ):
513+
514+ rand1 = np .random .uniform (0.7 , 1.0 , self .n_barriers )
515+ rand2 = np .random .uniform (12 , 20 )
516+ spacings = np .random .dirichlet (np .ones (self .n_barriers - 1 ))
517+
518+ return rand1 , rand2 , spacings
519+
520+ def set_parameters (self , params ) -> None :
521+ rand1 , rand2 , spacings = params
522+
523+ w_mins_base = self .generate_w_mins_base (self .r_min , self .r_max )
524+
525+ w_mins = w_mins_base * rand1
526+ w_mids = w_mins .copy ()
527+
528+ total_height = np .sum (w_mins )
529+ total_spacing = np .sum (spacings )
530+
531+ available_height = self .r_max - rand2
532+
533+ eps = 1e-8
534+ denom = total_height + total_spacing
535+ scale = (available_height ) / max (denom , eps )
536+
537+ if scale < 1.0 :
538+ w_mins *= scale
539+ spacings *= scale
540+
541+ y_mins = np .zeros (self .n_barriers )
542+ y_mins [0 ] = rand2
543+ current_pos = y_mins [0 ]
544+
545+ for i in range (1 , self .n_barriers ):
546+ current_pos += w_mins [i - 1 ] + spacings [i - 1 ]
547+ y_mins [i ] = current_pos
548+
549+ offsets = w_mins * 0.1
550+ y_mids = y_mins + offsets
551+
552+ w_maxs = np .clip (w_mins * 0.7 , 0.01 , None )
553+
554+ w_maxs = np .clip (w_mins * 0.7 , 0.01 , None )
555+ thetas = np .linspace (1 , 20 , self .n_barriers )
556+ thetas [0 ] = 1
557+
558+ self .y_mins = y_mins
559+ self .w_mins = w_mins
560+ self .y_mids = y_mids
561+ self .w_mids = w_mids
562+ self .thetas = thetas
563+ self .w_maxs = w_maxs
564+
565+ return y_mins , w_mins , y_mids , w_mids , thetas , w_maxs
566+
567+ def X_to_params (self , X : np .ndarray , barrier = None ):
568+ n = self .n_barriers if barrier is None else barrier
569+
570+ if len (X ) != 2 * n :
571+ raise ValueError (f"X length { len (X )} does not match expected 2*n={ 2 * n } " )
572+
573+ rand1 = X [:n ]
574+ rand2 = X [n ]
575+ spacings = X [n + 1 : n + 1 + (n - 1 )]
576+
577+ return rand1 , rand2 , spacings
578+
579+ def generate_barriers (self ) -> list [np .ndarray ]:
580+ barriers = []
581+ for args in zip (self .y_mins , self .w_mins , self .y_mids , self .w_mids , self .thetas , self .w_maxs ):
582+ x_all , y_all = self ._create_barrier (* args )
583+ xy_all = np .vstack ((x_all , y_all )).T
584+ barriers .append (xy_all )
585+ return barriers
586+
587+ def generate_magnets (self , barriers : list [np .ndarray ]) -> list [np .ndarray ]:
588+ magnets = []
589+ margin = 0.0
590+
591+ for barrier in barriers :
592+ x , y = barrier [:, 0 ], barrier [:, 1 ]
593+ mag_pts = np .column_stack ((x , y ))
594+
595+ angles = np .degrees (np .arctan2 (mag_pts [:, 1 ], mag_pts [:, 0 ]))
596+
597+ center_angle = (angles .max () + angles .min ()) / 2
598+
599+ full_half_span = (angles .max () - angles .min ()) / 2
600+ center_half_span = full_half_span * (1 - 2 * margin )
601+
602+ center_mask = np .abs (angles - center_angle ) < center_half_span
603+ magnets .append ([mag_pts [center_mask ]])
604+
605+ return magnets
0 commit comments