Skip to content

Commit 7b46e28

Browse files
committed
Interferometer Inversion implemented and added to tracer / fit modules
1 parent 3b9d2b7 commit 7b46e28

4 files changed

Lines changed: 445 additions & 79 deletions

File tree

autoarray/operators/inversion/inversions.py

Lines changed: 215 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,110 @@
11
import numpy as np
22

33
from autoarray import exc
4+
from autoarray.structures import visibilities as vis
5+
from autoarray.masked import masked_dataset as md
46
from autoarray.util import inversion_util
57

68

79
def inversion(masked_dataset, mapper, regularization):
810

9-
return InversionImaging.from_data_mapper_and_regularization(
10-
image=masked_dataset.image,
11-
noise_map=masked_dataset.noise_map,
12-
convolver=masked_dataset.convolver,
13-
mapper=mapper,
14-
regularization=regularization,
15-
)
11+
if isinstance(masked_dataset, md.MaskedImaging):
1612

13+
return InversionImaging.from_data_mapper_and_regularization(
14+
image=masked_dataset.image,
15+
noise_map=masked_dataset.noise_map,
16+
convolver=masked_dataset.convolver,
17+
mapper=mapper,
18+
regularization=regularization,
19+
)
20+
21+
elif isinstance(masked_dataset, md.MaskedInterferometer):
22+
23+
return InversionInterferometer.from_data_mapper_and_regularization(
24+
visibilities=masked_dataset.visibilities,
25+
noise_map=masked_dataset.noise_map,
26+
transformer=masked_dataset.transformer,
27+
mapper=mapper,
28+
regularization=regularization,
29+
)
30+
31+
32+
class Inversion(object):
33+
34+
def __init__(
35+
self,
36+
noise_map,
37+
mapper,
38+
regularization,
39+
regularization_matrix,
40+
curvature_reg_matrix,
41+
reconstruction,
42+
):
43+
44+
self.noise_map = noise_map
45+
self.mapper = mapper
46+
self.regularization = regularization
47+
self.regularization_matrix = regularization_matrix
48+
self.curvature_reg_matrix = curvature_reg_matrix
49+
self.reconstruction = reconstruction
50+
51+
@property
52+
def errors_with_covariance(self):
53+
return np.linalg.inv(self.curvature_reg_matrix)
54+
55+
@property
56+
def errors(self):
57+
return np.diagonal(self.errors_with_covariance)
58+
59+
@property
60+
def regularization_term(self):
61+
""" Compute the regularization term of an inversion. This term represents the sum of the difference in flux \
62+
between every pair of neighboring pixels. This is computed as:
63+
64+
s_T * H * s = solution_vector.T * regularization_matrix * solution_vector
65+
66+
The term is referred to as *G_l* in Warren & Dye 2003, Nightingale & Dye 2015.
67+
68+
The above works include the regularization_matrix coefficient (lambda) in this calculation. In PyAutoLens, \
69+
this is already in the regularization matrix and thus implicitly included in the matrix multiplication.
70+
"""
71+
return np.matmul(
72+
self.reconstruction.T,
73+
np.matmul(self.regularization_matrix, self.reconstruction),
74+
)
1775

18-
class InversionImaging(object):
76+
@property
77+
def log_det_curvature_reg_matrix_term(self):
78+
return self.log_determinant_of_matrix_cholesky(self.curvature_reg_matrix)
79+
80+
@property
81+
def log_det_regularization_matrix_term(self):
82+
return self.log_determinant_of_matrix_cholesky(self.regularization_matrix)
83+
84+
@staticmethod
85+
def log_determinant_of_matrix_cholesky(matrix):
86+
"""There are two terms in the inversion's Bayesian likelihood function which require the log determinant of \
87+
a matrix. These are (Nightingale & Dye 2015, Nightingale, Dye and Massey 2018):
88+
89+
ln[det(F + H)] = ln[det(curvature_reg_matrix)]
90+
ln[det(H)] = ln[det(regularization_matrix)]
91+
92+
The curvature_reg_matrix is positive-definite, which means the above log determinants can be computed \
93+
efficiently (compared to using np.det) by using a Cholesky decomposition first and summing the log of each \
94+
diagonal term.
95+
96+
Parameters
97+
-----------
98+
matrix : ndarray
99+
The positive-definite matrix the log determinant is computed for.
100+
"""
101+
try:
102+
return 2.0 * np.sum(np.log(np.diag(np.linalg.cholesky(matrix))))
103+
except np.linalg.LinAlgError:
104+
raise exc.InversionException()
105+
106+
107+
class InversionImaging(Inversion):
19108
def __init__(
20109
self,
21110
image,
@@ -63,14 +152,12 @@ def __init__(
63152
The vector containing the reconstructed fit to the hyper_galaxies.
64153
"""
65154

155+
super(InversionImaging, self).__init__(noise_map=noise_map, mapper=mapper, regularization=regularization,
156+
regularization_matrix=regularization_matrix, curvature_reg_matrix=curvature_reg_matrix,
157+
reconstruction=reconstruction)
158+
66159
self.image = image
67-
self.noise_map = noise_map
68-
self.mapper = mapper
69-
self.regularization = regularization
70160
self.blurred_mapping_matrix = blurred_mapping_matrix
71-
self.regularization_matrix = regularization_matrix
72-
self.curvature_reg_matrix = curvature_reg_matrix
73-
self.reconstruction = reconstruction
74161

75162
@classmethod
76163
def from_data_mapper_and_regularization(
@@ -115,22 +202,14 @@ def from_data_mapper_and_regularization(
115202

116203
@property
117204
def mapped_reconstructed_image(self):
118-
reconstructed_image = inversion_util.mapper_reconstructed_image_from_blurred_mapping_matrix_and_reconstruction(
119-
blurred_mapping_matrix=self.blurred_mapping_matrix,
205+
reconstructed_image = inversion_util.mapped_reconstructed_data_from_mapping_matrix_and_reconstruction(
206+
mapping_matrix=self.blurred_mapping_matrix,
120207
reconstruction=self.reconstruction,
121208
)
122209
return self.mapper.grid.mapping.array_from_array_1d(
123210
array_1d=reconstructed_image
124211
)
125212

126-
@property
127-
def errors_with_covariance(self):
128-
return np.linalg.inv(self.curvature_reg_matrix)
129-
130-
@property
131-
def errors(self):
132-
return np.diagonal(self.errors_with_covariance)
133-
134213
@property
135214
def residual_map(self):
136215
return inversion_util.inversion_residual_map_from_pixelization_values_and_reconstructed_data_1d(
@@ -160,49 +239,129 @@ def chi_squared_map(self):
160239
all_sub_mask_1d_indexes_for_pixelization_1d_index=self.mapper.all_sub_mask_1d_indexes_for_pixelization_1d_index,
161240
)
162241

163-
@property
164-
def regularization_term(self):
165-
""" Compute the regularization term of an inversion. This term represents the sum of the difference in flux \
166-
between every pair of neighboring pixels. This is computed as:
167242

168-
s_T * H * s = solution_vector.T * regularization_matrix * solution_vector
243+
class InversionInterferometer(Inversion):
244+
def __init__(
245+
self,
246+
visibilities,
247+
noise_map,
248+
mapper,
249+
regularization,
250+
transformed_mapping_matrices,
251+
regularization_matrix,
252+
curvature_reg_matrix,
253+
reconstruction,
254+
):
255+
""" An inversion, which given an input image and noise-map reconstructs the image using a linear inversion, \
256+
including a convolution that accounts for blurring.
169257
170-
The term is referred to as *G_l* in Warren & Dye 2003, Nightingale & Dye 2015.
258+
The inversion uses a 2D pixelization to perform the reconstruction by util each pixelization pixel to a \
259+
set of image pixels via a mapper. The reconstructed pixelization is smoothed via a regularization scheme to \
260+
prevent over-fitting noise.
171261
172-
The above works include the regularization_matrix coefficient (lambda) in this calculation. In PyAutoLens, \
173-
this is already in the regularization matrix and thus implicitly included in the matrix multiplication.
262+
Parameters
263+
-----------
264+
image_1d : ndarray
265+
Flattened 1D array of the observed image the inversion is fitting.
266+
noise_map : ndarray
267+
Flattened 1D array of the noise-map used by the inversion during the fit.
268+
convolver : imaging.convolution.Convolver
269+
The convolver used to blur the util matrix with the PSF.
270+
mapper : inversion.mappers.Mapper
271+
The util between the image-pixels (via its / sub-grid) and pixelization pixels.
272+
regularization : inversion.regularization.Regularization
273+
The regularization scheme applied to smooth the pixelization used to reconstruct the image for the \
274+
inversion
275+
276+
Attributes
277+
-----------
278+
blurred_mapping_matrix : ndarray
279+
The matrix representing the blurred mappings between the image's sub-grid of pixels and the pixelization \
280+
pixels.
281+
regularization_matrix : ndarray
282+
The matrix defining how the pixelization's pixels are regularized with one another for smoothing (H).
283+
curvature_matrix : ndarray
284+
The curvature_matrix between each pixelization pixel and all other pixelization pixels (F).
285+
curvature_reg_matrix : ndarray
286+
The curvature_matrix + regularization matrix.
287+
solution_vector : ndarray
288+
The vector containing the reconstructed fit to the hyper_galaxies.
174289
"""
175-
return np.matmul(
176-
self.reconstruction.T,
177-
np.matmul(self.regularization_matrix, self.reconstruction),
290+
291+
super(InversionInterferometer, self).__init__(noise_map=noise_map, mapper=mapper, regularization=regularization,
292+
regularization_matrix=regularization_matrix, curvature_reg_matrix=curvature_reg_matrix,
293+
reconstruction=reconstruction)
294+
295+
self.visibilities = visibilities
296+
self.transformed_mapping_matrices = transformed_mapping_matrices
297+
298+
@classmethod
299+
def from_data_mapper_and_regularization(
300+
cls, visibilities, noise_map, transformer, mapper, regularization
301+
):
302+
303+
transformed_mapping_matrices = transformer.transformed_mapping_matrices_from_mapping_matrix(
304+
mapping_matrix=mapper.mapping_matrix
178305
)
179306

180-
@property
181-
def log_det_curvature_reg_matrix_term(self):
182-
return self.log_determinant_of_matrix_cholesky(self.curvature_reg_matrix)
307+
real_data_vector = inversion_util.data_vector_from_transformed_mapping_matrix_and_data(
308+
transformed_mapping_matrix=transformed_mapping_matrices[0],
309+
visibilities=visibilities[:,0],
310+
noise_map=noise_map[:,0],
311+
)
183312

184-
@property
185-
def log_det_regularization_matrix_term(self):
186-
return self.log_determinant_of_matrix_cholesky(self.regularization_matrix)
313+
imag_data_vector = inversion_util.data_vector_from_transformed_mapping_matrix_and_data(
314+
transformed_mapping_matrix=transformed_mapping_matrices[1],
315+
visibilities=visibilities[:,1],
316+
noise_map=noise_map[:,1],
317+
)
187318

188-
@staticmethod
189-
def log_determinant_of_matrix_cholesky(matrix):
190-
"""There are two terms in the inversion's Bayesian likelihood function which require the log determinant of \
191-
a matrix. These are (Nightingale & Dye 2015, Nightingale, Dye and Massey 2018):
319+
real_curvature_matrix = inversion_util.curvature_matrix_from_transformed_mapping_matrix(
320+
transformed_mapping_matrix=transformed_mapping_matrices[0],
321+
noise_map=noise_map[:,0],
322+
)
192323

193-
ln[det(F + H)] = ln[det(curvature_reg_matrix)]
194-
ln[det(H)] = ln[det(regularization_matrix)]
324+
imag_curvature_matrix = inversion_util.curvature_matrix_from_transformed_mapping_matrix(
325+
transformed_mapping_matrix=transformed_mapping_matrices[1],
326+
noise_map=noise_map[:,1],
327+
)
195328

196-
The curvature_reg_matrix is positive-definite, which means the above log determinants can be computed \
197-
efficiently (compared to using np.det) by using a Cholesky decomposition first and summing the log of each \
198-
diagonal term.
329+
regularization_matrix = regularization.regularization_matrix_from_mapper(
330+
mapper=mapper
331+
)
332+
333+
real_curvature_reg_matrix = np.add(real_curvature_matrix, regularization_matrix)
334+
imag_curvature_reg_matrix = np.add(imag_curvature_matrix, regularization_matrix)
335+
336+
data_vector = np.add(real_data_vector, imag_data_vector)
337+
curvature_reg_matrix = np.add(real_curvature_reg_matrix, imag_curvature_reg_matrix)
199338

200-
Parameters
201-
-----------
202-
matrix : ndarray
203-
The positive-definite matrix the log determinant is computed for.
204-
"""
205339
try:
206-
return 2.0 * np.sum(np.log(np.diag(np.linalg.cholesky(matrix))))
340+
values = np.linalg.solve(curvature_reg_matrix, data_vector)
207341
except np.linalg.LinAlgError:
208342
raise exc.InversionException()
343+
344+
return InversionInterferometer(
345+
visibilities=visibilities,
346+
noise_map=noise_map,
347+
mapper=mapper,
348+
regularization=regularization,
349+
transformed_mapping_matrices=transformed_mapping_matrices,
350+
regularization_matrix=regularization_matrix,
351+
curvature_reg_matrix=curvature_reg_matrix,
352+
reconstruction=values,
353+
)
354+
355+
@property
356+
def mapped_reconstructed_visibilities(self):
357+
real_visibilities = inversion_util.mapped_reconstructed_data_from_mapping_matrix_and_reconstruction(
358+
mapping_matrix=self.transformed_mapping_matrices[0],
359+
reconstruction=self.reconstruction,
360+
)
361+
362+
imag_visibilities = inversion_util.mapped_reconstructed_data_from_mapping_matrix_and_reconstruction(
363+
mapping_matrix=self.transformed_mapping_matrices[1],
364+
reconstruction=self.reconstruction,
365+
)
366+
367+
return vis.Visibilities(visibilities_1d=np.stack((real_visibilities, imag_visibilities), axis=-1))

autoarray/util/inversion_util.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -101,25 +101,25 @@ def curvature_matrix_from_blurred_mapping_matrix_jit(
101101

102102

103103
@decorator_util.jit()
104-
def mapper_reconstructed_image_from_blurred_mapping_matrix_and_reconstruction(
105-
blurred_mapping_matrix, reconstruction
104+
def mapped_reconstructed_data_from_mapping_matrix_and_reconstruction(
105+
mapping_matrix, reconstruction
106106
):
107107
""" Compute the reconstructed hyper_galaxies vector from the blurrred util matrix *f* and solution vector *S*.
108108
109109
Parameters
110110
-----------
111-
blurred_mapping_matrix : ndarray
111+
mapping_matrix : ndarray
112112
The matrix representing the blurred mappings between sub-grid pixels and pixelization pixels.
113113
114114
"""
115-
reconstructed_data_vector = np.zeros(blurred_mapping_matrix.shape[0])
116-
for i in range(blurred_mapping_matrix.shape[0]):
115+
mapped_reconstructred_data = np.zeros(mapping_matrix.shape[0])
116+
for i in range(mapping_matrix.shape[0]):
117117
for j in range(reconstruction.shape[0]):
118-
reconstructed_data_vector[i] += (
119-
reconstruction[j] * blurred_mapping_matrix[i, j]
118+
mapped_reconstructred_data[i] += (
119+
reconstruction[j] * mapping_matrix[i, j]
120120
)
121121

122-
return reconstructed_data_vector
122+
return mapped_reconstructred_data
123123

124124

125125
@decorator_util.jit()
@@ -142,12 +142,10 @@ def data_vector_from_transformed_mapping_matrix_and_data(
142142
data_vector = np.zeros(transformed_mapping_matrix.shape[1])
143143

144144
for vis_1d_index in range(transformed_mapping_matrix.shape[0]):
145-
for pix_1_index in range(transformed_mapping_matrix.shape[1]):
146-
data_vector[pix_1_index] += (
147-
visibilities[vis_1d_index]
148-
* transformed_mapping_matrix[vis_1d_index, pix_1_index]
145+
for pix_1d_index in range(transformed_mapping_matrix.shape[1]):
146+
data_vector[pix_1d_index] += visibilities[vis_1d_index] \
147+
* transformed_mapping_matrix[vis_1d_index, pix_1d_index] \
149148
/ (noise_map[vis_1d_index] ** 2.0)
150-
)
151149

152150
return data_vector
153151

0 commit comments

Comments
 (0)