PURIFY
Next-generation radio interferometric imaging
Functions
mpi_algo_factory.cc File Reference
#include <numeric>
#include <random>
#include <utility>
#include <catch2/catch_all.hpp>
#include "purify/types.h"
#include "purify/directories.h"
#include "purify/distribute.h"
#include "purify/logging.h"
#include "purify/mpi_utilities.h"
#include "purify/pfitsio.h"
#include "purify/utilities.h"
#include <sopt/logging.h>
#include <sopt/mpi/communicator.h>
#include <sopt/mpi/utilities.h>
#include <sopt/power_method.h>
#include <sopt/wavelets.h>
#include "purify/algorithm_factory.h"
#include "purify/measurement_operator_factory.h"
#include "purify/wavelet_operator_factory.h"
+ Include dependency graph for mpi_algo_factory.cc:

Go to the source code of this file.

Functions

utilities::vis_params dirty_visibilities (const std::vector< std::string > &names)
 
utilities::vis_params dirty_visibilities (const std::vector< std::string > &names, sopt::mpi::Communicator const &comm)
 
 TEST_CASE ("Serial vs. Serial with MPI PADMM")
 
 TEST_CASE ("Serial vs. Serial with MPI Primal Dual", "[!shouldfail]")
 
 TEST_CASE ("Serial vs. Serial with MPI Forward Backward")
 

Function Documentation

◆ dirty_visibilities() [1/2]

utilities::vis_params dirty_visibilities ( const std::vector< std::string > &  names)

Definition at line 28 of file mpi_algo_factory.cc.

28  {
29  return utilities::read_visibility(names, false);
30 }
utilities::vis_params read_visibility(const std::string &vis_name, const bool w_term)
Reads an HDF5 file with u,v visibilities, constructs a vis_params object and returns it.
Definition: h5reader.h:166

References purify::utilities::read_visibility().

Referenced by dirty_visibilities(), and TEST_CASE().

◆ dirty_visibilities() [2/2]

utilities::vis_params dirty_visibilities ( const std::vector< std::string > &  names,
sopt::mpi::Communicator const &  comm 
)

Definition at line 32 of file mpi_algo_factory.cc.

33  {
34  if (comm.size() == 1) return dirty_visibilities(names);
35  if (comm.is_root()) {
36  auto result = dirty_visibilities(names);
37  auto const order = distribute::distribute_measurements(result, comm, distribute::plan::none);
38  return utilities::regroup_and_scatter(result, order, comm);
39  }
40  auto result = utilities::scatter_visibilities(comm);
41  return result;
42 }
utilities::vis_params dirty_visibilities(const std::vector< std::string > &names)
std::vector< t_int > distribute_measurements(Vector< t_real > const &u, Vector< t_real > const &v, Vector< t_real > const &w, t_int const number_of_nodes, distribute::plan const distribution_plan, t_int const &grid_size)
Distribute visiblities into groups.
Definition: distribute.cc:6
vis_params scatter_visibilities(vis_params const &params, std::vector< t_int > const &sizes, sopt::mpi::Communicator const &comm)
vis_params regroup_and_scatter(vis_params const &params, std::vector< t_int > const &groups, sopt::mpi::Communicator const &comm)

References dirty_visibilities(), purify::distribute::distribute_measurements(), purify::distribute::none, purify::utilities::regroup_and_scatter(), and purify::utilities::scatter_visibilities().

◆ TEST_CASE() [1/3]

TEST_CASE ( "Serial vs. Serial with MPI Forward Backward"  )

Definition at line 319 of file mpi_algo_factory.cc.

319  {
320  auto const world = sopt::mpi::Communicator::World();
321 
322  const std::string &test_dir = "expected/fb/";
323  const std::string &input_data_path = data_filename(test_dir + "input_data.vis");
324  const std::string &result_path = data_filename(test_dir + "mpi_fb_result.fits");
325 
326  auto uv_data = dirty_visibilities({input_data_path}, world);
327  uv_data.units = utilities::vis_units::radians;
328  if (world.is_root()) {
329  CAPTURE(uv_data.vis.head(5));
330  }
331  REQUIRE(world.all_sum_all(uv_data.size()) == 13107);
332 
333  t_uint const imsizey = 128;
334  t_uint const imsizex = 128;
335 
336  auto measurements_transform = factory::measurement_operator_factory<Vector<t_complex>>(
337  factory::distributed_measurement_operator::mpi_distribute_image, uv_data, imsizey, imsizex, 1,
338  1, 2, kernels::kernel_from_string.at("kb"), 4, 4);
339  auto const power_method_stuff = sopt::algorithm::power_method<Vector<t_complex>>(
340  *measurements_transform, 1000, 1e-5,
341  world.broadcast(Vector<t_complex>::Ones(imsizex * imsizey).eval()));
342  const t_real op_norm = std::get<0>(power_method_stuff);
343  measurements_transform->set_norm(op_norm);
344 
345  std::vector<std::tuple<std::string, t_uint>> const sara{
346  std::make_tuple("Dirac", 3u), std::make_tuple("DB1", 3u), std::make_tuple("DB2", 3u),
347  std::make_tuple("DB3", 3u), std::make_tuple("DB4", 3u), std::make_tuple("DB5", 3u),
348  std::make_tuple("DB6", 3u), std::make_tuple("DB7", 3u), std::make_tuple("DB8", 3u)};
349  auto const wavelets = factory::wavelet_operator_factory<Vector<t_complex>>(
350  factory::distributed_wavelet_operator::mpi_sara, sara, imsizey, imsizex);
351  t_real const sigma =
352  world.broadcast(0.016820222945913496) * std::sqrt(2); // see test_parameters file
353  t_real const beta = sigma * sigma;
354  t_real const gamma = 0.0001;
355  auto const fb = factory::fb_factory<sopt::algorithm::ImagingForwardBackward<t_complex>>(
356  factory::algo_distribution::mpi_serial, measurements_transform, wavelets, uv_data, sigma,
357  beta, gamma, imsizey, imsizex, sara.size(), 1000, true, true, false, 1e-2, 1e-3, 50);
358 
359  auto const diagnostic = (*fb)();
360  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
361  if (world.is_root()) {
362  pfitsio::write2d(image.real(), result_path);
363  // pfitsio::write2d(residual_image.real(), expected_residual_path);
364  }
365 
366  const std::string &expected_solution_path = data_filename(test_dir + "solution.fits");
367  const std::string &expected_residual_path = data_filename(test_dir + "residual.fits");
368 
369  const auto solution = pfitsio::read2d(expected_solution_path);
370  const auto residual = pfitsio::read2d(expected_residual_path);
371 
372  double average_intensity = diagnostic.x.real().sum() / diagnostic.x.size();
373  SOPT_HIGH_LOG("Average intensity = {}", average_intensity);
374  double mse = (Vector<t_complex>::Map(solution.data(), solution.size()) - diagnostic.x)
375  .real()
376  .squaredNorm() /
377  solution.size();
378  SOPT_HIGH_LOG("MSE = {}", mse);
379  CHECK(mse <= average_intensity * 1e-3);
380 }
#define CHECK(CONDITION, ERROR)
Definition: casa.cc:6
const std::string test_dir
Definition: operators.cc:16
const std::map< std::string, kernel > kernel_from_string
Definition: kernels.h:16
void write2d(const Image< t_real > &eigen_image, const pfitsio::header_params &header, const bool &overwrite)
Write image to fits file.
Definition: pfitsio.cc:30
Image< t_complex > read2d(const std::string &fits_name)
Read image from fits file.
Definition: pfitsio.cc:109
std::string data_filename(std::string const &filename)
Holds data and such.

References CHECK, purify::data_filename(), dirty_visibilities(), purify::kernels::kernel_from_string, purify::factory::mpi_distribute_image, purify::factory::mpi_sara, purify::factory::mpi_serial, purify::utilities::radians, purify::pfitsio::read2d(), operators_test::test_dir, purify::utilities::vis_params::units, and purify::pfitsio::write2d().

◆ TEST_CASE() [2/3]

TEST_CASE ( "Serial vs. Serial with MPI PADMM"  )

Definition at line 44 of file mpi_algo_factory.cc.

44  {
45  auto const world = sopt::mpi::Communicator::World();
46 
47  const std::string &test_dir = "expected/padmm/";
48  const std::string &input_data_path = data_filename(test_dir + "input_data.vis");
49 
50  auto uv_data = dirty_visibilities({input_data_path}, world);
51  uv_data.units = utilities::vis_units::radians;
52  if (world.is_root()) {
53  CAPTURE(uv_data.vis.head(5));
54  }
55  REQUIRE(world.all_sum_all(uv_data.size()) == 13107);
56 
57  t_uint const imsizey = 128;
58  t_uint const imsizex = 128;
59 
60  auto measurements_transform = factory::measurement_operator_factory<Vector<t_complex>>(
61  factory::distributed_measurement_operator::mpi_distribute_image, uv_data, imsizey, imsizex, 1,
62  1, 2, kernels::kernel_from_string.at("kb"), 4, 4);
63  Vector<t_complex> const init = Vector<t_complex>::Ones(imsizex * imsizey).eval();
64  auto const power_method_stuff =
65  sopt::algorithm::power_method<Vector<t_complex>>(*measurements_transform, 1000, 1e-5, init);
66  const t_real op_norm = std::get<0>(power_method_stuff);
67  measurements_transform->set_norm(op_norm);
68 
69  std::vector<std::tuple<std::string, t_uint>> const sara{
70  std::make_tuple("Dirac", 3u), std::make_tuple("DB1", 3u), std::make_tuple("DB2", 3u),
71  std::make_tuple("DB3", 3u), std::make_tuple("DB4", 3u), std::make_tuple("DB5", 3u),
72  std::make_tuple("DB6", 3u), std::make_tuple("DB7", 3u), std::make_tuple("DB8", 3u)};
73  auto const wavelets = factory::wavelet_operator_factory<Vector<t_complex>>(
74  factory::distributed_wavelet_operator::mpi_sara, sara, imsizey, imsizex);
75  t_real const sigma =
76  world.broadcast(0.016820222945913496) * std::sqrt(2); // see test_parameters file
77  SECTION("global") {
78  auto const padmm = factory::padmm_factory<sopt::algorithm::ImagingProximalADMM<t_complex>>(
79  factory::algo_distribution::mpi_serial, measurements_transform, wavelets, uv_data, sigma,
80  imsizey, imsizex, sara.size(), 300, true, true, false, 1e-2, 1e-3, 50, 1);
81 
82  auto const diagnostic = (*padmm)();
83  CHECK(diagnostic.niters == 10);
84 
85  const std::string &expected_solution_path = data_filename(test_dir + "solution.fits");
86  const std::string &expected_residual_path = data_filename(test_dir + "residual.fits");
87 
88  const auto solution = pfitsio::read2d(expected_solution_path);
89  const auto residual = pfitsio::read2d(expected_residual_path);
90 
91  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
92  CAPTURE(Vector<t_complex>::Map(solution.data(), solution.size()).real().head(10));
93  CAPTURE(Vector<t_complex>::Map(image.data(), image.size()).real().head(10));
94  CAPTURE(Vector<t_complex>::Map((image / solution).eval().data(), image.size()).real().head(10));
95  CHECK(image.isApprox(solution, 1e-4));
96 
97  const Vector<t_complex> residuals = measurements_transform->adjoint() *
98  (uv_data.vis - ((*measurements_transform) * diagnostic.x));
99  const Image<t_complex> residual_image =
100  Image<t_complex>::Map(residuals.data(), imsizey, imsizex);
101  CAPTURE(Vector<t_complex>::Map(residual.data(), residual.size()).real().head(10));
102  CAPTURE(Vector<t_complex>::Map(residuals.data(), residuals.size()).real().head(10));
103  CHECK(residual_image.real().isApprox(residual.real(), 1e-4));
104  }
105  SECTION("local") {
106  auto const padmm = factory::padmm_factory<sopt::algorithm::ImagingProximalADMM<t_complex>>(
107  factory::algo_distribution::mpi_distributed, measurements_transform, wavelets, uv_data,
108  sigma, imsizey, imsizex, sara.size(), 500, true, true, false, 1e-2, 1e-3, 50, 1);
109 
110  auto const diagnostic = (*padmm)();
111  t_real const epsilon = utilities::calculate_l2_radius(world.all_sum_all(uv_data.vis.size()),
112  world.broadcast(sigma));
113  CHECK(sopt::mpi::l2_norm(diagnostic.residual, padmm->l2ball_proximal_weights(), world) <
114  epsilon);
115  // the algorithm depends on nodes, so other than a basic bounds check,
116  // it is hard to know exact precision (might depend on probability theory...)
117  if (world.size() > 2 or world.size() == 0) return;
118  // testing the case where there are two nodes exactly.
119  const std::string &expected_solution_path = (world.size() == 2)
120  ? data_filename(test_dir + "mpi_solution.fits")
121  : data_filename(test_dir + "solution.fits");
122  const std::string &expected_residual_path = (world.size() == 2)
123  ? data_filename(test_dir + "mpi_residual.fits")
124  : data_filename(test_dir + "residual.fits");
125  if (world.size() == 1) CHECK(diagnostic.niters == 10);
126  if (world.size() == 2) CHECK(diagnostic.niters == 11);
127 
128  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
129  // if (world.is_root()) pfitsio::write2d(image.real(), expected_solution_path);
130  const auto solution = pfitsio::read2d(expected_solution_path);
131  CAPTURE(Vector<t_complex>::Map(solution.data(), solution.size()).real().head(10));
132  CAPTURE(Vector<t_complex>::Map(image.data(), image.size()).real().head(10));
133  CAPTURE(Vector<t_complex>::Map((image / solution).eval().data(), image.size()).real().head(10));
134  CHECK(image.isApprox(solution, 1e-4));
135 
136  const Vector<t_complex> residuals = measurements_transform->adjoint() *
137  (uv_data.vis - ((*measurements_transform) * diagnostic.x));
138  const Image<t_complex> residual_image =
139  Image<t_complex>::Map(residuals.data(), imsizey, imsizex);
140  // if (world.is_root()) pfitsio::write2d(residual_image.real(), expected_residual_path);
141  const auto residual = pfitsio::read2d(expected_residual_path);
142  CAPTURE(Vector<t_complex>::Map(residual.data(), residual.size()).real().head(10));
143  CAPTURE(Vector<t_complex>::Map(residuals.data(), residuals.size()).real().head(10));
144  CHECK(residual_image.real().isApprox(residual.real(), 1e-4));
145  }
146 }
t_real calculate_l2_radius(const t_uint y_size, const t_real &sigma, const t_real &n_sigma, const std::string distirbution)
A function that calculates the l2 ball radius for sopt.
Definition: utilities.cc:75
void padmm(const std::string &name, const Image< t_complex > &M31, const std::string &kernel, const t_int J, const utilities::vis_params &uv_data, const t_real sigma, const std::tuple< bool, t_real > &w_term)

References purify::utilities::calculate_l2_radius(), CHECK, purify::data_filename(), dirty_visibilities(), purify::kernels::kernel_from_string, purify::factory::mpi_distribute_image, purify::factory::mpi_distributed, purify::factory::mpi_sara, purify::factory::mpi_serial, padmm(), purify::utilities::radians, purify::pfitsio::read2d(), operators_test::test_dir, and purify::utilities::vis_params::units.

◆ TEST_CASE() [3/3]

TEST_CASE ( "Serial vs. Serial with MPI Primal Dual"  ,
""  [!shouldfail] 
)

Definition at line 148 of file mpi_algo_factory.cc.

148  {
149  auto const world = sopt::mpi::Communicator::World();
150 
151  const std::string &test_dir = "expected/primal_dual/";
152  const std::string &input_data_path = data_filename(test_dir + "input_data.vis");
153 
154  auto uv_data = dirty_visibilities({input_data_path}, world);
155  uv_data.units = utilities::vis_units::radians;
156  if (world.is_root()) {
157  CAPTURE(uv_data.vis.head(5));
158  }
159  REQUIRE(world.all_sum_all(uv_data.size()) == 13107);
160 
161  t_uint const imsizey = 128;
162  t_uint const imsizex = 128;
163 
164  auto const measurements_transform = factory::measurement_operator_factory<Vector<t_complex>>(
165  factory::distributed_measurement_operator::mpi_distribute_image, uv_data, imsizey, imsizex, 1,
166  1, 2, kernels::kernel_from_string.at("kb"), 4, 4);
167  auto const power_method_stuff = sopt::algorithm::power_method<Vector<t_complex>>(
168  *measurements_transform, 1000, 1e-5,
169  world.broadcast(Vector<t_complex>::Ones(imsizex * imsizey).eval()));
170  const t_real op_norm = std::get<0>(power_method_stuff);
171  measurements_transform->set_norm(op_norm);
172 
173  std::vector<std::tuple<std::string, t_uint>> const sara{
174  std::make_tuple("Dirac", 3u), std::make_tuple("DB1", 3u), std::make_tuple("DB2", 3u),
175  std::make_tuple("DB3", 3u), std::make_tuple("DB4", 3u), std::make_tuple("DB5", 3u),
176  std::make_tuple("DB6", 3u), std::make_tuple("DB7", 3u), std::make_tuple("DB8", 3u)};
177  auto const wavelets = factory::wavelet_operator_factory<Vector<t_complex>>(
178  factory::distributed_wavelet_operator::mpi_sara, sara, imsizey, imsizex);
179  t_real const sigma =
180  world.broadcast(0.016820222945913496) * std::sqrt(2); // see test_parameters file
181  SECTION("global") {
182  auto const primaldual =
183  factory::primaldual_factory<sopt::algorithm::ImagingPrimalDual<t_complex>>(
184  factory::algo_distribution::mpi_serial, measurements_transform, wavelets, uv_data,
185  sigma, imsizey, imsizex, sara.size(), 500, true, true, 1e-2, 1);
186 
187  auto const diagnostic = (*primaldual)();
188  CHECK(diagnostic.niters == 16);
189 
190  const std::string &expected_solution_path = data_filename(test_dir + "solution.fits");
191  const std::string &expected_residual_path = data_filename(test_dir + "residual.fits");
192 
193  const auto solution = pfitsio::read2d(expected_solution_path);
194  const auto residual = pfitsio::read2d(expected_residual_path);
195 
196  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
197  CAPTURE(Vector<t_complex>::Map(solution.data(), solution.size()).real().head(10));
198  CAPTURE(Vector<t_complex>::Map(image.data(), image.size()).real().head(10));
199  CAPTURE(Vector<t_complex>::Map((image / solution).eval().data(), image.size()).real().head(10));
200  CHECK(image.isApprox(solution, 1e-4));
201 
202  const Vector<t_complex> residuals = measurements_transform->adjoint() *
203  (uv_data.vis - ((*measurements_transform) * diagnostic.x));
204  const Image<t_complex> residual_image =
205  Image<t_complex>::Map(residuals.data(), imsizey, imsizex);
206  CAPTURE(Vector<t_complex>::Map(residual.data(), residual.size()).real().head(10));
207  CAPTURE(Vector<t_complex>::Map(residuals.data(), residuals.size()).real().head(10));
208  CHECK(residual_image.real().isApprox(residual.real(), 1e-4));
209  }
210  SECTION("local") {
211  auto const primaldual =
212  factory::primaldual_factory<sopt::algorithm::ImagingPrimalDual<t_complex>>(
213  factory::algo_distribution::mpi_distributed, measurements_transform, wavelets, uv_data,
214  sigma, imsizey, imsizex, sara.size(), 500, true, true, 1e-2, 1);
215 
216  auto const diagnostic = (*primaldual)();
217  t_real const epsilon = utilities::calculate_l2_radius(world.all_sum_all(uv_data.vis.size()),
218  world.broadcast(sigma));
219  CHECK(sopt::mpi::l2_norm(diagnostic.residual, primaldual->l2ball_proximal_weights(), world) <
220  epsilon);
221  // the algorithm depends on nodes, so other than a basic bounds check,
222  // it is hard to know exact precision (might depend on probability theory...)
223  if (world.size() > 2 or world.size() == 0) return;
224  // testing the case where there are two nodes exactly.
225  const std::string &expected_solution_path = (world.size() == 2)
226  ? data_filename(test_dir + "mpi_solution.fits")
227  : data_filename(test_dir + "solution.fits");
228  const std::string &expected_residual_path = (world.size() == 2)
229  ? data_filename(test_dir + "mpi_residual.fits")
230  : data_filename(test_dir + "residual.fits");
231  if (world.size() == 1) CHECK(diagnostic.niters == 16);
232  if (world.size() == 2) CHECK(diagnostic.niters == 18);
233  const auto solution = pfitsio::read2d(expected_solution_path);
234  const auto residual = pfitsio::read2d(expected_residual_path);
235 
236  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
237  // if (world.is_root()) pfitsio::write2d(image.real(), expected_solution_path);
238  CAPTURE(Vector<t_complex>::Map(solution.data(), solution.size()).real().head(10));
239  CAPTURE(Vector<t_complex>::Map(image.data(), image.size()).real().head(10));
240  CAPTURE(Vector<t_complex>::Map((image / solution).eval().data(), image.size()).real().head(10));
241  CHECK(image.isApprox(solution, 1e-4));
242 
243  const Vector<t_complex> residuals = measurements_transform->adjoint() *
244  (uv_data.vis - ((*measurements_transform) * diagnostic.x));
245  const Image<t_complex> residual_image =
246  Image<t_complex>::Map(residuals.data(), imsizey, imsizex);
247  // if (world.is_root()) pfitsio::write2d(residual_image.real(), expected_residual_path);
248  CAPTURE(Vector<t_complex>::Map(residual.data(), residual.size()).real().head(10));
249  CAPTURE(Vector<t_complex>::Map(residuals.data(), residuals.size()).real().head(10));
250  CHECK(residual_image.real().isApprox(residual.real(), 1e-4));
251  }
252  SECTION("random update") {
253  auto const measurements_transform_serial =
254  factory::measurement_operator_factory<Vector<t_complex>>(
255  factory::distributed_measurement_operator::serial, uv_data, imsizey, imsizex, 1, 1, 2,
256  kernels::kernel_from_string.at("kb"), 4, 4);
257  auto const power_method_stuff = sopt::algorithm::all_sum_all_power_method<Vector<t_complex>>(
258  world, *measurements_transform, 1000, 1e-5,
259  world.broadcast(Vector<t_complex>::Ones(imsizex * imsizey).eval()));
260  const t_real op_norm = std::get<0>(power_method_stuff);
261  measurements_transform->set_norm(op_norm);
262 
263  auto sara_dist = sopt::wavelets::distribute_sara(sara, world);
264  auto const wavelets_serial = factory::wavelet_operator_factory<Vector<t_complex>>(
265  factory::distributed_wavelet_operator::serial, sara_dist, imsizey, imsizex);
266 
267  auto const primaldual =
268  factory::primaldual_factory<sopt::algorithm::ImagingPrimalDual<t_complex>>(
269  factory::algo_distribution::mpi_random_updates, measurements_transform_serial,
270  wavelets_serial, uv_data, sigma, imsizey, imsizex, sara_dist.size(), 500, true, true,
271  1e-2, 1);
272 
273  auto const diagnostic = (*primaldual)();
274  t_real const epsilon = utilities::calculate_l2_radius(world.all_sum_all(uv_data.vis.size()),
275  world.broadcast(sigma));
276  CHECK(sopt::mpi::l2_norm(diagnostic.residual, primaldual->l2ball_proximal_weights(), world) <
277  epsilon);
278  if (world.size() > 1) return;
279  // the algorithm depends on nodes, so other than a basic bounds check,
280  // it is hard to know exact precision (might depend on probability theory...)
281  if (world.size() == 0)
282  return;
283  else if (world.size() == 2 or world.size() == 1) {
284  // testing the case where there are two nodes exactly.
285  const std::string &expected_solution_path =
286  (world.size() == 2) ? data_filename(test_dir + "mpi_random_solution.fits")
287  : data_filename(test_dir + "solution.fits");
288  const std::string &expected_residual_path =
289  (world.size() == 2) ? data_filename(test_dir + "mpi_random_residual.fits")
290  : data_filename(test_dir + "residual.fits");
291  if (world.size() == 1) CHECK(diagnostic.niters == 16);
292  if (world.size() == 2) CHECK(diagnostic.niters < 100);
293 
294  const auto solution = pfitsio::read2d(expected_solution_path);
295  const auto residual = pfitsio::read2d(expected_residual_path);
296 
297  const Image<t_complex> image = Image<t_complex>::Map(diagnostic.x.data(), imsizey, imsizex);
298  // if (world.is_root()) pfitsio::write2d(image.real(), expected_solution_path);
299  CAPTURE(Vector<t_complex>::Map(solution.data(), solution.size()).real().head(10));
300  CAPTURE(Vector<t_complex>::Map(image.data(), image.size()).real().head(10));
301  CAPTURE(
302  Vector<t_complex>::Map((image / solution).eval().data(), image.size()).real().head(10));
303  CHECK(image.isApprox(solution, 1e-3));
304 
305  const Vector<t_complex> residuals =
306  measurements_transform->adjoint() *
307  (uv_data.vis - ((*measurements_transform) * diagnostic.x));
308  const Image<t_complex> residual_image =
309  Image<t_complex>::Map(residuals.data(), imsizey, imsizex);
310  // if (world.is_root()) pfitsio::write2d(residual_image.real(), expected_residual_path);
311  CAPTURE(Vector<t_complex>::Map(residual.data(), residual.size()).real().head(10));
312  CAPTURE(Vector<t_complex>::Map(residuals.data(), residuals.size()).real().head(10));
313  CHECK(residual_image.real().isApprox(residual.real(), 1e-3));
314  } else
315  return;
316  }
317 }

References purify::utilities::calculate_l2_radius(), CHECK, purify::data_filename(), dirty_visibilities(), purify::kernels::kernel_from_string, purify::factory::mpi_distribute_image, purify::factory::mpi_distributed, purify::factory::mpi_random_updates, purify::factory::mpi_sara, purify::factory::mpi_serial, purify::utilities::radians, purify::pfitsio::read2d(), purify::factory::serial, operators_test::test_dir, and purify::utilities::vis_params::units.