autoarray 2026.9.26.1__tar.gz → 2026.9.27.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 (276) hide show
  1. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/AGENTS.md +14 -0
  2. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/PKG-INFO +1 -1
  3. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/__init__.py +1 -1
  4. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/dataset_interface.py +9 -0
  5. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/factory.py +10 -9
  6. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer/mapping.py +4 -2
  7. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer/sparse.py +16 -6
  8. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer_numba/sparse.py +1 -1
  9. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/transformer.py +12 -7
  10. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/transformer_util.py +11 -6
  11. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/array.py +131 -3
  12. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/coordinate_array.py +120 -9
  13. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.claude/hooks/end-at-deliverable.sh +0 -0
  14. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.claude/hooks/session-start.sh +0 -0
  15. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.claude/settings.json +0 -0
  16. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.gitattributes +0 -0
  17. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  18. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  19. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.github/copilot-instructions.md +0 -0
  20. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.github/workflows/main.yml +0 -0
  21. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/.url_check_allowlist.txt +0 -0
  22. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/AI_POLICY.md +0 -0
  23. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/CLAUDE.md +0 -0
  24. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/CONTRIBUTING.md +0 -0
  25. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/LICENSE +0 -0
  26. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/MANIFEST.in +0 -0
  27. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/README.md +0 -0
  28. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/abstract_ndarray.py +0 -0
  29. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/.gitignore +0 -0
  30. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/README.md +0 -0
  31. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/general.yaml +0 -0
  32. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/logging.yaml +0 -0
  33. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/visualize/README.md +0 -0
  34. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/visualize/general.yaml +0 -0
  35. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/config/visualize/plots.yaml +0 -0
  36. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/__init__.py +0 -0
  37. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/abstract/__init__.py +0 -0
  38. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/abstract/dataset.py +0 -0
  39. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/dataset_model.py +0 -0
  40. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/grids.py +0 -0
  41. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/imaging/__init__.py +0 -0
  42. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/imaging/dataset.py +0 -0
  43. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/imaging/simulator.py +0 -0
  44. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/interferometer/__init__.py +0 -0
  45. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/interferometer/dataset.py +0 -0
  46. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/interferometer/simulator.py +0 -0
  47. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/mock/__init__.py +0 -0
  48. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/mock/mock_dataset.py +0 -0
  49. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/plot/__init__.py +0 -0
  50. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/plot/imaging_plots.py +0 -0
  51. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/plot/interferometer_plots.py +0 -0
  52. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/dataset/preprocess.py +0 -0
  53. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/exc.py +0 -0
  54. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/__init__.py +0 -0
  55. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/fit_dataset.py +0 -0
  56. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/fit_imaging.py +0 -0
  57. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/fit_interferometer.py +0 -0
  58. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/fit_util.py +0 -0
  59. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/mock/__init__.py +0 -0
  60. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/mock/mock_fit_imaging.py +0 -0
  61. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/mock/mock_fit_interferometer.py +0 -0
  62. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/plot/__init__.py +0 -0
  63. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/plot/fit_imaging_plots.py +0 -0
  64. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fit/plot/fit_interferometer_plots.py +0 -0
  65. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/fixtures.py +0 -0
  66. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/__init__.py +0 -0
  67. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/abstract_2d.py +0 -0
  68. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/geometry_1d.py +0 -0
  69. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/geometry_2d.py +0 -0
  70. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/geometry_2d_irregular.py +0 -0
  71. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/geometry/geometry_util.py +0 -0
  72. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/__init__.py +0 -0
  73. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/__init__.py +0 -0
  74. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/abstract.py +0 -0
  75. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging/__init__.py +0 -0
  76. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging/abstract.py +0 -0
  77. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging/inversion_imaging_util.py +0 -0
  78. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging/mapping.py +0 -0
  79. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging/sparse.py +0 -0
  80. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging_numba/__init__.py +0 -0
  81. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging_numba/inversion_imaging_numba_util.py +0 -0
  82. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/imaging_numba/sparse.py +0 -0
  83. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer/__init__.py +0 -0
  84. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer/abstract.py +0 -0
  85. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer/inversion_interferometer_util.py +0 -0
  86. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer_numba/__init__.py +0 -0
  87. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/interferometer_numba/inversion_interferometer_numba_util.py +0 -0
  88. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/inversion_util.py +0 -0
  89. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/inversion/nnls_memo.py +0 -0
  90. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/linear_obj/__init__.py +0 -0
  91. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/linear_obj/func_list.py +0 -0
  92. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/linear_obj/linear_obj.py +0 -0
  93. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/linear_obj/neighbors.py +0 -0
  94. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/linear_obj/unique_mappings.py +0 -0
  95. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappers/__init__.py +0 -0
  96. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappers/abstract.py +0 -0
  97. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappers/mapper_numba_util.py +0 -0
  98. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappers/mapper_util.py +0 -0
  99. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappings/__init__.py +0 -0
  100. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mappings/mapping.py +0 -0
  101. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/__init__.py +0 -0
  102. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/border_relocator.py +0 -0
  103. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/__init__.py +0 -0
  104. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/abstract.py +0 -0
  105. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/abstract_weighted.py +0 -0
  106. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/hilbert.py +0 -0
  107. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/kmeans.py +0 -0
  108. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/image_mesh/overlay.py +0 -0
  109. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/__init__.py +0 -0
  110. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/abstract.py +0 -0
  111. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/delaunay.py +0 -0
  112. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/knn.py +0 -0
  113. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/rectangular.py +0 -0
  114. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/rectangular_uniform.py +0 -0
  115. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/interpolator/sibson.py +0 -0
  116. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/__init__.py +0 -0
  117. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/abstract.py +0 -0
  118. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/delaunay.py +0 -0
  119. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/delaunay_nn.py +0 -0
  120. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/knn.py +0 -0
  121. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/rectangular_bilinear_adapt_density.py +0 -0
  122. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/rectangular_bilinear_adapt_image.py +0 -0
  123. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/rectangular_rtu_adapt_density.py +0 -0
  124. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/rectangular_rtu_adapt_image.py +0 -0
  125. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh/rectangular_uniform.py +0 -0
  126. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh_geometry/__init__.py +0 -0
  127. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh_geometry/abstract.py +0 -0
  128. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh_geometry/delaunay.py +0 -0
  129. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mesh/mesh_geometry/rectangular.py +0 -0
  130. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/__init__.py +0 -0
  131. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_image_mesh.py +0 -0
  132. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_interpolator.py +0 -0
  133. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_inversion.py +0 -0
  134. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_inversion_imaging.py +0 -0
  135. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_inversion_interferometer.py +0 -0
  136. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_linear_obj.py +0 -0
  137. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_linear_obj_func_list.py +0 -0
  138. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_mapper.py +0 -0
  139. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_mesh.py +0 -0
  140. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_pixelization.py +0 -0
  141. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/mock/mock_regularization.py +0 -0
  142. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/pixelization.py +0 -0
  143. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/plot/__init__.py +0 -0
  144. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/plot/inversion_plots.py +0 -0
  145. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/plot/mapper_plots.py +0 -0
  146. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/__init__.py +0 -0
  147. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/abstract.py +0 -0
  148. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt.py +0 -0
  149. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt_power.py +0 -0
  150. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt_split.py +0 -0
  151. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt_split_power.py +0 -0
  152. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt_split_zeroth.py +0 -0
  153. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/adapt_split_zeroth_power.py +0 -0
  154. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/brightness_zeroth.py +0 -0
  155. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/constant.py +0 -0
  156. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/constant_split.py +0 -0
  157. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/constant_zeroth.py +0 -0
  158. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/curvature_mask.py +0 -0
  159. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/exponential_kernel.py +0 -0
  160. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/fourth_order_mask.py +0 -0
  161. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/gaussian_kernel.py +0 -0
  162. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/matern_adapt_kernel.py +0 -0
  163. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/matern_adapt_power_kernel.py +0 -0
  164. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/matern_kernel.py +0 -0
  165. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/regularization_util.py +0 -0
  166. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/inversion/regularization/zeroth.py +0 -0
  167. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/layout/__init__.py +0 -0
  168. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/layout/layout.py +0 -0
  169. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/layout/layout_util.py +0 -0
  170. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/layout/region.py +0 -0
  171. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/__init__.py +0 -0
  172. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/abstract_mask.py +0 -0
  173. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/__init__.py +0 -0
  174. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/grid_1d.py +0 -0
  175. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/grid_2d.py +0 -0
  176. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/indexes_2d.py +0 -0
  177. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/mask_1d.py +0 -0
  178. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/mask_2d.py +0 -0
  179. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/derive/zoom_2d.py +0 -0
  180. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mask_1d.py +0 -0
  181. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mask_1d_util.py +0 -0
  182. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mask_2d.py +0 -0
  183. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mask_2d_util.py +0 -0
  184. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mock/__init__.py +0 -0
  185. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mask/mock/mock_mask.py +0 -0
  186. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/mock.py +0 -0
  187. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/numba_util.py +0 -0
  188. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/__init__.py +0 -0
  189. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/coarse_interp_util.py +0 -0
  190. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/contour.py +0 -0
  191. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/convolver.py +0 -0
  192. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/derivative_util.py +0 -0
  193. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/interp_2d.py +0 -0
  194. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/mock/__init__.py +0 -0
  195. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/mock/mock_psf.py +0 -0
  196. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/over_sampling/__init__.py +0 -0
  197. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/over_sampling/decorator.py +0 -0
  198. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/over_sampling/over_sample_util.py +0 -0
  199. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/operators/over_sampling/over_sampler.py +0 -0
  200. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/__init__.py +0 -0
  201. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/array.py +0 -0
  202. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/grid.py +0 -0
  203. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/inversion.py +0 -0
  204. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/output.py +0 -0
  205. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/segmentdata.py +0 -0
  206. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/utils.py +0 -0
  207. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/plot/yx.py +0 -0
  208. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/preloads/__init__.py +0 -0
  209. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/preloads/abstract.py +0 -0
  210. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/preloads/imaging.py +0 -0
  211. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/preloads/interferometer.py +0 -0
  212. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/settings.py +0 -0
  213. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/__init__.py +0 -0
  214. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/abstract_structure.py +0 -0
  215. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/__init__.py +0 -0
  216. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/array_1d_util.py +0 -0
  217. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/array_2d_util.py +0 -0
  218. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/irregular.py +0 -0
  219. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/rgb.py +0 -0
  220. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/uniform_1d.py +0 -0
  221. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/arrays/uniform_2d.py +0 -0
  222. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/__init__.py +0 -0
  223. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/abstract.py +0 -0
  224. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/to_array.py +0 -0
  225. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/to_grid.py +0 -0
  226. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/to_vector_yx.py +0 -0
  227. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/decorators/transform.py +0 -0
  228. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/__init__.py +0 -0
  229. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/grid_1d_util.py +0 -0
  230. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/grid_2d_util.py +0 -0
  231. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/irregular_2d.py +0 -0
  232. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/sparse_2d_util.py +0 -0
  233. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/uniform_1d.py +0 -0
  234. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/grids/uniform_2d.py +0 -0
  235. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/header.py +0 -0
  236. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/mock/__init__.py +0 -0
  237. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/mock/mock_decorators.py +0 -0
  238. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/mock/mock_grid.py +0 -0
  239. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/plot/__init__.py +0 -0
  240. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/plot/structure_plots.py +0 -0
  241. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/__init__.py +0 -0
  242. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/abstract.py +0 -0
  243. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/array_np.py +0 -0
  244. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/coordinate_array_np.py +0 -0
  245. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/triangles/shape.py +0 -0
  246. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/vectors/__init__.py +0 -0
  247. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/vectors/abstract.py +0 -0
  248. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/vectors/irregular.py +0 -0
  249. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/vectors/uniform.py +0 -0
  250. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/structures/visibilities.py +0 -0
  251. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/type.py +0 -0
  252. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/__init__.py +0 -0
  253. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/cholesky_funcs.py +0 -0
  254. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/dataset_util.py +0 -0
  255. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/fnnls.py +0 -0
  256. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/jax_active_set.py +0 -0
  257. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/jax_nnls.py +0 -0
  258. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/util/misc_util.py +0 -0
  259. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray/validate.py +0 -0
  260. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/autoarray.egg-info/SOURCES.txt +0 -0
  261. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/docs/Makefile +0 -0
  262. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/docs/agents/jax_and_decorators.md +0 -0
  263. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/docs/conf.py +0 -0
  264. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/docs/index.md +0 -0
  265. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/docs/make.bat +0 -0
  266. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/cdf_audit.md +0 -0
  267. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/ghost_peak.png +0 -0
  268. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/ghost_peak_experiment.py +0 -0
  269. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/ghost_peak_findings.md +0 -0
  270. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/pca_rotation.png +0 -0
  271. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/files/pca_rotation_experiment.py +0 -0
  272. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/llms.txt +0 -0
  273. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/pyproject.toml +0 -0
  274. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/readthedocs.yml +0 -0
  275. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/setup.cfg +0 -0
  276. {autoarray-2026.9.26.1 → autoarray-2026.9.27.2}/setup.py +0 -0
@@ -138,3 +138,17 @@ outlives the turn to wait for CI, a review or a merge: no `send_later`, no
138
138
  armed hourly check-ins on 2026-08-31, and a mobile `/prm` re-armed a 60-minute
139
139
  `send_later` hourly all night on 2026-09-03 with no task active, draining usage.
140
140
  <!-- repos_sync:deliverable:end -->
141
+
142
+ <!-- repos_sync:filing:begin -->
143
+ ## Where to file
144
+
145
+ Questions, help with code or an analysis, ideas, bug reports and results from a
146
+ user or collaborator — or an agent acting for one — go to
147
+ <https://github.com/orgs/PyAutoLabs/discussions> in the matching category
148
+ (Help & Questions, Ideas & Proposals, Bugs & Errors, Show and tell;
149
+ Announcements is maintainers-only), never to this repo's Issues. An agent never
150
+ runs `gh issue create` for such a report: it drafts the title, category and
151
+ body and hands them to the human (sessions cannot create Discussions). Only the
152
+ development flow — Mind prompt → `/start_dev` → `/create_issue` → one issue per
153
+ task → PR — opens issues here. Why: `PyAutoMind/policy/community_surface.md`.
154
+ <!-- repos_sync:filing:end -->
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: autoarray
3
- Version: 2026.9.26.1
3
+ Version: 2026.9.27.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
@@ -111,7 +111,7 @@ conf.instance.register(__file__)
111
111
  # Last manual sync of the source stamp. Release wheels are stamped at build
112
112
  # time and the git tag is the release truth — deliberately never bumped per
113
113
  # release (PyAutoBuild#118/#120).
114
- __version__ = "2026.9.26.1"
114
+ __version__ = "2026.9.27.2"
115
115
 
116
116
  # ---------------------------------------------------------------------------
117
117
  # Public re-export of the autonerves configuration / serialization surface.
@@ -8,6 +8,7 @@ class DatasetInterface:
8
8
  transformer=None,
9
9
  sparse_operator=None,
10
10
  noise_covariance_matrix=None,
11
+ sparse_dirty_image=None,
11
12
  ):
12
13
  """
13
14
  Generic class which acts as an interface between a dataset and an inversion.
@@ -49,6 +50,13 @@ class DatasetInterface:
49
50
  noise_covariance_matrix
50
51
  A noise-map covariance matrix representing the covariance between noise in every `data` value, which
51
52
  can be used via a bespoke fit to account for correlated noise in the data.
53
+ sparse_dirty_image
54
+ The noise-weighted dirty image `Re(Fᴴ W d)` of this interface's `data`, used by the sparse (w-tilde)
55
+ interferometer inversion to form its data vector. The `sparse_operator` caches the dirty image of the
56
+ visibilities it was built from, so this is only needed when `data` differs from them (e.g. when the
57
+ visibilities of ordinary light profiles have been subtracted). If `None`, the operator's cached dirty
58
+ image is used. This is distinct from `Interferometer.dirty_image`, the unweighted dirty image of the
59
+ data used for visualization.
52
60
  """
53
61
  self.data = data
54
62
  self.noise_map = noise_map
@@ -57,6 +65,7 @@ class DatasetInterface:
57
65
  self.transformer = transformer
58
66
  self.sparse_operator = sparse_operator
59
67
  self.noise_covariance_matrix = noise_covariance_matrix
68
+ self.sparse_dirty_image = sparse_dirty_image
60
69
 
61
70
  @property
62
71
  def mask(self):
@@ -125,6 +125,10 @@ def inversion_imaging_from(
125
125
  An `Inversion` whose type is determined by the input `dataset` and `settings`.
126
126
  """
127
127
 
128
+ # Unlike `inversion_interferometer_from`, imaging keeps func-list-only inversions (e.g. an MGE with no
129
+ # pixelization) on the mapping formalism. A func-list-only imaging mapping inversion is cheap (one PSF
130
+ # convolution per basis function), and the imaging sparse data vector's handling of profile-subtracted
131
+ # data is tracked separately, so the interferometer routing change is deliberately not mirrored here.
128
132
  use_sparse_operator = True
129
133
 
130
134
  if all(
@@ -199,15 +203,12 @@ def inversion_interferometer_from(
199
203
  -------
200
204
  An `Inversion` whose type is determined by the input `dataset` and `settings`.
201
205
  """
202
- use_sparse_operator = True
203
-
204
- if all(
205
- isinstance(linear_obj, AbstractLinearObjFuncList)
206
- for linear_obj in linear_obj_list
207
- ):
208
- use_sparse_operator = False
209
-
210
- if dataset.sparse_operator is not None and use_sparse_operator:
206
+ # A sparse operator selects the w-tilde formalism for every linear object list, including func-list-only
207
+ # ones (e.g. an MGE with no pixelization). For interferometer data this avoids the dense
208
+ # `transform_mapping_matrix` of shape (N_vis, S), which dominates run time and memory at large N_vis, and
209
+ # `InversionInterferometerSparse` handles zero mappers (the func-func curvature blocks and the data vector
210
+ # need no mapper). `_use_interferometer_numba` returns False whenever a func-list is present.
211
+ if dataset.sparse_operator is not None:
211
212
 
212
213
  if _use_interferometer_numba(
213
214
  linear_obj_list=linear_obj_list,
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  from typing import Dict, List, Union
3
3
 
4
+ from autonerves import cached_property
5
+
4
6
  from autoarray.dataset.interferometer.dataset import Interferometer
5
7
  from autoarray.inversion.inversion.dataset_interface import DatasetInterface
6
8
  from autoarray.inversion.inversion.interferometer.abstract import (
@@ -50,7 +52,7 @@ class InversionInterferometerMapping(AbstractInversionInterferometer):
50
52
  dataset=dataset, linear_obj_list=linear_obj_list, settings=settings, xp=xp
51
53
  )
52
54
 
53
- @property
55
+ @cached_property
54
56
  def data_vector(self) -> np.ndarray:
55
57
  """
56
58
  The `data_vector` is a 1D vector whose values are solved for by the simultaneous linear equations constructed
@@ -71,7 +73,7 @@ class InversionInterferometerMapping(AbstractInversionInterferometer):
71
73
  noise_map=self.noise_map,
72
74
  )
73
75
 
74
- @property
76
+ @cached_property
75
77
  def curvature_matrix(self) -> np.ndarray:
76
78
  """
77
79
  The `curvature_matrix` is a 2D matrix which uses the mappings between the data and the linear objects to
@@ -1,6 +1,8 @@
1
1
  import numpy as np
2
2
  from typing import Dict, List, Optional, Union
3
3
 
4
+ from autonerves import cached_property
5
+
4
6
  from autoarray import exc
5
7
  from autoarray.dataset.interferometer.dataset import Interferometer
6
8
  from autoarray.inversion.inversion.dataset_interface import DatasetInterface
@@ -67,7 +69,7 @@ class InversionInterferometerSparse(AbstractInversionInterferometer):
67
69
  preloads=preloads,
68
70
  )
69
71
 
70
- @property
72
+ @cached_property
71
73
  def data_vector(self) -> np.ndarray:
72
74
  """
73
75
  The `data_vector` is a 1D vector whose values are solved for by the simultaneous linear equations constructed
@@ -88,10 +90,18 @@ class InversionInterferometerSparse(AbstractInversionInterferometer):
88
90
  which is exactly the entry the mapping (dense) formalism computes via the linear
89
91
  function's transformed mapping matrix. Linear function lists therefore require no
90
92
  separate branch here.
93
+
94
+ The cached dirty image is that of the visibilities the `sparse_operator` was built from. When
95
+ the inversion's data differs from them (e.g. a `DatasetInterface` whose data has the visibilities
96
+ of ordinary light profiles subtracted), the dataset's `sparse_dirty_image` of that data is used
97
+ instead, so that `D` is consistent with the data the chi-squared is computed from.
91
98
  """
92
- return self._xp.dot(
93
- self.mapping_matrix.T, self.dataset.sparse_operator.dirty_image
94
- )
99
+ dirty_image = getattr(self.dataset, "sparse_dirty_image", None)
100
+
101
+ if dirty_image is None:
102
+ dirty_image = self.dataset.sparse_operator.dirty_image
103
+
104
+ return self._xp.dot(self.mapping_matrix.T, dirty_image)
95
105
 
96
106
  def _sparse_triplets_curvature_from(self, mapper: Mapper):
97
107
  """
@@ -121,7 +131,7 @@ class InversionInterferometerSparse(AbstractInversionInterferometer):
121
131
  xp=self._xp,
122
132
  )
123
133
 
124
- @property
134
+ @cached_property
125
135
  def curvature_matrix(self) -> np.ndarray:
126
136
  """
127
137
  The `curvature_matrix` is a 2D matrix which uses the mappings between the data and the linear objects to
@@ -179,7 +189,7 @@ class InversionInterferometerSparse(AbstractInversionInterferometer):
179
189
 
180
190
  return curvature_matrix
181
191
 
182
- @property
192
+ @cached_property
183
193
  def curvature_matrix_diag(self) -> np.ndarray:
184
194
  """
185
195
  The `curvature_matrix` is a 2D matrix which uses the mappings between the data and the linear objects to
@@ -181,7 +181,7 @@ class InversionInterferometerSparseNumba(InversionInterferometerSparse):
181
181
 
182
182
  return inputs
183
183
 
184
- @property
184
+ @cached_property
185
185
  def curvature_matrix_diag(self) -> np.ndarray:
186
186
  """
187
187
  `F = Aᵀ W~ A` for the inversion's single mapper, from the `direct_conv` numba
@@ -199,9 +199,12 @@ class TransformerDFT:
199
199
  mask as this transformer's `real_space_mask`.
200
200
  """
201
201
  image_slim = transformer_util.image_direct_from(
202
- visibilities=visibilities.in_array,
202
+ visibilities=xp.stack(
203
+ (xp.real(visibilities.array), xp.imag(visibilities.array)), axis=-1
204
+ ),
203
205
  grid_radians=self.grid.array,
204
206
  uv_wavelengths=self.uv_wavelengths,
207
+ xp=xp,
205
208
  )
206
209
 
207
210
  image_native = array_2d_util.array_2d_native_from(
@@ -522,14 +525,15 @@ class TransformerNUFFT:
522
525
  if xp.__name__.startswith("jax"):
523
526
  import jax.numpy as jnp
524
527
 
525
- mm_T = jnp.asarray(mapping_matrix).T.astype(jnp.complex128)
526
- source_images = jnp.zeros((n_src, n_y, n_x), dtype=jnp.complex128)
528
+ # Real scatter, then cast: GPU complex128 scatter floor (autolens_profiling#308).
529
+ mm_T = jnp.asarray(mapping_matrix).T
530
+ source_images = jnp.zeros((n_src, n_y, n_x), dtype=mm_T.dtype)
527
531
  source_images = source_images.at[
528
532
  jnp.arange(n_src)[:, None],
529
533
  jnp.asarray(rows)[None, :],
530
534
  jnp.asarray(cols)[None, :],
531
535
  ].set(mm_T)
532
- flipped = source_images[:, ::-1, :]
536
+ flipped = source_images[:, ::-1, :].astype(jnp.complex128)
533
537
  x = jnp.asarray(self._x)
534
538
  y = jnp.asarray(self._y)
535
539
  shift = jnp.asarray(self._shift)
@@ -539,10 +543,11 @@ class TransformerNUFFT:
539
543
  )
540
544
  return vis_batched.T
541
545
 
542
- mm_T = np.asarray(mapping_matrix).T.astype(np.complex128)
543
- source_images = np.zeros((n_src, n_y, n_x), dtype=np.complex128)
546
+ # Real scatter, then cast: GPU complex128 scatter floor (autolens_profiling#308).
547
+ mm_T = np.asarray(mapping_matrix).T
548
+ source_images = np.zeros((n_src, n_y, n_x), dtype=mm_T.dtype)
544
549
  source_images[np.arange(n_src)[:, None], rows[None, :], cols[None, :]] = mm_T
545
- flipped = source_images[:, ::-1, :]
550
+ flipped = source_images[:, ::-1, :].astype(np.complex128)
546
551
  vis_batched = (
547
552
  _nufftax.nufft2d2(self._x, self._y, flipped, self.eps, -1)
548
553
  * self._shift[None, :]
@@ -50,7 +50,10 @@ def visibilities_from(
50
50
 
51
51
 
52
52
  def image_direct_from(
53
- visibilities: np.ndarray, grid_radians: np.ndarray, uv_wavelengths: np.ndarray
53
+ visibilities: np.ndarray,
54
+ grid_radians: np.ndarray,
55
+ uv_wavelengths: np.ndarray,
56
+ xp=np,
54
57
  ) -> np.ndarray:
55
58
  """
56
59
  Reconstruct a real-valued sky image from complex interferometric visibilities
@@ -69,6 +72,8 @@ def image_direct_from(
69
72
 
70
73
  uv_wavelengths
71
74
  The (u, v) spatial frequencies in units of wavelengths for each baseline.
75
+ xp
76
+ The array module (`numpy` or `jax.numpy`) the transform is computed with.
72
77
 
73
78
  Returns
74
79
  -------
@@ -78,15 +83,15 @@ def image_direct_from(
78
83
  # Compute the phase term for each (pixel, visibility) pair
79
84
  phase = (
80
85
  2.0
81
- * np.pi
86
+ * xp.pi
82
87
  * (
83
- np.outer(grid_radians[:, 1], uv_wavelengths[:, 0])
84
- + np.outer(grid_radians[:, 0], uv_wavelengths[:, 1])
88
+ xp.outer(grid_radians[:, 1], uv_wavelengths[:, 0])
89
+ + xp.outer(grid_radians[:, 0], uv_wavelengths[:, 1])
85
90
  )
86
91
  )
87
92
 
88
- real_part = np.dot(np.cos(phase), visibilities[:, 0])
89
- imag_part = np.dot(np.sin(phase), visibilities[:, 1])
93
+ real_part = xp.dot(xp.cos(phase), visibilities[:, 0])
94
+ imag_part = xp.dot(xp.sin(phase), visibilities[:, 1])
90
95
 
91
96
  image_1d = real_part - imag_part
92
97
 
@@ -1,11 +1,60 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional, Tuple
3
+
1
4
  import numpy as np
2
5
 
3
6
  from autoarray.structures.triangles.abstract import HEIGHT_FACTOR
4
7
 
5
8
  from autoarray.structures.triangles.abstract import AbstractTriangles
9
+ from autoarray.structures.triangles.shape import Point
6
10
  from autoarray.structures.triangles.shape import Shape
11
+ from autoarray.structures.triangles.shape import _barycentric_contains
12
+
13
+ MAX_CONTAINING_SIZE = 20
14
+
15
+ # Private A/B switch for the step-0 containment route (point-source CPU phase 4b,
16
+ # PyAutoArray#579). It only affects `ArrayTriangles` built by
17
+ # `CoordinateArrayTriangles.with_vertices` on the static initial lattice (those carrying a
18
+ # `Step0Layout`) and tested against a `Point`; every other containment keeps the general
19
+ # `shape.mask(self.triangles)` path. All routes return bit-identical kept indices:
20
+ #
21
+ # - "gather": the general path -- pad, ``(N, 3, 2)`` gather, no-op NaN ``where``.
22
+ # - "nopad": the ``(N, 3, 2)`` gather without the pad / NaN ``where`` (no index is -1).
23
+ # - "components": six ``(N,)`` 1-D gathers, one per vertex component; no ``(N, 3, 2)`` array.
24
+ # - "structured": strided slices of the traced vertex table reshaped to its lattice rows; no
25
+ # gather at all, the boolean mask is interleaved back to triangle order.
26
+ #
27
+ # The switch is read at trace time: a jitted function must be re-traced (fresh closure plus
28
+ # ``jax.clear_caches()``) after changing it.
29
+ _STEP0_CONTAINMENT = "structured"
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Step0Layout:
34
+ """
35
+ The closed-form layout of the static initial lattice's vertex table (see
36
+ `autoarray.structures.triangles.coordinate_array.static_lattice_layout`).
37
+
38
+ Every value is a Python int, so a layout is hashable and can sit in pytree aux data: under
39
+ ``jit`` / ``vmap`` it is a trace-time constant.
40
+
41
+ Attributes
42
+ ----------
43
+ n_rows, n_cols
44
+ The triangle lattice is ``n_rows x n_cols`` triangles, stored row-major.
45
+ grid
46
+ ``None`` when the vertex table does not follow the closed-form pattern (only the
47
+ "gather" / "nopad" / "components" routes then apply), else
48
+ ``(n_pairs, pair_width, pad, corners)``: the ``(V, 2)`` table padded by ``pad`` rows is a
49
+ ``(n_pairs, pair_width, 2)`` grid, and ``corners[p][k] = (p0, c0, na, nb)`` says that for
50
+ the parity class ``p = 2 * (i % 2) + (j % 2)`` of triangle row ``i`` and column ``j``,
51
+ vertex ``k`` of the class's triangle ``(a, b)`` (``i = 2a + i % 2``, ``j = 2b + j % 2``)
52
+ is grid entry ``(p0 + a, c0 + b)``.
53
+ """
7
54
 
8
- MAX_CONTAINING_SIZE = 15
55
+ n_rows: int
56
+ n_cols: int
57
+ grid: Optional[Tuple] = None
9
58
 
10
59
 
11
60
  class ArrayTriangles(AbstractTriangles):
@@ -14,6 +63,7 @@ class ArrayTriangles(AbstractTriangles):
14
63
  indices,
15
64
  vertices,
16
65
  max_containing_size=MAX_CONTAINING_SIZE,
66
+ step0_layout: Optional[Step0Layout] = None,
17
67
  **kwargs,
18
68
  ):
19
69
  """
@@ -26,10 +76,16 @@ class ArrayTriangles(AbstractTriangles):
26
76
  with the three indices of the vertices.
27
77
  vertices
28
78
  The vertices of the triangles.
79
+ step0_layout
80
+ Set only by `CoordinateArrayTriangles.with_vertices` on the static initial lattice:
81
+ the static layout of ``indices`` into ``vertices``, which lets `containing_indices`
82
+ test a `Point` without materialising the ``(N, 3, 2)`` triangle array (see
83
+ `_STEP0_CONTAINMENT`). A plain Python constant (pytree aux data), never traced.
29
84
  """
30
85
  self._indices = indices
31
86
  self._vertices = vertices
32
87
  self.max_containing_size = max_containing_size
88
+ self.step0_layout = step0_layout
33
89
 
34
90
  def __len__(self):
35
91
  return len(self.triangles)
@@ -159,7 +215,13 @@ class ArrayTriangles(AbstractTriangles):
159
215
  """
160
216
  import jax.numpy as jnp
161
217
 
162
- inside = shape.mask(self.triangles)
218
+ inside = None
219
+ # Exactly `Point`: `Circle`, `Triangle`, `Polygon` and `Square` subclass `Point` but
220
+ # override `mask`, so they must keep the general path.
221
+ if self.step0_layout is not None and type(shape) is Point:
222
+ inside = self._step0_point_mask(shape)
223
+ if inside is None:
224
+ inside = shape.mask(self.triangles)
163
225
 
164
226
  return jnp.where(
165
227
  inside,
@@ -167,6 +229,70 @@ class ArrayTriangles(AbstractTriangles):
167
229
  fill_value=-1,
168
230
  )[0]
169
231
 
232
+ def _step0_point_mask(self, point: "Point"):
233
+ """
234
+ `Point.mask` of the static initial lattice without the general ``(N, 3, 2)`` gather, by
235
+ the route `_STEP0_CONTAINMENT` names; ``None`` selects the general path.
236
+
237
+ Every route feeds `_barycentric_contains` the same six component values the general path
238
+ takes from ``self.triangles`` (at step 0 no index is -1, so its NaN ``where`` is a no-op),
239
+ in the same operation order, so the mask -- and the kept indices -- are bit-identical.
240
+ """
241
+ import jax.numpy as jnp
242
+
243
+ route = _STEP0_CONTAINMENT
244
+ vertices = self.vertices
245
+ indices = self.indices
246
+
247
+ if route == "nopad":
248
+ return point.mask(vertices[indices])
249
+
250
+ if route == "components":
251
+ v0 = vertices[:, 0]
252
+ v1 = vertices[:, 1]
253
+ i0 = indices[:, 0]
254
+ i1 = indices[:, 1]
255
+ i2 = indices[:, 2]
256
+ return _barycentric_contains(
257
+ v0[i0], v1[i0], v0[i1], v1[i1], v0[i2], v1[i2], point.x, point.y
258
+ )
259
+
260
+ if route == "structured" and self.step0_layout.grid is not None:
261
+ n_rows = self.step0_layout.n_rows
262
+ n_cols = self.step0_layout.n_cols
263
+ n_pairs, pair_width, pad, corners = self.step0_layout.grid
264
+ if pad:
265
+ vertices = jnp.pad(vertices, ((0, pad), (0, 0)))
266
+ grid = vertices.reshape(n_pairs, pair_width, 2)
267
+
268
+ half_rows = (n_rows + 1) // 2
269
+ half_cols = (n_cols + 1) // 2
270
+
271
+ classes = []
272
+ for corner in corners:
273
+ components = []
274
+ for p0, c0, na, nb in corner:
275
+ block = grid[p0 : p0 + na, c0 : c0 + nb]
276
+ components += [block[..., 0], block[..., 1]]
277
+ na, nb = corner[0][2], corner[0][3]
278
+ mask = _barycentric_contains(*components, point.x, point.y)
279
+ classes.append(
280
+ jnp.pad(mask, ((0, half_rows - na), (0, half_cols - nb)))
281
+ )
282
+
283
+ # classes[2 * pi + pj][a, b] is triangle (2a + pi, 2b + pj): interleave to row-major.
284
+ interleaved = jnp.stack(
285
+ (
286
+ jnp.stack((classes[0], classes[1]), axis=-1),
287
+ jnp.stack((classes[2], classes[3]), axis=-1),
288
+ ),
289
+ axis=1,
290
+ ).reshape(2 * half_rows, 2 * half_cols)
291
+
292
+ return interleaved[:n_rows, :n_cols].reshape(-1)
293
+
294
+ return None
295
+
170
296
  def for_indexes(self, indexes: np.ndarray) -> "ArrayTriangles":
171
297
  """
172
298
  Create a new ArrayTriangles containing indices and vertices corresponding to the given indexes
@@ -311,6 +437,7 @@ class ArrayTriangles(AbstractTriangles):
311
437
  indices=self.indices,
312
438
  vertices=vertices,
313
439
  max_containing_size=self.max_containing_size,
440
+ step0_layout=self.step0_layout,
314
441
  )
315
442
 
316
443
  def tree_flatten(self):
@@ -320,7 +447,7 @@ class ArrayTriangles(AbstractTriangles):
320
447
  return (
321
448
  self.indices,
322
449
  self.vertices,
323
- ), (self.max_containing_size,)
450
+ ), (self.max_containing_size, self.step0_layout)
324
451
 
325
452
  @classmethod
326
453
  def tree_unflatten(cls, aux_data, children):
@@ -331,6 +458,7 @@ class ArrayTriangles(AbstractTriangles):
331
458
  indices=children[0],
332
459
  vertices=children[1],
333
460
  max_containing_size=aux_data[0],
461
+ step0_layout=aux_data[1],
334
462
  )
335
463
 
336
464
 
@@ -7,6 +7,7 @@ import numpy as np
7
7
  from autoarray.structures.triangles.abstract import HEIGHT_FACTOR
8
8
  from autoarray.structures.triangles.abstract import AbstractTriangles
9
9
  from autoarray.structures.triangles.array import ArrayTriangles
10
+ from autoarray.structures.triangles.array import Step0Layout
10
11
 
11
12
 
12
13
  def _lattice_coordinates(
@@ -83,8 +84,27 @@ def static_vertex_table(
83
84
  axis=1,
84
85
  )
85
86
 
87
+ keys = _vertex_keys(coordinates)
88
+
89
+ _, first, inverse = np.unique(keys, axis=0, return_index=True, return_inverse=True)
90
+
91
+ vertices = np.ascontiguousarray(triangles.reshape(-1, 2)[first], dtype=np.float64)
92
+ indices = np.ascontiguousarray(inverse.reshape(-1, 3))
93
+
94
+ vertices.setflags(write=False)
95
+ indices.setflags(write=False)
96
+
97
+ return vertices, indices
98
+
99
+
100
+ def _vertex_keys(coordinates: np.ndarray) -> np.ndarray:
101
+ """
102
+ The exact integer lattice key of every ``(3N)`` vertex slot, ordered as
103
+ `static_vertex_table` orders them (see its docstring).
104
+ """
105
+ flip = np.where((coordinates[:, 0] + coordinates[:, 1]) % 2 != 0, -1, 1)[:, None]
86
106
  offsets = np.array([[0, 1], [1, -1], [-1, -1]])
87
- keys = np.stack(
107
+ return np.stack(
88
108
  (
89
109
  coordinates[:, None, 0] + flip * offsets[None, :, 0],
90
110
  2 * coordinates[:, None, 1] + flip * offsets[None, :, 1],
@@ -92,15 +112,90 @@ def static_vertex_table(
92
112
  axis=-1,
93
113
  ).reshape(-1, 2)
94
114
 
95
- _, first, inverse = np.unique(keys, axis=0, return_index=True, return_inverse=True)
96
115
 
97
- vertices = np.ascontiguousarray(triangles.reshape(-1, 2)[first], dtype=np.float64)
98
- indices = np.ascontiguousarray(inverse.reshape(-1, 3))
116
+ @lru_cache(maxsize=32)
117
+ def static_lattice_layout(
118
+ y_min: float, y_max: float, x_min: float, x_max: float, scale: float
119
+ ) -> Step0Layout:
120
+ """
121
+ The closed-form layout of `static_vertex_table`'s index map, so the step-0 containment can
122
+ read each triangle's vertices by strided slicing instead of a gather (PyAutoArray#579).
123
+
124
+ The lattice's triangles are row-major, ``n_rows x n_cols``. The vertex table is sorted on the
125
+ integer key ``(ky, kx)``; key rows alternate between two widths ``W0, W1`` (equal when the
126
+ lattice has an odd number of columns), so the ``(V, 2)`` table -- padded by one row when the
127
+ number of key rows is odd -- is a ``(n_pairs, W0 + W1, 2)`` grid of row pairs. Splitting the
128
+ triangles into the four parity classes of their (row, column) index fixes each class's flip
129
+ and the parity of every vertex's key row, so vertex ``k`` of the class's triangle ``(a, b)``
130
+ is grid entry ``(p0 + a, c0 + b)``: one contiguous 2-D slice per class and vertex.
131
+
132
+ The layout is derived from, and checked element-wise against, the actual index map in NumPy:
133
+ if any class did not follow the strided pattern, ``grid`` is ``None`` and the structured
134
+ route is not used for this geometry. Built once per geometry (cached) and made only of Python
135
+ ints, so it is hashable pytree aux data.
136
+ """
137
+ vertices, indices = static_vertex_table(y_min, y_max, x_min, x_max, scale)
138
+ coordinates = _lattice_coordinates(y_min, y_max, x_min, x_max, scale)
99
139
 
100
- vertices.setflags(write=False)
101
- indices.setflags(write=False)
140
+ ys = np.unique(coordinates[:, 0])
141
+ xs = np.unique(coordinates[:, 1])
142
+ n_rows, n_cols = ys.shape[0], xs.shape[0]
102
143
 
103
- return vertices, indices
144
+ grid_coordinates = np.stack(np.meshgrid(ys, xs, indexing="ij"), axis=-1)
145
+ if coordinates.shape[0] != n_rows * n_cols or not np.array_equal(
146
+ coordinates, grid_coordinates.reshape(-1, 2)
147
+ ):
148
+ return Step0Layout(n_rows=0, n_cols=0, grid=None)
149
+
150
+ unique_keys = np.unique(_vertex_keys(coordinates), axis=0)
151
+ _, row_counts = np.unique(unique_keys[:, 0], return_counts=True)
152
+ R = row_counts.shape[0]
153
+ W0 = int(row_counts[0])
154
+ W1 = int(row_counts[1]) if R > 1 else W0
155
+ if (
156
+ unique_keys.shape[0] != vertices.shape[0]
157
+ or np.any(row_counts[0::2] != W0)
158
+ or np.any(row_counts[1::2] != W1)
159
+ ):
160
+ return Step0Layout(n_rows=n_rows, n_cols=n_cols, grid=None)
161
+
162
+ # Key rows come in pairs of widths (W0, W1). Viewing the table as (n_pairs, W0 + W1, 2) --
163
+ # padded by one row when R is odd -- puts vertex (row r, column c) at
164
+ # (r // 2, (r % 2) * W0 + c).
165
+ pair_width = W0 + W1
166
+ n_pairs = (R + 1) // 2
167
+ row_start = np.concatenate(([0], np.cumsum(row_counts)[:-1]))
168
+ vertex_key_row = np.repeat(np.arange(R), row_counts)
169
+ vertex_key_col = np.arange(vertices.shape[0]) - row_start[vertex_key_row]
170
+ vertex_pair = (vertex_key_row // 2)[indices].reshape(n_rows, n_cols, 3)
171
+ vertex_offset = ((vertex_key_row % 2) * W0 + vertex_key_col)[indices].reshape(
172
+ n_rows, n_cols, 3
173
+ )
174
+
175
+ corners = []
176
+ for pi in (0, 1):
177
+ for pj in (0, 1):
178
+ corner = []
179
+ for k in range(3):
180
+ class_pair = vertex_pair[pi::2, pj::2, k]
181
+ class_offset = vertex_offset[pi::2, pj::2, k]
182
+ na, nb = class_pair.shape
183
+ p0, c0 = int(class_pair[0, 0]), int(class_offset[0, 0])
184
+ expected_pair = p0 + np.arange(na)[:, None] + 0 * class_pair
185
+ expected_offset = c0 + np.arange(nb)[None, :] + 0 * class_offset
186
+ if not (
187
+ np.array_equal(class_pair, expected_pair)
188
+ and np.array_equal(class_offset, expected_offset)
189
+ ):
190
+ return Step0Layout(n_rows=n_rows, n_cols=n_cols, grid=None)
191
+ corner.append((p0, c0, int(na), int(nb)))
192
+ corners.append(tuple(corner))
193
+
194
+ return Step0Layout(
195
+ n_rows=n_rows,
196
+ n_cols=n_cols,
197
+ grid=(n_pairs, pair_width, int(n_pairs * pair_width - vertices.shape[0]), tuple(corners)),
198
+ )
104
199
 
105
200
 
106
201
  class CoordinateArrayTriangles(AbstractTriangles, ABC):
@@ -113,6 +208,7 @@ class CoordinateArrayTriangles(AbstractTriangles, ABC):
113
208
  y_offset: float = 0.0,
114
209
  flipped: bool = False,
115
210
  vertex_table: Optional[Tuple[np.ndarray, np.ndarray]] = None,
211
+ step0_layout: Optional[Step0Layout] = None,
116
212
  ):
117
213
  """
118
214
  Represents a set of triangles by integer coordinates.
@@ -134,11 +230,17 @@ class CoordinateArrayTriangles(AbstractTriangles, ABC):
134
230
  sets it. Derived lattices (`for_indexes`, `up_sample`, `neighborhood`) do not inherit
135
231
  it, and it is not part of the pytree (`tree_flatten`), so an unflattened copy falls
136
232
  back to the flat table -- which is still correct, only unshared.
233
+ step0_layout
234
+ The `static_lattice_layout` of ``vertex_table``, set with it by
235
+ `for_limits_and_scale(..., static_vertices=True)` and passed by `with_vertices` to the
236
+ `ArrayTriangles` it returns, whose `containing_indices` then avoids the ``(N, 3, 2)``
237
+ gather. Dropped exactly where ``vertex_table`` is.
137
238
  """
138
239
  import jax.numpy as jnp
139
240
 
140
241
  self.coordinates = coordinates
141
242
  self.vertex_table = vertex_table
243
+ self.step0_layout = step0_layout if vertex_table is not None else None
142
244
  self.side_length = side_length
143
245
  self.flipped = flipped
144
246
 
@@ -191,10 +293,17 @@ class CoordinateArrayTriangles(AbstractTriangles, ABC):
191
293
  import jax.numpy as jnp
192
294
 
193
295
  vertex_table = None
296
+ step0_layout = None
194
297
  if static_vertices:
195
- vertex_table = static_vertex_table(
196
- float(y_min), float(y_max), float(x_min), float(x_max), float(scale)
298
+ geometry = (
299
+ float(y_min),
300
+ float(y_max),
301
+ float(x_min),
302
+ float(x_max),
303
+ float(scale),
197
304
  )
305
+ vertex_table = static_vertex_table(*geometry)
306
+ step0_layout = static_lattice_layout(*geometry)
198
307
 
199
308
  return cls(
200
309
  coordinates=jnp.array(
@@ -202,6 +311,7 @@ class CoordinateArrayTriangles(AbstractTriangles, ABC):
202
311
  ),
203
312
  side_length=scale,
204
313
  vertex_table=vertex_table,
314
+ step0_layout=step0_layout,
205
315
  )
206
316
 
207
317
  def tree_flatten(self):
@@ -437,6 +547,7 @@ class CoordinateArrayTriangles(AbstractTriangles, ABC):
437
547
  return ArrayTriangles(
438
548
  indices=self.indices,
439
549
  vertices=vertices,
550
+ step0_layout=self.step0_layout,
440
551
  )
441
552
 
442
553
  def for_indexes(self, indexes: np.ndarray) -> "CoordinateArrayTriangles":