autoarray 2026.5.8.2__tar.gz → 2026.5.14.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/PKG-INFO +4 -2
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/__init__.py +3 -1
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/dataset.py +23 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/abstract.py +2 -1
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/convolver.py +38 -12
- autoarray-2026.5.14.2/autoarray/operators/interp_2d.py +74 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/transformer.py +252 -3
- autoarray-2026.5.14.2/autoarray/settings.py +120 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/type.py +2 -1
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray.egg-info/SOURCES.txt +1 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/pyproject.toml +3 -2
- autoarray-2026.5.8.2/autoarray/settings.py +0 -79
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/copilot-instructions.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/workflows/main.yml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/AGENTS.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/CLAUDE.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/CONTRIBUTING.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/LICENSE +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/MANIFEST.in +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/PLAN.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/README.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/abstract_ndarray.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/.gitignore +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/README.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/general.yaml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/logging.yaml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/README.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/general.yaml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/plots.yaml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/abstract/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/abstract/dataset.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/dataset_model.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/grids.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/dataset.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/simulator.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/simulator.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/mock/mock_dataset.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/imaging_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/interferometer_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/preprocess.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/exc.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_dataset.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_imaging.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_interferometer.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/mock_fit_imaging.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/mock_fit_interferometer.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/fit_imaging_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/fit_interferometer_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fixtures.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/abstract_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_2d_irregular.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/dataset_interface.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/factory.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/inversion_imaging_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/mapping.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/sparse.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/inversion_imaging_numba_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/sparse.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/mapping.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/sparse.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/inversion_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/func_list.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/linear_obj.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/neighbors.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/unique_mappings.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/mapper_numba_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/mapper_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/border_relocator.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/abstract_weighted.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/hilbert.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/kmeans.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/overlay.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/delaunay.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/knn.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular_spline.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular_uniform.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/delaunay.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/knn.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_adapt_density.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_adapt_image.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_spline_adapt_density.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_spline_adapt_image.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_uniform.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/delaunay.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/rectangular.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_image_mesh.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_interpolator.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion_imaging.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion_interferometer.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_linear_obj.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_linear_obj_func_list.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_mapper.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_mesh.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_pixelization.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_regularization.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/pixelization.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/inversion_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/mapper_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt_split.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt_split_zeroth.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/brightness_zeroth.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant_split.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant_zeroth.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/exponential_kernel.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/gaussian_kernel.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/matern_adapt_kernel.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/matern_kernel.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/regularization_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/zeroth.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/layout.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/layout_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/region.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/abstract_mask.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/grid_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/grid_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/indexes_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/mask_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/mask_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/zoom_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_1d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_2d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mock/mock_mask.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mock.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/numba_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/contour.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/mock/mock_psf.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/decorator.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/over_sample_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/over_sampler.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/transformer_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/array.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/grid.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/inversion.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/output.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/segmentdata.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/utils.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/yx.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/abstract_structure.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/array_1d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/array_2d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/irregular.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/rgb.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/uniform_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/uniform_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_array.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_grid.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_vector_yx.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/transform.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/grid_1d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/grid_2d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/irregular_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/sparse_2d_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/uniform_1d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/uniform_2d.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/header.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/mock_decorators.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/mock_grid.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/plot/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/plot/structure_plots.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/array.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/array_np.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/coordinate_array.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/coordinate_array_np.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/shape.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/abstract.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/irregular.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/uniform.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/visibilities.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/__init__.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/cholesky_funcs.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/dataset_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/fnnls.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/misc_util.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/Makefile +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/conf.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/index.md +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/make.bat +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/eden.ini +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/eden.ini +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/release.sh +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/to_do_list +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/readthedocs.yml +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/setup.cfg +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/setup.py +0 -0
- {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/to_do_list +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: autoarray
|
|
3
|
-
Version: 2026.5.
|
|
3
|
+
Version: 2026.5.14.2
|
|
4
4
|
Summary: PyAuto Data Structures
|
|
5
5
|
Author-email: James Nightingale <James.Nightingale@newcastle.ac.uk>, Richard Hayes <richard@rghsoftware.co.uk>
|
|
6
6
|
License: MIT
|
|
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist: autoconf==2026.5.
|
|
21
|
+
Requires-Dist: autoconf==2026.5.14.2
|
|
22
22
|
Requires-Dist: astropy<=7.2.0,>=5.0
|
|
23
23
|
Requires-Dist: decorator>=4.0.0
|
|
24
24
|
Requires-Dist: dill>=0.3.1.1
|
|
@@ -32,6 +32,7 @@ Requires-Dist: autoconf[jax]; extra == "jax"
|
|
|
32
32
|
Provides-Extra: optional
|
|
33
33
|
Requires-Dist: autoarray[jax]; extra == "optional"
|
|
34
34
|
Requires-Dist: numba; extra == "optional"
|
|
35
|
+
Requires-Dist: nufftax; extra == "optional"
|
|
35
36
|
Requires-Dist: pynufft; extra == "optional"
|
|
36
37
|
Requires-Dist: tensorflow-probability==0.25.0; extra == "optional"
|
|
37
38
|
Provides-Extra: test
|
|
@@ -40,6 +41,7 @@ Provides-Extra: dev
|
|
|
40
41
|
Requires-Dist: pytest; extra == "dev"
|
|
41
42
|
Requires-Dist: black; extra == "dev"
|
|
42
43
|
Requires-Dist: numba; extra == "dev"
|
|
44
|
+
Requires-Dist: nufftax; extra == "dev"
|
|
43
45
|
Requires-Dist: pynufft==2022.2.2; extra == "dev"
|
|
44
46
|
Dynamic: license-file
|
|
45
47
|
|
|
@@ -60,6 +60,7 @@ from .mask.mask_1d import Mask1D
|
|
|
60
60
|
from .mask.mask_2d import Mask2D
|
|
61
61
|
from .operators.transformer import TransformerDFT
|
|
62
62
|
from .operators.transformer import TransformerNUFFT
|
|
63
|
+
from .operators.transformer import TransformerNUFFTPyNUFFT
|
|
63
64
|
from .operators.over_sampling.decorator import over_sample
|
|
64
65
|
from .operators.contour import Grid2DContour
|
|
65
66
|
from .layout.layout import Layout1D
|
|
@@ -77,6 +78,7 @@ from .inversion.mesh.mesh_geometry.delaunay import MeshGeometryDelaunay
|
|
|
77
78
|
from .inversion.mesh.interpolator.rectangular import InterpolatorRectangular
|
|
78
79
|
from .inversion.mesh.interpolator.delaunay import InterpolatorDelaunay
|
|
79
80
|
from .operators.convolver import Convolver
|
|
81
|
+
from .operators.interp_2d import interp_2d
|
|
80
82
|
from .structures.vectors.uniform import VectorYX2D
|
|
81
83
|
from .structures.vectors.irregular import VectorYX2DIrregular
|
|
82
84
|
from .structures.triangles.abstract import AbstractTriangles
|
|
@@ -101,4 +103,4 @@ from autoconf.fitsable import hdu_list_for_output_from
|
|
|
101
103
|
|
|
102
104
|
conf.instance.register(__file__)
|
|
103
105
|
|
|
104
|
-
__version__ = "2026.5.
|
|
106
|
+
__version__ = "2026.5.14.2"
|
|
@@ -251,6 +251,29 @@ class Interferometer(AbstractDataset):
|
|
|
251
251
|
enabling efficient pixelized source reconstruction via the sparse linear algebra formalism.
|
|
252
252
|
"""
|
|
253
253
|
|
|
254
|
+
if isinstance(self.transformer, TransformerNUFFT):
|
|
255
|
+
raise NotImplementedError(
|
|
256
|
+
"\n--------------------\n"
|
|
257
|
+
"`apply_sparse_operator` is not yet supported with the default "
|
|
258
|
+
"`TransformerNUFFT` (nufftax-backed) transformer.\n\n"
|
|
259
|
+
"The sparse-operator path consumes the dirty image returned by "
|
|
260
|
+
"`transformer.image_from(use_adjoint_scaling=True)` together with "
|
|
261
|
+
"the NUFFT precision operator; their relative scale matters. The "
|
|
262
|
+
"new `TransformerNUFFT` returns the strict mathematical adjoint "
|
|
263
|
+
"(matching `TransformerDFT`), whereas the legacy pynufft adjoint "
|
|
264
|
+
"applies an internal Kaiser-Bessel kernel deconvolution. The two "
|
|
265
|
+
"scales differ by a non-constant factor, so feeding the new "
|
|
266
|
+
"dirty image into the existing sparse-operator solver would "
|
|
267
|
+
"silently give wrong answers.\n\n"
|
|
268
|
+
"Workarounds:\n"
|
|
269
|
+
" - Build the dataset with `transformer_class=TransformerDFT` "
|
|
270
|
+
"(the JAX-likelihood scripts do this today), or\n"
|
|
271
|
+
" - Build the dataset with "
|
|
272
|
+
"`transformer_class=TransformerNUFFTPyNUFFT` to keep the legacy "
|
|
273
|
+
"pynufft adjoint scale (requires `pip install pynufft`).\n"
|
|
274
|
+
"----------------------"
|
|
275
|
+
)
|
|
276
|
+
|
|
254
277
|
if nufft_precision_operator is None:
|
|
255
278
|
|
|
256
279
|
logger.info(
|
|
@@ -802,7 +802,8 @@ class AbstractInversion:
|
|
|
802
802
|
def regularization_weights_mapper_dict(self) -> Dict[LinearObj, np.ndarray]:
|
|
803
803
|
regularization_weights_dict = {}
|
|
804
804
|
|
|
805
|
-
for
|
|
805
|
+
for mapper in self.cls_list_from(cls=Mapper):
|
|
806
|
+
index = self.linear_obj_list.index(mapper)
|
|
806
807
|
regularization_weights_dict[mapper] = self.regularization_weights_from(
|
|
807
808
|
index=index,
|
|
808
809
|
)
|
|
@@ -133,6 +133,13 @@ class ConvolverState:
|
|
|
133
133
|
|
|
134
134
|
self.fft_kernel = np.fft.rfft2(self.kernel.native.array, s=self.fft_shape)
|
|
135
135
|
self.fft_kernel_mapping = np.expand_dims(self.fft_kernel, 2)
|
|
136
|
+
# Pre-cached complex64 view for the use_mixed_precision=True path of
|
|
137
|
+
# convolved_image_from. Cast once here so the FFT branch does not
|
|
138
|
+
# repeat the astype per JIT trace — it would otherwise produce a fresh
|
|
139
|
+
# numpy buffer each call, which on CPU costs more than the fp32 FFT
|
|
140
|
+
# saves. convolved_mapping_matrix_from intentionally does NOT use a
|
|
141
|
+
# complex64 kernel — see that method's body for why.
|
|
142
|
+
self.fft_kernel_c64 = self.fft_kernel.astype(np.complex64)
|
|
136
143
|
|
|
137
144
|
|
|
138
145
|
class Convolver:
|
|
@@ -532,17 +539,23 @@ class Convolver:
|
|
|
532
539
|
|
|
533
540
|
state = self.state_from(mask=image.mask)
|
|
534
541
|
|
|
542
|
+
# When use_mixed_precision is on, the FFT runs in complex64 end-to-end:
|
|
543
|
+
# the input cube is allocated as float32, rfft2 emits complex64, the
|
|
544
|
+
# precomputed (complex128) kernel is cast on the fly, and irfft2
|
|
545
|
+
# returns float32 natively. No trailing astype is needed.
|
|
546
|
+
real_dtype = jnp.float32 if use_mixed_precision else jnp.float64
|
|
547
|
+
|
|
535
548
|
# Build combined native image in the FFT dtype
|
|
536
|
-
image_both_native = xp.zeros(state.fft_shape, dtype=
|
|
549
|
+
image_both_native = xp.zeros(state.fft_shape, dtype=real_dtype)
|
|
537
550
|
|
|
538
551
|
image_both_native = image_both_native.at[state.mask.slim_to_native_tuple].set(
|
|
539
|
-
jnp.asarray(image.array, dtype=
|
|
552
|
+
jnp.asarray(image.array, dtype=real_dtype)
|
|
540
553
|
)
|
|
541
554
|
|
|
542
555
|
if blurring_image is not None:
|
|
543
556
|
image_both_native = image_both_native.at[
|
|
544
557
|
state.blurring_mask.slim_to_native_tuple
|
|
545
|
-
].set(jnp.asarray(blurring_image.array, dtype=
|
|
558
|
+
].set(jnp.asarray(blurring_image.array, dtype=real_dtype))
|
|
546
559
|
else:
|
|
547
560
|
warnings.warn(
|
|
548
561
|
"No blurring_image provided. Only the direct image will be convolved. "
|
|
@@ -554,9 +567,14 @@ class Convolver:
|
|
|
554
567
|
image_both_native, s=state.fft_shape, axes=(0, 1)
|
|
555
568
|
)
|
|
556
569
|
|
|
570
|
+
# Pick the precomputed kernel matching the FFT dtype. ConvolverState
|
|
571
|
+
# caches both complex128 (default) and complex64 (mixed precision) at
|
|
572
|
+
# init time, so this is a constant lookup rather than a per-call cast.
|
|
573
|
+
fft_kernel = state.fft_kernel_c64 if use_mixed_precision else state.fft_kernel
|
|
574
|
+
|
|
557
575
|
# Multiply by PSF in Fourier space and invert
|
|
558
576
|
blurred_image_full = xp.fft.irfft2(
|
|
559
|
-
|
|
577
|
+
fft_kernel * fft_image_native, s=state.fft_shape, axes=(0, 1)
|
|
560
578
|
)
|
|
561
579
|
ky, kx = self.kernel.shape_native # (21, 21)
|
|
562
580
|
off_y = (ky - 1) // 2
|
|
@@ -572,15 +590,11 @@ class Convolver:
|
|
|
572
590
|
blurred_image_full, start_indices, state.fft_shape
|
|
573
591
|
)
|
|
574
592
|
|
|
575
|
-
# Return slim form;
|
|
593
|
+
# Return slim form; dtype already matches use_mixed_precision via the
|
|
594
|
+
# FFT path, so no explicit downcast.
|
|
576
595
|
blurred_slim = blurred_image_native[state.mask.slim_to_native_tuple]
|
|
577
596
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
if use_mixed_precision:
|
|
581
|
-
blurred_image = blurred_image.astype(jnp.float32)
|
|
582
|
-
|
|
583
|
-
return blurred_image
|
|
597
|
+
return Array2D(values=blurred_slim, mask=image.mask)
|
|
584
598
|
|
|
585
599
|
def convolved_mapping_matrix_from(
|
|
586
600
|
self,
|
|
@@ -677,7 +691,19 @@ class Convolver:
|
|
|
677
691
|
# -------------------------------------------------------------------------
|
|
678
692
|
# Mixed precision handling
|
|
679
693
|
# -------------------------------------------------------------------------
|
|
680
|
-
|
|
694
|
+
# mapping_matrix_native_from honors use_mixed_precision and produces a
|
|
695
|
+
# fp32 native cube. rfft2 of that cube emits complex64. We deliberately
|
|
696
|
+
# multiply by the complex128 precomputed kernel below, which upcasts
|
|
697
|
+
# the product back to complex128 so the irfft2 returns float64. This
|
|
698
|
+
# asymmetry is intentional: pixelization meshes with K >> 40 source
|
|
699
|
+
# pixels accumulate enough fp32 round-off through the NNLS active-set
|
|
700
|
+
# / log-determinant that the figure_of_merit drifts by O(1) units
|
|
701
|
+
# (verified on the delaunay_mge regression). The fp32 input cube and
|
|
702
|
+
# complex64 forward FFT still buy us a faster scatter and slightly
|
|
703
|
+
# cheaper rfft2; keeping the kernel multiply in complex128 preserves
|
|
704
|
+
# the precision the downstream linear algebra needs.
|
|
705
|
+
# convolved_image_from (used by light profiles) takes the full fp32
|
|
706
|
+
# path because its 40-column linear systems are well-conditioned.
|
|
681
707
|
|
|
682
708
|
# -------------------------------------------------------------------------
|
|
683
709
|
# Build native cube on the *native mask grid*
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
2D regular-grid bilinear interpolation with NumPy and JAX backends.
|
|
3
|
+
|
|
4
|
+
Mirrors the 1D dispatch pattern in
|
|
5
|
+
``autoarray.inversion.mesh.interpolator.rectangular_spline``.
|
|
6
|
+
|
|
7
|
+
NumPy path delegates to ``scipy.interpolate.RegularGridInterpolator``
|
|
8
|
+
(``bounds_error=False``, configurable ``fill_value``). JAX path is built on
|
|
9
|
+
``jax.scipy.ndimage.map_coordinates(order=1, mode='constant')``.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _interp_2d_numpy(points, x_axis, y_axis, values, fill_value=0.0):
|
|
16
|
+
"""
|
|
17
|
+
Evaluate ``values`` (regular grid on (x_axis, y_axis)) at ``(N, 2)`` query
|
|
18
|
+
points using bilinear interpolation. Out-of-bounds queries return
|
|
19
|
+
``fill_value``.
|
|
20
|
+
|
|
21
|
+
Matches the wiring used by ``DatasetInterp`` in PyAutoGalaxy: pass the
|
|
22
|
+
axes and the values array unchanged, do not transpose.
|
|
23
|
+
"""
|
|
24
|
+
from scipy import interpolate
|
|
25
|
+
|
|
26
|
+
interpolator = interpolate.RegularGridInterpolator(
|
|
27
|
+
points=(x_axis, y_axis),
|
|
28
|
+
values=values,
|
|
29
|
+
bounds_error=False,
|
|
30
|
+
fill_value=fill_value,
|
|
31
|
+
)
|
|
32
|
+
return interpolator(points)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _interp_2d_jax(points, x_axis, y_axis, values, fill_value=0.0):
|
|
36
|
+
"""
|
|
37
|
+
JAX-compatible counterpart to ``_interp_2d_numpy``. Uses
|
|
38
|
+
``jax.scipy.ndimage.map_coordinates(order=1, mode='constant')``.
|
|
39
|
+
|
|
40
|
+
The query ``points`` are in world coordinates with column 0 matching
|
|
41
|
+
``x_axis`` and column 1 matching ``y_axis`` (mirroring the scipy
|
|
42
|
+
interpolator's ``(x, y)`` argument order). World coords are converted to
|
|
43
|
+
pixel-fractional indices into ``values`` using axis spacings derived from
|
|
44
|
+
the first two axis points.
|
|
45
|
+
"""
|
|
46
|
+
import jax.numpy as jnp
|
|
47
|
+
from jax.scipy.ndimage import map_coordinates
|
|
48
|
+
|
|
49
|
+
x_axis = jnp.asarray(x_axis)
|
|
50
|
+
y_axis = jnp.asarray(y_axis)
|
|
51
|
+
values = jnp.asarray(values)
|
|
52
|
+
pts = jnp.asarray(points)
|
|
53
|
+
|
|
54
|
+
# World -> pixel-fractional indices.
|
|
55
|
+
dx = x_axis[1] - x_axis[0]
|
|
56
|
+
dy = y_axis[1] - y_axis[0]
|
|
57
|
+
col_coords = (pts[:, 0] - x_axis[0]) / dx # column 0 of points -> x-axis
|
|
58
|
+
row_coords = (pts[:, 1] - y_axis[0]) / dy # column 1 of points -> y-axis
|
|
59
|
+
|
|
60
|
+
# map_coordinates indexes values[i, j] where i is the first-axis index
|
|
61
|
+
# and j the second. The scipy call uses points=(x_axis, y_axis) so the
|
|
62
|
+
# first axis of `values` is interpreted as the x-axis.
|
|
63
|
+
coords = jnp.stack([col_coords, row_coords], axis=0)
|
|
64
|
+
|
|
65
|
+
return map_coordinates(
|
|
66
|
+
values, coords, order=1, mode="constant", cval=fill_value
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def interp_2d(points, x_axis, y_axis, values, fill_value=0.0, xp=np):
|
|
71
|
+
"""Dispatcher -- picks ``_interp_2d_numpy`` or ``_interp_2d_jax`` based on ``xp``."""
|
|
72
|
+
if xp is np:
|
|
73
|
+
return _interp_2d_numpy(points, x_axis, y_axis, values, fill_value=fill_value)
|
|
74
|
+
return _interp_2d_jax(points, x_axis, y_axis, values, fill_value=fill_value)
|
|
@@ -23,16 +23,36 @@ from autoarray.structures.arrays import array_2d_util
|
|
|
23
23
|
from autoarray.operators import transformer_util
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
try:
|
|
27
|
+
import nufftax as _nufftax
|
|
28
|
+
except ModuleNotFoundError:
|
|
29
|
+
_nufftax = None
|
|
30
|
+
|
|
31
|
+
|
|
26
32
|
def pynufft_exception():
|
|
27
33
|
raise ModuleNotFoundError(
|
|
28
34
|
"\n--------------------\n"
|
|
29
|
-
"You are attempting to perform interferometer analysis
|
|
35
|
+
"You are attempting to perform interferometer analysis with the legacy "
|
|
36
|
+
"pynufft-backed `TransformerNUFFTPyNUFFT`.\n\n"
|
|
30
37
|
"However, the optional library PyNUFFT (https://github.com/jyhmiinlin/pynufft) is not installed.\n\n"
|
|
31
38
|
"Install it via the command `pip install pynufft==2022.2.2`.\n\n"
|
|
32
39
|
"----------------------"
|
|
33
40
|
)
|
|
34
41
|
|
|
35
42
|
|
|
43
|
+
def nufftax_exception():
|
|
44
|
+
raise ModuleNotFoundError(
|
|
45
|
+
"\n--------------------\n"
|
|
46
|
+
"You are attempting to perform interferometer analysis with the default "
|
|
47
|
+
"JAX-native `TransformerNUFFT`.\n\n"
|
|
48
|
+
"However, the optional library nufftax (https://github.com/GragasLab/nufftax) is not installed.\n\n"
|
|
49
|
+
"Install it via the command `pip install nufftax`.\n\n"
|
|
50
|
+
"If you want to use the legacy pynufft backend instead, pass "
|
|
51
|
+
"`transformer_class=TransformerNUFFTPyNUFFT` and install pynufft.\n\n"
|
|
52
|
+
"----------------------"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
36
56
|
class TransformerDFT:
|
|
37
57
|
def __init__(
|
|
38
58
|
self,
|
|
@@ -175,13 +195,18 @@ class TransformerDFT:
|
|
|
175
195
|
)
|
|
176
196
|
|
|
177
197
|
|
|
178
|
-
class
|
|
198
|
+
class TransformerNUFFTPyNUFFT(NUFFT_cpu):
|
|
179
199
|
def __init__(
|
|
180
200
|
self, uv_wavelengths: np.ndarray, real_space_mask: Mask2D, xp=np, **kwargs
|
|
181
201
|
):
|
|
182
202
|
"""
|
|
183
203
|
Performs the Non-Uniform Fast Fourier Transform (NUFFT) for interferometric image reconstruction.
|
|
184
204
|
|
|
205
|
+
Legacy pynufft-backed transformer. The default `TransformerNUFFT` is now backed by `nufftax`
|
|
206
|
+
(JAX-native, differentiable, ~zero gridding error) — this class is retained so users who depend
|
|
207
|
+
on pynufft's specific gridding behaviour can opt in by passing
|
|
208
|
+
`transformer_class=TransformerNUFFTPyNUFFT`.
|
|
209
|
+
|
|
185
210
|
This transformer uses the PyNUFFT library to efficiently compute the Fourier transform
|
|
186
211
|
of an image defined on a regular real-space grid to a set of non-uniform uv-plane (Fourier space)
|
|
187
212
|
coordinates, as is typical in radio interferometry.
|
|
@@ -226,7 +251,7 @@ class TransformerNUFFT(NUFFT_cpu):
|
|
|
226
251
|
if isinstance(self, NUFFTPlaceholder):
|
|
227
252
|
pynufft_exception()
|
|
228
253
|
|
|
229
|
-
super(
|
|
254
|
+
super(TransformerNUFFTPyNUFFT, self).__init__()
|
|
230
255
|
|
|
231
256
|
self.uv_wavelengths = uv_wavelengths
|
|
232
257
|
self.real_space_mask = real_space_mask
|
|
@@ -469,3 +494,227 @@ class TransformerNUFFT(NUFFT_cpu):
|
|
|
469
494
|
)
|
|
470
495
|
|
|
471
496
|
return transformed_mapping_matrix
|
|
497
|
+
|
|
498
|
+
|
|
499
|
+
class TransformerNUFFT:
|
|
500
|
+
def __init__(
|
|
501
|
+
self,
|
|
502
|
+
uv_wavelengths: np.ndarray,
|
|
503
|
+
real_space_mask: Mask2D,
|
|
504
|
+
eps: float = 1e-12,
|
|
505
|
+
xp=np,
|
|
506
|
+
**kwargs,
|
|
507
|
+
):
|
|
508
|
+
"""
|
|
509
|
+
JAX-native Non-Uniform FFT for image -> visibilities, backed by `nufftax`.
|
|
510
|
+
|
|
511
|
+
This is the default `TransformerNUFFT` in PyAutoArray. It uses the
|
|
512
|
+
`nufftax` library (https://github.com/GragasLab/nufftax), a pure-JAX
|
|
513
|
+
NUFFT implementation that supports `jax.jit`, `jax.grad`, and
|
|
514
|
+
`jax.vmap`. It replaces the legacy `TransformerNUFFTPyNUFFT` (which
|
|
515
|
+
wraps the non-differentiable `pynufft` library) as the default backend.
|
|
516
|
+
|
|
517
|
+
Convention recipe (matches `TransformerDFT` to ~1e-13 relative across
|
|
518
|
+
odd/even/non-square image sizes):
|
|
519
|
+
|
|
520
|
+
image_flipped = image[::-1, :]
|
|
521
|
+
x = 2 * pi * u_lambda * pixel_scale_rad
|
|
522
|
+
y = 2 * pi * v_lambda * pixel_scale_rad
|
|
523
|
+
offset_x = 0.5 if N_x is even else 0.0
|
|
524
|
+
offset_y = 0.5 if N_y is even else 0.0
|
|
525
|
+
shift = exp(-i * (offset_x * x + offset_y * y))
|
|
526
|
+
visibilities = nufftax.nufft2d2(x, y, image_flipped, eps, -1) * shift
|
|
527
|
+
|
|
528
|
+
The `shift` factor is the half-pixel correction between autoarray's
|
|
529
|
+
grid centre at index `(N - 1) / 2` and nufftax's mode-0 at index
|
|
530
|
+
`N // 2`; pynufft applies this internally, nufftax does not.
|
|
531
|
+
|
|
532
|
+
Parameters
|
|
533
|
+
----------
|
|
534
|
+
uv_wavelengths
|
|
535
|
+
The (u, v) coordinates of the measured visibilities in wavelengths,
|
|
536
|
+
shape `(n_vis, 2)`.
|
|
537
|
+
real_space_mask
|
|
538
|
+
The 2D mask defining the real-space image grid.
|
|
539
|
+
eps
|
|
540
|
+
Requested NUFFT precision passed to nufftax. Defaults to `1e-12`
|
|
541
|
+
(effectively machine precision); relax to `1e-9` or `1e-6` for
|
|
542
|
+
faster execution if marginal accuracy is acceptable.
|
|
543
|
+
xp
|
|
544
|
+
Accepted for signature compatibility with the legacy class; not
|
|
545
|
+
stored. The active backend is selected per-call via the `xp`
|
|
546
|
+
argument to `visibilities_from` / `image_from`.
|
|
547
|
+
|
|
548
|
+
Attributes
|
|
549
|
+
----------
|
|
550
|
+
grid
|
|
551
|
+
The real-space pixel grid in radians (computed from the mask).
|
|
552
|
+
total_visibilities
|
|
553
|
+
Number of measured visibilities.
|
|
554
|
+
total_image_pixels
|
|
555
|
+
Number of unmasked pixels in the image grid.
|
|
556
|
+
adjoint_scaling
|
|
557
|
+
Scaling factor available for callers who want to apply an
|
|
558
|
+
optional normalisation to the adjoint output. Provided for
|
|
559
|
+
parity with the legacy class.
|
|
560
|
+
"""
|
|
561
|
+
from astropy import units
|
|
562
|
+
|
|
563
|
+
if _nufftax is None:
|
|
564
|
+
nufftax_exception()
|
|
565
|
+
|
|
566
|
+
self.uv_wavelengths = uv_wavelengths.astype("float")
|
|
567
|
+
self.real_space_mask = real_space_mask
|
|
568
|
+
self.grid = Grid2D.from_mask(mask=self.real_space_mask).in_radians
|
|
569
|
+
self.eps = eps
|
|
570
|
+
self.native_index_for_slim_index = copy.copy(
|
|
571
|
+
real_space_mask.derive_indexes.native_for_slim.astype("int")
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
pixel_scale_rad = self.grid.pixel_scales[0] * units.arcsec.to(units.rad)
|
|
575
|
+
# nufft2d2 frequency arguments:
|
|
576
|
+
# x is paired with the column-axis mode (image x)
|
|
577
|
+
# y is paired with the row-axis mode (image y)
|
|
578
|
+
# Both must lie in [-pi, pi); the 2*pi*Δ_rad scaling makes uv_lambda
|
|
579
|
+
# land in that range for any sane uv-coverage.
|
|
580
|
+
self._x = 2.0 * np.pi * self.uv_wavelengths[:, 0] * pixel_scale_rad
|
|
581
|
+
self._y = 2.0 * np.pi * self.uv_wavelengths[:, 1] * pixel_scale_rad
|
|
582
|
+
|
|
583
|
+
n_y, n_x = self.real_space_mask.shape_native
|
|
584
|
+
offset_x = 0.5 if n_x % 2 == 0 else 0.0
|
|
585
|
+
offset_y = 0.5 if n_y % 2 == 0 else 0.0
|
|
586
|
+
self._shift = np.exp(-1j * (offset_x * self._x + offset_y * self._y))
|
|
587
|
+
|
|
588
|
+
self.total_visibilities = uv_wavelengths.shape[0]
|
|
589
|
+
self.total_image_pixels = real_space_mask.pixels_in_mask
|
|
590
|
+
self.adjoint_scaling = (2.0 * n_y) * (2.0 * n_x)
|
|
591
|
+
|
|
592
|
+
def _forward_native(self, image_native_2d, xp=np):
|
|
593
|
+
"""Run nufft2d2 on a 2D native-shape image array, returning visibilities."""
|
|
594
|
+
if xp.__name__.startswith("jax"):
|
|
595
|
+
import jax.numpy as jnp
|
|
596
|
+
|
|
597
|
+
img = jnp.asarray(image_native_2d)[::-1, :].astype(jnp.complex128)
|
|
598
|
+
x = jnp.asarray(self._x)
|
|
599
|
+
y = jnp.asarray(self._y)
|
|
600
|
+
shift = jnp.asarray(self._shift)
|
|
601
|
+
return _nufftax.nufft2d2(x, y, img, self.eps, -1) * shift
|
|
602
|
+
|
|
603
|
+
img = image_native_2d[::-1, :].astype(np.complex128)
|
|
604
|
+
out = _nufftax.nufft2d2(self._x, self._y, img, self.eps, -1) * self._shift
|
|
605
|
+
return np.array(np.asarray(out))
|
|
606
|
+
|
|
607
|
+
def visibilities_from(self, image, xp=np) -> Visibilities:
|
|
608
|
+
"""
|
|
609
|
+
Forward NUFFT: real-space image -> visibilities at the configured uv points.
|
|
610
|
+
|
|
611
|
+
For numpy callers (`xp=np`) the result is materialised back to numpy
|
|
612
|
+
before being wrapped in `Visibilities`. For JAX callers (`xp=jnp`)
|
|
613
|
+
the result stays as a `jax.Array` so it can flow through `jax.jit`
|
|
614
|
+
/ `jax.grad` / `jax.vmap` without device round-trips.
|
|
615
|
+
"""
|
|
616
|
+
if xp.__name__.startswith("jax"):
|
|
617
|
+
import jax.numpy as jnp
|
|
618
|
+
|
|
619
|
+
image_native = jnp.zeros(image.mask.shape, dtype=image.dtype)
|
|
620
|
+
image_native = image_native.at[image.mask.slim_to_native_tuple].set(
|
|
621
|
+
image.array
|
|
622
|
+
)
|
|
623
|
+
else:
|
|
624
|
+
image_native = image.native.array
|
|
625
|
+
|
|
626
|
+
return Visibilities(visibilities=self._forward_native(image_native, xp=xp))
|
|
627
|
+
|
|
628
|
+
def image_from(
|
|
629
|
+
self,
|
|
630
|
+
visibilities: Visibilities,
|
|
631
|
+
use_adjoint_scaling: bool = False,
|
|
632
|
+
xp=np,
|
|
633
|
+
) -> Array2D:
|
|
634
|
+
"""
|
|
635
|
+
Adjoint NUFFT: visibilities -> real-space (dirty) image.
|
|
636
|
+
|
|
637
|
+
Implemented as `nufftax.nufft2d1` with `conj(shift)` applied to the
|
|
638
|
+
visibilities and a final row-flip to return to autoarray's native
|
|
639
|
+
orientation. The real part is taken to discard imaginary residue,
|
|
640
|
+
matching the legacy class' behaviour.
|
|
641
|
+
|
|
642
|
+
Note that this is the **mathematical adjoint** of `visibilities_from`,
|
|
643
|
+
with no kernel deconvolution applied. The dirty image therefore
|
|
644
|
+
differs in absolute scale from the legacy `TransformerNUFFTPyNUFFT`
|
|
645
|
+
adjoint (which applies pynufft's internal IFFT and kernel
|
|
646
|
+
deconvolution). The structure of the dirty image is the same, and
|
|
647
|
+
the values match `TransformerDFT.image_from` exactly.
|
|
648
|
+
|
|
649
|
+
**Scale-sensitive callers**: `Interferometer.apply_sparse_operator`
|
|
650
|
+
consumes the dirty-image scale together with a precision operator; it
|
|
651
|
+
is currently incompatible with this class and raises
|
|
652
|
+
`NotImplementedError`. Use `TransformerDFT` or
|
|
653
|
+
`TransformerNUFFTPyNUFFT` if you need the sparse-operator path.
|
|
654
|
+
"""
|
|
655
|
+
n_y, n_x = self.real_space_mask.shape_native
|
|
656
|
+
n_modes = (n_x, n_y) # nufftax wants (n1, n2) = (N_x, N_y)
|
|
657
|
+
|
|
658
|
+
if xp.__name__.startswith("jax"):
|
|
659
|
+
import jax.numpy as jnp
|
|
660
|
+
|
|
661
|
+
x = jnp.asarray(self._x)
|
|
662
|
+
y = jnp.asarray(self._y)
|
|
663
|
+
shift_conj = jnp.asarray(np.conj(self._shift))
|
|
664
|
+
c = jnp.asarray(visibilities.array) * shift_conj
|
|
665
|
+
f = _nufftax.nufft2d1(x, y, c, n_modes, self.eps, +1)
|
|
666
|
+
image = jnp.real(f)[::-1, :]
|
|
667
|
+
else:
|
|
668
|
+
c = visibilities.array * np.conj(self._shift)
|
|
669
|
+
f = _nufftax.nufft2d1(self._x, self._y, c, n_modes, self.eps, +1)
|
|
670
|
+
image = np.array(np.asarray(f)[::-1, :].real)
|
|
671
|
+
|
|
672
|
+
if use_adjoint_scaling:
|
|
673
|
+
image = image * self.adjoint_scaling
|
|
674
|
+
|
|
675
|
+
return Array2D(values=image, mask=self.real_space_mask)
|
|
676
|
+
|
|
677
|
+
def transform_mapping_matrix(self, mapping_matrix, xp=np):
|
|
678
|
+
"""
|
|
679
|
+
Apply the forward NUFFT to each column of a mapping matrix.
|
|
680
|
+
|
|
681
|
+
All columns are scattered into a single batched native-shape image
|
|
682
|
+
of shape ``(n_src, N_y, N_x)`` and passed through nufft2d2 in one
|
|
683
|
+
call (nufft2d2 supports batched ``f``). This avoids the
|
|
684
|
+
per-column Python loop that, under ``jax.jit``, would unroll into
|
|
685
|
+
``n_src`` separate NUFFT invocations and blow up the JIT graph
|
|
686
|
+
for pixelization-heavy fits (notably double-source-plane).
|
|
687
|
+
"""
|
|
688
|
+
n_src = mapping_matrix.shape[1]
|
|
689
|
+
rows, cols = self.real_space_mask.slim_to_native_tuple
|
|
690
|
+
n_y, n_x = self.real_space_mask.shape_native
|
|
691
|
+
|
|
692
|
+
if xp.__name__.startswith("jax"):
|
|
693
|
+
import jax.numpy as jnp
|
|
694
|
+
|
|
695
|
+
mm_T = jnp.asarray(mapping_matrix).T.astype(jnp.complex128)
|
|
696
|
+
source_images = jnp.zeros((n_src, n_y, n_x), dtype=jnp.complex128)
|
|
697
|
+
source_images = source_images.at[
|
|
698
|
+
jnp.arange(n_src)[:, None],
|
|
699
|
+
jnp.asarray(rows)[None, :],
|
|
700
|
+
jnp.asarray(cols)[None, :],
|
|
701
|
+
].set(mm_T)
|
|
702
|
+
flipped = source_images[:, ::-1, :]
|
|
703
|
+
x = jnp.asarray(self._x)
|
|
704
|
+
y = jnp.asarray(self._y)
|
|
705
|
+
shift = jnp.asarray(self._shift)
|
|
706
|
+
# nufft2d2 returns shape (n_trans, M); transpose to (M, n_src).
|
|
707
|
+
vis_batched = (
|
|
708
|
+
_nufftax.nufft2d2(x, y, flipped, self.eps, -1) * shift[None, :]
|
|
709
|
+
)
|
|
710
|
+
return vis_batched.T
|
|
711
|
+
|
|
712
|
+
mm_T = np.asarray(mapping_matrix).T.astype(np.complex128)
|
|
713
|
+
source_images = np.zeros((n_src, n_y, n_x), dtype=np.complex128)
|
|
714
|
+
source_images[np.arange(n_src)[:, None], rows[None, :], cols[None, :]] = mm_T
|
|
715
|
+
flipped = source_images[:, ::-1, :]
|
|
716
|
+
vis_batched = (
|
|
717
|
+
_nufftax.nufft2d2(self._x, self._y, flipped, self.eps, -1)
|
|
718
|
+
* self._shift[None, :]
|
|
719
|
+
)
|
|
720
|
+
return np.array(np.asarray(vis_batched).T)
|