LCOV - code coverage report
Current view: top level - solvers/tests - test_FGM.cpp (source / functions) Hit Total Coverage
Test: test_coverage.info.cleaned Lines: 104 104 100.0 %
Date: 2022-02-28 03:37:41 Functions: 13 13 100.0 %

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

Generated by: LCOV version 1.15