disco.core.optimization
This module implements the core geometric loss function used to assess the azimuthal symmetry of a deprojected disk image, and the functions that orchestrate the hybrid CNN-seeded optimisation strategy.
Geometric Loss Function
- disco.core.optimization.geometric_loss(params, image, base_cx, base_cy, crop_rad, rmin_pix, rmax_pix, dim=150, order=1)[source]
Evaluate the asymmetry of a disk image under a given deprojection geometry. This function serves as the objective for all optimisation routines in DISCO.
Algorithm:
The four-parameter vector
[incl, pa, dcx, dcy]defines the trial geometry: inclination and position angle (both in degrees) and centre offsets (in pixels) relative to(base_cx, base_cy).A square grid of dimension
dim × dimis constructed in the deprojected frame. The corresponding source-plane pixel coordinates are computed via the inclination and rotation transforms, then offset by the trial centre:\[X_c = X \cos i, \qquad X_{\rm rot} = \cos\phi \cdot X_c + \sin\phi \cdot Y, \qquad Y_{\rm rot} = -\sin\phi \cdot X_c + \cos\phi \cdot Y\]The deprojected image
deprojis obtained by interpolating the cropped inputimageat the source-plane coordinates viascipy.ndimage.map_coordinateswith the specifiedorder.The deprojected image is mapped into polar coordinates (\(r \in [0, \dim/2]\), \(\theta \in [-\pi, \pi]\), 180 azimuthal samples).
The azimuthal mean profile
profile[r]is computed over the radial range[rmin_pix, rmax_pix].The loss is the weighted Huber loss of the residuals between the polar image and its azimuthal mean:
\[L = \sum_{r,\theta} h_\delta\!\left( \frac{I_{\rm polar}(r,\theta) - \bar{I}(r)} {\bar{I}(r) + 0.01 \max(\bar{I})} \right) w(r)\]where \(h_\delta\) is the Huber function with \(\delta = 0.5\) and \(w(r) = \sqrt{\bar{I}(r)/\max(\bar{I})} \cdot [r \ge 0.03] \cdot r\) is a radially-weighted mask favouring the bright, outer disk.
If more than 10% of the mapped pixel coordinates fall outside the image boundary, or if the profile maximum is below \(10^{-8}\), the function returns the sentinel value \(10^{12}\).
- Parameters:
params (list) –
[incl, pa, dcx, dcy]— inclination (deg), position angle (deg), x centre offset (pix), y centre offset (pix).image (numpy.ndarray) – 2D cropped image array.
base_cx (float) – Reference centre x in the cropped array.
base_cy (float) – Reference centre y in the cropped array.
crop_rad (float) – Half-width of the cropped array in pixels.
rmin_pix (float) – Inner radius of the fitting annulus in pixels.
rmax_pix (float) – Outer radius of the fitting annulus in pixels.
dim (int) – Size of the evaluation grid. Default: 150.
order (int) – Interpolation order for
map_coordinates. Default: 1.
- Returns:
Scalar loss value.
- Return type:
Hybrid Geometry Optimiser
- disco.core.optimization.auto_tune_geometry_hybrid(data, header, pixel_scale, cx, cy, model, search_rad, rmin)[source]
Determine the optimal disk inclination, position angle, and centre offset using a two-stage hybrid strategy: CNN-seeded global search followed by local refinement.
Stage 1 — CNN prior:
disco.core.cnn_inference.predict_with_cnn()provides an initial estimate \((\hat{i}_{\rm CNN}, \hat{\phi}_{\rm CNN})\) that constrains the parameter bounds for the subsequent optimisation.Stage 2 — Differential evolution (coarse):
scipy.optimize.differential_evolutionminimisesgeometric_loss()on a Gaussian-smoothed version of the cropped image (dc_smooth, smoothing width \(\approx 0.4\,b_{\rm maj}\)). The search bounds are constrained around the CNN prior:\(i \in [\hat{i}_{\rm CNN} - \Delta_i, \hat{i}_{\rm CNN} + \Delta_i]\), where \(\Delta_i = 15°\) if \(\hat{i}_{\rm CNN} < 35°\), else \(20°\).
\(\phi \in [\hat{\phi}_{\rm CNN} - \Delta_\phi, \hat{\phi}_{\rm CNN} + \Delta_\phi]\), where \(\Delta_\phi = 25°\) if \(\hat{i}_{\rm CNN} < 35°\), else \(30°\).
Centre offsets: \(|\Delta x|, |\Delta y| \le 1.5\) pixels.
Parameters:
maxiter=50,tol=0.02,mutation=(0.5, 1.0),recombination=0.7,seed=42.Stage 3 — Nelder-Mead refinement (fine):
Starting from the differential-evolution result,
scipy.optimize.minimize(Nelder-Mead) refines on the full-resolution imagedcwith a \(400 \times 400\) grid (order=3).Stage 4 — Secondary refinement (if near boundary):
If the optimised centre offset is within 80% of the bound (
near_edge), a second differential-evolution + Nelder-Mead cycle is run with the accumulated offset applied to the reference centre, and tighter bounds (\(|\Delta x|, |\Delta y| \le 0.6\) pixels). The final offset is the sum of both stages.Consistency guard:
If the final inclination departs from \(\hat{i}_{\rm CNN}\) by more than 20°, the CNN prior is restored for both inclination and position angle, and the centre offset is zeroed.
- Parameters:
data (numpy.ndarray) – 2D FITS image array.
header (dict) – FITS header.
pixel_scale (float) – Arcsec per pixel.
cx (float) – Initial centroid x in pixels.
cy (float) – Initial centroid y in pixels.
model (DiscoNet) – Pre-loaded DiscoNet model.
search_rad (float) – Search radius in arcseconds.
rmin (float) – Inner radius in arcseconds.
- Returns:
(incl, pa, cnn_incl, cnn_pa, dcx, dcy)— optimised inclination (deg), optimised PA (deg), CNN prior inclination (deg), CNN prior PA (deg), x centre correction (pix), y centre correction (pix).- Return type:
Uncertainty Estimation
- disco.core.optimization.estimate_geometry_errors(data, pixel_scale, cx, cy, incl, pa, rmin, rmax)[source]
Estimate 1-sigma uncertainties on the inclination and position angle via a parabolic approximation of the geometric loss landscape.
The function first performs a local Nelder-Mead optimisation to confirm the minimum, evaluates the loss \(L_{\rm min}\), and then scans the loss along each angular axis independently at offsets \(\delta \in [-11.75°, +11.75°]\) (step 0.25°). A degree-2 polynomial is fitted to the valid loss values (\(L < 2.5\,L_{\rm min}\)). The estimated 1-sigma uncertainty is:
\[\sigma = \text{clip}\!\left(\sqrt{\frac{L_{\rm min} \times 5 \times 10^{-3}}{a}},\ 0.3°,\ 10°\right)\]where \(a\) is the parabola’s leading coefficient. Returns
(2.0, 2.0)if the minimum loss is non-finite or exceeds \(10^{11}\), or if fewer than 5 valid loss samples are available.- Parameters:
data (numpy.ndarray) – 2D image array.
pixel_scale (float) – Arcsec per pixel.
cx (float) – Centroid x in pixels.
cy (float) – Centroid y in pixels.
incl (float) – Best-fit inclination in degrees.
pa (float) – Best-fit position angle in degrees.
rmin (float) – Inner fitting radius in arcseconds.
rmax (float) – Outer fitting radius in arcseconds.
- Returns:
(err_incl, err_pa)— 1-sigma uncertainties in degrees.- Return type:
Centre Refinement
- disco.core.optimization.refine_center_geometry(data, header, pixel_scale, cx, cy, incl, pa, rmin, rmax)[source]
Refine the disk centroid by optimising centre offsets while holding inclination and position angle fixed.
A Nelder-Mead optimisation is performed with the centre offset constrained to \(\le 2\,b_{\rm maj}\) pixels. If the optimised offset does not improve the loss, or if the offset exceeds the bound, the original centre is returned unchanged.
- Parameters:
data (numpy.ndarray) – 2D image array.
header (dict) – FITS header.
pixel_scale (float) – Arcsec per pixel.
cx (float) – Initial centroid x in pixels.
cy (float) – Initial centroid y in pixels.
incl (float) – Fixed inclination in degrees.
pa (float) – Fixed position angle in degrees.
rmin (float) – Inner fitting radius in arcseconds.
rmax (float) – Outer fitting radius in arcseconds.
- Returns:
(cx_refined, cy_refined)in pixels.- Return type: