@@ -788,6 +788,106 @@ def _evaluate_placement(
788788 return float (- log_dist_sum )
789789
790790
791+ def _max_feasible_diameter_for_positions (
792+ positions : NDArray [np .float64 ],
793+ pupil : PupilGeometry ,
794+ diameter_m : float ,
795+ ) -> float :
796+ """Largest sub-aperture diameter allowed by fixed center positions."""
797+
798+ positions = np .asarray (positions , dtype = np .float64 )
799+ max_diameter = np .inf
800+ r_obs = pupil .central_obstruction_diameter_m / 2
801+ r_outer = diameter_m / 2
802+
803+ for x , y in positions :
804+ radial = float (np .hypot (x , y ))
805+ if pupil .boundary_vertices is not None :
806+ edge_clearance = _signed_distance_to_polygon_edge (
807+ float (x ), float (y ), pupil .boundary_vertices
808+ )
809+ max_diameter = min (max_diameter , 2.0 * edge_clearance )
810+ else :
811+ max_diameter = min (max_diameter , 2.0 * (r_outer - radial ))
812+
813+ if r_obs > 0.0 :
814+ max_diameter = min (max_diameter , 2.0 * (radial - r_obs ))
815+
816+ for vane in pupil .spider_vanes :
817+ vane_clearance = point_to_segment_distance (
818+ float (x ),
819+ float (y ),
820+ vane .x_start ,
821+ vane .y_start ,
822+ vane .x_end ,
823+ vane .y_end ,
824+ ) - vane .width_m / 2
825+ max_diameter = min (max_diameter , 2.0 * vane_clearance )
826+
827+ if positions .shape [0 ] > 1 :
828+ diffs = positions [:, None , :] - positions [None , :, :]
829+ distances = np .sqrt ((diffs ** 2 ).sum (axis = - 1 ))
830+ distances = distances [np .triu_indices (positions .shape [0 ], k = 1 )]
831+ max_diameter = min (max_diameter , float (np .min (distances )))
832+
833+ if not np .isfinite (max_diameter ):
834+ return 0.0
835+ return max (0.0 , float (max_diameter ))
836+
837+
838+ def _grow_refined_placement (
839+ positions : NDArray [np .float64 ],
840+ d_sub : float ,
841+ d_sub_hi : float ,
842+ n_sub : int ,
843+ pupil : PupilGeometry ,
844+ diameter_m : float ,
845+ ) -> tuple [NDArray [np .float64 ], float ]:
846+ """Grow a feasible placement using hard-constraint refinement.
847+
848+ The seed search is intentionally conservative. For crowded pupils with
849+ spider vanes, such as E-ELT N=48, a feasible seed can often be enlarged by
850+ jointly relaxing all sub-aperture centers. This pass keeps the binary
851+ search's proven feasible layout as the starting point and only accepts a
852+ larger diameter when the existing hard-constraint score remains feasible.
853+ """
854+
855+ if pupil .boundary_vertices is not None or not pupil .spider_vanes or n_sub < 24 :
856+ return np .asarray (positions , dtype = np .float64 ), float (d_sub )
857+
858+ best_positions = np .asarray (positions , dtype = np .float64 )
859+ best_d_sub = float (d_sub )
860+ low = best_d_sub
861+ high = max (float (d_sub_hi ), low )
862+ if high <= low :
863+ return best_positions , best_d_sub
864+
865+ for _ in range (12 ):
866+ trial_d_sub = 0.5 * (low + high )
867+ trial_r_sub = 0.5 * trial_d_sub
868+ refined = _refine_positions (
869+ best_positions ,
870+ n_sub ,
871+ trial_r_sub ,
872+ pupil ,
873+ diameter_m ,
874+ )
875+ max_diameter = _max_feasible_diameter_for_positions (
876+ refined ,
877+ pupil ,
878+ diameter_m ,
879+ )
880+ accepted_d_sub = min (trial_d_sub , max_diameter )
881+ if accepted_d_sub > best_d_sub :
882+ best_positions = refined
883+ best_d_sub = accepted_d_sub
884+ low = accepted_d_sub
885+ else :
886+ high = trial_d_sub
887+
888+ return best_positions , best_d_sub
889+
890+
791891def _place_single_subaperture (
792892 diameter_m : float , r_sub : float , pupil : PupilGeometry ,
793893) -> NDArray [np .float64 ]:
@@ -852,6 +952,7 @@ def optimize_subaperture_positions(
852952
853953 ring_d_max = _max_ring_d_sub (n_sub , pupil , diameter_m )
854954 d_sub_hi = max (diameter_m / np .sqrt (n_sub ), ring_d_max ) * 1.1
955+ d_sub_upper_bound = d_sub_hi
855956 d_sub_lo = 0.1
856957 best_d_sub = d_sub_lo
857958
@@ -919,6 +1020,15 @@ def optimize_subaperture_positions(
9191020 f"diameter={ diameter_m :.1f} m"
9201021 )
9211022
1023+ best_result , best_d_sub = _grow_refined_placement (
1024+ best_result ,
1025+ best_d_sub ,
1026+ d_sub_upper_bound ,
1027+ n_sub ,
1028+ pupil ,
1029+ diameter_m ,
1030+ )
1031+
9221032 return best_result , best_d_sub
9231033
9241034
0 commit comments