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.
Files changed (247) hide show
  1. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/PKG-INFO +4 -2
  2. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/__init__.py +3 -1
  3. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/dataset.py +23 -0
  4. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/abstract.py +2 -1
  5. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/convolver.py +38 -12
  6. autoarray-2026.5.14.2/autoarray/operators/interp_2d.py +74 -0
  7. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/transformer.py +252 -3
  8. autoarray-2026.5.14.2/autoarray/settings.py +120 -0
  9. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/type.py +2 -1
  10. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray.egg-info/SOURCES.txt +1 -0
  11. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/pyproject.toml +3 -2
  12. autoarray-2026.5.8.2/autoarray/settings.py +0 -79
  13. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  14. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  15. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/copilot-instructions.md +0 -0
  16. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/.github/workflows/main.yml +0 -0
  17. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/AGENTS.md +0 -0
  18. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/CLAUDE.md +0 -0
  19. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/CONTRIBUTING.md +0 -0
  20. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/LICENSE +0 -0
  21. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/MANIFEST.in +0 -0
  22. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/PLAN.md +0 -0
  23. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/README.md +0 -0
  24. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/abstract_ndarray.py +0 -0
  25. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/.gitignore +0 -0
  26. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/README.md +0 -0
  27. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/general.yaml +0 -0
  28. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/logging.yaml +0 -0
  29. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/README.md +0 -0
  30. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/general.yaml +0 -0
  31. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/config/visualize/plots.yaml +0 -0
  32. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/__init__.py +0 -0
  33. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/abstract/__init__.py +0 -0
  34. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/abstract/dataset.py +0 -0
  35. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/dataset_model.py +0 -0
  36. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/grids.py +0 -0
  37. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/__init__.py +0 -0
  38. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/dataset.py +0 -0
  39. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/imaging/simulator.py +0 -0
  40. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/__init__.py +0 -0
  41. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/interferometer/simulator.py +0 -0
  42. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/mock/__init__.py +0 -0
  43. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/mock/mock_dataset.py +0 -0
  44. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/__init__.py +0 -0
  45. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/imaging_plots.py +0 -0
  46. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/plot/interferometer_plots.py +0 -0
  47. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/dataset/preprocess.py +0 -0
  48. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/exc.py +0 -0
  49. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/__init__.py +0 -0
  50. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_dataset.py +0 -0
  51. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_imaging.py +0 -0
  52. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_interferometer.py +0 -0
  53. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/fit_util.py +0 -0
  54. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/__init__.py +0 -0
  55. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/mock_fit_imaging.py +0 -0
  56. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/mock/mock_fit_interferometer.py +0 -0
  57. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/__init__.py +0 -0
  58. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/fit_imaging_plots.py +0 -0
  59. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fit/plot/fit_interferometer_plots.py +0 -0
  60. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/fixtures.py +0 -0
  61. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/__init__.py +0 -0
  62. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/abstract_2d.py +0 -0
  63. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_1d.py +0 -0
  64. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_2d.py +0 -0
  65. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_2d_irregular.py +0 -0
  66. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/geometry/geometry_util.py +0 -0
  67. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/__init__.py +0 -0
  68. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/__init__.py +0 -0
  69. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/dataset_interface.py +0 -0
  70. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/factory.py +0 -0
  71. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/__init__.py +0 -0
  72. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/abstract.py +0 -0
  73. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/inversion_imaging_util.py +0 -0
  74. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/mapping.py +0 -0
  75. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging/sparse.py +0 -0
  76. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/__init__.py +0 -0
  77. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/inversion_imaging_numba_util.py +0 -0
  78. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/imaging_numba/sparse.py +0 -0
  79. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/__init__.py +0 -0
  80. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/abstract.py +0 -0
  81. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py +0 -0
  82. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/mapping.py +0 -0
  83. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/interferometer/sparse.py +0 -0
  84. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/inversion/inversion_util.py +0 -0
  85. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/__init__.py +0 -0
  86. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/func_list.py +0 -0
  87. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/linear_obj.py +0 -0
  88. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/neighbors.py +0 -0
  89. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/linear_obj/unique_mappings.py +0 -0
  90. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/__init__.py +0 -0
  91. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/abstract.py +0 -0
  92. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/mapper_numba_util.py +0 -0
  93. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mappers/mapper_util.py +0 -0
  94. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/__init__.py +0 -0
  95. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/border_relocator.py +0 -0
  96. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/__init__.py +0 -0
  97. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/abstract.py +0 -0
  98. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/abstract_weighted.py +0 -0
  99. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/hilbert.py +0 -0
  100. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/kmeans.py +0 -0
  101. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/image_mesh/overlay.py +0 -0
  102. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/__init__.py +0 -0
  103. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/abstract.py +0 -0
  104. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/delaunay.py +0 -0
  105. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/knn.py +0 -0
  106. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular.py +0 -0
  107. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular_spline.py +0 -0
  108. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/interpolator/rectangular_uniform.py +0 -0
  109. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/__init__.py +0 -0
  110. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/abstract.py +0 -0
  111. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/delaunay.py +0 -0
  112. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/knn.py +0 -0
  113. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_adapt_density.py +0 -0
  114. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_adapt_image.py +0 -0
  115. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_spline_adapt_density.py +0 -0
  116. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_spline_adapt_image.py +0 -0
  117. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh/rectangular_uniform.py +0 -0
  118. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/__init__.py +0 -0
  119. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/abstract.py +0 -0
  120. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/delaunay.py +0 -0
  121. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mesh/mesh_geometry/rectangular.py +0 -0
  122. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/__init__.py +0 -0
  123. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_image_mesh.py +0 -0
  124. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_interpolator.py +0 -0
  125. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion.py +0 -0
  126. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion_imaging.py +0 -0
  127. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_inversion_interferometer.py +0 -0
  128. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_linear_obj.py +0 -0
  129. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_linear_obj_func_list.py +0 -0
  130. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_mapper.py +0 -0
  131. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_mesh.py +0 -0
  132. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_pixelization.py +0 -0
  133. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/mock/mock_regularization.py +0 -0
  134. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/pixelization.py +0 -0
  135. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/__init__.py +0 -0
  136. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/inversion_plots.py +0 -0
  137. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/plot/mapper_plots.py +0 -0
  138. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/__init__.py +0 -0
  139. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/abstract.py +0 -0
  140. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt.py +0 -0
  141. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt_split.py +0 -0
  142. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/adapt_split_zeroth.py +0 -0
  143. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/brightness_zeroth.py +0 -0
  144. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant.py +0 -0
  145. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant_split.py +0 -0
  146. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/constant_zeroth.py +0 -0
  147. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/exponential_kernel.py +0 -0
  148. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/gaussian_kernel.py +0 -0
  149. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/matern_adapt_kernel.py +0 -0
  150. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/matern_kernel.py +0 -0
  151. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/regularization_util.py +0 -0
  152. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/inversion/regularization/zeroth.py +0 -0
  153. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/__init__.py +0 -0
  154. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/layout.py +0 -0
  155. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/layout_util.py +0 -0
  156. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/layout/region.py +0 -0
  157. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/__init__.py +0 -0
  158. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/abstract_mask.py +0 -0
  159. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/__init__.py +0 -0
  160. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/grid_1d.py +0 -0
  161. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/grid_2d.py +0 -0
  162. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/indexes_2d.py +0 -0
  163. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/mask_1d.py +0 -0
  164. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/mask_2d.py +0 -0
  165. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/derive/zoom_2d.py +0 -0
  166. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_1d.py +0 -0
  167. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_1d_util.py +0 -0
  168. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_2d.py +0 -0
  169. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mask_2d_util.py +0 -0
  170. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mock/__init__.py +0 -0
  171. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mask/mock/mock_mask.py +0 -0
  172. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/mock.py +0 -0
  173. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/numba_util.py +0 -0
  174. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/__init__.py +0 -0
  175. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/contour.py +0 -0
  176. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/mock/__init__.py +0 -0
  177. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/mock/mock_psf.py +0 -0
  178. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/__init__.py +0 -0
  179. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/decorator.py +0 -0
  180. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/over_sample_util.py +0 -0
  181. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/over_sampling/over_sampler.py +0 -0
  182. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/operators/transformer_util.py +0 -0
  183. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/__init__.py +0 -0
  184. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/array.py +0 -0
  185. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/grid.py +0 -0
  186. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/inversion.py +0 -0
  187. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/output.py +0 -0
  188. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/segmentdata.py +0 -0
  189. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/utils.py +0 -0
  190. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/plot/yx.py +0 -0
  191. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/__init__.py +0 -0
  192. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/abstract_structure.py +0 -0
  193. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/__init__.py +0 -0
  194. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/array_1d_util.py +0 -0
  195. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/array_2d_util.py +0 -0
  196. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/irregular.py +0 -0
  197. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/rgb.py +0 -0
  198. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/uniform_1d.py +0 -0
  199. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/arrays/uniform_2d.py +0 -0
  200. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/__init__.py +0 -0
  201. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/abstract.py +0 -0
  202. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_array.py +0 -0
  203. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_grid.py +0 -0
  204. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/to_vector_yx.py +0 -0
  205. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/decorators/transform.py +0 -0
  206. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/__init__.py +0 -0
  207. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/grid_1d_util.py +0 -0
  208. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/grid_2d_util.py +0 -0
  209. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/irregular_2d.py +0 -0
  210. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/sparse_2d_util.py +0 -0
  211. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/uniform_1d.py +0 -0
  212. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/grids/uniform_2d.py +0 -0
  213. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/header.py +0 -0
  214. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/__init__.py +0 -0
  215. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/mock_decorators.py +0 -0
  216. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/mock/mock_grid.py +0 -0
  217. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/plot/__init__.py +0 -0
  218. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/plot/structure_plots.py +0 -0
  219. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/__init__.py +0 -0
  220. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/abstract.py +0 -0
  221. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/array.py +0 -0
  222. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/array_np.py +0 -0
  223. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/coordinate_array.py +0 -0
  224. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/coordinate_array_np.py +0 -0
  225. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/triangles/shape.py +0 -0
  226. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/__init__.py +0 -0
  227. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/abstract.py +0 -0
  228. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/irregular.py +0 -0
  229. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/vectors/uniform.py +0 -0
  230. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/structures/visibilities.py +0 -0
  231. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/__init__.py +0 -0
  232. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/cholesky_funcs.py +0 -0
  233. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/dataset_util.py +0 -0
  234. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/fnnls.py +0 -0
  235. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/autoarray/util/misc_util.py +0 -0
  236. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/Makefile +0 -0
  237. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/conf.py +0 -0
  238. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/index.md +0 -0
  239. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/docs/make.bat +0 -0
  240. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/eden.ini +0 -0
  241. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/eden.ini +0 -0
  242. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/release.sh +0 -0
  243. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/files/to_do_list +0 -0
  244. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/readthedocs.yml +0 -0
  245. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/setup.cfg +0 -0
  246. {autoarray-2026.5.8.2 → autoarray-2026.5.14.2}/setup.py +0 -0
  247. {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.8.2
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.8.2
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.8.2"
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 index, mapper in enumerate(self.cls_list_from(cls=Mapper)):
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=jnp.float64)
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=jnp.float64)
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=jnp.float64))
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
- state.fft_kernel * fft_image_native, s=state.fft_shape, axes=(0, 1)
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; optionally cast for downstream stability
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
- blurred_image = Array2D(values=blurred_slim, mask=image.mask)
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
- fft_complex_dtype = jnp.complex64 if use_mixed_precision else jnp.complex128
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.\n\n"
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 TransformerNUFFT(NUFFT_cpu):
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(TransformerNUFFT, self).__init__()
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)