@@ -61,6 +61,36 @@ def _validate_convolve_over_sample_size(
6161 )
6262
6363
64+ _SPARSE_OPERATOR_CONVOLVE_OVER_SAMPLE_SIZE_ERROR = (
65+ "The sparse linear algebra formalism precomputes PSF products at "
66+ "image resolution and is incompatible with an oversampled PSF "
67+ "(convolve_over_sample_size > 1)."
68+ )
69+
70+
71+ def _warn_sparse_operator_discarded (sparse_operator , method_name : str ) -> None :
72+ """
73+ Log a warning when a dataset rebuild discards an attached sparse operator.
74+
75+ The sparse operator precomputes PSF products of every pair of masked noise-map
76+ values, so it is invalidated by any change to the mask or noise-map. Rebuilds
77+ which make such changes (e.g. `apply_mask`, `apply_noise_scaling`) therefore do
78+ not carry it, and the returned dataset falls back to the (slower) mapping matrix
79+ formalism unless the operator is re-applied. This warning stops that fallback
80+ from happening silently.
81+ """
82+ if sparse_operator is None :
83+ return
84+
85+ logger .warning (
86+ f"IMAGING - { method_name } changes the mask or noise-map, which invalidates the "
87+ f"sparse operator attached to this dataset, so it has been discarded. Pixelized "
88+ f"reconstructions will use the slower mapping matrix formalism unless the sparse "
89+ f"operator is re-applied (via apply_sparse_operator() or "
90+ f"apply_sparse_operator_cpu()) as the last dataset operation."
91+ )
92+
93+
6494class Imaging (AbstractDataset ):
6595 def __init__ (
6696 self ,
@@ -439,6 +469,10 @@ def apply_mask(self, mask: Mask2D) -> "Imaging":
439469 values = self .over_sample_size_pixelization .native , mask = mask
440470 )
441471
472+ _warn_sparse_operator_discarded (
473+ sparse_operator = self .sparse_operator , method_name = "apply_mask"
474+ )
475+
442476 dataset = Imaging (
443477 data = data ,
444478 noise_map = noise_map ,
@@ -520,13 +554,19 @@ def apply_noise_scaling(
520554
521555 noise_map = Array2D (values = noise_map , mask = self .data .mask )
522556
557+ _warn_sparse_operator_discarded (
558+ sparse_operator = self .sparse_operator , method_name = "apply_noise_scaling"
559+ )
560+
523561 dataset = Imaging (
524562 data = data ,
525563 noise_map = noise_map ,
526564 psf = self .psf ,
527565 noise_covariance_matrix = self .noise_covariance_matrix ,
528566 over_sample_size_lp = self .over_sample_size_lp ,
529567 over_sample_size_pixelization = self .over_sample_size_pixelization ,
568+ convolve_over_sample_size_lp = self .convolve_over_sample_size_lp ,
569+ convolve_over_sample_size_pixelization = self .convolve_over_sample_size_pixelization ,
530570 check_noise_map = False ,
531571 )
532572
@@ -551,6 +591,10 @@ def apply_over_sampling(
551591 This function resets the cached properties so that the new over sampling is used in the grid and grid
552592 pixelization.
553593
594+ The `noise_covariance_matrix` and any attached `sparse_operator` are carried over, as neither depends on
595+ the over sampling (the sparse operator depends only on the noise-map, PSF kernel and mask). This means
596+ `apply_sparse_operator()` / `apply_sparse_operator_cpu()` may be called before or after this method.
597+
554598 Parameters
555599 ----------
556600 over_sample_size_lp
@@ -566,12 +610,14 @@ def apply_over_sampling(
566610 data = self .data ,
567611 noise_map = self .noise_map ,
568612 psf = self .psf ,
613+ noise_covariance_matrix = self .noise_covariance_matrix ,
569614 over_sample_size_lp = over_sample_size_lp or self .over_sample_size_lp ,
570615 over_sample_size_pixelization = over_sample_size_pixelization
571616 or self .over_sample_size_pixelization ,
572617 convolve_over_sample_size_lp = self .convolve_over_sample_size_lp ,
573618 convolve_over_sample_size_pixelization = self .convolve_over_sample_size_pixelization ,
574619 check_noise_map = False ,
620+ sparse_operator = self .sparse_operator ,
575621 )
576622
577623 return dataset
@@ -620,9 +666,7 @@ def apply_sparse_operator(
620666
621667 if self .psf is not None and self .psf .convolve_over_sample_size > 1 :
622668 raise exc .DatasetException (
623- "The sparse linear algebra formalism precomputes PSF products at "
624- "image resolution and is incompatible with an oversampled PSF "
625- "(convolve_over_sample_size > 1)."
669+ _SPARSE_OPERATOR_CONVOLVE_OVER_SAMPLE_SIZE_ERROR
626670 )
627671
628672 logger .info (
@@ -645,6 +689,8 @@ def apply_sparse_operator(
645689 noise_covariance_matrix = self .noise_covariance_matrix ,
646690 over_sample_size_lp = self .over_sample_size_lp ,
647691 over_sample_size_pixelization = self .over_sample_size_pixelization ,
692+ convolve_over_sample_size_lp = self .convolve_over_sample_size_lp ,
693+ convolve_over_sample_size_pixelization = self .convolve_over_sample_size_pixelization ,
648694 check_noise_map = False ,
649695 sparse_operator = sparse_operator ,
650696 )
@@ -668,6 +714,11 @@ def apply_sparse_operator_cpu(
668714 A new `Imaging` dataset with a precomputed Numba-based sparse operator attached,
669715 enabling efficient pixelized source reconstruction on CPU hardware.
670716 """
717+ if self .psf is not None and self .psf .convolve_over_sample_size > 1 :
718+ raise exc .DatasetException (
719+ _SPARSE_OPERATOR_CONVOLVE_OVER_SAMPLE_SIZE_ERROR
720+ )
721+
671722 try :
672723 import numba
673724 except ModuleNotFoundError :
@@ -711,6 +762,8 @@ def apply_sparse_operator_cpu(
711762 noise_covariance_matrix = self .noise_covariance_matrix ,
712763 over_sample_size_lp = self .over_sample_size_lp ,
713764 over_sample_size_pixelization = self .over_sample_size_pixelization ,
765+ convolve_over_sample_size_lp = self .convolve_over_sample_size_lp ,
766+ convolve_over_sample_size_pixelization = self .convolve_over_sample_size_pixelization ,
714767 check_noise_map = False ,
715768 sparse_operator = sparse_operator ,
716769 )
0 commit comments