#include "itkFitFibersToImageFilter.h"

#include <boost/progress.hpp>
#include <mitkDiffusionFunctionCollection.h>

namespace itk{

FitFibersToImageFilter::FitFibersToImageFilter()
  : m_PeakImage(nullptr)
  , m_DiffImage(nullptr)
  , m_ScalarImage(nullptr)
  , m_MaskImage(nullptr)
  , m_FitIndividualFibers(true)
  , m_GradientTolerance(1e-5)
  , m_Lambda(1.0)
  , m_MaxIterations(20)
  , m_Coverage(0)
  , m_Overshoot(0)
  , m_RMSE(0.0)
  , m_FilterOutliers(false)
  , m_MeanWeight(1.0)
  , m_MedianWeight(1.0)
  , m_MinWeight(1.0)
  , m_MaxWeight(1.0)
  , m_Verbose(true)
  , m_NumUnknowns(0)
  , m_NumResiduals(0)
  , m_NumCoveredDirections(0)
  , m_SignalModel(nullptr)
  , sz_x(0)
  , sz_y(0)
  , sz_z(0)
  , m_MeanTractDensity(0)
  , m_MeanSignal(0)
  , fiber_count(0)
  , m_Regularization(VnlCostFunction::REGU::VOXEL_VARIANCE)
{
  this->SetNumberOfRequiredOutputs(3);
}

FitFibersToImageFilter::~FitFibersToImageFilter()
{

}

void FitFibersToImageFilter::CreateDiffSystem()
{
  sz_x = m_DiffImage->GetLargestPossibleRegion().GetSize(0);
  sz_y = m_DiffImage->GetLargestPossibleRegion().GetSize(1);
  sz_z = m_DiffImage->GetLargestPossibleRegion().GetSize(2);
  dim_four_size = m_DiffImage->GetVectorLength();
  int num_voxels = sz_x*sz_y*sz_z;

  if (m_MaskImage.IsNotNull() &&
      (m_MaskImage->GetLargestPossibleRegion().GetSize()[0]!=sz_x ||
       m_MaskImage->GetLargestPossibleRegion().GetSize()[1]!=sz_y ||
       m_MaskImage->GetLargestPossibleRegion().GetSize()[2]!=sz_z))
    mitkThrow() << "Mask and peak image must have same size!";

  auto spacing = m_DiffImage->GetSpacing();

  m_NumResiduals = num_voxels * dim_four_size;

  MITK_INFO << "Num. unknowns: " << m_NumUnknowns;
  MITK_INFO << "Num. residuals: " << m_NumResiduals;
  MITK_INFO << "Creating system ...";

  A.set_size(m_NumResiduals, m_NumUnknowns);
  b.set_size(m_NumResiduals); b.fill(0.0);

  m_MeanTractDensity = 0;
  m_MeanSignal = 0;
  m_NumCoveredDirections = 0;
  fiber_count = 0;
  vnl_vector<int> voxel_indicator; voxel_indicator.set_size(sz_x*sz_y*sz_z); voxel_indicator.fill(0);

  unsigned int numFibers = 0;
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
    numFibers += m_Tractograms.at(bundle)->GetNumFibers();
  boost::progress_display disp(numFibers);
  m_GroupSizes.clear();
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
  {
    vtkSmartPointer<vtkPolyData> polydata = m_Tractograms.at(bundle)->GetFiberPolyData();
    m_GroupSizes.push_back(m_Tractograms.at(bundle)->GetNumFibers());
    for (unsigned int i=0; i<m_Tractograms.at(bundle)->GetNumFibers(); ++i)
    {
      ++disp;
      vtkCell* cell = polydata->GetCell(i);
      int numPoints = cell->GetNumberOfPoints();
      vtkPoints* points = cell->GetPoints();

      if (numPoints<2)
        MITK_INFO << "FIBER WITH ONLY ONE POINT ENCOUNTERED!";

      for (int j=0; j<numPoints-1; ++j)
      {
        PointType3 startVertex = mitk::imv::GetItkPoint(points->GetPoint(j));
        itk::Index<3> startIndex;
        itk::ContinuousIndex<float, 3> startIndexCont;
        m_DiffImage->TransformPhysicalPointToIndex(startVertex, startIndex);
        m_DiffImage->TransformPhysicalPointToContinuousIndex(startVertex, startIndexCont);

        PointType3 endVertex = mitk::imv::GetItkPoint(points->GetPoint(j+1));
        itk::Index<3> endIndex;
        itk::ContinuousIndex<float, 3> endIndexCont;
        m_DiffImage->TransformPhysicalPointToIndex(endVertex, endIndex);
        m_DiffImage->TransformPhysicalPointToContinuousIndex(endVertex, endIndexCont);

        mitk::DiffusionSignalModel<>::GradientType fiber_dir;
        fiber_dir[0] = endVertex[0]-startVertex[0];
        fiber_dir[1] = endVertex[1]-startVertex[1];
        fiber_dir[2] = endVertex[2]-startVertex[2];
        fiber_dir.Normalize();

        std::vector< std::pair< itk::Index<3>, double > > segments = mitk::imv::IntersectImage(spacing, startIndex, endIndex, startIndexCont, endIndexCont);
        for (std::pair< itk::Index<3>, double > seg : segments)
        {
          if (!m_DiffImage->GetLargestPossibleRegion().IsInside(seg.first) || (m_MaskImage.IsNotNull() && m_MaskImage->GetPixel(seg.first)==0))
            continue;

          int x = seg.first[0];
          int y = seg.first[1];
          int z = seg.first[2];

          mitk::DiffusionSignalModel<>::PixelType simulated_pixel = m_SignalModel->SimulateMeasurement(fiber_dir)*seg.second;
          VectorImgType::PixelType measured_pixel = m_DiffImage->GetPixel(seg.first);

          double simulated_mean = 0;
          double measured_mean = 0;
          int num_nonzero_g = 0;
          for (int g=0; g<dim_four_size; ++g)
          {
            if( m_SignalModel->GetGradientDirection(g).GetNorm()<mitk::eps )
              continue;
            simulated_mean += simulated_pixel[g];
            measured_mean += (double)measured_pixel[g];
            ++num_nonzero_g;
          }
          simulated_mean /= num_nonzero_g;
          measured_mean /= num_nonzero_g;
          simulated_pixel -= simulated_mean;

          if (voxel_indicator[x + sz_x*y + sz_x*sz_y*z]==0)
            m_MeanSignal += measured_mean;
          m_MeanTractDensity += simulated_mean;
          voxel_indicator[x + sz_x*y + sz_x*sz_y*z] = 1;

          for (int g=0; g<dim_four_size; ++g)
          {
            unsigned int linear_index = x + sz_x*y + sz_x*sz_y*z + sz_x*sz_y*sz_z*g;

            if (m_FitIndividualFibers)
            {
              b[linear_index] = (double)measured_pixel[g] - measured_mean;
              A.put(linear_index, fiber_count, A.get(linear_index, fiber_count) + simulated_pixel[g]);
            }
            else
            {
              b[linear_index] = (double)measured_pixel[g] - measured_mean;
              A.put(linear_index, bundle, A.get(linear_index, bundle) + simulated_pixel[g]);
            }
          }
        }

      }

      ++fiber_count;
    }
  }

  m_NumCoveredDirections = voxel_indicator.sum();
  if (m_NumCoveredDirections==0 || m_MeanSignal<0.0001)
    mitkThrow() << "No overlap between fibers and non-zero image!";
  m_MeanTractDensity /= m_NumCoveredDirections;
  m_MeanSignal /= m_NumCoveredDirections;
  b /= m_MeanSignal;
  b *= m_MeanTractDensity;
}

void FitFibersToImageFilter::CreatePeakSystem()
{
  sz_x = m_PeakImage->GetLargestPossibleRegion().GetSize(0);
  sz_y = m_PeakImage->GetLargestPossibleRegion().GetSize(1);
  sz_z = m_PeakImage->GetLargestPossibleRegion().GetSize(2);
  dim_four_size = m_PeakImage->GetLargestPossibleRegion().GetSize(3)/3 + 1; // +1 for zero - peak
  int num_voxels = sz_x*sz_y*sz_z;

  itk::Vector< double, 3 > spacing;
  spacing[0] = m_PeakImage->GetSpacing()[0];
  spacing[1] = m_PeakImage->GetSpacing()[1];
  spacing[2] = m_PeakImage->GetSpacing()[2];

  PointType3 origin;
  origin[0] = m_PeakImage->GetOrigin()[0];
  origin[1] = m_PeakImage->GetOrigin()[1];
  origin[2] = m_PeakImage->GetOrigin()[2];

  UcharImgType::RegionType::SizeType size;
  size[0] = m_PeakImage->GetLargestPossibleRegion().GetSize()[0];
  size[1] = m_PeakImage->GetLargestPossibleRegion().GetSize()[1];
  size[2] = m_PeakImage->GetLargestPossibleRegion().GetSize()[2];
  UcharImgType::RegionType imageRegion; imageRegion.SetSize(size);

  itk::Matrix<double, 3, 3> direction;
  for (int r=0; r<3; r++)
    for (int c=0; c<3; c++)
      direction[r][c] = m_PeakImage->GetDirection()[r][c];

  if (m_MaskImage.IsNull())
  {
    m_MaskImage = UcharImgType::New();
    m_MaskImage->SetSpacing( spacing );
    m_MaskImage->SetOrigin( origin );
    m_MaskImage->SetDirection( direction );
    m_MaskImage->SetRegions( imageRegion );
    m_MaskImage->Allocate();
    m_MaskImage->FillBuffer(1);
  }
  else if (m_MaskImage->GetLargestPossibleRegion().GetSize()[0]!=sz_x ||
           m_MaskImage->GetLargestPossibleRegion().GetSize()[1]!=sz_y ||
           m_MaskImage->GetLargestPossibleRegion().GetSize()[2]!=sz_z)
    mitkThrow() << "Mask and peak image must have same size!";

  m_NumResiduals = num_voxels * dim_four_size;

  MITK_INFO << "Num. unknowns: " << m_NumUnknowns;
  MITK_INFO << "Num. residuals: " << m_NumResiduals;
  MITK_INFO << "Creating system ...";

  A.set_size(m_NumResiduals, m_NumUnknowns);
  b.set_size(m_NumResiduals); b.fill(0.0);

  m_MeanTractDensity = 0;
  m_MeanSignal = 0;
  m_NumCoveredDirections = 0;
  fiber_count = 0;

  m_GroupSizes.clear();

  int numFibers = 0;
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
    numFibers += m_Tractograms.at(bundle)->GetNumFibers();
  boost::progress_display disp(numFibers);
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
  {
    vtkSmartPointer<vtkPolyData> polydata = m_Tractograms.at(bundle)->GetFiberPolyData();

    m_GroupSizes.push_back(m_Tractograms.at(bundle)->GetNumFibers());
    for (unsigned int i=0; i<m_Tractograms.at(bundle)->GetNumFibers(); ++i)
    {
      ++disp;
      vtkCell* cell = polydata->GetCell(i);
      int numPoints = cell->GetNumberOfPoints();
      vtkPoints* points = cell->GetPoints();

      if (numPoints<2)
        MITK_INFO << "FIBER WITH ONLY ONE POINT ENCOUNTERED!";

      for (int j=0; j<numPoints-1; ++j)
      {
        PointType3 startVertex = mitk::imv::GetItkPoint(points->GetPoint(j));
        itk::Index<3> startIndex;
        itk::ContinuousIndex<float, 3> startIndexCont;
        m_MaskImage->TransformPhysicalPointToIndex(startVertex, startIndex);
        m_MaskImage->TransformPhysicalPointToContinuousIndex(startVertex, startIndexCont);

        PointType3 endVertex = mitk::imv::GetItkPoint(points->GetPoint(j+1));
        itk::Index<3> endIndex;
        itk::ContinuousIndex<float, 3> endIndexCont;
        m_MaskImage->TransformPhysicalPointToIndex(endVertex, endIndex);
        m_MaskImage->TransformPhysicalPointToContinuousIndex(endVertex, endIndexCont);

        vnl_vector_fixed<float,3> fiber_dir;
        fiber_dir[0] = endVertex[0]-startVertex[0];
        fiber_dir[1] = endVertex[1]-startVertex[1];
        fiber_dir[2] = endVertex[2]-startVertex[2];
        fiber_dir.normalize();

        std::vector< std::pair< itk::Index<3>, double > > segments = mitk::imv::IntersectImage(spacing, startIndex, endIndex, startIndexCont, endIndexCont);
        for (std::pair< itk::Index<3>, double > seg : segments)
        {
          if (!m_MaskImage->GetLargestPossibleRegion().IsInside(seg.first) || m_MaskImage->GetPixel(seg.first)==0)
            continue;

          itk::Index<4> idx4;
          idx4[0]=seg.first[0];
          idx4[1]=seg.first[1];
          idx4[2]=seg.first[2];
          idx4[3]=0;

          double w = 1;
          int peak_id = dim_four_size-1;

          double peak_mag = 0;
          GetClosestPeak(idx4, m_PeakImage, fiber_dir, peak_id, w, peak_mag);
          w *= seg.second;

          int x = idx4[0];
          int y = idx4[1];
          int z = idx4[2];

          unsigned int linear_index = x + sz_x*y + sz_x*sz_y*z + sz_x*sz_y*sz_z*peak_id;

          if (b[linear_index] == 0 && peak_id<dim_four_size-1)
          {
            m_NumCoveredDirections++;
            m_MeanSignal += peak_mag;
          }
          m_MeanTractDensity += w;

          if (m_FitIndividualFibers)
          {
            b[linear_index] = peak_mag;
            A.put(linear_index, fiber_count, A.get(linear_index, fiber_count) + w);
          }
          else
          {
            b[linear_index] = peak_mag;
            A.put(linear_index, bundle, A.get(linear_index, bundle) + w);
          }
        }
      }

      ++fiber_count;
    }
  }

  if (m_NumCoveredDirections==0 || m_MeanSignal<0.0001)
    mitkThrow() << "No overlap between fibers and non-zero image!";

  m_MeanTractDensity /= m_NumCoveredDirections;
  m_MeanSignal /= m_NumCoveredDirections;
  b /= m_MeanSignal;
  b *= m_MeanTractDensity;
}

void FitFibersToImageFilter::CreateScalarSystem()
{
  sz_x = m_ScalarImage->GetLargestPossibleRegion().GetSize(0);
  sz_y = m_ScalarImage->GetLargestPossibleRegion().GetSize(1);
  sz_z = m_ScalarImage->GetLargestPossibleRegion().GetSize(2);
  int num_voxels = sz_x*sz_y*sz_z;

  if (m_MaskImage.IsNotNull() &&
      (m_MaskImage->GetLargestPossibleRegion().GetSize()[0]!=sz_x ||
       m_MaskImage->GetLargestPossibleRegion().GetSize()[1]!=sz_y ||
       m_MaskImage->GetLargestPossibleRegion().GetSize()[2]!=sz_z))
    mitkThrow() << "Mask and peak image must have same size!";

  auto spacing = m_ScalarImage->GetSpacing();
  m_NumResiduals = num_voxels;

  MITK_INFO << "Num. unknowns: " << m_NumUnknowns;
  MITK_INFO << "Num. residuals: " << m_NumResiduals;
  MITK_INFO << "Creating system ...";

  A.set_size(m_NumResiduals, m_NumUnknowns);
  b.set_size(m_NumResiduals); b.fill(0.0);

  m_MeanTractDensity = 0;
  m_MeanSignal = 0;
  int numCoveredVoxels = 0;
  fiber_count = 0;

  int numFibers = 0;
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
    numFibers += m_Tractograms.at(bundle)->GetNumFibers();
  boost::progress_display disp(numFibers);
  m_GroupSizes.clear();
  for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
  {
    vtkSmartPointer<vtkPolyData> polydata = m_Tractograms.at(bundle)->GetFiberPolyData();

    m_GroupSizes.push_back(m_Tractograms.at(bundle)->GetNumFibers());
    for (unsigned int i=0; i<m_Tractograms.at(bundle)->GetNumFibers(); ++i)
    {
      ++disp;
      vtkCell* cell = polydata->GetCell(i);
      int numPoints = cell->GetNumberOfPoints();
      vtkPoints* points = cell->GetPoints();

      for (int j=0; j<numPoints-1; ++j)
      {
        PointType3 startVertex = mitk::imv::GetItkPoint(points->GetPoint(j));
        itk::Index<3> startIndex;
        itk::ContinuousIndex<float, 3> startIndexCont;
        m_ScalarImage->TransformPhysicalPointToIndex(startVertex, startIndex);
        m_ScalarImage->TransformPhysicalPointToContinuousIndex(startVertex, startIndexCont);

        PointType3 endVertex = mitk::imv::GetItkPoint(points->GetPoint(j+1));
        itk::Index<3> endIndex;
        itk::ContinuousIndex<float, 3> endIndexCont;
        m_ScalarImage->TransformPhysicalPointToIndex(endVertex, endIndex);
        m_ScalarImage->TransformPhysicalPointToContinuousIndex(endVertex, endIndexCont);

        std::vector< std::pair< itk::Index<3>, double > > segments = mitk::imv::IntersectImage(spacing, startIndex, endIndex, startIndexCont, endIndexCont);
        for (std::pair< itk::Index<3>, double > seg : segments)
        {
          if (!m_ScalarImage->GetLargestPossibleRegion().IsInside(seg.first) || (m_MaskImage.IsNotNull() && m_MaskImage->GetPixel(seg.first)==0))
            continue;

          float image_value = m_ScalarImage->GetPixel(seg.first);
          int x = seg.first[0];
          int y = seg.first[1];
          int z = seg.first[2];

          unsigned int linear_index = x + sz_x*y + sz_x*sz_y*z;

          if (b[linear_index] == 0)
          {
            numCoveredVoxels++;
            m_MeanSignal += image_value;
          }
          m_MeanTractDensity += seg.second;

          if (m_FitIndividualFibers)
          {
            b[linear_index] = image_value;
            A.put(linear_index, fiber_count, A.get(linear_index, fiber_count) + seg.second);
          }
          else
          {
            b[linear_index] = image_value;
            A.put(linear_index, bundle, A.get(linear_index, bundle) + seg.second);
          }
        }
      }

      ++fiber_count;
    }
  }

  if (numCoveredVoxels==0 || m_MeanSignal<0.0001)
    mitkThrow() << "No overlap between fibers and non-zero image!";

  m_MeanTractDensity /= numCoveredVoxels;
  m_MeanSignal /= numCoveredVoxels;
  b /= m_MeanSignal;
  b *= m_MeanTractDensity;
}

void FitFibersToImageFilter::GenerateData()
{
  m_NumUnknowns = m_Tractograms.size();
  if (m_FitIndividualFibers)
  {
    m_NumUnknowns = 0;
    for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
      m_NumUnknowns += m_Tractograms.at(bundle)->GetNumFibers();
  }
  else
    m_FilterOutliers = false;
  if (m_NumUnknowns<1)
  {
    MITK_INFO << "No fibers in tractogram.";
    return;
  }

  fiber_count = 0;
  sz_x = 0;
  sz_y = 0;
  sz_z = 0;
  m_MeanTractDensity = 0;
  m_MeanSignal = 0;
  if (m_PeakImage.IsNotNull())
    CreatePeakSystem();
  else if (m_DiffImage.IsNotNull())
    CreateDiffSystem();
  else if (m_ScalarImage.IsNotNull())
    CreateScalarSystem();
  else
    mitkThrow() << "No input image set!";

  MITK_INFO << "Initializing optimizer";
  double init_lambda = fiber_count;  // initialization for lambda estimation

  itk::TimeProbe clock;
  clock.Start();

  cost = VnlCostFunction(m_NumUnknowns);
  cost.SetProblem(A, b, init_lambda, m_Regularization);
  cost.SetGroupSizes(m_GroupSizes);
  m_Weights.set_size(m_NumUnknowns);
  m_Weights.fill( 1.0/m_NumUnknowns );
  vnl_lbfgsb minimizer(cost);
  vnl_vector<double> l; l.set_size(m_NumUnknowns); l.fill(0);
  vnl_vector<long> bound_selection; bound_selection.set_size(m_NumUnknowns); bound_selection.fill(1);
  minimizer.set_bound_selection(bound_selection);
  minimizer.set_lower_bound(l);
  minimizer.set_projected_gradient_tolerance(m_GradientTolerance);

  if (m_Regularization==VnlCostFunction::REGU::MSM)
    MITK_INFO << "Regularization type: MSM";
  else if (m_Regularization==VnlCostFunction::REGU::VARIANCE)
    MITK_INFO << "Regularization type: VARIANCE";
  else if (m_Regularization==VnlCostFunction::REGU::LASSO)
    MITK_INFO << "Regularization type: LASSO";
  else if (m_Regularization==VnlCostFunction::REGU::VOXEL_VARIANCE)
    MITK_INFO << "Regularization type: VOXEL_VARIANCE";
  else if (m_Regularization==VnlCostFunction::REGU::GROUP_LASSO)
    MITK_INFO << "Regularization type: GROUP_LASSO";
  else if (m_Regularization==VnlCostFunction::REGU::GROUP_VARIANCE)
    MITK_INFO << "Regularization type: GROUP_VARIANCE";
  else if (m_Regularization==VnlCostFunction::REGU::NONE)
    MITK_INFO << "Regularization type: NONE";

  cost.m_Lambda = m_Lambda;
  MITK_INFO << "Using regularization factor of " << cost.m_Lambda << " (λ: " << m_Lambda << ")";

  MITK_INFO << "Fitting fibers";
  minimizer.set_trace(m_Verbose);

  minimizer.set_max_function_evals(m_MaxIterations);
  minimizer.minimize(m_Weights);

  std::vector< double > weights;
  if (m_FilterOutliers)
  {
    for (auto w : m_Weights)
      weights.push_back(w);
    std::sort(weights.begin(), weights.end());
    MITK_INFO << "Setting upper weight bound to " << weights.at(m_NumUnknowns*0.99);
    vnl_vector<double> u; u.set_size(m_NumUnknowns); u.fill(weights.at(m_NumUnknowns*0.99));
    minimizer.set_upper_bound(u);
    bound_selection.fill(2);
    minimizer.set_bound_selection(bound_selection);
    minimizer.minimize(m_Weights);
    weights.clear();
  }

  for (auto w : m_Weights)
    weights.push_back(w);
  std::sort(weights.begin(), weights.end());

  m_MeanWeight = m_Weights.mean();
  m_MedianWeight = weights.at(m_NumUnknowns*0.5);
  m_MinWeight = weights.at(0);
  m_MaxWeight = weights.at(m_NumUnknowns-1);

  MITK_INFO << "*************************";
  MITK_INFO << "Weight statistics";
  MITK_INFO << "Sum: " << m_Weights.sum();
  MITK_INFO << "Mean: " << m_MeanWeight;
  MITK_INFO << "1% quantile: " << weights.at(m_NumUnknowns*0.01);
  MITK_INFO << "5% quantile: " << weights.at(m_NumUnknowns*0.05);
  MITK_INFO << "25% quantile: " << weights.at(m_NumUnknowns*0.25);
  MITK_INFO << "Median: " << m_MedianWeight;
  MITK_INFO << "75% quantile: " << weights.at(m_NumUnknowns*0.75);
  MITK_INFO << "95% quantile: " << weights.at(m_NumUnknowns*0.95);
  MITK_INFO << "99% quantile: " << weights.at(m_NumUnknowns*0.99);
  MITK_INFO << "Min: " << m_MinWeight;
  MITK_INFO << "Max: " << m_MaxWeight;
  MITK_INFO << "*************************";
  MITK_INFO << "NumEvals: " << minimizer.get_num_evaluations();
  MITK_INFO << "NumIterations: " << minimizer.get_num_iterations();
  MITK_INFO << "Residual cost: " << minimizer.get_end_error();
  m_RMSE = cost.S->get_rms_error(m_Weights);
  MITK_INFO << "Final RMSE: " << m_RMSE;

  clock.Stop();
  int h = clock.GetTotal()/3600;
  int m = ((int)clock.GetTotal()%3600)/60;
  int s = (int)clock.GetTotal()%60;
  MITK_INFO << "Optimization took " << h << "h, " << m << "m and " << s << "s";

  MITK_INFO << "Weighting fibers";
  m_RmsDiffPerBundle.set_size(m_Tractograms.size());
  std::streambuf *old = cout.rdbuf(); // <-- save
  std::stringstream ss;
  std::cout.rdbuf (ss.rdbuf());
  if (m_FitIndividualFibers)
  {
    unsigned int fiber_count = 0;
    for (unsigned int bundle=0; bundle<m_Tractograms.size(); bundle++)
    {
      vnl_vector< double > temp_weights;
      temp_weights.set_size(m_Weights.size());
      temp_weights.copy_in(m_Weights.data_block());

      for (unsigned int i=0; i<m_Tractograms.at(bundle)->GetNumFibers(); i++)
      {
        m_Tractograms.at(bundle)->SetFiberWeight(i, m_Weights[fiber_count]);
        temp_weights[fiber_count] = 0;

        ++fiber_count;
      }
      double d_rms = cost.S->get_rms_error(temp_weights) - m_RMSE;
      m_RmsDiffPerBundle[bundle] = d_rms;
    }
  }
  else
  {
    for (unsigned int i=0; i<m_Tractograms.size(); ++i)
    {
      vnl_vector< double > temp_weights;
      temp_weights.set_size(m_Weights.size());
      temp_weights.copy_in(m_Weights.data_block());
      temp_weights[i] = 0;
      double d_rms = cost.S->get_rms_error(temp_weights) - m_RMSE;
      m_RmsDiffPerBundle[i] = d_rms;

      m_Tractograms.at(i)->SetFiberWeights(m_Weights[i]);
    }
  }
  std::cout.rdbuf (old);

  // transform back
  A *= m_MeanSignal/100.0;
  b *= m_MeanSignal/100.0;

  MITK_INFO << "Generating output images ...";
  if (m_PeakImage.IsNotNull())
    GenerateOutputPeakImages();
  else if (m_DiffImage.IsNotNull())
    GenerateOutputDiffImages();
  else if (m_ScalarImage.IsNotNull())
    GenerateOutputScalarImages();

  m_Coverage = m_Coverage/m_MeanSignal;
  m_Overshoot = m_Overshoot/m_MeanSignal;

  MITK_INFO << std::fixed << "Coverage: " << setprecision(2) << 100.0*m_Coverage << "%";
  MITK_INFO << std::fixed << "Overshoot: " << setprecision(2) << 100.0*m_Overshoot << "%";
}

void FitFibersToImageFilter::GenerateOutputDiffImages()
{
  VectorImgType::PixelType pix; pix.SetSize(m_DiffImage->GetVectorLength()); pix.Fill(0);
  itk::ImageDuplicator< VectorImgType >::Pointer duplicator = itk::ImageDuplicator< VectorImgType >::New();
  duplicator->SetInputImage(m_DiffImage);
  duplicator->Update();
  m_UnderexplainedImageDiff = duplicator->GetOutput();
  m_UnderexplainedImageDiff->FillBuffer(pix);

  duplicator->SetInputImage(m_UnderexplainedImageDiff);
  duplicator->Update();
  m_OverexplainedImageDiff = duplicator->GetOutput();
  m_OverexplainedImageDiff->FillBuffer(pix);

  duplicator->SetInputImage(m_OverexplainedImageDiff);
  duplicator->Update();
  m_ResidualImageDiff = duplicator->GetOutput();
  m_ResidualImageDiff->FillBuffer(pix);

  duplicator->SetInputImage(m_ResidualImageDiff);
  duplicator->Update();
  m_FittedImageDiff = duplicator->GetOutput();
  m_FittedImageDiff->FillBuffer(pix);

  vnl_vector<double> fitted_b; fitted_b.set_size(b.size());
  cost.S->multiply(m_Weights, fitted_b);

  itk::ImageRegionIterator<VectorImgType> it1 = itk::ImageRegionIterator<VectorImgType>(m_DiffImage, m_DiffImage->GetLargestPossibleRegion());
  itk::ImageRegionIterator<VectorImgType> it2 = itk::ImageRegionIterator<VectorImgType>(m_FittedImageDiff, m_FittedImageDiff->GetLargestPossibleRegion());
  itk::ImageRegionIterator<VectorImgType> it3 = itk::ImageRegionIterator<VectorImgType>(m_ResidualImageDiff, m_ResidualImageDiff->GetLargestPossibleRegion());
  itk::ImageRegionIterator<VectorImgType> it4 = itk::ImageRegionIterator<VectorImgType>(m_UnderexplainedImageDiff, m_UnderexplainedImageDiff->GetLargestPossibleRegion());
  itk::ImageRegionIterator<VectorImgType> it5 = itk::ImageRegionIterator<VectorImgType>(m_OverexplainedImageDiff, m_OverexplainedImageDiff->GetLargestPossibleRegion());

  m_MeanSignal = 0;
  m_Coverage = 0;
  m_Overshoot = 0;

  while( !it2.IsAtEnd() )
  {
    itk::Index<3> idx3 = it2.GetIndex();
    VectorImgType::PixelType original_pix =it1.Get();
    VectorImgType::PixelType fitted_pix =it2.Get();
    VectorImgType::PixelType residual_pix =it3.Get();
    VectorImgType::PixelType underexplained_pix =it4.Get();
    VectorImgType::PixelType overexplained_pix =it5.Get();

    int num_nonzero_g = 0;
    double original_mean = 0;
    for (int g=0; g<dim_four_size; ++g)
    {
      if( m_SignalModel->GetGradientDirection(g).GetNorm()>=mitk::eps )
      {
        original_mean += original_pix[g];
        ++num_nonzero_g;
      }
    }
    original_mean /= num_nonzero_g;

    for (int g=0; g<dim_four_size; ++g)
    {
      unsigned int linear_index = idx3[0] + sz_x*idx3[1] + sz_x*sz_y*idx3[2] + sz_x*sz_y*sz_z*g;

      fitted_pix[g] = fitted_b[linear_index] + original_mean;
      residual_pix[g] = original_pix[g] - fitted_b[linear_index] - original_mean;

      if (residual_pix[g]<0)
      {
        overexplained_pix[g] = residual_pix[g];
        m_Coverage += b[linear_index] + original_mean;
        m_Overshoot -= residual_pix[g];
      }
      else if (residual_pix[g]>=0)
      {
        underexplained_pix[g] = residual_pix[g];
        m_Coverage += fitted_b[linear_index] + original_mean;
      }
      m_MeanSignal += b[linear_index] + original_mean;
    }

    it2.Set(fitted_pix);
    it3.Set(residual_pix);
    it4.Set(underexplained_pix);
    it5.Set(overexplained_pix);

    ++it1;
    ++it2;
    ++it3;
    ++it4;
    ++it5;
  }
}

void FitFibersToImageFilter::GenerateOutputScalarImages()
{
  itk::ImageDuplicator< DoubleImgType >::Pointer duplicator = itk::ImageDuplicator< DoubleImgType >::New();
  duplicator->SetInputImage(m_ScalarImage);
  duplicator->Update();
  m_UnderexplainedImageScalar = duplicator->GetOutput();
  m_UnderexplainedImageScalar->FillBuffer(0);

  duplicator->SetInputImage(m_UnderexplainedImageScalar);
  duplicator->Update();
  m_OverexplainedImageScalar = duplicator->GetOutput();
  m_OverexplainedImageScalar->FillBuffer(0);

  duplicator->SetInputImage(m_OverexplainedImageScalar);
  duplicator->Update();
  m_ResidualImageScalar = duplicator->GetOutput();
  m_ResidualImageScalar->FillBuffer(0);

  duplicator->SetInputImage(m_ResidualImageScalar);
  duplicator->Update();
  m_FittedImageScalar = duplicator->GetOutput();
  m_FittedImageScalar->FillBuffer(0);

  vnl_vector<double> fitted_b; fitted_b.set_size(b.size());
  cost.S->multiply(m_Weights, fitted_b);

  itk::ImageRegionIterator<DoubleImgType> it1 = itk::ImageRegionIterator<DoubleImgType>(m_ScalarImage, m_ScalarImage->GetLargestPossibleRegion());
  itk::ImageRegionIterator<DoubleImgType> it2 = itk::ImageRegionIterator<DoubleImgType>(m_FittedImageScalar, m_FittedImageScalar->GetLargestPossibleRegion());
  itk::ImageRegionIterator<DoubleImgType> it3 = itk::ImageRegionIterator<DoubleImgType>(m_ResidualImageScalar, m_ResidualImageScalar->GetLargestPossibleRegion());
  itk::ImageRegionIterator<DoubleImgType> it4 = itk::ImageRegionIterator<DoubleImgType>(m_UnderexplainedImageScalar, m_UnderexplainedImageScalar->GetLargestPossibleRegion());
  itk::ImageRegionIterator<DoubleImgType> it5 = itk::ImageRegionIterator<DoubleImgType>(m_OverexplainedImageScalar, m_OverexplainedImageScalar->GetLargestPossibleRegion());

  m_MeanSignal = 0;
  m_Coverage = 0;
  m_Overshoot = 0;

  while( !it2.IsAtEnd() )
  {
    itk::Index<3> idx3 = it2.GetIndex();
    DoubleImgType::PixelType original_pix =it1.Get();
    DoubleImgType::PixelType fitted_pix =it2.Get();
    DoubleImgType::PixelType residual_pix =it3.Get();
    DoubleImgType::PixelType underexplained_pix =it4.Get();
    DoubleImgType::PixelType overexplained_pix =it5.Get();

    unsigned int linear_index = idx3[0] + sz_x*idx3[1] + sz_x*sz_y*idx3[2];

    fitted_pix = fitted_b[linear_index];
    residual_pix = original_pix - fitted_pix;

    if (residual_pix<0)
    {
      overexplained_pix = residual_pix;
      m_Coverage += b[linear_index];
      m_Overshoot -= residual_pix;
    }
    else if (residual_pix>=0)
    {
      underexplained_pix = residual_pix;
      m_Coverage += fitted_b[linear_index];
    }
    m_MeanSignal += b[linear_index];

    it2.Set(fitted_pix);
    it3.Set(residual_pix);
    it4.Set(underexplained_pix);
    it5.Set(overexplained_pix);

    ++it1;
    ++it2;
    ++it3;
    ++it4;
    ++it5;
  }
}

VnlCostFunction::REGU FitFibersToImageFilter::GetRegularization() const
{
  return m_Regularization;
}

void FitFibersToImageFilter::SetRegularization(const VnlCostFunction::REGU &Regularization)
{
  m_Regularization = Regularization;
}

void FitFibersToImageFilter::GenerateOutputPeakImages()
{
  itk::ImageDuplicator< PeakImgType >::Pointer duplicator = itk::ImageDuplicator< PeakImgType >::New();
  duplicator->SetInputImage(m_PeakImage);
  duplicator->Update();
  m_UnderexplainedImage = duplicator->GetOutput();
  m_UnderexplainedImage->FillBuffer(0.0);

  duplicator->SetInputImage(m_UnderexplainedImage);
  duplicator->Update();
  m_OverexplainedImage = duplicator->GetOutput();
  m_OverexplainedImage->FillBuffer(0.0);

  duplicator->SetInputImage(m_OverexplainedImage);
  duplicator->Update();
  m_ResidualImage = duplicator->GetOutput();
  m_ResidualImage->FillBuffer(0.0);

  duplicator->SetInputImage(m_ResidualImage);
  duplicator->Update();
  m_FittedImage = duplicator->GetOutput();
  m_FittedImage->FillBuffer(0.0);

  vnl_vector<double> fitted_b; fitted_b.set_size(b.size());
  cost.S->multiply(m_Weights, fitted_b);

  for (unsigned int r=0; r<b.size(); r++)
  {
    itk::Index<4> idx4;
    unsigned int linear_index = r;
    idx4[0] = linear_index % sz_x; linear_index /= sz_x;
    idx4[1] = linear_index % sz_y; linear_index /= sz_y;
    idx4[2] = linear_index % sz_z; linear_index /= sz_z;
    int peak_id = linear_index % dim_four_size;

    if (peak_id<dim_four_size-1)
    {
      vnl_vector_fixed<float,3> peak_dir;

      idx4[3] = peak_id*3;
      peak_dir[0] = m_PeakImage->GetPixel(idx4);
      idx4[3] += 1;
      peak_dir[1] = m_PeakImage->GetPixel(idx4);
      idx4[3] += 1;
      peak_dir[2] = m_PeakImage->GetPixel(idx4);

      peak_dir.normalize();
      peak_dir *= fitted_b[r];

      idx4[3] = peak_id*3;
      m_FittedImage->SetPixel(idx4, peak_dir[0]);

      idx4[3] += 1;
      m_FittedImage->SetPixel(idx4, peak_dir[1]);

      idx4[3] += 1;
      m_FittedImage->SetPixel(idx4, peak_dir[2]);
    }
  }

  m_MeanSignal = 0;
  m_Coverage = 0;
  m_Overshoot = 0;

  itk::Index<4> idx4;
  for (idx4[0]=0; idx4[0]<sz_x; ++idx4[0])
    for (idx4[1]=0; idx4[1]<sz_y; ++idx4[1])
      for (idx4[2]=0; idx4[2]<sz_z; ++idx4[2])
      {
        itk::Index<3> idx3; idx3[0] = idx4[0]; idx3[1] = idx4[1]; idx3[2] = idx4[2];
        if (m_MaskImage.IsNotNull() && m_MaskImage->GetPixel(idx3)==0)
          continue;

        vnl_vector_fixed<float,3> peak_dir;
        vnl_vector_fixed<float,3> fitted_dir;
        vnl_vector_fixed<float,3> overshoot_dir;
        for (idx4[3]=0; idx4[3]<(itk::IndexValueType)m_PeakImage->GetLargestPossibleRegion().GetSize(3); ++idx4[3])
        {
          peak_dir[idx4[3]%3] = m_PeakImage->GetPixel(idx4);
          fitted_dir[idx4[3]%3] = m_FittedImage->GetPixel(idx4);
          m_ResidualImage->SetPixel(idx4, m_PeakImage->GetPixel(idx4) - m_FittedImage->GetPixel(idx4));

          if (idx4[3]%3==2)
          {
            m_MeanSignal += peak_dir.magnitude();

            itk::Index<4> tidx= idx4;
            if (peak_dir.magnitude()>fitted_dir.magnitude())
            {
              m_Coverage += fitted_dir.magnitude();
              m_UnderexplainedImage->SetPixel(tidx, peak_dir[2]-fitted_dir[2]); tidx[3]--;
              m_UnderexplainedImage->SetPixel(tidx, peak_dir[1]-fitted_dir[1]); tidx[3]--;
              m_UnderexplainedImage->SetPixel(tidx, peak_dir[0]-fitted_dir[0]);
            }
            else
            {
              overshoot_dir[0] = fitted_dir[0]-peak_dir[0];
              overshoot_dir[1] = fitted_dir[1]-peak_dir[1];
              overshoot_dir[2] = fitted_dir[2]-peak_dir[2];
              m_Coverage += peak_dir.magnitude();
              m_Overshoot += overshoot_dir.magnitude();
              m_OverexplainedImage->SetPixel(tidx, overshoot_dir[2]); tidx[3]--;
              m_OverexplainedImage->SetPixel(tidx, overshoot_dir[1]); tidx[3]--;
              m_OverexplainedImage->SetPixel(tidx, overshoot_dir[0]);
            }
          }
        }
      }
}

void FitFibersToImageFilter::GetClosestPeak(itk::Index<4> idx, PeakImgType::Pointer peak_image , vnl_vector_fixed<float,3> fiber_dir, int& id, double& w, double& peak_mag )
{
  int m_NumDirs = peak_image->GetLargestPossibleRegion().GetSize()[3]/3;
  vnl_vector_fixed<float,3> out_dir; out_dir.fill(0);
  float angle = 0.9;

  for (int i=0; i<m_NumDirs; i++)
  {
    vnl_vector_fixed<float,3> dir;
    idx[3] = i*3;
    dir[0] = peak_image->GetPixel(idx);
    idx[3] += 1;
    dir[1] = peak_image->GetPixel(idx);
    idx[3] += 1;
    dir[2] = peak_image->GetPixel(idx);

    float mag = dir.magnitude();
    if (mag<mitk::eps)
      continue;

    float a = fabs(dot_product(dir, fiber_dir))/mag;
    if (a>angle)
    {
      angle = a;
      w = angle;
      peak_mag = mag;
      id = i;
    }
  }
}

std::vector<mitk::FiberBundle::Pointer> FitFibersToImageFilter::GetTractograms() const
{
  return m_Tractograms;
}

void FitFibersToImageFilter::SetTractograms(const std::vector<mitk::FiberBundle::Pointer> &tractograms)
{
  m_Tractograms = tractograms;
}

void FitFibersToImageFilter::SetSignalModel(mitk::DiffusionSignalModel<> *SignalModel)
{
  m_SignalModel = SignalModel;
}

}


/*
 * Copyright Nick Thompson, 2020
 * Use, modification and distribution are subject to the
 * Boost Software License, Version 1.0. (See accompanying file
 * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 */
#ifndef BOOST_MATH_QUADRATURE_WAVELET_TRANSFORMS_HPP
#define BOOST_MATH_QUADRATURE_WAVELET_TRANSFORMS_HPP
#include <boost/math/special_functions/daubechies_wavelet.hpp>
#include <boost/math/quadrature/trapezoidal.hpp>

namespace boost::math::quadrature {

template<class F, typename Real, int p>
class daubechies_wavelet_transform
{
public:
    daubechies_wavelet_transform(F f, int grid_refinements = -1, Real tol = 100*std::numeric_limits<Real>::epsilon(),
    int max_refinements = 12) : f_{f}, psi_(grid_refinements), tol_{tol}, max_refinements_{max_refinements}
    {}

    daubechies_wavelet_transform(F f, boost::math::daubechies_wavelet<Real, p> wavelet, Real tol = 100*std::numeric_limits<Real>::epsilon(),
    int max_refinements = 12) : f_{f}, psi_{wavelet}, tol_{tol}, max_refinements_{max_refinements}
    {}

    auto operator()(Real s, Real t)->decltype(std::declval<F>()(std::declval<Real>())) const
    {
        using std::sqrt;
        using std::abs;
        using boost::math::quadrature::trapezoidal;
        auto g = [&] (Real u) {
            return f_(s*u+t)*psi_(u);
        };
        auto [a,b] = psi_.support();
        return sqrt(abs(s))*trapezoidal(g, a, b, tol_, max_refinements_);
    }

private:
    F f_;
    boost::math::daubechies_wavelet<Real, p> psi_;
    Real tol_;
    int max_refinements_;
};

}
#endif


// Copyright (c) 2014-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "chainparams.h"
#include "validation.h"
#include "net.h"

#include "test/test_bitcoin.h"

#include <boost/signals2/signal.hpp>
#include <boost/test/unit_test.hpp>

BOOST_FIXTURE_TEST_SUITE(main_tests, TestingSetup)

BOOST_AUTO_TEST_CASE(block_subsidy_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    const auto& consensus = chainParams->GetConsensus();

    BOOST_CHECK_EQUAL(GetBlockSubsidy(0, consensus), COIN);
    BOOST_CHECK_EQUAL(GetBlockSubsidy(1, consensus), 1 * COIN);
    BOOST_CHECK_EQUAL(GetBlockSubsidy(2, consensus), COIN);
    BOOST_CHECK_EQUAL(GetBlockSubsidy(consensus.nSubsidyHalvingInterval + 1, consensus), COIN / 2);
    BOOST_CHECK_EQUAL(GetBlockSubsidy(2 * consensus.nSubsidyHalvingInterval + 1, consensus), COIN / 4);
    const int offset = 2 * consensus.nSubsidyHalvingInterval;
    const int nHeight = offset + consensus.nSubsidyHalvingIntervalPhase2 + 1;
    BOOST_CHECK_EQUAL(GetBlockSubsidy(nHeight, consensus), COIN / 8);
}

BOOST_AUTO_TEST_CASE(subsidy_limit_test)
{
    const auto chainParams = CreateChainParams(CBaseChainParams::MAIN);
    CAmount nSum = 0;
    for (int nHeight = 0; nHeight < 23500; nHeight += 100) {
        CAmount nSubsidy = GetBlockSubsidy(nHeight, chainParams->GetConsensus());
        BOOST_CHECK(nSubsidy <= COIN);
        nSum += nSubsidy * 100;
        BOOST_CHECK(MoneyRange(nSum));
    }
    BOOST_CHECK_EQUAL(nSum, 23500 * COIN);
}

bool ReturnFalse() { return false; }
bool ReturnTrue() { return true; }

BOOST_AUTO_TEST_CASE(test_combiner_all)
{
    boost::signals2::signal<bool (), CombinerAll> Test;
    BOOST_CHECK(Test());
    Test.connect(&ReturnFalse);
    BOOST_CHECK(!Test());
    Test.connect(&ReturnTrue);
    BOOST_CHECK(!Test());
    Test.disconnect(&ReturnFalse);
    BOOST_CHECK(Test());
    Test.disconnect(&ReturnTrue);
    BOOST_CHECK(Test());
}
BOOST_AUTO_TEST_SUITE_END()


/*
 Copyright (C) 2016 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/test/unit_test.hpp>
#include <oret/datapaths.hpp>
#include <oret/fileutilities.hpp>
#include <oret/toplevelfixture.hpp>

#include <ored/model/crossassetmodeldata.hpp>
#include <ored/utilities/correlationmatrix.hpp>

using namespace QuantLib;
using namespace QuantExt;
using namespace boost::unit_test_framework;
using namespace std;
using namespace ore;
using namespace ore::data;

using ore::test::TopLevelFixture;

namespace {

boost::shared_ptr<vector<boost::shared_ptr<IrLgmData>>> irConfigsData() {

    // Create three instances
    boost::shared_ptr<IrLgmData> lgmData1(new data::IrLgmData());
    boost::shared_ptr<IrLgmData> lgmData2(new data::IrLgmData());
    boost::shared_ptr<IrLgmData> lgmData3(new data::IrLgmData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> terms = {"5Y", "2Y", "6M"};
    vector<std::string> strikes = {"ATM", "ATM", "ATM"};

    // First instance
    lgmData1->ccy() = "EUR";

    lgmData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData1->reversionType() = parseReversionType("HULLWHITE");
    lgmData1->volatilityType() = parseVolatilityType("HAGAN");
    std::vector<Time> hTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> hValues = {1.0, 2.0, 3.0, 4.0};
    std::vector<Time> aTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> aValues = {1.0, 2.0, 3.0, 4.0};

    lgmData1->calibrateH() = false;
    lgmData1->hParamType() = parseParamType("PIECEWISE");

    lgmData1->hTimes() = hTimes;

    lgmData1->hValues() = hValues;

    lgmData1->calibrateA() = false;
    lgmData1->aParamType() = parseParamType("PIECEWISE");
    lgmData1->aTimes() = aTimes;
    lgmData1->aValues() = aValues;
    lgmData1->shiftHorizon() = 1.0;

    lgmData1->optionExpiries() = expiries;
    lgmData1->optionTerms() = terms;
    lgmData1->optionStrikes() = strikes;

    lgmData1->scaling() = 1.0;

    // Second instance
    lgmData2->ccy() = "USD";

    lgmData2->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData2->reversionType() = parseReversionType("HULLWHITE");
    lgmData2->volatilityType() = parseVolatilityType("HAGAN");

    lgmData2->calibrateH() = false;
    lgmData2->hParamType() = parseParamType("PIECEWISE");
    lgmData2->hTimes() = hTimes;
    lgmData2->hValues() = hValues;

    lgmData2->calibrateA() = false;
    lgmData2->aParamType() = parseParamType("PIECEWISE");

    lgmData2->aTimes() = aTimes;
    lgmData2->aValues() = aValues;
    lgmData2->shiftHorizon() = 1.0;

    lgmData2->optionExpiries() = expiries;
    lgmData2->optionTerms() = terms;
    lgmData2->optionStrikes() = strikes;

    lgmData2->scaling() = 1.0;

    // Third instance
    lgmData3->ccy() = "JPY";

    lgmData3->calibrationType() = parseCalibrationType("BOOTSTRAP");
    lgmData3->reversionType() = parseReversionType("HULLWHITE");
    lgmData3->volatilityType() = parseVolatilityType("HAGAN");

    lgmData3->calibrateH() = false;
    lgmData3->hParamType() = parseParamType("PIECEWISE");
    lgmData3->hTimes() = hTimes;
    lgmData3->hValues() = hValues;

    lgmData3->calibrateA() = false;
    lgmData3->aParamType() = parseParamType("PIECEWISE");

    lgmData3->aTimes() = aTimes;
    lgmData3->aValues() = aValues;
    lgmData3->shiftHorizon() = 1.0;

    lgmData3->optionExpiries() = expiries;
    lgmData3->optionTerms() = terms;
    lgmData3->optionStrikes() = strikes;

    lgmData3->scaling() = 1.0;

    boost::shared_ptr<vector<boost::shared_ptr<IrLgmData>>> lgmDataVector(new vector<boost::shared_ptr<IrLgmData>>);
    *lgmDataVector = {lgmData1, lgmData2, lgmData3};
    return lgmDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<InfDkData>>> infConfigsData() {

    // Create two instances
    boost::shared_ptr<InfDkData> infDkData1(new data::InfDkData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> strikes = {"0.03", "0.03", "0.03"};

    // First instance
    infDkData1->infIndex() = "EUHICPXT";
    infDkData1->currency() = "EUR";
    infDkData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    infDkData1->reversionType() = parseReversionType("HULLWHITE");
    infDkData1->volatilityType() = parseVolatilityType("HAGAN");
    std::vector<Time> hTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> hValues = {1.0, 2.0, 3.0, 4.0};
    std::vector<Time> aTimes = {1.0, 2.0, 3.0, 4.0};
    std::vector<Real> aValues = {1.0, 2.0, 3.0, 4.0};

    infDkData1->calibrateH() = false;
    infDkData1->hParamType() = parseParamType("PIECEWISE");

    infDkData1->hTimes() = hTimes;
    infDkData1->hValues() = hValues;

    infDkData1->calibrateA() = false;
    infDkData1->aParamType() = parseParamType("PIECEWISE");
    infDkData1->aTimes() = aTimes;
    infDkData1->aValues() = aValues;
    infDkData1->shiftHorizon() = 1.0;

    infDkData1->optionExpiries() = expiries;
    infDkData1->optionStrikes() = strikes;
    infDkData1->scaling() = 1.0;

    boost::shared_ptr<vector<boost::shared_ptr<InfDkData>>> infDkDataVector(new vector<boost::shared_ptr<InfDkData>>);
    *infDkDataVector = {infDkData1};
    return infDkDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<FxBsData>>> fxConfigsData() {

    // Create two instances
    boost::shared_ptr<FxBsData> fxBsData1(new data::FxBsData());
    boost::shared_ptr<FxBsData> fxBsData2(new data::FxBsData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> strikes = {"ATMF", "ATMF", "ATMF"};
    std::vector<Time> times = {1.0, 2.0, 3.0, 4.0};

    // First instance
    fxBsData1->foreignCcy() = "USD";
    fxBsData1->domesticCcy() = "EUR";
    fxBsData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    fxBsData1->calibrateSigma() = true;
    fxBsData1->sigmaParamType() = parseParamType("CONSTANT");
    fxBsData1->sigmaTimes() = times;
    fxBsData1->optionExpiries() = expiries;
    fxBsData1->optionStrikes() = strikes;

    // Second instance
    fxBsData2->foreignCcy() = "JPY";
    fxBsData2->domesticCcy() = "EUR";
    fxBsData2->calibrationType() = parseCalibrationType("BOOTSTRAP");
    fxBsData2->calibrateSigma() = true;
    fxBsData2->sigmaParamType() = parseParamType("CONSTANT");
    fxBsData2->sigmaTimes() = times;
    fxBsData2->optionExpiries() = expiries;
    fxBsData2->optionStrikes() = strikes;

    boost::shared_ptr<vector<boost::shared_ptr<FxBsData>>> fxBsDataVector(new vector<boost::shared_ptr<FxBsData>>);
    *fxBsDataVector = {fxBsData1, fxBsData2};
    return fxBsDataVector;
}

boost::shared_ptr<vector<boost::shared_ptr<EqBsData>>> eqConfigsData() {

    // Create two instances
    boost::shared_ptr<EqBsData> eqBsData1(new data::EqBsData());

    vector<std::string> expiries = {"1Y", "2Y", "36M"};
    vector<std::string> strikes = {"ATMF", "ATMF", "ATMF"};
    std::vector<Time> times = {1.0, 2.0, 3.0, 4.0};

    // First instance
    eqBsData1->eqName() = "SP5";
    eqBsData1->currency() = "EUR";
    eqBsData1->calibrationType() = parseCalibrationType("BOOTSTRAP");
    eqBsData1->calibrateSigma() = true;
    eqBsData1->sigmaParamType() = parseParamType("CONSTANT");
    eqBsData1->sigmaTimes() = times;
    eqBsData1->optionExpiries() = expiries;
    eqBsData1->optionStrikes() = strikes;

    boost::shared_ptr<vector<boost::shared_ptr<EqBsData>>> eqBsDataVector(new vector<boost::shared_ptr<EqBsData>>);
    *eqBsDataVector = {eqBsData1};
    return eqBsDataVector;
}

boost::shared_ptr<data::CrossAssetModelData> crossAssetData() {

    boost::shared_ptr<data::CrossAssetModelData> crossAssetData(new data::CrossAssetModelData());

    crossAssetData->domesticCurrency() = "EUR";
    crossAssetData->currencies() = {"EUR", "USD", "JPY"}; // need to check how to set this up
    crossAssetData->equities() = {"SP5"};
    crossAssetData->infIndices() = {"EUHICPXT"};
    crossAssetData->irConfigs() = *irConfigsData();
    crossAssetData->fxConfigs() = *fxConfigsData();
    crossAssetData->eqConfigs() = *eqConfigsData();
    crossAssetData->infConfigs() = *infConfigsData();

    CorrelationMatrixBuilder cmb;
    cmb.addCorrelation("IR:EUR", "IR:USD", 1.0);
    cmb.addCorrelation("IR:EUR", "IR:JPY", 1.0);
    cmb.addCorrelation("IR:USD", "IR:JPY", 1.0);
    cmb.addCorrelation("INF:EUHICPXT", "IR:EUR", 1.0);

    crossAssetData->correlations() = cmb.data();

    crossAssetData->bootstrapTolerance() = 0.001;

    return crossAssetData;
}

// Fixture to remove output files
class F : public TopLevelFixture {
public:
    F() {}
    ~F() { clearOutput(TEST_OUTPUT_PATH); }
};
} // namespace

BOOST_FIXTURE_TEST_SUITE(OREDataTestSuite, TopLevelFixture)

BOOST_FIXTURE_TEST_SUITE(CrossAssetModelDataTests, F)

BOOST_AUTO_TEST_CASE(testToXMLFromXML) {

    BOOST_TEST_MESSAGE("Testing toXML/fromXML...");

    data::CrossAssetModelData data = *crossAssetData();
    XMLDocument OutDoc;

    XMLNode* simulationNode = OutDoc.allocNode("Simulation");
    OutDoc.appendNode(simulationNode);

    XMLNode* crossAssetModelNode = data.toXML(OutDoc);
    XMLUtils::appendNode(simulationNode, crossAssetModelNode);

    std::string filename = TEST_OUTPUT_FILE("simulationtest.xml");
    OutDoc.toFile(filename);

    data::CrossAssetModelData newData;
    newData.fromFile(filename);

    BOOST_CHECK(data == newData);

    newData.irConfigs() = {};
    BOOST_CHECK(data != newData);
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


// MIT License
//
// Copyright (c) 2018 Michal Siedlaczek
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

//! \file
//! \author     Michal Siedlaczek
//! \copyright  MIT License

#pragma once

#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <nonstd/expected.hpp>
#include <pstl/algorithm>
#include <pstl/execution>
#include <tbb/blocked_range.h>
#include <tbb/parallel_reduce.h>

#include <irkit/algorithm/max.hpp>
#include <irkit/coding/copy.hpp>
#include <irkit/index.hpp>
#include <irkit/io.hpp>
#include <irkit/list/standard_block_list.hpp>

namespace ts = type_safe;

namespace irk::index {

namespace detail {

    using boost::accumulators::stats;
    using boost::accumulators::tag::max;
    using boost::accumulators::tag::mean;
    using boost::accumulators::tag::variance;
    using boost::accumulators::accumulator_set;

    struct StatTuple {
        double max{};
        double mean{};
        double var{};
    };

    template<class Fun>
    std::vector<float>
    unzip(const std::vector<StatTuple>& stat_vector, Fun field)
    {
        std::vector<float> unzipped(stat_vector.size());
        std::transform(
            std::execution::par_unseq,
            stat_vector.begin(),
            stat_vector.end(),
            unzipped.begin(),
            field);
        return unzipped;
    }

    void write_table(const std::vector<float>& vec, const path& file)
    {
        std::ofstream of(file.c_str());
        io::write_vector(vec, of);
    }

    class ScoreStatsFn {
    public:
        ScoreStatsFn(fs::path dir_path, std::string name)
            : dir_(std::move(dir_path)),
              max_scores_path_(dir_ / (name + ".max")),
              mean_scores_path_(dir_ / (name + ".mean")),
              var_scores_path_(dir_ / (name + ".var")),
              name_(std::move(name))
        {}

        template<class RandomAccessRng, class UnaryFun>
        void
        operator()(const RandomAccessRng& term_ids, UnaryFun scored_postings)
        {
            std::vector<StatTuple> stat_vec(term_ids.size());
            std::transform(
                std::execution::par_unseq,
                term_ids.begin(),
                term_ids.end(),
                stat_vec.begin(),
                [&](auto term_id) {
                    accumulator_set<float, stats<mean, variance, max>> acc;
                    for (const auto& posting : scored_postings(term_id)) {
                        acc(posting.score());
                    }
                    return StatTuple{boost::accumulators::max(acc),
                                     boost::accumulators::mean(acc),
                                     boost::accumulators::variance(acc)};
                });

            std::vector<float> max_vec =
                unzip(stat_vec, [](const auto& tuple) { return tuple.max; });
            std::vector<float> mean_vec =
                unzip(stat_vec, [](const auto& tuple) { return tuple.mean; });
            std::vector<float> var_vec =
                unzip(stat_vec, [](const auto& tuple) { return tuple.var; });

            write_table(max_vec, max_scores_path_);
            write_table(mean_vec, mean_scores_path_);
            write_table(var_vec, var_scores_path_);
        }

    private:
        boost::filesystem::path dir_;
        boost::filesystem::path max_scores_path_;
        boost::filesystem::path mean_scores_path_;
        boost::filesystem::path var_scores_path_;
        std::string name_;
    };

    template<class ScoreTag, class DataSourceT>
    class ScoreFn {
    public:
        ScoreFn(fs::path dir_path, std::string type_name, int bits)
            : bits(bits),
              name(fmt::format("{}-{}", type_name, bits)),
              dir(std::move(dir_path)),
              scores_path(dir / (name + ".scores")),
              score_offsets_path(dir / (name + ".offsets")),
              score_max_path(dir / (name + ".maxscore")),
              type_name(std::move(type_name))
        {}

        std::pair<double, double> min_max(const inverted_index_view& index)
        {
            std::pair<double, double> initial = {
                std::numeric_limits<double>::max(),
                std::numeric_limits<double>::lowest()};
            auto reduce_pair = [](auto lhs, auto rhs) {
                auto p = std::make_pair<double, double>(
                    irk::min_val(lhs.first, rhs.first),
                    irk::max_val(lhs.second, rhs.second));
                return p;
            };
            auto reduce_range = [&index, &reduce_pair](
                                    const tbb::blocked_range<term_id_t>& range,
                                    const auto& init) {
                auto min = std::numeric_limits<double>::max();
                auto max = std::numeric_limits<double>::lowest();
                for (auto term = range.begin(); term != range.end(); ++term) {
                    auto scorer = index.term_scorer(term, ScoreTag{});
                    for (const auto& posting : index.postings(term)) {
                        double score =
                            scorer(posting.document(), posting.payload());
                        min = irk::min_val(min, score);
                        max = irk::max_val(max, score);
                    }
                }
                return reduce_pair(std::make_pair(min, max), init);
            };
            auto [min, max] = tbb::parallel_reduce(
                tbb::blocked_range<term_id_t>(0, index.term_count()),
                initial,
                reduce_range,
                reduce_pair);
            return {irk::min_val(min, 0.0), irk::max_val(max, 0.0)};
        }

        nonstd::expected<void, std::string> operator()()
        {
            auto type = index::QuantizationProperties::parse_type(type_name);
            if (not type) {
                return nonstd::make_unexpected(type.error());
            }
            auto props = index::Properties::read(dir);

            namespace ba = boost::accumulators;
            using stat_accumulator = ba::
                accumulator_set<double, ba::stats<ba::tag::mean, ba::tag::variance, ba::tag::max>>;
            auto source = DataSourceT::from(dir);
            if (not source) {
                return source.get_unexpected();
            }
            inverted_index_view index(source.value());

            auto log = spdlog::get("score");

            int64_t offset = 0;
            std::ofstream sout(scores_path.c_str());
            std::ofstream offout(score_offsets_path.c_str());
            std::ofstream maxout(score_max_path.c_str());
            std::vector<std::size_t> offsets;
            std::vector<std::uint32_t> max_scores;
            std::vector<std::uint32_t> exp_scores;
            std::vector<std::uint32_t> var_scores;
            offsets.reserve(index.term_count());
            max_scores.reserve(index.term_count());

            if (log) {
                log->info("Calculating max score");
            }
            const auto [min_score, max_score] = min_max(index);
            if (log) {
                log->info("Max score: {}; Min score: {}", max_score, min_score);
            }

            LinearQuantizer quantize(
                RealRange(min_score, max_score),
                IntegralRange(1, (1u << bits) - 1u));

            for (term_id_t term_id = 0; term_id < index.terms().size();
                 term_id++) {
                offsets.push_back(offset);
                ir::Standard_Block_List_Builder<std::uint32_t,
                                                stream_vbyte_codec<std::uint32_t>,
                                                false>
                    list_builder(index.skip_block_size());
                stat_accumulator acc;
                auto scorer = index.term_scorer(term_id, ScoreTag{});
                for (const auto& posting : index.postings(term_id)) {
                    double score =
                        scorer(posting.document(), posting.payload());
                    list_builder.add(quantize(score));
                    acc(score);
                }
                max_scores.push_back(ba::max(acc));
                offset += list_builder.write(sout);
            }
            const auto offset_table = irk::build_offset_table<>(offsets);
            offout << offset_table;
            const auto maxscore_table =
                irk::build_compact_table<uint32_t>(max_scores);
            maxout << maxscore_table;

            index::QuantizationProperties qprops;
            qprops.type = type.value();
            qprops.nbits = bits;
            qprops.min = min_score;
            qprops.max = max_score;
            props.quantized_scores[name] = qprops;
            index::Properties::write(props, dir);

            return nonstd::expected<void, std::string>();
        }

    private:
        const int bits;
        const std::string name;
        const fs::path dir;
        const fs::path scores_path;
        const fs::path score_offsets_path;
        const fs::path score_max_path;
        const std::string type_name;
    };

}  // namespace detail

template<class ScoreTag, class DataSourceT>
nonstd::expected<void, std::string>
score_index(const fs::path& dir_path, unsigned int bits)
{
    std::string name(ScoreTag{});
    detail::ScoreFn<ScoreTag, DataSourceT> fn(dir_path, name, bits);
    fn();
    return nonstd::expected<void, std::string>();
}

template<class ScoreTag, class Index, class DataSourceT>
nonstd::expected<void, std::string>
calc_score_stats(const fs::path& dir_path)
{
    auto source = DataSourceT::from(dir_path);
    if (not source) {
        return source.get_unexpected();
    }
    Index index(&source.value());
    detail::ScoreStatsFn fn{dir_path, std::string{ScoreTag{}}};
    std::vector<term_id_t> term_ids(index.term_count());
    std::iota(term_ids.begin(), term_ids.end(), term_id_t{0});
    fn(term_ids, [&](term_id_t id) {
        return index.postings(id).scored(index.term_scorer(id, ScoreTag{}));
    });
    return nonstd::expected<void, std::string>();
}

}  // namespace irk::index


#include <numpy_eigen/boost_python_headers.hpp>
#include <aslam/cameras/PinholeProjection.hpp>
#include <aslam/cameras/OmniProjection.hpp>
#include <aslam/cameras/ExtendedUnifiedProjection.hpp>
#include <aslam/cameras/DoubleSphereProjection.hpp>
#include <aslam/cameras/NoDistortion.hpp>
#include <aslam/cameras/EquidistantDistortion.hpp>
#include <aslam/cameras/FovDistortion.hpp>
#include <aslam/cameras/RadialTangentialDistortion.hpp>
#include <boost/serialization/nvp.hpp>
#include <sm/python/boost_serialization_pickle.hpp>
#include <sm/python/unique_register_ptr_to_python.hpp>
//#include <aslam/python/ExportDesignVariableAdapter.hpp>
//#include <aslam/backend/DesignVariableAdapter.hpp>
//#include <aslam/python/ExportAPrioriInformationError.hpp>

using namespace boost::python;
using namespace aslam::cameras;
//using namespace aslam::python;

// template<typename DERIVED_Y>
// void distort(const Eigen::MatrixBase<DERIVED_Y> & y) const;
template<typename D>
Eigen::Vector2d distort1(const D * distortion, Eigen::Vector2d k) {
  distortion->distort(k);
  return k;
}

// template<typename DERIVED_Y, typename DERIVED_JY>
// void distort(const Eigen::MatrixBase<DERIVED_Y> & y, const Eigen::MatrixBase<DERIVED_JY> & outJy) const;

template<typename D>
boost::python::tuple distort2(const D * distortion, Eigen::Vector2d k) {
  Eigen::Matrix2d J;
  distortion->distort(k, J);
  return boost::python::make_tuple(k, J);
}

// template<typename DERIVED>
// void undistort(const Eigen::MatrixBase<DERIVED> & y) const;
template<typename D>
Eigen::Vector2d undistort1(const D * distortion, Eigen::Vector2d k) {

  distortion->undistort(k);
  return k;
}

// template<typename DERIVED, typename DERIVED_JY>
// void undistort(const Eigen::MatrixBase<DERIVED> & y,
// 					const Eigen::MatrixBase<DERIVED_JY> & outJy) const;
template<typename D>
boost::python::tuple undistort2(const D * distortion, Eigen::Vector2d k) {
  Eigen::Matrix2d J;
  distortion->undistort(k, J);
  return boost::python::make_tuple(k, J);
}

// template<typename DERIVED_Y, typename DERIVED_JD>
// void distortParameterJacobian(const Eigen::MatrixBase<DERIVED_Y> & imageY, const Eigen::MatrixBase<DERIVED_JD> & outJd) const;
template<typename D>
Eigen::MatrixXd distortParameterJacobian(const D * distortion,
                                         Eigen::Vector2d imageY) {
  Eigen::MatrixXd J;
  distortion->distortParameterJacobian(imageY, J);
  return J;
}

template<typename C>
Eigen::VectorXd e2k(const C * camera, Eigen::Vector3d const & p) {
  Eigen::VectorXd k;
  camera->euclideanToKeypoint(p, k);
  return k;
}

template<typename C>
boost::python::tuple e2kJp(const C * camera, Eigen::Vector3d const & p) {
  Eigen::MatrixXd Jp;
  Eigen::VectorXd k;
  bool isValid = camera->euclideanToKeypoint(p, k, Jp);
  return boost::python::make_tuple(k, Jp, isValid);
}

template<typename C>
Eigen::VectorXd eh2k(const C * camera, Eigen::Vector4d const & p) {
  Eigen::VectorXd k;
  camera->homogeneousToKeypoint(p, k);
  return k;
}

template<typename C>
boost::python::tuple eh2kJp(const C * camera, Eigen::Vector4d const & p) {
  Eigen::MatrixXd Jp;
  Eigen::VectorXd k;
  bool valid = camera->homogeneousToKeypoint(p, k, Jp);
  return boost::python::make_tuple(k, Jp, valid);
}

template<typename C>
Eigen::Vector3d k2e(const C * camera, Eigen::VectorXd const & k) {
  Eigen::Vector3d p;
  camera->keypointToEuclidean(k, p);
  return p;
}

template<typename C>
boost::python::tuple k2eJk(const C * camera, Eigen::VectorXd const & k) {
  Eigen::MatrixXd Jk;
  Eigen::Vector3d p;
  bool valid = camera->keypointToEuclidean(k, p, Jk);
  return boost::python::make_tuple(p, Jk, valid);
}

template<typename C>
Eigen::Vector4d k2eh(const C * camera, Eigen::VectorXd const & k) {
  Eigen::VectorXd ph;
  camera->keypointToHomogeneous(k, ph);
  return ph;
}

template<typename C>
boost::python::tuple k2ehJk(const C * camera, Eigen::VectorXd const & k) {
  Eigen::MatrixXd Jk;
  Eigen::Vector4d p;
  bool valid = camera->keypointToHomogeneous(k, p, Jk);
  return boost::python::make_tuple(p, Jk, valid);
}

template<typename T>
Eigen::MatrixXd getParameters(T * D) {

  Eigen::MatrixXd P;
  D->getParameters(P);
  return P;

}

// template <typename C>
// void exportGenericProjectionDesignVariable(std::string name) {

// 	// export the corresponding DV
// 	exportDesignVariableAdapter< C >( name + "DesignVariable");

// }

template<typename C, typename T>
void exportGenericProjectionFunctions(T & proj) {
  proj.def("keypointDimension", &C::keypointDimension,
           "Get the dimension of the keypoint type");
  proj.def(
      "isProjectionInvertible",
      &C::isProjectionInvertible,
      "Is the sensor model invertible? Ususally this is only true for a range/bearing sensor.");
  proj.def(
      "createRandomKeypoint",
      &C::createRandomKeypoint,
      "Create a valid, random keypoint. This is useful for unit testing and experiments.");
  proj.def(
      "createRandomVisiblePoint",
      &C::createRandomVisiblePoint,
      "Create a valid point in space visible by the camera.\np = createRandomVisiblePoint(depth).");
  proj.def(
      "euclideanToKeypoint", &e2k<C>,
      "Map a 3x1 Euclidean point to a keypoint.\nk = euclideanToKeypoint(p)");
  proj.def(
      "euclideanToKeypointJp",
      &e2kJp<C>,
      "Map a 3x1 Euclidean point to a keypoint and get the Jacobian of the mapping with respect to small changes in the point.\n(k, Jp) = euclideanToKeypoint(p)");
  proj.def(
      "homogeneousToKeypoint",
      &eh2k<C>,
      "Map a 4x1 homogeneous Euclidean point to a keypoint.\nk = euclideanToKeypoint(p)");
  proj.def(
      "homogeneousToKeypointJp",
      &eh2kJp<C>,
      "Map a 4x1 homogeneous Euclidean point to a keypoint and get the Jacobian of the mapping with respect to small changes in the point.\n(k, Jp) = homogeneousToKeypointJp(p)");
  proj.def(
      "keypointToHomogeneous",
      &k2eh<C>,
      "Map a keypoint to a 4x1 homogeneous Euclidean point.\np = keypointToHomogeneous(k)");
  proj.def(
      "keypointToHomogeneousJk",
      &eh2kJp<C>,
      "Map a keypoint to a 4x1 homogeneous Euclidean point and get the Jacobian of the mapping with respect to small changes in the keypoint.\n(p, Jk) = keypointToHomogeneousJk(k)");
  proj.def(
      "keypointToEuclidean", &k2e<C>,
      "Map a keypoint to a 3x1 Euclidean point.\np = keypointToEuclidean(k)");
  proj.def(
      "keypointToEuclideanJk",
      &e2kJp<C>,
      "Map a keypoint to a 3x1 Euclidean point and get the Jacobian of the mapping with respect to small changes in the keypoint.\n(p, Jk) = keypointToEuclideanJk(k)");
  /*
  proj.def("isValid", &C::template isValid<Eigen::VectorXd>);
           */
  proj.def("setParameters", &C::setParameters,
           "Set the Parameter Vector/Matrix");
  proj.def("getParameters", &getParameters<C>,
           "Get the Parameter Vector/Matrix");
}

template<typename C, typename T>
void exportGenericDistortionFunctions(T & dist) {
  dist.def("distort", &distort1<C>, "Apply distortion to a keypoint");
  dist.def(
      "distortJd", &distort2<C>,
      "Apply distortion and get the Jacobian with respect to the input point");
  dist.def("undistort", &undistort1<C>, "Undistort a keypoint");
  dist.def(
      "undistort",
      &undistort2<C>,
      "Undistort a keypoint and get the Jacobian with respect to small chages in the input (distorted) keypoint.");
  dist.def(
      "distortParameterJacobian",
      &distortParameterJacobian<C>,
      "Get the Jacobian of the distortion operation with respect to small changes in the distortion parameters");
  dist.def("setParameters", &C::setParameters,
           "Set the Parameter Vector/Matrix");
  dist.def("getParameters", &getParameters<C>,
           "Get the Parameter Vector/Matrix");
}

void exportFovDistortionFunctions() {
  class_<FovDistortion, boost::shared_ptr<FovDistortion> > distortion("FovDistortion", init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<FovDistortion> >();

  exportGenericDistortionFunctions<FovDistortion>(distortion);
  distortion.def(init<double>(("FovDistortion(double w)")));
  distortion.def("w", &FovDistortion::w);
}

void exportRadialTangentialDistortionFunctions() {

  class_<RadialTangentialDistortion,
      boost::shared_ptr<RadialTangentialDistortion> > rtDistortion(
      "RadialTangentialDistortion", init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<RadialTangentialDistortion> >();

  exportGenericDistortionFunctions<RadialTangentialDistortion>(rtDistortion);

  rtDistortion.def(
      init<double, double, double, double>(
          ("RadialTangentialDistortion(double k1, double k2, double p1, double p2)")));
  rtDistortion.def("k1", &RadialTangentialDistortion::k1);
  rtDistortion.def("k2", &RadialTangentialDistortion::k2);
  rtDistortion.def("p1", &RadialTangentialDistortion::p1);
  rtDistortion.def("p2", &RadialTangentialDistortion::p2);
  //rtDistortion.def("getLinesPack", &RadialTangentialDistortion::getLinesPack);
  //rtDistortion.def("distortionError", &RadialTangentialDistortion::distortionError);

  //exportGenericProjectionDesignVariable<RadialTangentialDistortion>("RadialTangentialDistortion");
}

template<typename D>
void exportOmniProjection(std::string name) {

  D & (OmniProjection<D>::*omnidistortion)() = &OmniProjection<D>::distortion;

  class_<OmniProjection<D>, boost::shared_ptr<OmniProjection<D> > > omniProjection(
      name.c_str(), init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<OmniProjection<D> > >();

  omniProjection.def(init<>((name + "(distortion_t distortion)").c_str())).def(
      init<double, double, double, double, double, int, int, D>(
          (name
              + "(double xi, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV, distortion_t distortion)")
              .c_str())).def(
      init<double, double, double, double, double, int, int>(
          (name
              + "(double xi, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV)")
              .c_str()))
  /// \brief the xi parameter that controls the spherical projection.
      .def("xi", &OmniProjection<D>::xi)
  /// \brief The horizontal focal length in pixels.
      .def("fu", &OmniProjection<D>::fu)
  /// \brief The vertical focal length in pixels.
      .def("fv", &OmniProjection<D>::fv)
  /// \brief The horizontal image center in pixels.
      .def("cu", &OmniProjection<D>::cu)
  /// \brief The vertical image center in pixels.
      .def("cv", &OmniProjection<D>::cv)
  /// \brief The horizontal resolution in pixels.
      .def("ru", &OmniProjection<D>::ru)
  /// \brief The vertical resolution in pixels.
      .def("rv", &OmniProjection<D>::rv).def("focalLengthCol",
                                             &OmniProjection<D>::focalLengthCol)
      .def("focalLengthRow", &OmniProjection<D>::focalLengthRow).def(
      "opticalCenterCol", &OmniProjection<D>::opticalCenterCol).def(
      "opticalCenterRow", &OmniProjection<D>::opticalCenterRow).def(
      "distortion", omnidistortion, return_internal_reference<>()).def(
      "setDistortion", &OmniProjection<D>::setDistortion).def_pickle(
      sm::python::pickle_suite<OmniProjection<D> >());
  exportGenericProjectionFunctions<OmniProjection<D> >(omniProjection);
  //exportGenericProjectionDesignVariable< OmniProjection<D> >(name);
}

template<typename D>
void exportExtendedUnifiedProjection(std::string name) {

  D & (ExtendedUnifiedProjection<D>::*distortion)() = &ExtendedUnifiedProjection<D>::distortion;

  class_<ExtendedUnifiedProjection<D>, boost::shared_ptr<ExtendedUnifiedProjection<D> > > extendedUnifiedProjection(
      name.c_str(), init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<ExtendedUnifiedProjection<D> > >();

  extendedUnifiedProjection.def(init<>((name + "(distortion_t distortion)").c_str())).def(
      init<double, double, double, double, double, double, int, int, D>(
          (name
              + "(double alpha, double beta, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV, distortion_t distortion)")
              .c_str())).def(
      init<double, double, double, double, double, double, int, int>(
          (name
              + "(double alpha, double beta, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV)")
              .c_str()))
  /// \brief the alpha parameter that relates ellipsoid and pinhole projection.
      .def("alpha", &ExtendedUnifiedProjection<D>::alpha)
  /// \brief the beta parameter that controls the ellipsoid shape.
      .def("beta", &ExtendedUnifiedProjection<D>::beta)
  /// \brief The horizontal focal length in pixels.
      .def("fu", &ExtendedUnifiedProjection<D>::fu)
  /// \brief The vertical focal length in pixels.
      .def("fv", &ExtendedUnifiedProjection<D>::fv)
  /// \brief The horizontal image center in pixels.
      .def("cu", &ExtendedUnifiedProjection<D>::cu)
  /// \brief The vertical image center in pixels.
      .def("cv", &ExtendedUnifiedProjection<D>::cv)
  /// \brief The horizontal resolution in pixels.
      .def("ru", &ExtendedUnifiedProjection<D>::ru)
  /// \brief The vertical resolution in pixels.
      .def("rv", &ExtendedUnifiedProjection<D>::rv).def("focalLengthCol",
                                             &ExtendedUnifiedProjection<D>::focalLengthCol)
      .def("focalLengthRow", &ExtendedUnifiedProjection<D>::focalLengthRow).def(
      "opticalCenterCol", &ExtendedUnifiedProjection<D>::opticalCenterCol).def(
      "opticalCenterRow", &ExtendedUnifiedProjection<D>::opticalCenterRow).def(
      "distortion", distortion, return_internal_reference<>()).def(
      "setDistortion", &ExtendedUnifiedProjection<D>::setDistortion).def_pickle(
      sm::python::pickle_suite<ExtendedUnifiedProjection<D> >());
  exportGenericProjectionFunctions<ExtendedUnifiedProjection<D> >(extendedUnifiedProjection);
  //exportGenericProjectionDesignVariable< ExtendedUnifiedProjection<D> >(name);
}

template<typename D>
void exportDoubleSphereProjection(std::string name) {

  D & (DoubleSphereProjection<D>::*distortion)() = &DoubleSphereProjection<D>::distortion;

  class_<DoubleSphereProjection<D>, boost::shared_ptr<DoubleSphereProjection<D> > > doubleSphereProjection(
      name.c_str(), init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<DoubleSphereProjection<D> > >();

  doubleSphereProjection.def(init<>((name + "(distortion_t distortion)").c_str())).def(
      init<double, double, double, double, double, double, int, int, D>(
          (name
              + "(double xi, double alpha, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV, distortion_t distortion)")
              .c_str())).def(
      init<double, double, double, double, double, double, int, int>(
          (name
              + "(double xi, double alpha, double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV)")
              .c_str()))
  /// \brief the xi parameter corresponding to distance between spheres.
      .def("xi", &DoubleSphereProjection<D>::xi)
  /// \brief the alpha parameter that relates second sphere and pinhole projection.
      .def("alpha", &DoubleSphereProjection<D>::alpha)
  /// \brief The horizontal focal length in pixels.
      .def("fu", &DoubleSphereProjection<D>::fu)
  /// \brief The vertical focal length in pixels.
      .def("fv", &DoubleSphereProjection<D>::fv)
  /// \brief The horizontal image center in pixels.
      .def("cu", &DoubleSphereProjection<D>::cu)
  /// \brief The vertical image center in pixels.
      .def("cv", &DoubleSphereProjection<D>::cv)
  /// \brief The horizontal resolution in pixels.
      .def("ru", &DoubleSphereProjection<D>::ru)
  /// \brief The vertical resolution in pixels.
      .def("rv", &DoubleSphereProjection<D>::rv).def("focalLengthCol",
                                             &DoubleSphereProjection<D>::focalLengthCol)
      .def("focalLengthRow", &DoubleSphereProjection<D>::focalLengthRow).def(
      "opticalCenterCol", &DoubleSphereProjection<D>::opticalCenterCol).def(
      "opticalCenterRow", &DoubleSphereProjection<D>::opticalCenterRow).def(
      "distortion", distortion, return_internal_reference<>()).def(
      "setDistortion", &DoubleSphereProjection<D>::setDistortion).def_pickle(
      sm::python::pickle_suite<DoubleSphereProjection<D> >());
  exportGenericProjectionFunctions<DoubleSphereProjection<D> >(doubleSphereProjection);
  //exportGenericProjectionDesignVariable< DoubleSphereProjection<D> >(name);
}

template<typename D>
void exportPinholeProjection(std::string name) {

  typename PinholeProjection<D>::distortion_t & (PinholeProjection<D>::*distortion1)() = &PinholeProjection<D>::distortion;

  class_<PinholeProjection<D>, boost::shared_ptr<PinholeProjection<D> > > pinholeProjection(
      name.c_str(), init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<PinholeProjection<D> > >();

  pinholeProjection.def(init<>((name + "(distortion_t distortion)").c_str()))
      .def(
      init<double, double, double, double, int, int, D>(
          (name
              + "(double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV, distortion_t distortion)")
              .c_str())).def(
      init<double, double, double, double, int, int>(
          (name
              + "(double focalLengthU, double focalLengthV, double imageCenterU, double imageCenterV, int resolutionU, int resolutionV)")
              .c_str()))
  /// \brief The horizontal focal length in pixels.
      .def("fu", &PinholeProjection<D>::fu)
  /// \brief The vertical focal length in pixels.
      .def("fv", &PinholeProjection<D>::fv)
  /// \brief The horizontal image center in pixels.
      .def("cu", &PinholeProjection<D>::cu)
  /// \brief The vertical image center in pixels.
      .def("cv", &PinholeProjection<D>::cv)
  /// \brief The horizontal resolution in pixels.
      .def("ru", &PinholeProjection<D>::ru)
  /// \brief The vertical resolution in pixels.
      .def("rv", &PinholeProjection<D>::rv)
      .def("focalLengthCol", &PinholeProjection<D>::focalLengthCol)
      .def("focalLengthRow", &PinholeProjection<D>::focalLengthRow)
      .def("opticalCenterCol", &PinholeProjection<D>::opticalCenterCol)
      .def("opticalCenterRow", &PinholeProjection<D>::opticalCenterRow)
  //.def("distortion",&PinholeProjection<D>::distortion, return_internal_reference<>())
      .def("distortion", distortion1, return_internal_reference<>()).def(
      "setDistortion", &PinholeProjection<D>::setDistortion).def_pickle(
      sm::python::pickle_suite<PinholeProjection<D> >());
  exportGenericProjectionFunctions<PinholeProjection<D> >(pinholeProjection);
  //exportGenericProjectionDesignVariable< PinholeProjection<D> >(name);
}

void exportCameraProjections() {
  using namespace boost::python;
  using namespace aslam::cameras;
  //using namespace aslam::python;

  class_<NoDistortion, boost::shared_ptr<NoDistortion> > noDistortion(
      "NoDistortion", init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<NoDistortion> >();

  exportGenericDistortionFunctions<NoDistortion>(noDistortion);

  class_<EquidistantDistortion, boost::shared_ptr<EquidistantDistortion> > equidistantDistortion(
      "EquidistantDistortion", init<>());
  sm::python::unique_register_ptr_to_python<boost::shared_ptr<EquidistantDistortion> >();

  equidistantDistortion.def(init<double, double, double, double>());
  exportGenericDistortionFunctions<EquidistantDistortion>(
      equidistantDistortion);
  //exportGenericProjectionDesignVariable<NoDistortion>("NoDistortion");

  exportRadialTangentialDistortionFunctions();
  exportFovDistortionFunctions();

  exportPinholeProjection<NoDistortion>("PinholeProjection");
  exportPinholeProjection<RadialTangentialDistortion>(
      "DistortedPinholeProjection");
  exportPinholeProjection<EquidistantDistortion>(
      "EquidistantPinholeProjection");
  exportPinholeProjection<FovDistortion>(
        "FovPinholeProjection");

  exportOmniProjection<NoDistortion>("OmniProjection");
  exportOmniProjection<RadialTangentialDistortion>("DistortedOmniProjection");
  exportOmniProjection<FovDistortion>("FovOmniProjection");

  exportExtendedUnifiedProjection<NoDistortion>("ExtendedUnifiedProjection");

  exportDoubleSphereProjection<NoDistortion>("DoubleSphereProjection");

  // distortion:
  // exportAPrioriInformationError<aslam::backend::DesignVariableAdapter< RadialTangentialDistortion > >("RadialTangentialDistortionAPrioriInformationError");
  // exportAPrioriInformationError<aslam::backend::DesignVariableAdapter< PinholeProjection<RadialTangentialDistortion> > >("DistortedPinholeProjectionAPrioriInformationError");
  // exportAPrioriInformationError<aslam::backend::DesignVariableAdapter< OmniProjection<RadialTangentialDistortion> > >("DistortedOmniProjectionAPrioriInformationError");

}


/*******************************************************************************
 *         Copyright 2003-2012 LASMEA UMR 6602 CNRS/U.B.P
 *         Copyright 2011-2012 LRI    UMR 8623 CNRS/Univ Paris Sud XI
 *
 *          Distributed under the Boost Software License, Version 1.0.
 *                 See accompanying file LICENSE.txt or copy at
 *                     http://www.boost.org/LICENSE_1_0.txt
 ******************************************************************************/
#ifndef NT2_TOOLBOX_INTERPOL_FUNCTIONS_GENERIC_IDX_LINEAR_HPP_INCLUDED
#define NT2_TOOLBOX_INTERPOL_FUNCTIONS_GENERIC_IDX_LINEAR_HPP_INCLUDED

#include <nt2/toolbox/interpol/functions/idx_linear.hpp>
#include <nt2/include/functions/is_nge.hpp>
#include <nt2/include/functions/is_nle.hpp>
#include <nt2/include/functions/if_else.hpp>
#include <nt2/include/functions/sx.hpp>
#include <nt2/include/functions/logical_or.hpp>
#include <nt2/include/functions/oneminus.hpp>
#include <nt2/include/functions/max.hpp>
#include <nt2/include/functions/min.hpp>
#include <nt2/include/functions/minusone.hpp>
#include <nt2/include/functions/reshape.hpp>
#include <nt2/include/functions/first_index.hpp>
#include <nt2/include/functions/last_index.hpp>
#include <nt2/include/functions/firstnonsingleton.hpp>
#include <nt2/include/functions/linear_interp.hpp>
#include <nt2/include/functions/toint.hpp>
#include <nt2/include/functions/oneplus.hpp>
#include <nt2/include/functions/floor.hpp>
#include <nt2/include/functions/sx.hpp>
#include <nt2/include/functions/along.hpp>
#include <nt2/include/constants/nan.hpp>
#include <nt2/core/container/table/table.hpp>
#include <nt2/sdk/meta/as_integer.hpp>
#include <nt2/sdk/simd/logical.hpp>
#include <boost/fusion/include/make_vector.hpp>
// #include <boost/assert.hpp>

namespace nt2 { namespace ext
{

  NT2_FUNCTOR_IMPLEMENTATION( nt2::tag::run_assign_, tag::cpu_
                            , (A0)(A1)(N1)
                            , ((ast_<A0, nt2::container::domain>))
                              ((node_<A1,nt2::tag::idx_linear_,N1,nt2::container::domain>))
                            )
  {
    typedef typename boost::proto::result_of::child_c<A1&,0>::value_type value_t;
    typedef typename boost::proto::result_of::child_c<A1&,1>::value_type   idx_t;
    typedef typename value_t::value_type                              value_type;
    typedef typename meta::as_integer<value_type>::type               index_type;
    typedef typename A0::value_type                                    sale_type;
    typedef A0&                                                      result_type;
    typedef boost::fusion::vector<value_type, value_type>                 f_type;
    typedef typename A0::extent_type                                       ext_t;

    result_type operator()(A0& yi, A1& inputs) const
    {
      //      BOOST_ASSERT_MSG(are_sx_compatible(xi, y), "Inputs dimensions are not compatible");
      const idx_t & xi   =  boost::proto::child_c<1>(inputs);
      const value_t & y     =  boost::proto::child_c<0>(inputs);
      yi.resize(inputs.extent());
      bool extrap = false;
      std::size_t dim =nt2::firstnonsingleton(xi);
      value_type extrapval1 = Nan<value_type>();
      value_type extrapval2 = extrapval1;
      choices(inputs, extrap, extrapval1, extrapval2, dim, N1());
      ext_t sizee; sizee[0] = 1;
      sizee[dim-1] = numel(xi);
      const index_type il = nt2::last_index(y, dim);
      const value_type f = value_type(nt2::first_index(y, dim));
      const value_type l = value_type(il);
      table<value_type> index = nt2::min(nt2::max(f, nt2::floor(nt2::reshape(xi, sizee))), nt2::minusone(l));
      table<index_type> i_index = nt2::toint(index);
      table<value_type> dx    =  nt2::sx(nt2::tag::minus_(), nt2::reshape(xi, sizee), index);
      yi =  linear_interp(dx, nt2::along(y, i_index, dim), nt2::along(y, nt2::min(oneplus(i_index), il), dim));
      if (!extrap) {
        yi = nt2::sx(nt2::tag::if_else_(), nt2::reshape(boost::simd::is_nge(xi, value_type(f)), sizee),extrapval1, yi);
        yi = nt2::sx(nt2::tag::if_else_(), nt2::reshape(boost::simd::is_nle(xi, value_type(l)), sizee),extrapval2, yi);
      }
      return yi;
    }
  private :
    // Two inputs y and xi
    static void choices(const A1&, bool &,  value_type&, value_type& ,
                        std::size_t&, boost::mpl::long_<2> const &)  //nothing to get
      { }
    // Three inputs y and xi and a bool or a floating or a _ (meaning nothing here)
    static void choices(const A1& inputs,
                        bool & extrap, value_type& extrapval1, value_type& extrapval2,
                        std::size_t& dim, boost::mpl::long_<3> const &)
      {
        typedef typename boost::proto::result_of::child_c<A1&,2>::type             child2;
        typedef typename meta::scalar_of<child2>::type                    cref_param_type;
        typedef typename meta::strip<cref_param_type>::type                    param_type;
        get(inputs, extrap, extrapval1, extrapval2, nt2::meta::as_<param_type>());
      }
    static void get(const A1& inputs, bool & extrap,
                    value_type&, value_type&, const nt2::meta::as_<bool> &) //get the bool
      {
        extrap =  boost::proto::child_c<2>(inputs);
      }
    static void get(const A1& inputs, bool &,  value_type& extrapval1, value_type& extrapval2,
                    const nt2::meta::as_<value_type> &)                     //get extrapval1,  compute extrapval2
      {
        extrapval1 =  extrapval2 = boost::proto::child_c<2>(inputs);
      }
//     static void get(const A1& inputs, bool &,  value_type& extrapval1, value_type& extrapval2,
//                     const nt2::meta::as_<f_type> &)                     //get extrapval1,  compute extrapval2
//       {
//         extrapval1 = boost::fusion::at_c<0>(boost::proto::child_c<2>(inputs));
//         extrapval2 = boost::fusion::at_c<1>(boost::proto::child_c<2>(inputs));
//       }
//     static void get(const A1& inputs, bool &,  value_type& extrapval1, value_type& extrapval2,
//                     const nt2::meta::as_<nt2::table<value_type> > &)                     //get extrapval1,  compute extrapval2
//       {
//         std::cout << "icitte" << std::endl;
//         std::cout << boost::proto::child_c<2>(inputs) << std::endl;
//         extrapval1 = boost::proto::child_c<2>(inputs)(begin_);
//         extrapval2 = boost::proto::child_c<2>(inputs)(begin_+1);
//       }
    static void get(const A1& inputs, bool &, value_type&, value_type&,
                    const nt2::meta::as_<nt2::container::colon_> &)        //nothing to get
      {
      }
    // Four inputs y and xi and two floating or a _ and an integer
    static void choices(const A1& inputs,
                        bool &, value_type& extrapval1, value_type& extrapval2,
                        std::size_t& dim, boost::mpl::long_<4> const &)
      {
        typedef typename boost::proto::result_of::child_c<A1&,2>::type             child2;
        typedef typename meta::scalar_of<child2>::type                    cref_param_type;
        typedef typename meta::strip<cref_param_type>::type                    param_type;
        get(inputs, extrapval1, extrapval2, dim,nt2::meta::as_<param_type>());
      }
    static void get(const A1& inputs, value_type& extrapval1, value_type& extrapval2, std::size_t&,
                    const nt2::meta::as_<value_type> &)          //get extrapval1 and extrapval2
      {
        extrapval1 = boost::proto::child_c<2>(inputs);
        extrapval2 = boost::proto::child_c<3>(inputs);
      }
    static void get(const A1& inputs, value_type&, value_type&, std::size_t& dim,
                    const nt2::meta::as_<nt2::container::colon_> &)   //get dimension
      {
        dim =  boost::proto::child_c<3>(inputs);
      }
    // Five inputs y and xi, two floating  an integer
    static void choices(const A1& inputs,
                        bool & extrap, value_type& extrapval1, value_type& extrapval2,
                        std::size_t& dim, boost::mpl::long_<5> const &) //get extrapval1 and extrapval2 and dimension
      {
        dim =  boost::proto::child_c<4>(inputs);
        typedef typename boost::proto::result_of::child_c<A1&,2>::type             child2;
        typedef typename meta::scalar_of<child2>::type                    cref_param_type;
        typedef typename meta::strip<cref_param_type>::type                    param_type;
        get(inputs, extrap, extrapval1, extrapval2, dim, nt2::meta::as_<param_type>());
      }
    static void get(const A1& inputs, bool& extrap, value_type& , value_type&, std::size_t&,
                    const nt2::meta::as_<bool> &)          //get bool
      {
        extrap = boost::proto::child_c<2>(inputs);
      }
    static void get(const A1& inputs, bool&, value_type& extrapval1, value_type& extrapval2 , std::size_t&,
                    const nt2::meta::as_<value_type> &)          //get two floating
      {
        extrapval1 = boost::proto::child_c<2>(inputs);
        extrapval2 = boost::proto::child_c<3>(inputs);
      }

  };
} }

#endif


#include <boost/numeric/interval/transc.hpp>


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_TEST_TEST_CNUMERIC_HPP
#define BOOST_HANA_TEST_TEST_CNUMERIC_HPP

#include <boost/hana/comparable.hpp>
#include <boost/hana/constant.hpp>
#include <boost/hana/core/convert.hpp>
#include <boost/hana/core/is_a.hpp>
#include <boost/hana/core/models.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/detail/std/is_integral.hpp>
#include <boost/hana/logical.hpp>
#include <boost/hana/orderable.hpp>

namespace boost { namespace hana {
    namespace test {
        template <typename T>
        struct CNumeric { using value_type = T; };

        template <typename T, T v>
        struct cnumeric_type {
            static constexpr T value = v;
            struct hana { using datatype = CNumeric<T>; };

            //! @todo Find a way to make this explicit.
            constexpr operator bool() const { return value; }
        };

        template <typename T, T v>
        constexpr cnumeric_type<T, v> cnumeric{};
    }

    //////////////////////////////////////////////////////////////////////////
    // Constant
    //////////////////////////////////////////////////////////////////////////
    template <typename T>
    struct value_impl<test::CNumeric<T>> {
        template <typename X>
        static constexpr auto apply()
        { return X::value; }
    };

    template <typename T, typename C>
    struct to_impl<test::CNumeric<T>, C, when<
        _models<Constant, C>{} &&
        detail::std::is_integral<typename C::value_type>{}
    >>
        : embedding<is_embedded<typename C::value_type, T>{}>
    {
        static_assert(detail::std::is_integral<T>{},
        "trying to convert a Constant to a test::CNumeric of a non-integral "
        "type; test::CNumeric may only hold integral types");

        template <typename X>
        static constexpr auto apply(X const&) {
            constexpr T v = hana::value<X>();
            return test::cnumeric<T, v>;
        }
    };

    //////////////////////////////////////////////////////////////////////////
    // Comparable
    //////////////////////////////////////////////////////////////////////////
    template <typename T, typename U>
    struct equal_impl<test::CNumeric<T>, test::CNumeric<U>> {
        template <typename X, typename Y>
        static constexpr auto apply(X const&, Y const&)
        { return test::cnumeric<bool, X::value == Y::value>; }
    };

    //////////////////////////////////////////////////////////////////////////
    // Orderable
    //////////////////////////////////////////////////////////////////////////
    template <typename T, typename U>
    struct less_impl<test::CNumeric<T>, test::CNumeric<U>> {
        template <typename X, typename Y>
        static constexpr auto apply(X const&, Y const&)
        { return test::cnumeric<bool, (X::value < Y::value)>; }
    };
}}

#endif // !BOOST_HANA_TEST_TEST_CNUMERIC_HPP


#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <set>
#include <boost/circular_buffer.hpp>
#include <range/v3/algorithm.hpp>
#include <range/v3/numeric.hpp>
#include <range/v3/view.hpp>
namespace views = ranges::views;
struct Memory
{
  boost::circular_buffer<int> cb_;
  std::set<boost::circular_buffer<int>> set_;
  Memory(int n) : cb_(n)
  {
    for (int i = 0; i < n; ++i) {
      cb_.push_back(0);
    }
  }
  friend std::istream &operator>>(std::istream &is, Memory &m)
  {
    std::istream_iterator<int> ib(is), ie;
    std::vector<int> v;
    std::copy(ib, ie, std::back_inserter(v));
    std::copy(v.begin(), v.end(), m.cb_.begin());
    m.set_.insert(m.cb_);
    return is;
  }
  bool run()
  {
    auto i = ranges::max_element(cb_);
    int origin = *i;
    int t = (*i) / cb_.size();
    int r = (*i) % cb_.size();
    ranges::for_each(cb_, [t](auto &a) { a += t; });

    int n = (i - cb_.begin());
    for (int j = 0; j < r; ++j) {
      cb_[n + j + 1] += 1;
    }
    *i -= origin;
    auto [ti, b] = set_.insert(cb_);
    return b;
  }
};

int main(int argc, char **argv)
{
  if (argc > 1) {
    std::ifstream ifs(argv[1]);
    Memory m(16);
    ifs >> m;
    int n = 0;
    for (; m.run(); ++n) {
    }
    std::cout << (n + 1) << std::endl;
  }
}