LCOV - code coverage report
Current view: top level - elsa/solvers/tests - test_CG.cpp (source / functions) Hit Total Coverage
Test: coverage-all.lcov Lines: 143 143 100.0 %
Date: 2022-08-25 03:05:39 Functions: 5 5 100.0 %

          Line data    Source code
       1             : /**
       2             :  * @file test_CG.cpp
       3             :  *
       4             :  * @brief Tests for the CG class
       5             :  *
       6             :  * @author Nikola Dinev
       7             :  */
       8             : 
       9             : #include "doctest/doctest.h"
      10             : 
      11             : #include "CG.h"
      12             : #include "Scaling.h"
      13             : #include "Logger.h"
      14             : #include "VolumeDescriptor.h"
      15             : #include "JosephsMethod.h"
      16             : #include "CircleTrajectoryGenerator.h"
      17             : #include "PhantomGenerator.h"
      18             : #include "TypeCasts.hpp"
      19             : #include "testHelpers.h"
      20             : 
      21             : using namespace elsa;
      22             : using namespace doctest;
      23             : 
      24             : TEST_SUITE_BEGIN("solvers");
      25             : 
      26             : template <template <typename> typename T, typename data_t>
      27             : constexpr data_t return_data_t(const T<data_t>&);
      28             : 
      29             : TYPE_TO_STRING(CG<float>);
      30             : TYPE_TO_STRING(CG<double>);
      31             : 
      32             : TEST_CASE_TEMPLATE("CG: Solving a simple linear problem", TestType, CG<float>, CG<double>)
      33           4 : {
      34           4 :     using data_t = decltype(return_data_t(std::declval<TestType>()));
      35             :     // eliminate the timing info from console for the tests
      36           4 :     Logger::setLevel(Logger::LogLevel::OFF);
      37             : 
      38           4 :     GIVEN("a linear problem")
      39           4 :     {
      40           4 :         IndexVector_t numCoeff(2);
      41           4 :         numCoeff << 13, 24;
      42           4 :         VolumeDescriptor dd{numCoeff};
      43             : 
      44           4 :         Eigen::Matrix<data_t, -1, 1> bVec(dd.getNumberOfCoefficients());
      45           4 :         bVec.setRandom();
      46           4 :         DataContainer<data_t> dcB{dd, bVec};
      47             : 
      48           4 :         bVec.setRandom();
      49           4 :         bVec = bVec.cwiseAbs();
      50           4 :         Scaling<data_t> scalingOp{dd, DataContainer<data_t>{dd, bVec}};
      51             : 
      52           4 :         QuadricProblem<data_t> prob{scalingOp, dcB, true};
      53             : 
      54           4 :         data_t epsilon = std::numeric_limits<data_t>::epsilon();
      55             : 
      56           4 :         WHEN("setting up a cg solver")
      57           4 :         {
      58           2 :             TestType solver{prob, epsilon};
      59             : 
      60           2 :             THEN("the clone works correctly")
      61           2 :             {
      62           2 :                 auto cgClone = solver.clone();
      63             : 
      64           2 :                 REQUIRE_NE(cgClone.get(), &solver);
      65           2 :                 REQUIRE_EQ(*cgClone, solver);
      66             : 
      67           2 :                 AND_THEN("it works as expected")
      68           2 :                 {
      69           2 :                     auto solution = solver.solve(dd.getNumberOfCoefficients());
      70             : 
      71           2 :                     DataContainer<data_t> resultsDifference = scalingOp.apply(solution) - dcB;
      72             : 
      73             :                     // should have converged for the given number of iterations
      74           2 :                     REQUIRE_LE((resultsDifference).squaredL2Norm(),
      75           2 :                                epsilon * epsilon * dcB.squaredL2Norm());
      76           2 :                 }
      77           2 :             }
      78           2 :         }
      79             : 
      80           4 :         WHEN("setting up a preconditioned cg solver")
      81           4 :         {
      82           2 :             bVec = 1 / bVec.array();
      83           2 :             TestType solver{prob, Scaling<data_t>{dd, DataContainer<data_t>{dd, bVec}}, epsilon};
      84             : 
      85           2 :             THEN("the clone works correctly")
      86           2 :             {
      87           2 :                 auto cgClone = solver.clone();
      88             : 
      89           2 :                 REQUIRE_NE(cgClone.get(), &solver);
      90           2 :                 REQUIRE_EQ(*cgClone, solver);
      91             : 
      92           2 :                 AND_THEN("it works as expected")
      93           2 :                 {
      94             :                     // a perfect preconditioner should allow for convergence in a single step
      95           2 :                     auto solution = solver.solve(1);
      96             : 
      97           2 :                     DataContainer<data_t> resultsDifference = scalingOp.apply(solution) - dcB;
      98             : 
      99             :                     // should have converged for the given number of iterations
     100           2 :                     REQUIRE_UNARY(checkApproxEq((resultsDifference).squaredL2Norm(),
     101           2 :                                                 epsilon * epsilon * dcB.squaredL2Norm()));
     102           2 :                 }
     103           2 :             }
     104           2 :         }
     105           4 :     }
     106           4 : }
     107             : 
     108             : TEST_CASE_TEMPLATE("CG: Solving a Tikhonov problem", TestType, CG<float>, CG<double>)
     109           4 : {
     110           4 :     using data_t = decltype(return_data_t(std::declval<TestType>()));
     111             :     // eliminate the timing info from console for the tests
     112           4 :     Logger::setLevel(Logger::LogLevel::OFF);
     113             : 
     114           4 :     GIVEN("a Tikhonov problem")
     115           4 :     {
     116           4 :         IndexVector_t numCoeff(2);
     117           4 :         numCoeff << 13, 24;
     118           4 :         VolumeDescriptor dd{numCoeff};
     119             : 
     120           4 :         Eigen::Matrix<data_t, -1, 1> bVec(dd.getNumberOfCoefficients());
     121           4 :         bVec.setRandom();
     122           4 :         DataContainer<data_t> dcB{dd, bVec};
     123             : 
     124           4 :         bVec.setRandom();
     125           4 :         bVec = bVec.cwiseProduct(bVec);
     126           4 :         Scaling<data_t> scalingOp{dd, DataContainer<data_t>{dd, bVec}};
     127             : 
     128           4 :         auto lambda = static_cast<data_t>(0.1);
     129           4 :         Scaling<data_t> lambdaOp{dd, lambda};
     130             : 
     131           4 :         QuadricProblem<data_t> prob{scalingOp + lambdaOp, dcB, true};
     132             : 
     133           4 :         data_t epsilon = std::numeric_limits<data_t>::epsilon();
     134             : 
     135           4 :         WHEN("setting up a cg solver")
     136           4 :         {
     137           2 :             TestType solver{prob, epsilon};
     138             : 
     139           2 :             THEN("the clone works correctly")
     140           2 :             {
     141           2 :                 auto cgClone = solver.clone();
     142             : 
     143           2 :                 REQUIRE_NE(cgClone.get(), &solver);
     144           2 :                 REQUIRE_EQ(*cgClone, solver);
     145             : 
     146           2 :                 AND_THEN("it works as expected")
     147           2 :                 {
     148           2 :                     auto solution = solver.solve(dd.getNumberOfCoefficients());
     149             : 
     150           2 :                     DataContainer<data_t> resultsDifference =
     151           2 :                         (scalingOp + lambdaOp).apply(solution) - dcB;
     152             : 
     153             :                     // should have converged for the given number of iterations
     154           2 :                     REQUIRE_LE(resultsDifference.squaredL2Norm(),
     155           2 :                                epsilon * epsilon * dcB.squaredL2Norm());
     156           2 :                 }
     157           2 :             }
     158           2 :         }
     159             : 
     160           4 :         WHEN("setting up a preconditioned cg solver")
     161           4 :         {
     162           2 :             bVec = 1 / (bVec.array() + lambda);
     163           2 :             TestType solver{prob, Scaling<data_t>{dd, DataContainer<data_t>{dd, bVec}}, epsilon};
     164             : 
     165           2 :             THEN("the clone works correctly")
     166           2 :             {
     167           2 :                 auto cgClone = solver.clone();
     168             : 
     169           2 :                 REQUIRE_NE(cgClone.get(), &solver);
     170           2 :                 REQUIRE_EQ(*cgClone, solver);
     171             : 
     172           2 :                 AND_THEN("it works as expected")
     173           2 :                 {
     174             :                     // a perfect preconditioner should allow for convergence in a single step
     175           2 :                     auto solution = solver.solve(1);
     176             : 
     177           2 :                     DataContainer<data_t> result = (scalingOp + lambdaOp).apply(solution);
     178             : 
     179             :                     // should have converged for the given number of iterations
     180           2 :                     REQUIRE_UNARY(checkApproxEq(result.squaredL2Norm(), dcB.squaredL2Norm()));
     181             :                     // REQUIRE_LE(resultsDifference.squaredL2Norm(),
     182             :                     //            epsilon * epsilon * dcB.squaredL2Norm());
     183           2 :                 }
     184           2 :             }
     185           2 :         }
     186           4 :     }
     187           4 : }
     188             : 
     189             : TEST_CASE("CG: Solving a simple phantom reconstruction")
     190           1 : {
     191             :     // eliminate the timing info from console for the tests
     192           1 :     Logger::setLevel(Logger::LogLevel::OFF);
     193             : 
     194           1 :     GIVEN("a Phantom reconstruction problem")
     195           1 :     {
     196             : 
     197           1 :         IndexVector_t size(2);
     198           1 :         size << 16, 16; // TODO: determine optimal phantom size for efficient testing
     199           1 :         auto phantom = PhantomGenerator<real_t>::createModifiedSheppLogan(size);
     200           1 :         auto& volumeDescriptor = phantom.getDataDescriptor();
     201             : 
     202           1 :         index_t numAngles{30}, arc{180};
     203           1 :         auto sinoDescriptor = CircleTrajectoryGenerator::createTrajectory(
     204           1 :             numAngles, phantom.getDataDescriptor(), arc, static_cast<real_t>(size(0)) * 100.0f,
     205           1 :             static_cast<real_t>(size(0)));
     206             : 
     207           1 :         JosephsMethod projector(downcast<VolumeDescriptor>(volumeDescriptor), *sinoDescriptor);
     208             : 
     209           1 :         auto sinogram = projector.apply(phantom);
     210             : 
     211           1 :         WLSProblem problem(projector, sinogram);
     212           1 :         real_t epsilon = std::numeric_limits<real_t>::epsilon();
     213             : 
     214           1 :         WHEN("setting up a CG solver")
     215           1 :         {
     216           1 :             CG solver{problem, epsilon};
     217             : 
     218           1 :             THEN("the clone works correctly")
     219           1 :             {
     220           1 :                 auto cgClone = solver.clone();
     221             : 
     222           1 :                 REQUIRE_NE(cgClone.get(), &solver);
     223           1 :                 REQUIRE_EQ(*cgClone, solver);
     224             : 
     225           1 :                 AND_THEN("it works as expected")
     226           1 :                 {
     227           1 :                     auto reconstruction = solver.solve(10);
     228             : 
     229           1 :                     DataContainer resultsDifference = reconstruction - phantom;
     230             : 
     231             :                     // should have converged for the given number of iterations
     232             :                     // does not converge to the optimal solution because of the regularization term
     233           1 :                     REQUIRE_UNARY(checkApproxEq(resultsDifference.squaredL2Norm(),
     234           1 :                                                 epsilon * epsilon * phantom.squaredL2Norm(), 0.1));
     235           1 :                 }
     236           1 :             }
     237           1 :         }
     238           1 :     }
     239           1 : }
     240             : 
     241             : TEST_SUITE_END();

Generated by: LCOV version 1.14