diff --git a/autolens/imaging/simulator.py b/autolens/imaging/simulator.py index b63990250..3ed891f6d 100644 --- a/autolens/imaging/simulator.py +++ b/autolens/imaging/simulator.py @@ -59,6 +59,27 @@ def via_tracer_from(self, tracer : Tracer, grid : aa.type.Grid2DLike, xp=None) - background_sky_level=self.background_sky_level, ) + if self.psf.convolve_over_sample_size > 1: + + image = tracer.convolved_padded_image_2d_from( + grid=grid, psf=self.psf, xp=xp + ) + + over_sample_size = grid.over_sample_size.resized_from( + new_shape=image.shape_native, mask_pad_value=1 + ) + + dataset = self.via_image_from( + image=image, + over_sample_size=over_sample_size, + image_is_convolved=True, + xp=xp, + ) + + return dataset.trimmed_after_convolution_from( + kernel_shape=self.psf.kernel_shape_image_resolution + ) + image = tracer.padded_image_2d_from( grid=grid, psf_shape_2d=self.psf.kernel.shape_native, xp=xp ) diff --git a/test_autolens/imaging/test_simulate_and_fit_imaging.py b/test_autolens/imaging/test_simulate_and_fit_imaging.py index 8883b4214..f8232fad3 100644 --- a/test_autolens/imaging/test_simulate_and_fit_imaging.py +++ b/test_autolens/imaging/test_simulate_and_fit_imaging.py @@ -922,4 +922,64 @@ def test__fit_figure_of_merit__mge_mass_model(masked_imaging_7x7, masked_imaging file_path = Path(__file__).resolve().parent / "data_temp" if file_path.exists(): - shutil.rmtree(file_path) \ No newline at end of file + shutil.rmtree(file_path) + +def test__perfect_fit__chi_squared_0__oversampled_psf(): + # Simulate with an oversampled PSF (convolution at 2x the image resolution) + # and fit the same tracer with the same PSF at s=2: exact round trip. + s = 2 + pixel_scales = 0.2 + + grid = al.Grid2D.uniform( + shape_native=(21, 21), pixel_scales=pixel_scales, over_sample_size=s + ) + + kernel_n = 11 + c = (np.arange(kernel_n) - (kernel_n - 1) / 2.0) * (pixel_scales / s) + yy, xx = np.meshgrid(-c, c, indexing="ij") + kernel = np.exp(-0.5 * (yy**2 + xx**2) / 0.15**2) + psf = al.Convolver( + kernel=al.Array2D.no_mask(values=kernel, pixel_scales=pixel_scales / s), + normalize=True, + convolve_over_sample_size=s, + ) + + lens_galaxy = al.Galaxy( + redshift=0.5, + light=al.lp.Sersic(centre=(0.0, 0.0), intensity=0.5, effective_radius=0.4), + mass=al.mp.Isothermal(centre=(0.0, 0.0), einstein_radius=1.0), + ) + source_galaxy = al.Galaxy( + redshift=1.0, + light=al.lp.Exponential(centre=(0.05, 0.05), intensity=0.3, effective_radius=0.2), + ) + tracer = al.Tracer(galaxies=[lens_galaxy, source_galaxy]) + + simulator = al.SimulatorImaging( + exposure_time=300.0, psf=psf, add_poisson_noise_to_data=False + ) + dataset = simulator.via_tracer_from(tracer=tracer, grid=grid) + + assert dataset.data.shape_native == (21, 21) + + dataset.noise_map = al.Array2D.ones( + shape_native=dataset.data.shape_native, pixel_scales=pixel_scales + ) + + mask = al.Mask2D.circular( + shape_native=dataset.data.shape_native, pixel_scales=pixel_scales, radius=1.8 + ) + + masked = al.Imaging( + data=dataset.data, + noise_map=dataset.noise_map, + psf=psf, + over_sample_size_lp=s, + over_sample_size_pixelization=s, + convolve_over_sample_size_lp=s, + convolve_over_sample_size_pixelization=s, + ).apply_mask(mask=mask) + + fit = al.FitImaging(dataset=masked, tracer=tracer) + + assert fit.chi_squared == pytest.approx(0.0, abs=1.0e-10)