/*=============================================================================

  NifTK: A software platform for medical image computing.

  Copyright (c) University College London (UCL). All rights reserved.

  This software is distributed WITHOUT ANY WARRANTY; without even
  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  PURPOSE.

  See LICENSE.txt in the top level directory for details.

=============================================================================*/

#include "mitkVideoToSurface.h"
#include <niftkSequentialCpuQds.h>
#include <mitkCameraCalibrationFacade.h>
#include <mitkOpenCVMaths.h>
#include <mitkOpenCVPointTypes.h>
#include <mitkOpenCVFileIOUtils.h>
#include <cv.h>
#include <highgui.h>
#include <niftkFileHelper.h>

#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/lexical_cast.hpp>

namespace mitk {

//-----------------------------------------------------------------------------
VideoToSurface::VideoToSurface()
: m_SaveVideo(false)
, m_VideoIn("")
, m_VideoOut("")
, m_Directory("")
, m_TrackerIndex(0)
, m_ReferenceIndex(-1)
, m_InitOK(false)
, m_HaltOnVideoReadFail(true)
, m_FlipVideo(false)
, m_VisualiseTrackingStatus(false)
, m_LeftIntrinsicMatrix (new cv::Mat(3,3,CV_64FC1))
, m_LeftDistortionVector (new cv::Mat(1,4,CV_64FC1))
, m_RightIntrinsicMatrix (new cv::Mat(3,3,CV_64FC1))
, m_RightDistortionVector (new cv::Mat(1,4,CV_64FC1))
, m_RightToLeftRotationMatrix (new cv::Mat(3,3,CV_64FC1))
, m_RightToLeftTranslationVector (new cv::Mat(3,1,CV_64FC1))
, m_LeftCameraToTracker (new cv::Mat(4,4,CV_64FC1))
, m_VideoWidth(1920)
, m_VideoHeight(540)
, m_Capture(NULL)
, m_LeftWriter(NULL)
, m_AllowableTimingError (20e6) // 20 milliseconds
, m_StartFrame(0)
, m_PatchHeight (270)
, m_PatchWidth (480)
, m_PatchOriginX (720)
, m_PatchOriginY (135)
, m_HistogramMaximumDepth(200)
, m_TriangulationTolerance(5.0)
, m_EndFrame(0)
{
}

//-----------------------------------------------------------------------------
VideoToSurface::~VideoToSurface()
{

}

//-----------------------------------------------------------------------------
void VideoToSurface::SetMatcherCameraToTracker(mitk::VideoTrackerMatching::Pointer trackerMatcher)
{
  if ( ! m_InitOK )
  {
    MITK_ERROR << "Can't set trackerMatcher handeye before projector initialiastion";
    return;
  }
  trackerMatcher->SetCameraToTracker(*m_LeftCameraToTracker, m_TrackerIndex);
  return;
}
//-----------------------------------------------------------------------------
void VideoToSurface::Initialise(std::string directory,
    std::string calibrationParameterDirectory)
{
  m_InitOK = false;

  Initialise ( directory );
  try
  {
    mitk::LoadStereoCameraParametersFromDirectory
      ( calibrationParameterDirectory,
      m_LeftIntrinsicMatrix,m_LeftDistortionVector,m_RightIntrinsicMatrix,
      m_RightDistortionVector,m_RightToLeftRotationMatrix,
      m_RightToLeftTranslationVector,m_LeftCameraToTracker);
  }
  catch ( int e )
  {
    MITK_ERROR << "Failed to load camera parameters";
    m_InitOK = false;
    return;
  }

  return;
}

//-----------------------------------------------------------------------------
void VideoToSurface::Initialise(std::string directory)
{
  m_InitOK = false;
  m_Directory = directory;

  m_OutDirectory = m_Directory + niftk::GetFileSeparator() +  "Surface_Recon_Results" ;

  m_InitOK = true;
  return;
}

//-----------------------------------------------------------------------------
void VideoToSurface::FindVideoData(mitk::VideoTrackerMatching::Pointer trackerMatcher)
{
  if ( m_Capture == NULL )
  {
    m_VideoIn = niftk::FindVideoFile ( m_Directory , niftk::Basename (niftk::Basename ( trackerMatcher->GetFrameMap() )));
    if ( m_VideoIn == "" )
    {
      m_InitOK = false;
      return;
    }
    try
    {
      m_Capture = mitk::InitialiseVideoCapture(m_VideoIn, ( ! m_HaltOnVideoReadFail ));
    }
    catch (std::exception& e)
    {
      MITK_ERROR << "Caught exception " << e.what();
      exit(1);
    }
  }

  if ( m_SaveVideo || m_SaveSurfaces )
  {
    try
    {
      niftk::CreateDirAndParents ( m_OutDirectory );
    }
    catch (std::exception& e)
    {
      MITK_ERROR << "Caught exception " << e.what();
    }

    cv::Size S = cv::Size((int) m_VideoWidth, (int) m_VideoHeight );
    double fps = static_cast<double>(m_Capture->get(CV_CAP_PROP_FPS));
    double halfFPS = fps/2.0;
    m_LeftWriter = mitk::CreateVideoWriter(std::string( m_OutDirectory + niftk::GetFileSeparator() + niftk::Basename(m_VideoIn) +  "_leftchannel_reconstruction.avi").c_str(),halfFPS,S);
  }

  return;
}

//-----------------------------------------------------------------------------
void VideoToSurface::SetSaveVideo ( bool savevideo )
{
  if ( m_InitOK )
  {
    MITK_WARN << "Changing save video  state after initialisation, will need to re-initialise";
  }
  m_SaveVideo = savevideo;

  m_InitOK = false;
  return;
}

//-----------------------------------------------------------------------------
void VideoToSurface::Reconstruct(mitk::VideoTrackerMatching::Pointer trackerMatcher)
{
  if ( ! m_InitOK )
  {
    MITK_WARN << "Called project before initialise.";
    return;
  }

  ofstream out ( std::string( m_OutDirectory + niftk::GetFileSeparator() + niftk::Basename(m_VideoIn) +  "_reconstruction.txt" ) );
  out << "# Framenumber TimeStamp TimingError PatchDepthMean PatchDepthStdDev PointsInPatch MeanTriangulationError" << std::endl;
  this->FindVideoData(trackerMatcher);

  int framenumber = 0 ;
  int key = 0;

  niftk::SequentialCpuQds  featureMatcher(m_PatchWidth, m_PatchHeight);
  while ( framenumber < trackerMatcher->GetNumberOfFrames() )
  {
    cv::Mat leftImage;
    cv::Mat rightImage;
    m_Capture->read(leftImage);
    m_Capture->read(rightImage);
    if ( m_FlipVideo )
    {
      int flipMode = 0 ; // flip around the x axis
      cv::flip(leftImage,leftImage,flipMode);
      cv::flip(rightImage,rightImage,flipMode);
    }

    if ( ( m_StartFrame < m_EndFrame ) && ( framenumber < m_StartFrame || framenumber > m_EndFrame ) )
    {
      MITK_INFO << "Skipping frames " << framenumber << " and " << framenumber + 1;
      framenumber ++;
      framenumber ++;
      if  ( framenumber > m_EndFrame )
      {
        framenumber = trackerMatcher->GetNumberOfFrames();
      }
    }
    else
    {
      IplImage  IplLeftImage = leftImage;
      IplImage  IplRightImage = rightImage;
      CvMat CvLeftIntrinsicMatrix = *m_LeftIntrinsicMatrix;
      CvMat CvLeftDistortionVector = *m_LeftDistortionVector;
      CorrectDistortionInSingleImage ( CvMat(*m_LeftIntrinsicMatrix), CvMat (*m_LeftDistortionVector), IplLeftImage);
      CorrectDistortionInSingleImage ( CvMat(*m_RightIntrinsicMatrix),CvMat(*m_RightDistortionVector), IplRightImage);

      long long timingError;
      cv::Mat WorldToLeftCamera = trackerMatcher->GetCameraTrackingMatrix(framenumber, &timingError, m_TrackerIndex).inv();

      unsigned long long matrixTimeStamp;
      unsigned long long absTimingError = static_cast<unsigned long long>(std::abs(timingError));
      if ( timingError < 0 )
      {
        matrixTimeStamp = trackerMatcher->GetVideoFrameTimeStamp(framenumber) + absTimingError;
      }
      else
      {
        matrixTimeStamp = trackerMatcher->GetVideoFrameTimeStamp(framenumber) - absTimingError;
      }

      m_WorldToLeftCameraMatrices.push_back(WorldToLeftCamera);

      cv::Mat leftPatch = this->GetPatch (leftImage);
      cv::Mat rightPatch = this->GetPatch (rightImage);

      IplImage IplLeftPatch = leftPatch;
      IplImage IplRightPatch = rightPatch;
      featureMatcher.Process ( &IplLeftPatch, &IplRightPatch ) ;

      cv::Mat disparityImage = featureMatcher.CreateDisparityImage();

      std::vector < std::pair < cv::Point2d , cv::Point2d > > matchedPairs;
      std::vector < std::vector < unsigned char > > rgbValues;

      unsigned int channels = leftPatch.channels();
      unsigned int depth = leftPatch.depth();
      for ( unsigned int row = 0 ; row < m_PatchHeight ; ++row )
      {
        for ( unsigned int column = 0 ; column < m_PatchWidth ; ++column )
        {
          cv::Point2d match = featureMatcher.GetMatch ( column, row );

          if ( match.x != 0 )
          {
            //get the rgb from both images and average it.
            unsigned char *leftPatchPointer  = leftPatch.ptr<uchar>(row, column);
            unsigned char *rightPatchPointer  = rightPatch.ptr<uchar>(match.y,match.x);

            std::vector<unsigned char> rgb;
            for ( unsigned int i = 0 ; i < channels ; ++i )
            {
              rgb.push_back((leftPatchPointer[i] + rightPatchPointer[i])/2);
            }
            matchedPairs.push_back ( std::pair <cv::Point2d, cv::Point2d>
                ( cv::Point2d ( column + m_PatchOriginX, row + m_PatchOriginY ) ,
                  cv::Point2d ( match.x + m_PatchOriginX, match.y + m_PatchOriginY )) );
            rgbValues.push_back ( rgb );
          }
        }
      }
      //and preserveVectorSize to maintain corresponence between triangulatedPoints and rgbValues
      bool preserveVectorSize = true;
      std::vector < std::pair < cv::Point3d, double > > triangulatedPoints =
        mitk::TriangulatePointPairsUsingGeometry ( matchedPairs ,
            *m_LeftIntrinsicMatrix, *m_RightIntrinsicMatrix,
            *m_RightToLeftRotationMatrix, *m_RightToLeftTranslationVector,
            m_TriangulationTolerance, preserveVectorSize);

      std::vector <cv::Point3d> points;
      double meanError = 0;
      std::vector <unsigned int> histogram;
      for ( unsigned int i = 0 ; i < m_HistogramMaximumDepth + 1 ; i ++ )
      {
        histogram.push_back(0);
      }

      unsigned int goodPoints = 0;
      for ( std::vector < std::pair <cv::Point3d, double> >::iterator it = triangulatedPoints.begin() ; it <  triangulatedPoints.end() ; ++it )
      {
        if ( mitk::IsNotNaNorInf ( it->first ) )
        {
          unsigned int bin = static_cast<unsigned int> ( floor ( it->first.z + 0.5 ) );
          if ( bin > m_HistogramMaximumDepth )
          {
            bin = m_HistogramMaximumDepth;
          }
          histogram[bin]++;

          points.push_back ( it->first );

          meanError += it->second;
          goodPoints ++;
        }
      }
      meanError /= static_cast<double>( goodPoints );

      cv::Point3d stddev;

      cv::Point3d centroid = mitk::GetCentroid ( points, false, &stddev );

      this->AnnotateImage ( leftImage, disparityImage, timingError, centroid.z, stddev.z,
        goodPoints, histogram, meanError );

      if ( m_SaveVideo )
      {
        if ( m_LeftWriter != NULL )
        {
           m_LeftWriter->write(leftImage);
        }
      }

      if ( m_SaveSurfaces )
      {
        std::stringstream outname;
        outname << m_OutDirectory << niftk::GetFileSeparator() << trackerMatcher->GetVideoFrameTimeStamp(framenumber) << "_surface.txt";
        ofstream fileout;
        fileout.open(outname.str());
        if ( fileout )
        {
          for ( unsigned int i = 0 ; i <  triangulatedPoints.size() ; ++i )
          {
            if ( mitk::IsNotNaNorInf ( triangulatedPoints[i].first ) )
            {
              fileout << triangulatedPoints[i].first.x << " " <<
                 triangulatedPoints[i].first.y << " " <<
                  triangulatedPoints[i].first.z << " ";
              for ( unsigned int c = 0 ; c < channels ; ++c )
              {
                fileout << (int)rgbValues[i][c];
                if ( c < channels -1 )
                {
                  fileout << " ";
                }
              }
              fileout << std::endl;
            }
          }
        }
        fileout.close();
      }

      out << framenumber << " " << trackerMatcher->GetVideoFrameTimeStamp(framenumber) << " " << timingError << " " <<  centroid.z << " "
        << stddev.z << " " << goodPoints << " " << meanError << std::endl;
      framenumber ++;
      framenumber ++;
      leftImage.release();
      rightImage.release();
      leftPatch.release();
      rightPatch.release();
      disparityImage.release();
    }
  }
  if ( m_LeftWriter != NULL )
  {
    m_LeftWriter->release();
  }
  out.close();
}

//-----------------------------------------------------------------------------
void VideoToSurface::AnnotateImage(cv::Mat& image, const cv::Mat& patch, const long long& timingError,
     const double& patchDepthMean,
     const double& patchDepthStdDev,
     const unsigned int& patchVectorSize,
     const std::vector < unsigned int >& patchDepthHistogram,
     const double& meanTriangulationError )
{

  cv::Point2d textLocation = cv::Point2d ( m_VideoWidth - ( m_VideoWidth * 0.03 ) , m_VideoHeight * 0.07  );
  cv::Point2d location = cv::Point2d ( m_VideoWidth - ( m_VideoWidth * 0.035 ) , m_VideoHeight * 0.02  );
  cv::Point2d location1 = cv::Point2d ( m_VideoWidth - ( m_VideoWidth * 0.035 ) + ( m_VideoWidth * 0.025 ) ,
               (m_VideoHeight * 0.06) + m_VideoHeight * 0.02);
  if ( timingError < m_AllowableTimingError )
  {
    cv::rectangle ( image, location, location1  , cvScalar (0,255,0), CV_FILLED);
    cv::putText(image , "T" + boost::lexical_cast<std::string>(m_TrackerIndex), textLocation ,0,1.0, cvScalar ( 255,255,255), 4.0);
  }
  else
  {
    cv::rectangle ( image, location, location1  , cvScalar (0,0,255), CV_FILLED);
    cv::putText(image , "T" + boost::lexical_cast<std::string>(m_TrackerIndex), textLocation ,0,1.0, cvScalar ( 255,255,255), 4.0);
  }

  int patchChannels = patch.channels();
  int imageChannels = image.channels();
  int channels = patchChannels;
  if ( imageChannels < patchChannels )
  {
    channels = imageChannels;
  }
  for ( unsigned int row = 0 ; row < m_PatchHeight ; ++row )
  {
    for ( unsigned int column = 0 ; column < m_PatchWidth ; ++ column )
    {
      const unsigned char *patchPointer  = patch.ptr<uchar>(row, column);
      unsigned char *imagePointer = image.ptr<uchar>(row + m_PatchOriginY , column + m_PatchOriginX);
      for ( unsigned int i = 0 ; i < channels ; ++i )
      {
        imagePointer[i]   = patchPointer[i];
      }
    }
  }

  //a histogram up the side of the image
  double histogramXStart = 10;
  double histogramXEnd = 100;
  double histogramYStart = m_VideoHeight - 10;
  double histogramYEnd = m_VideoHeight -  patchDepthHistogram.size() - 10;

  assert ( histogramYEnd > 0 );
  unsigned int histMax = 0;

  assert ( patchDepthHistogram.size() ==  ( m_HistogramMaximumDepth + 1 ) );
  for ( unsigned int i = 0 ; i < m_HistogramMaximumDepth + 1 ; i ++ )
  {
    if ( patchDepthHistogram[i] > histMax )
    {
      histMax = patchDepthHistogram[i] ;
    }
  }
  double histogramScaler = ( histogramXEnd - histogramXStart ) / static_cast<double>(histMax);

  cv::rectangle ( image, cv::Point2d(histogramXStart,histogramYStart), cv::Point2d ( histogramXEnd, histogramYEnd ), cvScalar ( 255,255,255)  );
  for ( unsigned int i = 0 ; i < m_HistogramMaximumDepth + 1 ; i ++ )
  {
    cv::line ( image, cv::Point2d ( histogramXStart, histogramYStart - i), cv::Point2d ( histogramXStart + ( histogramScaler * static_cast<double>(patchDepthHistogram[i])) , histogramYStart - i), cvScalar ( 255,255,255));
  }

  cv::rectangle ( image, cv::Point2d ( histogramXStart-5, histogramYStart - patchDepthMean - patchDepthStdDev), cv::Point2d ( histogramXEnd + 5 , histogramYStart - patchDepthMean + patchDepthStdDev), cvScalar ( 255,0 , 0 ));

  cv::Point2d errorTextLocation = cv::Point2d ( m_VideoWidth - ( m_VideoWidth * 0.08 ) , m_VideoHeight * 0.14  );
  cv::Point2d depthTextLocation = cv::Point2d ( m_VideoWidth - ( m_VideoWidth * 0.08 ) , m_VideoHeight * 0.21  );
  cv::putText(image , "Mean Error: " + boost::lexical_cast<std::string>(meanTriangulationError), errorTextLocation ,0,0.5, cvScalar ( 255,255,255), 1.0);
  cv::putText(image , "Mean Depth: " + boost::lexical_cast<std::string>(patchDepthMean), depthTextLocation ,0,0.5, cvScalar ( 255,255,255), 1.0);

}

//-----------------------------------------------------------------------------
cv::Mat VideoToSurface::GetPatch ( const cv::Mat& image )
{
  unsigned int channels = image.channels();
  unsigned int depth = image.depth();

 // cv::Mat patch ( m_PatchHeight, m_PatchWidth, depth, channels );
  cv::Mat patch ( m_PatchHeight, m_PatchWidth, CV_8UC3 );

  for ( unsigned int row = 0 ; row < m_PatchHeight ; ++row )
  {
    for ( unsigned int column = 0 ; column < m_PatchWidth ; ++ column )
    {
      unsigned char *patchPointer  = patch.ptr<uchar>(row, column);
      const unsigned char *imagePointer = image.ptr<uchar>(row + m_PatchOriginY , column + m_PatchOriginX );
      for ( unsigned int i = 0 ; i < channels ; ++i )
      {
        unsigned char value = imagePointer[i];
        patchPointer[i] = value;
      }
    }
  }

  return patch;
}

} // end namespace


// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: Copyright 2019-2021 Heal Research

#ifndef OPERON_NNLS_TINY_OPTIMIZER
#define OPERON_NNLS_TINY_OPTIMIZER

#include <Eigen/Core>
#include "interpreter/interpreter.hpp"

#if defined(CERES_TINY_SOLVER)
#include "tiny_solver.h"
#elif defined(HAVE_CERES)
#include <ceres/tiny_solver.h>
#endif

namespace Operon {

// simple functor that wraps everything together and provides residuals
struct ResidualEvaluator {
    ResidualEvaluator(Interpreter const& interpreter, Tree const& tree, Dataset const& dataset, const Operon::Span<const Operon::Scalar> targetValues, Range const range)
        : interpreter_(interpreter)
        , tree_(tree)
        , dataset_(dataset)
        , range_(range)
        , target_(targetValues)
        , numParameters_(tree_.get().GetCoefficients().size())
    {
    }

    template <typename T>
    bool operator()(T const* const* parameters, T* residuals) const
    {
        Operon::Span<T> result(residuals, target_.size());
        GetInterpreter().Evaluate<T>(tree_.get(), dataset_.get(), range_, result, parameters[0]);
        Eigen::Map<Eigen::Array<T, Eigen::Dynamic, 1, Eigen::ColMajor>> resMap(residuals, target_.size());
        Eigen::Map<const Eigen::Array<Operon::Scalar, Eigen::Dynamic, 1, Eigen::ColMajor>> targetMap(target_.data(), target_.size());
        resMap -= targetMap.cast<T>();
        return true;
    }

    size_t NumParameters() const { return numParameters_; }
    size_t NumResiduals() const { return target_.size(); }

    Interpreter const& GetInterpreter() const { return interpreter_.get(); }

private:
    std::reference_wrapper<Interpreter const> interpreter_;
    std::reference_wrapper<Tree const> tree_;
    std::reference_wrapper<Dataset const> dataset_;
    Range range_;
    Operon::Span<const Operon::Scalar> target_;
    size_t numParameters_; // cache the number of parameters in the tree
};

// this cost function is adapted to work with both solvers from Ceres: the normal one and the tiny solver
// for this, a number of template parameters are necessary:
// - the CostFunctor is the actual functor for computing the residuals
// - the JetT type represents a dual number, the user can specify the type for the Scalar part (float, double) and the Stride (Ceres-specific)
// - the StorageOrder specifies the format of the jacobian (row-major for the big Ceres solver, column-major for the tiny solver)

template <typename CostFunctor, typename JetT, int StorageOrder = Eigen::RowMajor>
struct TinyCostFunction {
    using Scalar = typename JetT::Scalar;
    static constexpr int Stride = JetT::DIMENSION;
    static constexpr int Storage = StorageOrder;

    enum {
        NUM_RESIDUALS = Eigen::Dynamic,
        NUM_PARAMETERS = Eigen::Dynamic,
    };

    TinyCostFunction(CostFunctor const& functor)
        : functor_(functor)
    {
    }

    bool Evaluate(Scalar const* parameters, Scalar* residuals, Scalar* jacobian) const
    {
        if (jacobian == nullptr) {
            return functor_(&parameters, residuals);
        }

        auto numParameters = NumParameters();
        auto numResiduals = NumResiduals();

        // Allocate scratch space for the strided evaluation.
        Operon::Vector<JetT> input_jets(numParameters);
        Operon::Vector<JetT> output_jets(numResiduals);

        auto ptr = &input_jets[0];

        for (int j = 0; j < numParameters; ++j) {
            input_jets[j].a = parameters[j];
        }

        int current_derivative_section = 0;
        int current_derivative_section_cursor = 0;

        Eigen::Map<Eigen::Matrix<Scalar, -1, -1, StorageOrder>> jMap(jacobian, numResiduals, numParameters);

        // Evaluate all of the strides. Each stride is a chunk of the derivative to
        // evaluate, typically some size proportional to the size of the SIMD
        // registers of the CPU.
        int num_strides = static_cast<int>(
            std::ceil(static_cast<float>(numParameters) / static_cast<float>(Stride)));

        for (int pass = 0; pass < num_strides; ++pass) {
            // Set most of the jet components to zero, except for
            // non-constant #Stride parameters.
            const int initial_derivative_section = current_derivative_section;
            const int initial_derivative_section_cursor = current_derivative_section_cursor;

            int active_parameter_count = 0;
            for (int j = 0; j < numParameters; ++j) {
                input_jets[j].v.setZero();
                if (active_parameter_count < Stride && j >= current_derivative_section_cursor) {
                    input_jets[j].v[active_parameter_count] = 1.0;
                    ++active_parameter_count;
                }
            }

            if (!functor_(&ptr, &output_jets[0])) {
                return false;
            }

            active_parameter_count = 0;
            current_derivative_section = initial_derivative_section;
            current_derivative_section_cursor = initial_derivative_section_cursor;

            // Copy the pieces of the jacobians into their final place.
            for (int j = current_derivative_section_cursor; j < numParameters; ++j) {
                if (active_parameter_count < Stride) {
                    for (int k = 0; k < numResiduals; ++k) {
                        jMap(k, j) = output_jets[k].v[active_parameter_count];
                    }
                    ++active_parameter_count;
                    ++current_derivative_section_cursor;
                }
            }

            // Only copy the residuals over once (even though we compute them on every loop).
            if (pass == num_strides - 1) {
                std::transform(output_jets.begin(), output_jets.end(), residuals, [](auto const& jet) { return jet.a; });
            }
        }
        return true;
    }

    // required by tiny solver
    bool operator()(Scalar const* parameters, Scalar* residuals, Scalar* jacobian) const
    {
        return Evaluate(parameters, residuals, jacobian);
    }

    int NumResiduals() const { return functor_.NumResiduals(); }
    int NumParameters() const { return functor_.NumParameters(); }

private:
    CostFunctor functor_;
};
}

#endif


//Copyright (c) 2008-2016 Emil Dotchevski and Reverge Studios, Inc.

//Distributed under 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)

#include <boost/qvm/vec_access.hpp>
#include "test_qvm_vector.hpp"

using namespace boost::qvm;

template <int I>
void
check_idx( test_qvm::vector<V1,10> & v, float & (*f)( test_qvm::vector<V1,10> & ) )
    {
    BOOST_TEST((&A<I>(v)==&v.a[I]));
    BOOST_TEST((&f(v)==&v.a[I]));
    }

int
main()
    {
    test_qvm::vector<V1,10> v;
#define CHECK_A(i) check_idx<i>(v,A##i);
    CHECK_A(0);
    CHECK_A(1);
    CHECK_A(2);
    CHECK_A(3);
    CHECK_A(4);
    CHECK_A(5);
    CHECK_A(6);
    CHECK_A(7);
    CHECK_A(8);
    CHECK_A(9);
#undef CHECK_A
    BOOST_TEST(&A<0>(v)==&X(v));
    BOOST_TEST(&A<1>(v)==&Y(v));
    BOOST_TEST(&A<2>(v)==&Z(v));
    BOOST_TEST(&A<3>(v)==&W(v));
    return boost::report_errors();
    }


//| Copyright Inria May 2015
//| This project has received funding from the European Research Council (ERC) under
//| the European Union's Horizon 2020 research and innovation programme (grant
//| agreement No 637972) - see http://www.resibots.eu
//|
//| Contributor(s):
//|   - Jean-Baptiste Mouret (jean-baptiste.mouret@inria.fr)
//|   - Antoine Cully (antoinecully@gmail.com)
//|   - Konstantinos Chatzilygeroudis (konstantinos.chatzilygeroudis@inria.fr)
//|   - Federico Allocati (fede.allocati@gmail.com)
//|   - Vaios Papaspyros (b.papaspyros@gmail.com)
//|   - Roberto Rama (bertoski@gmail.com)
//|
//| This software is a computer library whose purpose is to optimize continuous,
//| black-box functions. It mainly implements Gaussian processes and Bayesian
//| optimization.
//| Main repository: http://github.com/resibots/limbo
//| Documentation: http://www.resibots.eu/limbo
//|
//| This software is governed by the CeCILL-C license under French law and
//| abiding by the rules of distribution of free software.  You can  use,
//| modify and/ or redistribute the software under the terms of the CeCILL-C
//| license as circulated by CEA, CNRS and INRIA at the following URL
//| "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and  rights to copy,
//| modify and redistribute granted by the license, users are provided only
//| with a limited warranty  and the software's author,  the holder of the
//| economic rights,  and the successive licensors  have only  limited
//| liability.
//|
//| In this respect, the user's attention is drawn to the risks associated
//| with loading,  using,  modifying and/or developing or reproducing the
//| software by the user in light of its specific status of free software,
//| that may mean  that it is complicated to manipulate,  and  that  also
//| therefore means  that it is reserved for developers  and  experienced
//| professionals having in-depth computer knowledge. Users are therefore
//| encouraged to load and test the software's suitability as regards their
//| requirements in conditions enabling the security of their systems and/or
//| data to be ensured and,  more generally, to use and operate it in the
//| same conditions as regards security.
//|
//| The fact that you are presently reading this means that you have had
//| knowledge of the CeCILL-C license and that you accept its terms.
//|
#ifndef LIMBO_BAYES_OPT_CBOPTIMIZER_HPP
#define LIMBO_BAYES_OPT_CBOPTIMIZER_HPP

#include <algorithm>
#include <iostream>
#include <iterator>

#include <boost/parameter/aux_/void.hpp>

#include <Eigen/Core>

#include <limbo/bayes_opt/bo_base.hpp>
#include <limbo/tools/macros.hpp>
#include <limbo/tools/random_generator.hpp>
#ifdef USE_NLOPT
#include <limbo/opt/nlopt_no_grad.hpp>
#elif defined USE_LIBCMAES
#include <limbo/opt/cmaes.hpp>
#else
#include <limbo/opt/grid_search.hpp>
#endif

namespace limbo {
    namespace defaults {
        struct bayes_opt_cboptimizer {
            BO_PARAM(int, hp_period, -1);
            BO_PARAM(bool, bounded, true);
        };
    }

    namespace experimental {
        BOOST_PARAMETER_TEMPLATE_KEYWORD(constraint_modelfun)

        namespace bayes_opt {

            using cboptimizer_signature = boost::parameter::parameters<boost::parameter::optional<limbo::tag::acquiopt>,
                boost::parameter::optional<limbo::tag::statsfun>,
                boost::parameter::optional<limbo::tag::initfun>,
                boost::parameter::optional<limbo::tag::acquifun>,
                boost::parameter::optional<limbo::tag::stopcrit>,
                boost::parameter::optional<limbo::tag::modelfun>,
                boost::parameter::optional<limbo::experimental::tag::constraint_modelfun>>;

            // clang-format off
        /**
        The classic Bayesian optimization algorithm.

        \\rst
        References: :cite:`brochu2010tutorial,Mockus2013`
        \\endrst

        This class takes the same template parameters as BoBase. It adds:
        \\rst
        +---------------------+------------+----------+---------------+
        |type                 |typedef     | argument | default       |
        +=====================+============+==========+===============+
        |acqui. optimizer     |acqui_opt_t | acquiopt | see below     |
        +---------------------+------------+----------+---------------+
        \\endrst

        The default value of acqui_opt_t is:
        - ``opt::Cmaes<Params>`` if libcmaes was found in `waf configure`
        - ``opt::NLOptNoGrad<Params, nlopt::GN_DIRECT_L_RAND>`` if NLOpt was found but libcmaes was not found
        - ``opt::GridSearch<Params>`` otherwise (please do not use this: the algorithm will not work at all!)
        */
        template <class Params,
          class A1 = boost::parameter::void_,
          class A2 = boost::parameter::void_,
          class A3 = boost::parameter::void_,
          class A4 = boost::parameter::void_,
          class A5 = boost::parameter::void_,
          class A6 = boost::parameter::void_,
          class A7 = boost::parameter::void_>
            // clang-format on
            class CBOptimizer : public limbo::bayes_opt::BoBase<Params, A1, A2, A3, A4, A5, A6> {
            public:
                // defaults
                struct defaults {
#ifdef USE_NLOPT
                    using acquiopt_t = limbo::opt::NLOptNoGrad<Params, nlopt::GN_DIRECT_L_RAND>;
#elif defined(USE_LIBCMAES)
                    using acquiopt_t = limbo::opt::Cmaes<Params>;
#else
#warning NO NLOpt, and NO Libcmaes: the acquisition function will be optimized by a grid search algorithm (which is usually bad). Please install at least NLOpt or libcmaes to use limbo!.
                    using acquiopt_t = limbo::opt::GridSearch<Params>;
#endif
                    using kf_t = limbo::kernel::Exp<Params>;
                    using mean_t = limbo::mean::Constant<Params>;
                    using constraint_model_t = limbo::model::GP<Params, kf_t, mean_t>;
                };
                /// link to the corresponding BoBase (useful for typedefs)
                using base_t = limbo::bayes_opt::BoBase<Params, A1, A2, A3, A4, A5, A6>;
                using model_t = typename base_t::model_t;

                using acquisition_function_t = typename base_t::acquisition_function_t;
                // extract the types
                using args = typename cboptimizer_signature::bind<A1, A2, A3, A4, A5, A6, A7>::type;
                using acqui_optimizer_t = typename boost::parameter::binding<args, limbo::tag::acquiopt, typename defaults::acquiopt_t>::type;

                using constraint_model_t = typename boost::parameter::binding<args, limbo::experimental::tag::constraint_modelfun, typename defaults::constraint_model_t>::type;

                /// The main function (run the Bayesian optimization algorithm)
                template <typename StateFunction, typename AggregatorFunction = FirstElem>
                void optimize(const StateFunction& sfun, const AggregatorFunction& afun = AggregatorFunction(), bool reset = true)
                {
                    _nb_constraints = StateFunction::nb_constraints();
                    _dim_out = StateFunction::dim_out();

                    this->_init(sfun, afun, reset);

                    if (!this->_observations.empty()) {
                        _split_observations();
                        _model.compute(this->_samples, _obs[0]);
                        if (_nb_constraints > 0)
                            _constraint_model.compute(this->_samples, _obs[1]);
                    }
                    else {
                        _model = model_t(StateFunction::dim_in(), StateFunction::dim_out());
                        if (_nb_constraints > 0)
                            _constraint_model = constraint_model_t(StateFunction::dim_in(), _nb_constraints);
                    }

                    acqui_optimizer_t acqui_optimizer;

                    while (!this->_stop(*this, afun)) {
                        acquisition_function_t acqui(_model, _constraint_model, this->_current_iteration);

                        auto acqui_optimization =
                            [&](const Eigen::VectorXd& x, bool g) { return acqui(x, afun, g); };
                        Eigen::VectorXd starting_point = tools::random_vector(StateFunction::dim_in(), Params::bayes_opt_cboptimizer::bounded());
                        Eigen::VectorXd new_sample = acqui_optimizer(acqui_optimization, starting_point, Params::bayes_opt_cboptimizer::bounded());
                        this->eval_and_add(sfun, new_sample);

                        this->_update_stats(*this, afun);

                        _model.add_sample(this->_samples.back(), _obs[0].back());
                        if (_nb_constraints > 0)
                            _constraint_model.add_sample(this->_samples.back(), _obs[1].back());

                        if (Params::bayes_opt_cboptimizer::hp_period() > 0
                            && (this->_current_iteration + 1) % Params::bayes_opt_cboptimizer::hp_period() == 0) {
                            _model.optimize_hyperparams();
                            if (_nb_constraints > 0)
                                _constraint_model.optimize_hyperparams();
                        }

                        this->_current_iteration++;
                        this->_total_iterations++;
                    }
                }

                /// return the best observation so far (i.e. max(f(x)))
                template <typename AggregatorFunction = FirstElem>
                const Eigen::VectorXd& best_observation(const AggregatorFunction& afun = AggregatorFunction()) const
                {
                    _dim_out = _model.dim_out();
                    _split_observations();

                    std::vector<Eigen::VectorXd> obs = _feasible_observations();
                    if (obs.size() > 0)
                        _obs[0] = obs;

                    auto rewards = std::vector<double>(_obs[0].size());
                    std::transform(_obs[0].begin(), _obs[0].end(), rewards.begin(), afun);
                    auto max_e = std::max_element(rewards.begin(), rewards.end());
                    return _obs[0][std::distance(rewards.begin(), max_e)];
                }

                /// return the best sample so far (i.e. the argmax(f(x)))
                template <typename AggregatorFunction = FirstElem>
                const Eigen::VectorXd& best_sample(const AggregatorFunction& afun = AggregatorFunction()) const
                {
                    _dim_out = _model.dim_out();
                    _split_observations();

                    std::vector<Eigen::VectorXd> obs = _feasible_observations();
                    if (obs.size() > 0)
                        _obs[0] = obs;

                    auto rewards = std::vector<double>(_obs[0].size());
                    std::transform(_obs[0].begin(), _obs[0].end(), rewards.begin(), afun);
                    auto max_e = std::max_element(rewards.begin(), rewards.end());
                    return this->_samples[std::distance(rewards.begin(), max_e)];
                }

                const model_t& model() const { return _model; } // returns model for the objective function
            protected:
                model_t _model;
                constraint_model_t _constraint_model;

                size_t _nb_constraints;
                mutable size_t _dim_out;
                mutable std::vector<std::vector<Eigen::VectorXd>> _obs;

                std::vector<Eigen::VectorXd> _feasible_observations() const
                {
                    std::vector<Eigen::VectorXd> feasible_obs;
                    for (size_t i = 0; i < _obs[0].size(); ++i) {
                        if (_obs[1][i].prod() > 0)
                            feasible_obs.push_back(_obs[0][i]);
                    }

                    return feasible_obs;
                }

                void _split_observations() const
                {
                    _obs.clear();
                    _obs.resize(2);

                    for (size_t i = 0; i < this->_observations.size(); ++i) {
                        assert(size_t(this->_observations[i].size()) == _dim_out + _nb_constraints);

                        Eigen::VectorXd vec_obj(_dim_out);
                        for (size_t j = 0; j < _dim_out; ++j)
                            vec_obj(j) = this->_observations[i](j);
                        _obs[0].push_back(vec_obj);

                        if (_nb_constraints > 0) {
                            Eigen::VectorXd vec_con(_nb_constraints);
                            for (int j = _dim_out, ind = 0; j < this->_observations[i].size(); ++j, ++ind)
                                vec_con(ind) = this->_observations[i](j);
                            _obs[1].push_back(vec_con);
                        }
                    }
                }
            };
        }
    }
}

#endif


#include <boost/test/unit_test.hpp>

#include "distribution.h"
#include "json/json_spirit_utils.h"

using namespace std;
using namespace json_spirit;

BOOST_AUTO_TEST_SUITE(distribution_tests)

#define PRECISION 1e-8

BOOST_AUTO_TEST_CASE( test_simple_distribution )
{
    BalanceMap mapBalance;

    mapBalance[CBitcoinAddress(1)] = 10;
    mapBalance[CBitcoinAddress(2)] = 30;

    DividendDistributor distributor(mapBalance);
    distributor.Distribute(100, 0.01);

    BOOST_CHECK_EQUAL(25.0, distributor.GetDistribution(CBitcoinAddress(1)).GetDividendAmount());
    BOOST_CHECK_EQUAL(75.0, distributor.GetDistribution(CBitcoinAddress(2)).GetDividendAmount());
    BOOST_CHECK_EQUAL(100.0, distributor.TotalDistributed());

    vector<Object> vTransactionOuts;
    distributor.GenerateOutputs(1, vTransactionOuts);

    BOOST_CHECK_EQUAL(1, vTransactionOuts.size());

    Object& outs = *vTransactionOuts.begin();
    BOOST_CHECK_CLOSE(25.0, find_value(outs, CAoDcoinAddress(1).ToString()).get_real(), PRECISION);
    BOOST_CHECK_CLOSE(75.0, find_value(outs, CAoDcoinAddress(2).ToString()).get_real(), PRECISION);
}

BOOST_AUTO_TEST_CASE( test_empty_distribution )
{
    BalanceMap mapBalance;

    DividendDistributor distributor(mapBalance);
    BOOST_CHECK_THROW(distributor.Distribute(100, 0.01), runtime_error);
}

BOOST_AUTO_TEST_CASE( test_off_decimal_distribution )
{
    BalanceMap mapBalance;

    mapBalance[CBitcoinAddress(1)] = 1;
    mapBalance[CBitcoinAddress(2)] = 1;
    mapBalance[CBitcoinAddress(3)] = 1;

    DividendDistributor distributor(mapBalance);
    distributor.Distribute(10, 1);

    BOOST_CHECK_CLOSE(3.3333333333, distributor.GetDistribution(CBitcoinAddress(1)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_CLOSE(3.3333333333, distributor.GetDistribution(CBitcoinAddress(2)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_CLOSE(3.3333333333, distributor.GetDistribution(CBitcoinAddress(3)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_CLOSE(10.0, distributor.TotalDistributed(), PRECISION);
}

BOOST_AUTO_TEST_CASE( test_not_enough_dividends_to_pay_fee )
{
    BalanceMap mapBalance;

    mapBalance[CBitcoinAddress(1)] = 90;
    mapBalance[CBitcoinAddress(2)] = 10;

    DividendDistributor distributor(mapBalance);
    distributor.Distribute(0.1, 0.010001);

    BOOST_CHECK_CLOSE(0.1, distributor.GetDistribution(CBitcoinAddress(1)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_EQUAL(1, distributor.GetDistributions().size());
    BOOST_CHECK_CLOSE(0.1, distributor.TotalDistributed(), PRECISION);

    distributor.Distribute(0.1, 0.01);

    BOOST_CHECK_CLOSE(0.09, distributor.GetDistribution(CBitcoinAddress(1)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_CLOSE(0.01, distributor.GetDistribution(CBitcoinAddress(2)).GetDividendAmount(), PRECISION);
    BOOST_CHECK_CLOSE(0.10, distributor.TotalDistributed(), PRECISION);
}

BOOST_AUTO_TEST_CASE( test_nobody_has_enough_funds )
{
    BalanceMap mapBalance;

    mapBalance[CBitcoinAddress(1)] = 1;
    mapBalance[CBitcoinAddress(2)] = 1;

    DividendDistributor distributor(mapBalance);

    BOOST_CHECK_THROW(distributor.Distribute(20000, 10001), runtime_error);

    BOOST_CHECK_EQUAL(0, distributor.GetDistributions().size());

    BOOST_CHECK_EQUAL(0, distributor.TotalDistributed());
}

BOOST_AUTO_TEST_CASE( test_split_transaction )
{
    BalanceMap mapBalance;

    mapBalance[CBitcoinAddress(1)] = 1;
    mapBalance[CBitcoinAddress(2)] = 8;
    mapBalance[CBitcoinAddress(3)] = 1;

    DividendDistributor distributor(mapBalance);
    distributor.Distribute(10, 0.01);

    vector<Object> vTransactionOuts;
    vector<Object>::iterator iTransaction;
    Object outs;

    distributor.GenerateOutputs(1, vTransactionOuts);
    BOOST_CHECK_EQUAL(1, vTransactionOuts.size());

    iTransaction = vTransactionOuts.begin();
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(1).ToString()).get_real(), PRECISION);
    BOOST_CHECK_CLOSE(8.0, find_value(outs, CAoDcoinAddress(2).ToString()).get_real(), PRECISION);
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(3).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(3, outs.size());

    distributor.GenerateOutputs(2, vTransactionOuts);
    BOOST_CHECK_EQUAL(2, vTransactionOuts.size());

    iTransaction = vTransactionOuts.begin();
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(1).ToString()).get_real(), PRECISION);
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(3).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(2, outs.size());

    iTransaction++;
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(8.0, find_value(outs, CAoDcoinAddress(2).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(1, outs.size());

    distributor.GenerateOutputs(3, vTransactionOuts);
    BOOST_CHECK_EQUAL(3, vTransactionOuts.size());

    iTransaction = vTransactionOuts.begin();
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(1).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(1, outs.size());

    iTransaction++;
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(8.0, find_value(outs, CAoDcoinAddress(2).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(1, outs.size());

    iTransaction++;
    outs = *iTransaction;
    BOOST_CHECK_CLOSE(1.0, find_value(outs, CAoDcoinAddress(3).ToString()).get_real(), PRECISION);
    BOOST_CHECK_EQUAL(1, outs.size());

    BOOST_CHECK_THROW(distributor.GenerateOutputs(4, vTransactionOuts), runtime_error);
    BOOST_CHECK_THROW(distributor.GenerateOutputs(0, vTransactionOuts), runtime_error);
    BOOST_CHECK_THROW(distributor.GenerateOutputs(-1, vTransactionOuts), runtime_error);
}

BOOST_AUTO_TEST_SUITE_END()


//---------------------------------------------------------------------------//
// Copyright (c) 2013-2014 Kyle Lutz <kyle.r.lutz@gmail.com>
//
// Distributed under 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
//
// See http://kylelutz.github.com/compute for more information.
//---------------------------------------------------------------------------//

#include <iostream>
#include <algorithm>

#include <QtGlobal>
#if QT_VERSION >= 0x050000
#include <QtWidgets>
#else
#include <QtGui>
#endif
#include <QtOpenGL>

#include <boost/program_options.hpp>

#include <boost/compute/command_queue.hpp>
#include <boost/compute/kernel.hpp>
#include <boost/compute/program.hpp>
#include <boost/compute/source.hpp>
#include <boost/compute/system.hpp>
#include <boost/compute/image2d.hpp>
#include <boost/compute/image_sampler.hpp>
#include <boost/compute/interop/qt.hpp>
#include <boost/compute/interop/opengl.hpp>

namespace compute = boost::compute;
namespace po = boost::program_options;

// opencl source code
const char source[] = BOOST_COMPUTE_STRINGIZE_SOURCE(
    __kernel void resize_image(__read_only image2d_t input,
                               const sampler_t sampler,
                               __write_only image2d_t output)
    {
        const uint x = get_global_id(0);
        const uint y = get_global_id(1);

        const float w = get_image_width(output);
        const float h = get_image_height(output);

        float2 coord = { ((float) x / w) * get_image_width(input),
                         ((float) y / h) * get_image_height(input) };

        float4 pixel = read_imagef(input, sampler, coord);
        write_imagef(output, (int2)(x, h - y - 1), pixel);
    };
);

class ImageWidget : public QGLWidget
{
    Q_OBJECT

public:
    ImageWidget(QString fileName, QWidget *parent = 0);
    ~ImageWidget();

    void initializeGL();
    void resizeGL(int width, int height);
    void paintGL();

private:
    QImage qt_image_;
    compute::context context_;
    compute::command_queue queue_;
    compute::program program_;
    compute::image2d image_;
    compute::image_sampler sampler_;
    GLuint gl_texture_;
    compute::opengl_texture cl_texture_;
};

ImageWidget::ImageWidget(QString fileName, QWidget *parent)
    : QGLWidget(parent),
      qt_image_(fileName)
{
    gl_texture_ = 0;
}

ImageWidget::~ImageWidget()
{
}

void ImageWidget::initializeGL()
{
    // setup opengl
    glDisable(GL_LIGHTING);

    // create the OpenGL/OpenCL shared context
    context_ = compute::opengl_create_shared_context();

    // get gpu device
    compute::device gpu = context_.get_device();
    std::cout << "device: " << gpu.name() << std::endl;

    // setup command queue
    queue_ = compute::command_queue(context_, gpu);

    // allocate image on the device
    compute::image_format format =
        compute::qt_qimage_format_to_image_format(qt_image_.format());

    image_ = compute::image2d(
        context_, CL_MEM_READ_ONLY, format, qt_image_.width(), qt_image_.height()
    );

    // transfer image to the device
    compute::qt_copy_qimage_to_image2d(qt_image_, image_, queue_);

    // setup image sampler (use CL_FILTER_NEAREST to disable linear interpolation)
    sampler_ = compute::image_sampler(
        context_, false, CL_ADDRESS_NONE, CL_FILTER_LINEAR
    );

    // build resize program
    program_ = compute::program::build_with_source(source, context_);
}

void ImageWidget::resizeGL(int width, int height)
{
    // resize viewport
    glViewport(0, 0, width, height);

    // delete old texture
    if(gl_texture_){
      glDeleteTextures(1, &gl_texture_);
      gl_texture_ = 0;
    }

    // generate new texture
    glGenTextures(1, &gl_texture_);
    glBindTexture(GL_TEXTURE_2D, gl_texture_);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(
        GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0
    );

    // create opencl object for the texture
    cl_texture_ = compute::opengl_texture(
        context_, GL_TEXTURE_2D, 0, gl_texture_, CL_MEM_WRITE_ONLY
    );
}

void ImageWidget::paintGL()
{
    float w = width();
    float h = height();

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0.0, w, 0.0, h, -1.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // setup the resize kernel
    compute::kernel kernel(program_, "resize_image");
    kernel.set_arg(0, image_);
    kernel.set_arg(1, sampler_);
    kernel.set_arg(2, cl_texture_);

    // acquire the opengl texture so it can be used in opencl
    compute::opengl_enqueue_acquire_gl_objects(1, &cl_texture_.get(), queue_);

    // execute the resize kernel
    const size_t global_work_offset[] = { 0, 0 };
    const size_t global_work_size[] = { size_t(width()), size_t(height()) };

    queue_.enqueue_nd_range_kernel(
        kernel, 2, global_work_offset, global_work_size, 0
    );

    // release the opengl texture so it can be used by opengl
    compute::opengl_enqueue_release_gl_objects(1, &cl_texture_.get(), queue_);

    // ensure opencl is finished before rendering in opengl
    queue_.finish();

    // draw a single quad with the resized image texture
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, gl_texture_);

    glBegin(GL_QUADS);
    glTexCoord2f(0, 0); glVertex2f(0, 0);
    glTexCoord2f(0, 1); glVertex2f(0, h);
    glTexCoord2f(1, 1); glVertex2f(w, h);
    glTexCoord2f(1, 0); glVertex2f(w, 0);
    glEnd();
}

// the resize image example demonstrates how to interactively resize a
// 2D image and display it using OpenGL. a image sampler is used to perform
// hardware-accelerated linear interpolation for the resized image.
int main(int argc, char *argv[])
{
    // setup command line arguments
    po::options_description options("options");
    options.add_options()
        ("help", "show usage instructions")
        ("file", po::value<std::string>(), "image file name (e.g. /path/to/image.png)")
    ;
    po::positional_options_description positional_options;
    positional_options.add("file", 1);

    // parse command line
    po::variables_map vm;
    po::store(
        po::command_line_parser(argc, argv)
            .options(options)
            .positional(positional_options)
            .run(),
        vm
    );
    po::notify(vm);

    // check for file argument
    if(vm.count("help") || !vm.count("file")){
        std::cout << options << std::endl;
        return -1;
    }

    // get file name
    std::string file_name = vm["file"].as<std::string>();

    // setup qt application
    QApplication app(argc, argv);

    // setup image widget
    ImageWidget widget(QString::fromStdString(file_name));
    widget.show();

    // run qt application
    return app.exec();
}

#include "resize_image.moc"


/*
 *
 * Copyright (c) 2002, 2003 Kresimir Fresl, Toon Knapen and Karl Meerbergen
 *
 * Permission to copy, modify, use and distribute this software
 * for any non-commercial or commercial purpose is granted provided
 * that this license appear on all copies of the software source code.
 *
 * Authors assume no responsibility whatsoever for its use and makes
 * no guarantees about its quality, correctness or reliability.
 *
 * KF acknowledges the support of the Faculty of Civil Engineering,
 * University of Zagreb, Croatia.
 *
 */

#ifndef BOOST_NUMERIC_BINDINGS_TRAITS_UBLAS_HERMITIAN_H
#define BOOST_NUMERIC_BINDINGS_TRAITS_UBLAS_HERMITIAN_H

#include <boost/numeric/bindings/traits/traits.hpp>

#ifndef BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS

#ifndef BOOST_UBLAS_HAVE_BINDINGS
#  include <boost/numeric/ublas/hermitian.hpp>
#endif
#include <boost/numeric/bindings/traits/ublas_matrix.hpp>
#include <boost/numeric/bindings/traits/detail/ublas_uplo.hpp>

namespace boost { namespace numeric { namespace bindings { namespace traits {

  // ublas::hermitian_matrix<>
  template <typename T, typename F1, typename F2, typename A, typename M>
  struct matrix_detail_traits<boost::numeric::ublas::hermitian_matrix<T, F1, F2, A>, M>
  {
#ifndef BOOST_NUMERIC_BINDINGS_NO_SANITY_CHECK
    BOOST_STATIC_ASSERT( (boost::is_same<boost::numeric::ublas::hermitian_matrix<T, F1, F2, A>, typename boost::remove_const<M>::type>::value) );
#endif
#ifdef BOOST_BINDINGS_FORTRAN
    BOOST_STATIC_ASSERT((boost::is_same<
      typename F2::orientation_category,
      boost::numeric::ublas::column_major_tag
    >::value));
#endif

    typedef boost::numeric::ublas::hermitian_matrix<T, F1, F2, A> identifier_type;
    typedef M                                                     matrix_type;

    typedef hermitian_packed_t matrix_structure;
    typedef typename detail::ublas_ordering<
      typename F2::orientation_category
    >::type ordering_type;
    typedef typename detail::ublas_uplo< F1 >::type uplo_type;

    typedef T                                           value_type ;
    typedef typename detail::generate_const<M,T>::type* pointer ;

    static pointer storage (matrix_type& hm) {
      typedef typename detail::generate_const<M,A>::type array_type ;
      return vector_traits<array_type>::storage (hm.data());
    }
    static int size1 (matrix_type& hm) { return hm.size1(); }
    static int size2 (matrix_type& hm) { return hm.size2(); }
    static int storage_size (matrix_type& hm) {
      return (size1 (hm) + 1) * size2 (hm) / 2;
    }
  };

  namespace detail {
     template <typename M>
     int matrix_bandwidth( M const& m, upper_t ) {
        return matrix_traits<M const>::upper_bandwidth( m ) ;
     }

     template <typename M>
     int matrix_bandwidth( M const& m, lower_t ) {
        // When the lower triangular band matrix is stored the
	// upper bandwidth must be zero
	assert( 0 == matrix_traits<M const>::upper_bandwidth( m ) ) ;
        return matrix_traits<M const>::lower_bandwidth( m ) ;
     }
  } // namespace detail

  // ublas::hermitian_adaptor<>
  template <typename M, typename F1, typename MA>
  struct matrix_detail_traits<boost::numeric::ublas::hermitian_adaptor<M, F1>, MA>
  {
#ifndef BOOST_NUMERIC_BINDINGS_NO_SANITY_CHECK
    BOOST_STATIC_ASSERT( (boost::is_same<boost::numeric::ublas::hermitian_adaptor<M, F1>, typename boost::remove_const<MA>::type>::value) );
#endif

    typedef boost::numeric::ublas::hermitian_adaptor<M, F1> identifier_type;
    typedef MA                                              matrix_type;
    typedef hermitian_t                                     matrix_structure;
    typedef typename matrix_traits<M>::ordering_type        ordering_type;
    typedef typename detail::ublas_uplo< F1 >::type         uplo_type;

    typedef typename M::value_type                                 value_type;
    typedef typename detail::generate_const<MA, value_type>::type* pointer;

  private:
    typedef typename detail::generate_const<MA, typename MA::matrix_closure_type>::type m_type;

  public:
    static pointer storage (matrix_type& hm) {
      return matrix_traits<m_type>::storage (hm.data());
    }
    static int size1 (matrix_type& hm) { return hm.size1(); }
    static int size2 (matrix_type& hm) { return hm.size2(); }
    static int storage_size (matrix_type& hm) {
      return size1 (hm) * size2 (hm);
    }
    static int leading_dimension (matrix_type& hm) {
      return matrix_traits<m_type>::leading_dimension (hm.data());
    }
    // For banded M
    static int upper_bandwidth(matrix_type& hm) {
       return detail::matrix_bandwidth( hm.data(), uplo_type() );
    }
    static int lower_bandwidth(matrix_type& hm) {
       return detail::matrix_bandwidth( hm.data(), uplo_type() );
    }
  };

}}}}

#endif // BOOST_NUMERIC_BINDINGS_POOR_MANS_TRAITS

#endif // BOOST_NUMERIC_BINDINGS_TRAITS_UBLAS_HERMITIAN_H


//  (C) Copyright John Maddock 2000.
//  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)

#include <boost/type_traits/is_void.hpp>
#include <boost/type_traits/add_const.hpp>
#include <boost/mpl/list.hpp>
#include <boost/mpl/front.hpp>
#include <boost/mpl/remove_if.hpp>
#include <boost/mpl/transform.hpp>
#include <boost/static_assert.hpp>

template <class List>
struct lambda_test
{
   typedef typename boost::mpl::remove_if<List, boost::is_void<boost::mpl::_> >::type reduced_list;
   typedef typename boost::mpl::transform<reduced_list, boost::add_const<boost::mpl::_> >::type const_list;
   typedef typename boost::mpl::front<const_list>::type type;
};

int main()
{
   typedef boost::mpl::list<const void, int, float, void, double> list_type;

   lambda_test<list_type>::type i = 0;
   return i;
}


#ifndef NEIGHBOURFINDER_HPP
#define NEIGHBOURFINDER_HPP

#include <Eigen/Dense>
#include <nanoflann.hpp>
#include "../global.hpp"

typedef Eigen::Matrix< int, Eigen::Dynamic, Eigen::Dynamic> MatDynInt; //matrix MxN of type unsigned int
typedef Eigen::Matrix< float, Eigen::Dynamic, Eigen::Dynamic> MatDynFloat;
typedef Eigen::Matrix< float, Eigen::Dynamic, registration::NUM_FEATURES> FeatureMat;
typedef Eigen::Matrix< float, Eigen::Dynamic, 3> Vec3Mat;

namespace registration {

template <typename VecMatType>
class NeighbourFinder
{
    /*
    GOAL
    This class searches for the k nearest neighbours in 'inSourcePoints' for
    each element in the 'inQueriedPoints' set. It outputs the indices of each
    neighbour and the squared (!) distances between each element of
    'inQueriedPoints' and its neighbours.

    INPUT
    -inQueriedPoints:
    -inSourcePoints:

    PARAMETERS
    -numNeighbours(= 3): number of nearest neighbours
    -leafSize(= 15): should be between 5 and 50 or so

    OUTPUT
    -outNeighbourIndices
    -outNeighbourSquaredDistances.
    */

    public:
        //NeighbourFinder();
        ~NeighbourFinder(); //destructor

        void set_source_points(const VecMatType * const inQueriedPoints);
        void set_queried_points(const VecMatType * const _inSourcePoints);
        MatDynInt get_indices() const { return _outNeighbourIndices;}
        MatDynFloat get_distances() const { return _outNeighbourSquaredDistances;}
        void set_parameters(const size_t numNeighbours);
        void update();

    protected:

    private:
        //# Inputs
        const VecMatType * _inQueriedPoints = NULL;
        const VecMatType * _inSourcePoints = NULL;

        //# Outputs
        MatDynInt _outNeighbourIndices;
        MatDynFloat _outNeighbourSquaredDistances;
        //# User parameters

        //# Internal Data structures
        nanoflann::KDTreeEigenMatrixAdaptor<VecMatType> * _kdTree = NULL;

        //# Interal parameters
        size_t _numDimensions = 0;
        size_t _numSourceElements = 0;
        size_t _numQueriedElements = 0;
        size_t _numNeighbours = 3;
        size_t _leafSize = 15;

};

/*
See http://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file
for an explanation why we instantiate our templated class with every matrix type we will use here.
*/
//
template class NeighbourFinder<FeatureMat>;
template class NeighbourFinder<Vec3Mat>;
//

template <typename VecMatType>
NeighbourFinder<VecMatType>::~NeighbourFinder()
{
    //destructor
    if (_kdTree != NULL) { delete _kdTree; _kdTree = NULL;}
}

template <typename VecMatType>
void NeighbourFinder<VecMatType>::set_source_points(const VecMatType * const inSourcePoints){
    //# Set input
    _inSourcePoints = inSourcePoints;

    //# Update internal parameters
    _numDimensions = _inSourcePoints->cols();
    _numSourceElements = _inSourcePoints->rows();

    //# Update internal data structures
    //## The kd-tree has to be rebuilt.
    if (_kdTree != NULL) { delete _kdTree; _kdTree = NULL;}
    _kdTree = new nanoflann::KDTreeEigenMatrixAdaptor<VecMatType>(*_inSourcePoints,
                                                                _leafSize);
    _kdTree->index->buildIndex();
}

template <typename VecMatType>
void NeighbourFinder<VecMatType>::set_queried_points(const VecMatType * const inQueriedPoints){
    //# Set input
    _inQueriedPoints = inQueriedPoints;

    //# Update internal parameters
    _numQueriedElements = _inQueriedPoints->rows();

    //# Adjust internal data structures
    //## The indices and distance matrices have to be resized.
    _outNeighbourIndices.setZero(_numQueriedElements,_numNeighbours);
    _outNeighbourSquaredDistances.setZero(_numQueriedElements,_numNeighbours);

}

template <typename VecMatType>
void NeighbourFinder<VecMatType>::set_parameters(const size_t numNeighbours){
    //# Check if what user requests, changes the parameter value
    bool parameterChanged = false;
    if (_numNeighbours != numNeighbours){ parameterChanged = true;}

    //# Set parameter
    _numNeighbours = numNeighbours;

    //# Resize the output matrices if the parameter is changed
    if (parameterChanged == true) {
        _outNeighbourIndices.setZero(_numQueriedElements,_numNeighbours);
        _outNeighbourSquaredDistances.setZero(_numQueriedElements,_numNeighbours);
    }
}

template <typename VecMatType>
void NeighbourFinder<VecMatType>::update(){

    //# Query the kd-tree
    //## Loop over the queried features
    //### Initialize variables we'll need during the loop
    unsigned int i = 0;
    unsigned int j = 0;
    std::vector<float> queriedFeature(_numDimensions);
    std::vector<size_t> neighbourIndices(_numNeighbours);
    std::vector<float> neighbourSquaredDistances(_numNeighbours);
    nanoflann::KNNResultSet<float> knnResultSet(_numNeighbours);

    //### Execute loop
    for ( ; i < _numQueriedElements ; ++i ) {
        //### Initiliaze the knnResultSet
        knnResultSet.init(&neighbourIndices[0], &neighbourSquaredDistances[0]);

        //### convert input features to 'queriedFeature' std::vector structure
        //### (required by nanoflann's kd-tree).
        for (j = 0 ; j < _numDimensions ; ++j) {
            queriedFeature[j] = (*_inQueriedPoints)(i,j);
        }

        //### Query the kd-tree
//        size_t numNeighboursFound = kdTree.knnSearch(&queriedFeature[0], _numNeighbours, &neighbourIndices[0], &neighbourSquaredDistances[0]);
        _kdTree->index->findNeighbors(knnResultSet, &queriedFeature[0],
                                    nanoflann::SearchParams(32, 0.0001 /*eps*/, true));

        //### Copy the result into the outputs by looping over the k nearest
        //### neighbours
        for (j = 0 ; j < _numNeighbours ; ++j) {
            _outNeighbourIndices(i,j) = neighbourIndices[j];
            _outNeighbourSquaredDistances(i,j) = neighbourSquaredDistances[j];
        }
    }
}//end k_nearest_neighbours()

} //namespace registration

#endif // NEIGHBOURFINDER_HPP


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2015 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_filtering.hh"

#include <boost/python.hpp>

#include "graph.hh"
#include "graph_selectors.hh"
#include "graph_properties.hh"

#include "graph_avg_correlations.hh"

#include <iostream>

using namespace std;
using namespace boost;
using namespace graph_tool;

typedef ConstantPropertyMap<int,GraphInterface::edge_t> dummy_weight;

boost::python::object
get_vertex_avg_combined_correlation(GraphInterface& gi,
                                    GraphInterface::deg_t deg1,
                                    GraphInterface::deg_t deg2,
                                    const vector<long double>& bins)
{
    boost::python::object avg, dev;
    boost::python::object ret_bins;

    run_action<>()(gi, get_avg_correlation<GetCombinedPair>
                   (avg, dev, bins, ret_bins),
                   scalar_selectors(), scalar_selectors(),
                   boost::mpl::vector<dummy_weight>())
        (any(degree_selector(deg1)), any(degree_selector(deg2)),
         any(dummy_weight()));
    return boost::python::make_tuple(avg, dev, ret_bins);
}

using namespace boost::python;

void export_avg_combined_correlations()
{
    def("vertex_avg_combined_correlation",
        &get_vertex_avg_combined_correlation);
}


// Author(s): Unknown
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// Distributed under 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)
//
/// \file action_rename_test.cpp
/// \brief Action rename test.

#define BOOST_TEST_MODULE action_rename_test
#include "mcrl2/lps/action_rename.h"
#include "mcrl2/lps/action_summand.h"
#include "mcrl2/lps/deadlock_summand.h"
#include "mcrl2/lps/linearise.h"
#include "mcrl2/lps/parse.h"
#include "mcrl2/lps/remove.h"
#include "mcrl2/lps/rewrite.h"
#include <boost/test/included/unit_test_framework.hpp>

using namespace mcrl2;
using lps::stochastic_specification;
using lps::action_rename_specification;
using lps::action_summand;
using lps::action_summand_vector;
using lps::deadlock_summand;

void test1()
{
  // Check a renaming when more than one renaming rule
  // for one action is present.
  const std::string SPEC =
    "act a:Nat;                               \n"
    "proc P(n:Nat) = sum m: Nat. a(m). P(m);  \n"
    "init P(0);                               \n";

  const std::string AR_SPEC =
    "act b,c:Nat;\n"
    "var n:Nat;\n"
    "rename \n"
    "  (n>4)  -> a(n) => b(n); \n"
    "  (n<22) -> a(n) => c(n); \n";

  stochastic_specification spec=lps::linearise_no_alpha(SPEC);
  std::istringstream ar_spec_stream(AR_SPEC);
  action_rename_specification ar_spec = parse_action_rename_specification(ar_spec_stream, spec);
  stochastic_specification new_spec = action_rename(ar_spec,spec);
  BOOST_CHECK(new_spec.process().summand_count()==3);
}

void test2()
{
  // Check whether new declarations in the rename file
  // are read properly. Check for renamings of more than one action.
  const std::string SPEC =
    "act a,b:Nat;                             \n"
    "proc P(n:Nat) = sum m: Nat. a(m). P(m);  \n"
    "init P(0);                               \n";

  const std::string AR_SPEC =
    "map f:Nat->Nat; \n"
    "var n':Nat; \n"
    "eqn f(n')=3; \n"
    "act c:Nat; \n"
    "var n:Nat; \n"
    "rename \n"
    "  (f(n)>23) -> a(n) => b(n); \n"
    "  b(n) => c(n); \n";

  stochastic_specification spec=lps::linearise_no_alpha(SPEC);
  std::istringstream ar_spec_stream(AR_SPEC);
  action_rename_specification ar_spec = parse_action_rename_specification(ar_spec_stream, spec);
  stochastic_specification new_spec = action_rename(ar_spec,spec);
  BOOST_CHECK(new_spec.process().summand_count()==2);
}

void test3()
{
  // Check whether constants in an action_rename file are properly translated.
  const std::string SPEC =
    "act a,b:Nat;                             \n"
    "proc P(n:Nat) = a(1). P(n)+b(1).P(1);  \n"
    "init P(0);                               \n";

  const std::string AR_SPEC =
    "rename \n"
    "  a(1) => delta; \n";

  stochastic_specification spec=lps::linearise_no_alpha(SPEC);
  std::istringstream ar_spec_stream(AR_SPEC);
  action_rename_specification ar_spec = parse_action_rename_specification(ar_spec_stream, spec);
  data::rewriter R (spec.data(), mcrl2::data::rewriter::strategy());
  stochastic_specification new_spec = action_rename(ar_spec,spec);
  lps::rewrite(new_spec, R);
  lps::remove_trivial_summands(new_spec);
  BOOST_CHECK(new_spec.process().summand_count()==2);
}

void test4()
{
  const std::string SPEC =
    "sort D = struct d1 | d2;\n"
    "     E = D;\n"
    "\n"
    "act a : E;\n"
    "\n"
    "proc P(d:D) = a(d) . P(d1);\n"
    "\n"
    "init P(d2);\n";

  const std::string AR_SPEC =
    "var e : E;\n"
    "rename\n"
    "(e==d1) -> a(e) => tau;\n";

  stochastic_specification spec=lps::linearise_no_alpha(SPEC);
  std::istringstream ar_spec_stream(AR_SPEC);
  action_rename_specification ar_spec = parse_action_rename_specification(ar_spec_stream, spec);
  data::rewriter R (spec.data(), mcrl2::data::rewriter::strategy());
  stochastic_specification new_spec = action_rename(ar_spec,spec);
  lps::rewrite(new_spec, R);
  lps::remove_trivial_summands(new_spec);
  BOOST_CHECK(new_spec.process().summand_count()==2);
}

void test5() // Test whether partial renaming to delta is going well. See bug report #1009.
{
  const std::string SPEC =
    "sort Command = struct com1 | com2;\n"
    "sort State = struct st1 | st2;\n"
    "\n"
    "proc Parent(id: Nat, children: List(Nat)) =\n"
    "  sum child: Nat, command: Command . (child in children) -> sc(id, child, command) . Parent()\n"
    "+ sum child: Nat, state: State . (child in children) -> rs(child, id, state) . Parent();\n"
    "\n"
    "proc Child(id: Nat, parent: Nat) =\n"
    "  sum command: Command . rc(parent, id, command) . Child()\n"
    "+ sum state: State . ss(id, parent, state) . Child();\n"
    "\n"
    "act sc, rc, cc: Nat # Nat # Command;\n"
    "    rs, ss, cs: Nat # Nat # State;\n"
    "\n"
    "act none;\n"
    "\n"
    "init\n"
    "  allow({cc, cs, sc, rs},\n"
    "  comm({sc|rc -> cc, rs|ss->cs},\n"
    "    Parent(0, [1, 2]) ||\n"
    "    Child(1, 0) ||\n"
    "    Child(2, 0)\n"
    "  ));\n";

  const std::string AR_SPEC =
    "var c: Command;\n"
    "    s: State;\n"
    "rename\n"
    "  sc(0, 1, c) => delta;\n"
    "  rs(1, 0, s) => delta;\n";

  stochastic_specification spec=lps::linearise_no_alpha(SPEC);
  std::istringstream ar_spec_stream(AR_SPEC);
  action_rename_specification ar_spec = parse_action_rename_specification(ar_spec_stream, spec);
  data::rewriter R (spec.data(), mcrl2::data::rewriter::strategy());
  stochastic_specification new_spec = action_rename(ar_spec,spec);
  lps::rewrite(new_spec, R);
  lps::remove_trivial_summands(new_spec);
  BOOST_CHECK(new_spec.process().summand_count()==8);
}

// Check whether renaming with a regular expression works well
static void test_regex1()
{
  const std::string SPEC =
  "act a_out, b_out, cout, ab_out, ac_out;\n"
  "init a_out|ab_out . b_out . cout . delta;";

  stochastic_specification spec = lps::linearise_no_alpha(SPEC);
  // This should rename a_out and ac_out, leaving the rest
  stochastic_specification new_spec = action_rename(std::regex("^([^b]*)_out"), "out_$1", spec);

  BOOST_CHECK(std::string(new_spec.action_labels().front().name()) == "out_a");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().front().name()) == "b_out");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().tail().front().name()) == "cout");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().tail().tail().front().name()) == "ab_out");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().tail().tail().tail().front().name()) == "out_ac");

  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "out_a|ab_out"; }));
  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "b_out"; }));
  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "cout"; }));
}

// Check whether renaming some actions to delta works
void test_regex2()
{
  const std::string SPEC =
  "act a_out, b_out, cout, ab_out, ac_out;\n"
  "init a_out|ab_out . b_out . cout . delta;";

  stochastic_specification spec = lps::linearise_no_alpha(SPEC);
  // This should rename a_out, leaving the rest
  stochastic_specification new_spec = action_rename(std::regex("^a_out"), "delta", spec);

  BOOST_CHECK(std::string(new_spec.action_labels().front().name()) == "b_out");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().front().name()) == "cout");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().tail().front().name()) == "ab_out");

  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "b_out"; }));
  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "cout"; }));

  BOOST_CHECK(new_spec.process().deadlock_summands().size() == 2);
  auto find_result = std::find_if(spec.process().action_summands().begin(), spec.process().action_summands().end(),
    [](const action_summand& as){ return lps::pp(as.multi_action()) == "a_out|ab_out"; });
  BOOST_CHECK(find_result != spec.process().action_summands().end());
  if(find_result != spec.process().action_summands().end())
  {
    BOOST_CHECK(std::any_of(new_spec.process().deadlock_summands().begin(), new_spec.process().deadlock_summands().end(),
                                [&find_result](const deadlock_summand& ds){ return ds.condition() == find_result->condition(); }));
  }
}

// Check whether renaming some actions to tau works
void test_regex3()
{
  const std::string SPEC =
  "act a_out, b_out, cout, ab_out, ac_out;\n"
  "init a_out|ab_out . b_out . cout . delta;";

  stochastic_specification spec = lps::linearise_no_alpha(SPEC);
  // This should rename a_out and cout, leaving the rest
  stochastic_specification new_spec = action_rename(std::regex("^(a_out|cout)$"), "tau", spec);

  BOOST_CHECK(std::string(new_spec.action_labels().front().name()) == "b_out");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().front().name()) == "ab_out");
  BOOST_CHECK(std::string(new_spec.action_labels().tail().tail().front().name()) == "ac_out");

  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "ab_out"; }));
  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "b_out"; }));
  BOOST_CHECK(std::any_of(new_spec.process().action_summands().begin(), new_spec.process().action_summands().end(),
                              [](const action_summand& as){ return lps::pp(as.multi_action()) == "tau"; }));
}

// Check whether the list of actions contains no duplicates after renaming multiple actions
// to one action.
void test_regex4()
{
  const std::string SPEC =
  "act a_out, b_out;\n"
  "init a_out . b_out . delta;";

  stochastic_specification spec = lps::linearise_no_alpha(SPEC);
  // This should rename both actions to 'out'
  stochastic_specification new_spec = action_rename(std::regex("^(a|b)_out$"), "out", spec);

  BOOST_CHECK(new_spec.action_labels().size() == 1);
  BOOST_CHECK(std::string(new_spec.action_labels().front().name()) == "out");
}

BOOST_AUTO_TEST_CASE(test_main)
{
  test1();
  test2();
  test3();
  test4();
  test5();

  test_regex1();
  test_regex2();
  test_regex3();
  test_regex4();
}


#include <sdl/SDLWindow.hpp>
#include <Assets.hpp>
#include <gl/Program.hpp>
#include <gl/VAO.hpp>
#include <boost/any.hpp>
#include <tetra/EventStream.hpp>
#include <tetra/AdaptiveOrtho.hpp>
#include <sdl/SDLEvents.hpp>
#include <tetra/TicTocClock.hpp>

#include <array>
#include <exception>

using namespace std;
using namespace tetra;

struct Vertex
{
    array<float, 2> pos;
};

vector<unsigned short> permute(int n)
{
    auto count = (n*(n-1)); // n-1'th triangle number times 2
    auto indices = vector<unsigned short>{};
    indices.reserve(count);

    for (int i = 0; i < n-1; i++)
    {
        for (int j = i+1; j < n; j++)
        {
            indices.push_back(i);
            indices.push_back(j);
        }
    }
    return indices;
}

Program buildCobwebProgram()
{
    auto vertex = Shader{ShaderType::VERTEX};
    auto fragment = Shader{ShaderType::FRAGMENT};
    auto geometry = Shader{ShaderType::GEOMETRY};
    fragment.compile(loadShaderSrc("lissajous.frag"));
    vertex.compile(loadShaderSrc("lissajous.vert"));
    geometry.compile(loadShaderSrc("lissajous.geom"));

    auto program = ProgramLinker{}
        .vertexAttributes({"vertex"})
        .attach(vertex)
        .attach(fragment)
        .attach(geometry)
        .link();

    auto projLocation = program.uniformLocation("projection");
    return program;
}

class CobwebPipeline
{
public:
    CobwebPipeline(EventStream& eventStream);
    CobwebPipeline(const CobwebPipeline&) = delete;
    CobwebPipeline(CobwebPipeline&& from) = default;

    void render();

    void setVertices(const std::vector<Vertex>& vertices);
private:
    AdaptiveOrtho adaptiveOrtho;
    Program program;
    Vao vao;
    Buffer<Vertex> vertexBuffer;
    Buffer<unsigned short> indexBuffer;
    GLint projLocation;
};

CobwebPipeline::CobwebPipeline(EventStream& eventStream)
    : program{buildCobwebProgram()}
    , vao{Vao{}}
    , vertexBuffer{AttribBinder<Vertex>{vao}.attrib(&Vertex::pos).bind()}
    , indexBuffer{BindTarget::ElementArray}
    , adaptiveOrtho{eventStream}
{
    projLocation = program.uniformLocation("projection");
}

void
CobwebPipeline::setVertices(const vector<Vertex>& vertices)
{
    auto vbSize = vertexBuffer.size();
    vertexBuffer.write(vertices);

    // Only redo this if the number of vertices changes because the
    // permutation scales like N^2 (technically N*(N-1))
    if (vbSize != vertexBuffer.size())
    {
        indexBuffer.write(permute(vertexBuffer.size()));
    }
}

void
CobwebPipeline::render()
{
    vao.bind();
    program.use();
    program.uniform(projLocation, adaptiveOrtho.value());
    indexBuffer.drawElements(Primitive::Lines);
}

void sdlmain()
{
    auto eventStream = EventStream{};
    auto sdl = SDL{eventStream};
    auto window = SDLWindow::Builder{eventStream}
        .width(1000).height(750)
        .build();
    auto gl = window.contextBuilder()
        .majorVersion(3)
        .minorVersion(3)
        .build();

    auto frameTimer = HighResTicToc{};
    auto totalTime = HighResTicToc{};

    auto max = 2.0f*3.1415f;
    auto count = 75;
    auto vertices = vector<Vertex>{};
    auto cobwebPipeline = CobwebPipeline{eventStream};

    auto computeVertices = [&]() {
        auto ft = totalTime.toc();

        vertices.clear();
        for (int i = 0; i < count; i++)
        {
            auto r = (float)i/count;
            auto angle = r*max;

            vertices.push_back({ 0.9f*sinf(1.5f*angle+ft)*cosf(1.0f*angle)
                               , 0.9f*cosf(1.0*angle+ft)*cosf(1.0f*angle)
                               });
        }
    };

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);

    while (sdl.running())
    {
        sdl.pushEvents();
        eventStream.dispatch();

        cout << "frame time: " << frameTimer.ticToc() << endl;
        computeVertices();
        cobwebPipeline.setVertices(vertices);

        auto frame = window.draw();
        glClearColor(0.0, 0.0, 0.0, 0.0);
        glClear(GL_COLOR_BUFFER_BIT);

        cobwebPipeline.render();
    }
}

int main()
{
    try
    {
        sdlmain();
    }
    catch (exception& ex)
    {
        cout << ex.what() << endl;
        return 1;
    }

    return 0;
}


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2015 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_filtering.hh"
#include "graph_selectors.hh"
#include "graph_properties.hh"
#include "graph.hh"

#include "graph_augment.hh"

#include <boost/graph/edmonds_karp_max_flow.hpp>

#include <boost/bind.hpp>

using namespace graph_tool;
using namespace boost;

struct get_edmonds_karp_max_flow
{
    template <class Graph, class VertexIndex, class EdgeIndex, class CapacityMap,
              class ResidualMap>
    void operator()(Graph& g, VertexIndex vertex_index, EdgeIndex edge_index,
                    size_t max_e, size_t src, size_t sink, CapacityMap cm,
                    ResidualMap res) const
    {
        typedef typename graph_traits<Graph>::edge_descriptor edge_t;
        unchecked_vector_property_map<bool,EdgeIndex>
            augmented(edge_index, max_e);
        unchecked_vector_property_map<edge_t,EdgeIndex>
            reverse_map(edge_index, max_e);
        unchecked_vector_property_map<default_color_type,VertexIndex>
            color(vertex_index, num_vertices(g));
        unchecked_vector_property_map<edge_t,VertexIndex>
            pred(vertex_index, num_vertices(g));

        typedef typename std::remove_const<Graph>::type GT;
        GT& u = const_cast<GT&>(g);
        augment_graph(u,
                      augmented.get_checked(),
                      get_checked(cm),
                      reverse_map.get_checked(),
                      get_checked(res));

        boost::edmonds_karp_max_flow(g, vertex(src, g), vertex(sink, g),
                                     get_unchecked(cm),
                                     get_unchecked(res),
                                     reverse_map, color, pred);

        deaugment_graph(u, augmented.get_checked());
    }
};

void edmonds_karp_max_flow(GraphInterface& gi, size_t src, size_t sink,
                           boost::any capacity, boost::any res)
{
    run_action<graph_tool::detail::always_directed>()
        (gi, std::bind(get_edmonds_karp_max_flow(),
                       placeholders::_1, gi.GetVertexIndex(), gi.GetEdgeIndex(),
                       gi.GetMaxEdgeIndex(),
                       src, sink, placeholders::_2, placeholders::_3),
         writable_edge_scalar_properties(), writable_edge_scalar_properties())
        (capacity,res);
}


//-*****************************************************************************
// Copyright 2015 Christopher Jon Horvath
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//-*****************************************************************************

//-*****************************************************************************
// The basic architecture of these Waves is based on the TweakWaves application
// written by Chris Horvath for Tweak Films in 2001.  This, in turn, was based
// on the SIGGRAPH papers and courses by Jerry Tessendorf, and by the paper
// "A Simple Fluid Solver based on the FTT" by Jos Stam.
//
// The TMA, JONSWAP, and Pierson Moskowitz Wave Spectra, as well as the
// directional spreading functions are formulated based on the descriptions
// given in "Ocean Waves: The Stochastic Approach",
// by Michel K. Ochi, published by Cambridge Ocean Technology Series, 1998,2005.
//
// This library is written as a working implementation of the paper:
// Christopher J. Horvath. 2015.
// Empirical directional wave spectra for computer graphics.
// In Proceedings of the 2015 Symposium on Digital Production (DigiPro '15),
// Los Angeles, Aug. 8, 2015, pp. 29-39.
//-*****************************************************************************

#include "OceanTestShaders.h"
#include "OceanTestFoundation.h"
#include "OceanTestViewScene.h"
#include <boost/program_options.hpp>
#include <cstdlib>

namespace po = boost::program_options;
using namespace OceanTest;

int main(int argc, char *argv[]) {
  OceanTest::ewav::Parametersf params;
  OceanTest::Sky::Parameters sparams;
  OceanTest::Mesh::DrawParameters dparams;
  OceanTest::ViewScene::Parameters vparams;

  int threads              = -1;
  int dispersion           = 2;
  int spectrum             = 2;
  int directionalSpreading = 3;
  int filter               = 0;
  int random               = 0;

  po::options_description desc("Encino Waves 2015");
  desc.add_options()

    // clang-format off

    ( "help,h", "prints this help message" )

    ( "threads",
      po::value<int>( &threads )
      ->default_value( threads ),
      "Threads to use. Default of -1 means use all." )

    ( "resolution",
      po::value<int>( &params.resolutionPowerOfTwo )
      ->default_value( params.resolutionPowerOfTwo ),
      "Power of Two of the sim resolution" )

    ( "domain",
      po::value<float>( &params.domain )
      ->default_value( params.domain ),
      "Size, in meters, of largest wave" )

    ( "gravity",
      po::value<float>( &params.gravity )
      ->default_value( params.gravity ),
      "Gravitational constant, in meters per second squared" )

    ( "surfaceTension",
      po::value<float>( &params.surfaceTension )
      ->default_value( params.surfaceTension ),
      "Surface tension constant, in Newtons per meter" )

    ( "density",
      po::value<float>( &params.density )
      ->default_value( params.density ),
      "Water density, in kilograms per meter cubed" )

    ( "depth",
      po::value<float>( &params.depth )
      ->default_value( params.depth ),
      "Average depth of the ocean, in meters" )

    ( "windSpeed",
      po::value<float>( &params.windSpeed )
      ->default_value( params.windSpeed ),
      "Average wind speed, in meters per second" )

    ( "fetch",
      po::value<float>( &params.fetch )
      ->default_value( params.fetch ),
      "Wind fetch, in KILOMETERS" )

    ( "pinch",
      po::value<float>( &params.pinch )
      ->default_value( params.pinch ),
      "Lateral displacement, normalized" )

    ( "amplitudeGain",
      po::value<float>( &params.amplitudeGain )
      ->default_value( params.amplitudeGain ),
      "Gain on the wave height" )

    ( "dispersion",
      po::value<int>( &dispersion )
      ->default_value( dispersion ),
      "Dispersion: 0 for Deep, 1 for Finite Depth, 2 for Capillary" )

    ( "spectrum",
      po::value<int>( &spectrum )
      ->default_value( spectrum ),
      "Spectrum: 0 for Pierson-Moskowitz, 1 for JONSWAP, 2 for TMA" )

    ( "directionalSpreading",
      po::value<int>( &directionalSpreading )
      ->default_value( directionalSpreading ),
      "Directional Spreading: 0 for Balanced Cos2 Theta, "
      "1 for Mitsuyasu, 2 for Hasselmann, 3 for Donelan-Banner" )

    ( "swell",
      po::value<float>( &params.directionalSpreading.swell )
      ->default_value( params.directionalSpreading.swell ),
      "The mix between a wind-driven local sea and a swell caused by a "
      "distant storm." )

    ( "filter",
      po::value<int>( &filter )
      ->default_value( filter ),
      "Filter: 0 for nullptr, 1 for Smoothed Invertible Band-Pass" )

    ( "filterSoftWidth",
      po::value<float>( &params.filter.softWidth )
      ->default_value( params.filter.softWidth ),
      "Size in meters of the softness of wavelength filter falloff." )

    ( "filterSmall",
      po::value<float>( &params.filter.smallWavelength )
      ->default_value( params.filter.smallWavelength ),
      "Size in meters of the smallest kept wavelengths." )

    ( "filterBig",
      po::value<float>( &params.filter.bigWavelength )
      ->default_value( params.filter.bigWavelength ),
      "Size in meters of the biggest kept wavelengths." )

    ( "filterMin",
      po::value<float>( &params.filter.min )
      ->default_value( params.filter.min ),
      "Minimum value of filter." )

    ( "filterInvert", "Invert the filter" )

    ( "troughDamping",
     po::value<float>( &params.troughDamping )
     ->default_value( params.troughDamping ),
     "Trough damping." )

    ( "troughDampingSmallWavelength",
      po::value<float>( &params.troughDampingSmallWavelength )
      ->default_value( params.troughDampingSmallWavelength ),
      "Trough damping small wavelength." )

    ( "troughDampingBigWavelength",
      po::value<float>( &params.troughDampingBigWavelength )
      ->default_value( params.troughDampingBigWavelength ),
      "Trough damping big wavelength." )

    ( "troughDampingSoftWidth",
      po::value<float>( &params.troughDampingSoftWidth )
      ->default_value( params.troughDampingSoftWidth ),
      "Trough damping soft width." )

    ( "random",
      po::value<int>( &random )
      ->default_value( random ),
      "Random Distribution: 0 for Normal, 1 for Log-Normal" )

    ( "seed",
      po::value<int>( &params.random.seed )
      ->default_value( params.random.seed ),
      "Random Seed" )

    ( "repeat",
      po::value<int>( &dparams.repeat )
      ->default_value( dparams.repeat ),
      "Number of times, between 1 & 3, to repeat the drawing" )

    ( "time",
      po::value<double>( &sparams.time )
      ->default_value( sparams.time ),
      "Time of day, 0 - 24" )

    ( "turbidity",
      po::value<double>( &sparams.turbidity )
      ->default_value( sparams.turbidity ),
      "Turbidity" )

    ( "skyTexture",
      po::value<std::string>( &sparams.filename ),
      "Sky Texture" )

    ( "outFileBase",
      po::value<std::string>()
      ->default_value( "EncinoWaves" ),
      "The output file name base" )

    ;

  // clang-format on

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).options(desc).run(), vm);
  po::notify(vm);

  //-*************************************************************************
  if (vm.count("help")) {
    std::cout << desc << std::endl;
    return 0;
  }

  if (vm.count("filterInvert")) {
    params.filter.invert = true;
  }

  if (vm.count("outFileBase")) {
    vparams.outputFileBase = vm["outFileBase"].as<std::string>();
  }

  params.dispersion.type = (ewav::DispersionType)dispersion;
  params.spectrum.type = (ewav::SpectrumType)spectrum;
  params.directionalSpreading.type =
    (ewav::DirectionalSpreadingType)directionalSpreading;
  params.filter.type = (ewav::FilterType)filter;
  params.random.type = (ewav::RandomType)random;

  std::shared_ptr<EncinoWaves::SimpleSimViewer::BaseSim> simPtr(
    new OceanTest::ViewScene(params, sparams, dparams, vparams));

  EncinoWaves::SimpleSimViewer::SimpleViewSim(simPtr, true);

  return 0;
}


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE PruneMatches0SubgraphTest

#include "blip.hpp"
#include <algorithm>
#include <boost/test/unit_test.hpp>
#include <iostream>
#include <memory>
#include <vector>
#include <popart/logging.hpp>
#include <popart/subgraph/prunematches.hpp>

using namespace fwtools::subgraph;
using namespace blip;

namespace {

template <class CostModel>
void test(const std::vector<blip::Type> &types,
          const std::vector<Edge> &edges,
          std::vector<Match> expected_matches_before,
          std::vector<Match> expected_matches_after,
          float threshold = -1.0f) {
  // prepare the schedule from the inputs
  std::vector<const Blip *> sched;
  std::vector<std::unique_ptr<Blip>> blips;
  for (auto &t : types) {
    blips.emplace_back(std::unique_ptr<Blip>(new Blip(t, 10.0f, {})));
  }
  for (auto edge : edges) {
    blips[edge.destId]->addIn(
        edge.inIndex, blips[edge.sourceId].get(), edge.outIndex);
    blips[edge.sourceId]->addOut(blips[edge.destId].get(), edge.outIndex);
  }
  for (int i = 0; i < blips.size(); ++i) {
    sched.push_back(blips[i].get());
  }

  // get the matches
  algo0::RinseMatcherAlgo0<const Blip> rinseMatcher(sched);
  auto matches = rinseMatcher.getRepeatedSequences();
  matches      = separateByOverlaps(matches);
  setValues(matches, sched);
  std::sort(matches.rbegin(), matches.rend());
  // no thresholding in initial match generation
  matches = algo0::getFinalMatches(matches, -1, sched.size());

  std::sort(matches.begin(), matches.end());
  std::sort(expected_matches_before.begin(), expected_matches_before.end());

  std::stringstream ss;
  ss << "\nExpected matches before prune:";
  for (auto &x : expected_matches_before) {
    ss << "\n" << x;
  }
  ss << "\nComputed matches before prune:";
  for (auto &x : matches) {
    ss << "\n" << x;
  }
  popart::logging::debug(ss.str());
  BOOST_CHECK(matches == expected_matches_before);

  matches = fwtools::subgraph::prune::pruneMatches<const Blip, CostModel>(
      matches, sched, threshold);

  std::sort(matches.begin(), matches.end());
  std::sort(expected_matches_after.begin(), expected_matches_after.end());

  std::stringstream ss2;
  ss2 << "\nExpected matches after prune:";
  for (auto &x : expected_matches_after) {
    ss2 << "\n" << x;
  }
  ss2 << "\nComputed matches after prune:";
  for (auto &x : matches) {
    ss2 << "\n" << x;
  }
  popart::logging::debug(ss2.str());
  BOOST_CHECK(matches == expected_matches_after);
};
} // namespace

BOOST_AUTO_TEST_CASE(PruneMatches) {

  // ----------------------------------------------------------
  popart::logging::info("isomorphism preserving edges test");

  std::vector<blip::Type> types;
  //       0  1  2  3  4  5  6  7  8
  types = {0, 0, 0, 1, 2, 3, 1, 2, 3};
  //                0-----0           (output index 0 -> input index 0)
  //                         0-----0
  //                1--2              (output index 1 -> input index 2)
  //                         1--2
  //
  //       x  x  x  XXXXXXX  XXXXXXX

  std::vector<blip::Edge> edges;
  edges = {{3, 5, 0, 0}, {6, 8, 0, 0}, {3, 4, 1, 2}, {6, 7, 1, 2}};

  std::vector<Match> expected_matches_before;
  expected_matches_before = {
      {{0, 1, 2}, 1}, // 0
      {{3, 6}, 3}     // 1 2 3
  };
  // Under the Mod 3 cost model, the final length 3 cost is pruned to length 2
  std::vector<Match> expected_matches_after = {
      {{0, 1, 2}, 1}, //
      {{4, 7}, 2}     //
  };
  test<ModThreeCostModel>(
      types, edges, expected_matches_before, expected_matches_after);

  types                   = {0, 1, 2, 0, 1, 2, 5, 5, 5, 5, 5, 5};
  edges                   = {};
  expected_matches_before = {
      {{6, 7, 8, 9, 10, 11}, 1}, //
      {{0, 3}, 3},               //
      {{6, 9}, 3}                //
  };

  expected_matches_after = {
      // {{6, 7, 8, 9, 10, 11}, 1}, //
      {{1, 4}, 2}, //
      {{7, 10}, 2} //
  };
  test<ModThreeCostModel>(
      types, edges, expected_matches_before, expected_matches_after, 15.0f);
}

BOOST_AUTO_TEST_CASE(PruneMatches_Regression1) {

  class CustomCostModel {
  public:
    float value(int64_t begin,
                int64_t end,
                const std::vector<const Blip *> &sched,
                const std::map<const Blip *, int> &sched_index) {

      // A cost model that favours our error case.
      if (begin > 0) {
        // pick longest
        return (end - begin) * 1e-3;
      } else {
        // take priority over other starts and pick shortest, unless it's really
        // short
        if (end - begin >= 2)
          return 1 + (end - begin) * -1e-3;
        else
          return 0;
      }
    }
  };

  // ----------------------------------------------------------
  popart::logging::info("testing pruneMatches regression1");

  std::vector<blip::Type> types;
  // clang-format off
  //
  //            0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16
  types      = {0, 1, 2, 3, 5, 6, 0, 1, 2, 3, 7, 1, 2, 3, 8, 1, 2};

  // Expected matches:
  // m_1           *  *              *  *        *  *        *  *
  // m_2           *  *  *           *  *  *     *  *  *
  // m_3        *  *  *  *        *  *  *  *
  //
  // Expected state when pruning m_3 (after processing m_1, m_2 as whole):
  // isInternal 0  0  1  0  0  0  0  0  1  0  0  0  1  0  0  0  0
  // isFirst    0  1  0  0  0  0  0  1  0  0  0  1  0  0  0  1  0
  // isLast     0  0  1  1  0  0  0  0  1  1  0  0  1  1  0  0  1
  //
  // Resulting in:
  // goodStart  1  1  0  0  1  1  1  1  0  0  1  1  0  0  1  1  0
  // goodEnd       1  0  0  1  1  1  1  0  0  1  1  0  0  1  1  0
  //
  // And hence the following considered prunings are:
  // m_3'       *                 *
  // m_3'       *  *  *  *        *  *  *  *
  // m_3'          *  *  *           *  *  *
  //
  // Previous implementation considered this (erroneous) pruning:
  // m_3'       *  *  *           *  *  *
  //
  // clang-format on

  std::vector<blip::Edge> edges;
  edges = {};

  std::vector<Match> expected_matches_before;
  expected_matches_before = {
      {{1, 7, 11, 15}, 2}, // m_1
      {{1, 7, 11}, 3},     // m_2
      {{0, 6}, 4},         // m_3
  };

  // Under the Mod 3 cost model, the final length 3 cost is pruned to length 2
  std::vector<Match> expected_matches_after = {
      {{1, 7, 11, 15}, 2}, // m_1
      {{1, 7, 11}, 3},     // m_2
      {{0, 6}, 4},         // m_3
  };
  test<CustomCostModel>(
      types, edges, expected_matches_before, expected_matches_after);
}