/**	\file TestTangentFixedRadiusArc.cpp
*	\brief
*/

/****************************************************************************/
/*	TestTangentFixedRadiusArc.cpp											*/
/****************************************************************************/
/*                                                                          */
/*  Copyright 2008 - 2010 Paul Kohut                                        */
/*  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.                          */
/*                                                                          */
/****************************************************************************/

//#include "stdafx.h"
#include <string>
#include <fstream>
#include "LatLongConversions.h"
#include "..\GeoFormulas\Conversions.h"
#include "..\GeoFormulas\GeoFormulas.h"
#include <boost/regex.hpp>

using namespace boost;
using namespace GeoCalcs;
using namespace std;

bool ParseTestTangentFixedRadiusArc(string sString)
{
	bool bPassed = true;
	TrimWhitespace(sString);
	string soTestId, soGeodesic1StartLat, soGeodesic1StartLon, soGeodesic1Azimuth, soGeodesic2StartLat, soGeodesic2StartLon;
	string soGeodesic2Azimuth, soArcRadius, soArcDirection, soArcCenterLat, soArcCenterLon;
	string soTangentPt1Lat, soTangentPt1Lon, soTangentPt2Lat, soTangentPt2Lon;
	try
	{
		regex_constants::syntax_option_type flags =  regex_constants::icase | regex_constants::perl;

		string sRxPat = "([a-z]+|[A-Z]+\\d+)[,]";
		sRxPat += "([0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[NS])[,]([0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[WE])[,]";
		sRxPat += "([-+]?[0-9]*[.]?[0-9]+)[,]";
		sRxPat += "([0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[NS])[,]([0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[WE])[,]";
		sRxPat += "([-+]?[0-9]*[.]?[0-9]+)[,]";
		sRxPat += "([-+]?[0-9]*[.]?[0-9]+)[,]";
		sRxPat += "([N/A]+|[-+]?[1])[,]";
		sRxPat += "([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[NS])[,]([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[WE])[,]";
		sRxPat += "([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[NS])[,]([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[WE])[,]";
		sRxPat += "([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[NS])[,]([N/A]+|[0-9]*[:][0-9]*[:][0-9]*[.][0-9]*[WE])";

		regex pat(sRxPat, flags);

		int const sub_matches[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, };
		sregex_token_iterator it(sString.begin(), sString.end(), pat, sub_matches);
		if(it != sregex_token_iterator())
		{
			soTestId = *it++;
			soGeodesic1StartLat = *it++;
			soGeodesic1StartLon = *it++;
			soGeodesic1Azimuth = *it++;
			soGeodesic2StartLat = *it++;
			soGeodesic2StartLon = *it++;
			soGeodesic2Azimuth = *it++;
			soArcRadius = *it++;
			soArcDirection = *it++;
			soArcCenterLat = *it++;
			soArcCenterLon = *it++;
			soTangentPt1Lat = *it++;
			soTangentPt1Lon = *it++;
			soTangentPt2Lat = *it++;
			soTangentPt2Lon = *it++;
		}
	}
	catch(regex_error & e)
	{
		cout << "\n" << e.what();
		return false;
	}

	LLPoint geodesic1Start(Deg2Rad(ParseLatitude(soGeodesic1StartLat)), Deg2Rad(ParseLongitude(soGeodesic1StartLon)));
	double geodesic1Azimuth = Deg2Rad(atof(soGeodesic1Azimuth.c_str()));
	LLPoint geodesic2Start(Deg2Rad(ParseLatitude(soGeodesic2StartLat)), Deg2Rad(ParseLongitude(soGeodesic2StartLon)));
	double geodesic2Azimuth = Deg2Rad(atof(soGeodesic2Azimuth.c_str()));
	double arcRadius = NmToMeters(atof(soArcRadius.c_str()));

	int arcDirection = 0;
	if(soArcDirection != "N/A")
	{
		arcDirection = atoi(soArcDirection.c_str());
	}

	LLPoint tangentPt1;
	LLPoint tangentPt2;
	LLPoint arcCenter;
	if(arcDirection != 0)
	{
		tangentPt1.Set(Deg2Rad(ParseLatitude(soTangentPt1Lat)), Deg2Rad(ParseLongitude(soTangentPt1Lon)));
		tangentPt2.Set(Deg2Rad(ParseLatitude(soTangentPt2Lat)), Deg2Rad(ParseLongitude(soTangentPt2Lon)));
		arcCenter.Set(Deg2Rad(ParseLatitude(soArcCenterLat)), Deg2Rad(ParseLongitude(soArcCenterLon)));
	}

//	CGeoCalcs geo1;
	LLPoint tanPt1, tanPt2, centerPt;

	int nIndex = TangentFixedRadiusArc(geodesic1Start, geodesic1Azimuth, geodesic2Start, geodesic2Azimuth,
						arcRadius, arcDirection, centerPt, tanPt1, tanPt2, 1e-9);

	if(nIndex == 0)
	{
		if(soArcDirection != "N/A")
		{
			bPassed = false;
			cout << "\n" << soTestId << " Failed: Returned with no solution.";
		}

	} else {

		string sArcCenterLat = ConvertLatitudeDdToDms(Rad2Deg(centerPt.latitude));
		string sArcCenterLon = ConvertLongitudeDdToDms(Rad2Deg(centerPt.longitude));
		string sTan1Lat = ConvertLatitudeDdToDms(Rad2Deg(tanPt1.latitude));
		string sTan1Lon = ConvertLongitudeDdToDms(Rad2Deg(tanPt1.longitude));
		string sTan2Lat = ConvertLatitudeDdToDms(Rad2Deg(tanPt2.latitude));
		string sTan2Lon = ConvertLongitudeDdToDms(Rad2Deg(tanPt2.longitude));

		if(atoi(soArcDirection.c_str()) != arcDirection)
		{
			cout << "\n" << soTestId << " failed: Expected Arc direction: " << soArcDirection << "  calced: " << arcDirection;
			bPassed = false;
		}

		if(sArcCenterLat.compare(soArcCenterLat) != 0)
		{
			double dLat = Deg2Rad(ParseLatitude(soArcCenterLat));
			if(IsApprox(dLat, centerPt.latitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Arc center latitude: " << soArcCenterLat << "  calced: " << sArcCenterLat;
			} else {
				cout << "\n" << soTestId << " failed: Expected Arc center latitude: " << soArcCenterLat << "  calced: " << sArcCenterLat;
				bPassed = false;
			}
		}

		if(sArcCenterLon.compare(soArcCenterLon) != 0)
		{
			double dLon = Deg2Rad(ParseLongitude(soArcCenterLon));
			if(IsApprox(dLon, centerPt.longitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Arc center longitude: " << soArcCenterLon << "  calced: " << sArcCenterLon;
			} else {
				cout << "\n" << soTestId << " failed: Expected Arc center longitude: " << soArcCenterLon << "  calced: " << sArcCenterLon;
				bPassed = false;
			}
		}

		if(sTan1Lat.compare(soTangentPt1Lat) != 0)
		{
			double dLat = Deg2Rad(ParseLatitude(soTangentPt1Lat));
			if(IsApprox(dLat, tanPt1.latitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Tangent pt 1 latitude: " << soTangentPt1Lat << "  calced: " << sTan1Lat;
			} else {
				cout << "\n" << soTestId << " failed: Expected Tangent pt 1 latitude: " << soTangentPt1Lat << "  calced: " << sTan1Lat;
				bPassed = false;
			}
		}

		if(sTan1Lon.compare(soTangentPt1Lon) != 0)
		{
			double dLon = Deg2Rad(ParseLongitude(soTangentPt1Lon));
			if(IsApprox(dLon, tanPt1.longitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Tangent pt 1 longitude: " << soTangentPt1Lon << "  calced: " << sTan1Lon;
			} else {
				cout << "\n" << soTestId << " failed: Expected Tangent pt 1 longitude: " << soTangentPt1Lon << "  calced: " << sTan1Lon;
				bPassed = false;
			}
		}

		if(sTan2Lat.compare(soTangentPt2Lat) != 0)
		{
			double dLat = Deg2Rad(ParseLatitude(soTangentPt2Lat));
			if(IsApprox(dLat, tanPt2.latitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Tangent pt 2 latitude: " << soTangentPt2Lat << "  calced: " << sTan2Lat;
			} else {
				cout << "\n" << soTestId << " failed: Expected Tangent pt 2 latitude: " << soTangentPt2Lat << "  calced: " << sTan2Lat;
				bPassed = false;
			}
		}

		if(sTan2Lon.compare(soTangentPt2Lon) != 0)
		{
			double dLon = Deg2Rad(ParseLongitude(soTangentPt2Lon));
			if(IsApprox(dLon, tanPt2.longitude, 1e-10))
			{
				cout << "\n" << soTestId << " within rounding tolerance of 1e-10: Input Tangent pt 2 longitude: " << soTangentPt2Lon << "  calced: " << sTan2Lon;
			} else {
				cout << "\n" << soTestId << " failed: Expected Tangent pt 2 longitude: " << soTangentPt2Lon << "  calced: " << sTan2Lon;
				bPassed = false;
			}
		}

	}

	return bPassed;
}

int TestTangentFixedRadiusArc(const string & sFilePath)
{
	ifstream infile;
	infile.exceptions(ifstream::eofbit | ifstream::failbit | ifstream::badbit);
	int nCount = 0;
	int nCommentCount = 0;
	bool bPassed = true;
	try
	{
		string sLine;
		infile.open(sFilePath.c_str(), ifstream::in);

		while(!infile.eof())
		{
			getline(infile, sLine);
			if(sLine.at(0) == '#')
			{
				nCommentCount++;
			}
			else
			{
				if(!ParseTestTangentFixedRadiusArc(sLine))
					bPassed = false;
				nCount++;
			}
		}
		infile.close();
		return bPassed;
	}

	catch(ifstream::failure e)
	{
		int nError = -99;
		// Per C++ standards for ifstream::failbit with global function getline
		// No characters were extracted because the end was prematurely found.Notice
		// that some eofbit cases will also set failbit.
		// In this case the end of the file is read and causes both flags to be raised,
		// so this presumably means all the data has been read correctly.
		if((infile.rdstate() & ifstream::failbit) && (infile.rdstate() & ifstream::eofbit) != 0)
			nError = bPassed;
		else if((infile.rdstate() & ifstream::failbit) != 0)
			nError = -1;
		else if((infile.rdstate() & ifstream::badbit) != 0)
			nError = -2;
		else if((infile.rdstate() & ifstream::eofbit) != 0)
			nError = -3;
		if(infile.is_open())
			infile.close();
		return nError;
	}
}


/**
The MIT License

Copyright (c) 2014-2017 The University of Utah

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.

\file heat_equation.cpp

\page example-heat-equation Example: Solving a Heat Equation

# Goal
Illustrate how to use Nebo to implement a simple two-dimensional heat transfer simulation:
\f[
\frac{\partial \phi}{\partial t}
 = \frac{\partial}{\partial x}\left(\alpha\frac{\partial \phi}{\partial x}\right)
 + \frac{\partial}{\partial y}\left(\alpha\frac{\partial \phi}{\partial y}\right)
\f]

# Key Concepts
 -# Use Nebo assignment, reductions, stencils, masks, and `cond` to create a simple heat transfer equation.
 -# Show a very simple way to implement Dirichlet boundary conditions.
 -# Use ghost fields in conjunction with operators.

\sa \ref example-stencil-type-inference
\sa \ref example-stencils
\sa \ref example-masks

# Example Code
\c examples/heat_equation.cpp
\include heat_equation.cpp

*/

#include <spatialops/structured/FVStaggered.h>
#include <spatialops/structured/Grid.h>
#include <spatialops/structured/FieldHelper.h>

#include <boost/program_options.hpp>
namespace po = boost::program_options;

using namespace SpatialOps;
using std::cout;
using std::endl;

// If we are compiling with GPU CUDA support, create fields on the device.
// Otherwise, create them on the host.
#ifdef ENABLE_CUDA
# define LOCATION GPU_INDEX
#else
# define LOCATION CPU_INDEX
#endif

//==============================================================================

template<typename FieldT>
void
initialize_thermal_diffusivity( const Grid& grid, FieldT & alpha )
{
  SpatFldPtr<FieldT> x = SpatialFieldStore::get<FieldT>( alpha );
  SpatFldPtr<FieldT> y = SpatialFieldStore::get<FieldT>( alpha );

  grid.set_coord<XDIR>( *x );
  grid.set_coord<YDIR>( *y );
  //alpha <<= 0.1 + 0.4 * (*x + *y + 1.0);
  //alpha <<= 0.1 + 0.4 * (*x + *y);
  //alpha <<= (*x + *y + .2) /2;
  alpha <<= 1.0;

# ifdef ENABLE_CUDA
  alpha.add_device( CPU_INDEX );  // transfer to facilitate printing its values
# endif
//  std::cout << "Initial alpha:" << std::endl;
  //
  //print_field( alpha, std::cout, true );
}

//==============================================================================

void
initialize_mask_points( const Grid& grid,
                        std::vector<IntVec>& leftSet,
                        std::vector<IntVec>& rightSet,
                        std::vector<IntVec>& topSet,
                        std::vector<IntVec>& bottomSet )
{
  for( int i=-1; i<=grid.extent(1); ++i ){
    leftSet .push_back( IntVec(-1, i, 0) );
    rightSet.push_back( IntVec(grid.extent(0), i, 0) );
  }

  for(int i = -1; i <= grid.extent(0); i++){
    topSet   .push_back( IntVec(i, -1, 0) );
    bottomSet.push_back( IntVec(i, grid.extent(1), 0) );
  }
}

//==============================================================================

template<typename FieldT>
double
find_deltaT( const FieldT& alpha, const Grid& grid )
{
  const double deltaX = grid.spacing<XDIR>();
  const double deltaY = grid.spacing<YDIR>();
  const double sqrdDeltaX = deltaX * deltaX;
  const double sqrdDeltaY = deltaY * deltaY;
  const double sqrdDeltaXYmult = sqrdDeltaX * sqrdDeltaY;
  const double sqrdDeltaXYplus = sqrdDeltaX + sqrdDeltaY;

  return 0.25 * sqrdDeltaXYmult / ( sqrdDeltaXYplus * nebo_max(alpha) );
}

//==============================================================================

int main( int iarg, char* carg[] )
{
  int nx,ny;
  double lx,ly;
  std::string fname;
  {
    po::options_description desc("Supported Options");
    desc.add_options()
        ( "help", "print help message\n" )
        ( "nx",   po::value<int>(&nx)->default_value(6), "number of points in x-direction" )
        ( "ny",   po::value<int>(&ny)->default_value(6), "number of points in y-direction" )
        ( "Lx",   po::value<double>(&lx)->default_value(1.0), "Domain length in in x-direction" )
        ( "Ly",   po::value<double>(&ly)->default_value(1.0), "Domain length in in y-direction" )
        ( "file", po::value<std::string>(&fname)->default_value("data.txt"), "data file name");

    po::variables_map args;
    po::store( po::parse_command_line(iarg,carg,desc), args );
    po::notify(args);

    if( args.count("help") ){
      cout << desc << endl
           << "Examples:" << endl
           << " heat_equation --nx 5 --ny 10" << endl
           << " heat_equation --nx 12" << endl
           << " heat_equation --nx 10 --ny 1" << endl
           << endl;
      return -1;
    }
  }

  typedef SVolField FieldT;

  //----------------------------------------------------------------------------
  // Define the domain size and number of points
  const DoubleVec domainLength(lx,ly,1);  // a cube of unit length
  const IntVec fieldDim( nx, ny, 1 );     // 6 x 6 x 1 points

  //----------------------------------------------------------------------------
  // Create fields
  const GhostData nghost(1);
  const IntVec bc(true, true, true);
  const BoundaryCellInfo bcInfo = BoundaryCellInfo::build<FieldT>( bc, bc );
  const MemoryWindow window( get_window_with_ghost( fieldDim, nghost, bcInfo) );

  FieldT   phi( window, bcInfo, nghost, nullptr, InternalStorage, LOCATION );
  FieldT   rhs( window, bcInfo, nghost, nullptr, InternalStorage, LOCATION );
  FieldT alpha( window, bcInfo, nghost, nullptr, InternalStorage, LOCATION );

  const Grid grid( fieldDim, domainLength );

  //----------------------------------------------------------------------------
  // Initialize alpha, thermal diffusivity
  initialize_thermal_diffusivity( grid, alpha );

  //----------------------------------------------------------------------------
  // Build and initialize masks:
  std::vector<IntVec> leftSet, rightSet, topSet, bottomSet;

  initialize_mask_points( grid, leftSet, rightSet, topSet, bottomSet );

  SpatialMask<FieldT> left  ( phi, leftSet   );
  SpatialMask<FieldT> right ( phi, rightSet  );
  SpatialMask<FieldT> top   ( phi, topSet    );
  SpatialMask<FieldT> bottom( phi, bottomSet );

# ifdef ENABLE_CUDA
  // Masks are created on CPU so we need to explicitly transfer them to GPU
  left  .add_consumer( GPU_INDEX );
  right .add_consumer( GPU_INDEX );
  top   .add_consumer( GPU_INDEX );
  bottom.add_consumer( GPU_INDEX );
# endif

  //----------------------------------------------------------------------------
  // Build stencils:
  OperatorDatabase opDB;         // holds stencils that can be retrieved easily
  build_stencils( grid, opDB );  // builds stencils and places them in opDB

  typedef BasicOpTypes<SVolField>::DivX          DivX;  // x-divergence operator type
  typedef BasicOpTypes<SVolField>::GradX        GradX;  // x-gradient operator type
  typedef BasicOpTypes<SVolField>::InterpC2FX InterpX;  // x-interpolant operator type

  const DivX&       divX = *opDB.retrieve_operator<DivX   >(); // retrieve the DivX operator
  const GradX&     gradX = *opDB.retrieve_operator<GradX  >(); // retrieve the GradX operator
  const InterpX& interpX = *opDB.retrieve_operator<InterpX>(); // retrieve the InterpX operator

  typedef BasicOpTypes<SVolField>::DivY          DivY;  // y-divergence operator type
  typedef BasicOpTypes<SVolField>::GradY        GradY;  // y-gradient operator type
  typedef BasicOpTypes<SVolField>::InterpC2FY InterpY;  // y-interpolant operator type

  const DivY&       divY = *opDB.retrieve_operator<DivY   >(); // retrieve the DivY operator
  const GradY&     gradY = *opDB.retrieve_operator<GradY  >(); // retrieve the GradY operator
  const InterpY& interpY = *opDB.retrieve_operator<InterpY>(); // retrieve the InterpY operator

  //----------------------------------------------------------------------------
  // Determine a safe deltaT:
  const double deltaT = find_deltaT( alpha, grid );

  //----------------------------------------------------------------------------
  // Initialize phi:
  phi <<= cond( left, 10.0 )
              ( right, 0.0 )
              ( 5.0 );

//  std::cout << "Initial phi:" << std::endl;
//  print_field( phi, std::cout, true );

  //----------------------------------------------------------------------------
  // Take time steps:
  const int nSteps = 40;
  for( int i=0; i<=nSteps; ++i ){

    // update the solution using a forward-Euler update
    phi <<= phi + deltaT * (
          divX( interpX(alpha) * gradX(phi) )
        + divY( interpY(alpha) * gradY(phi) ) );

    // Reset boundaries:
    phi <<= cond( left,         10.0 )
                ( right,         0.0 )
                ( top || bottom, 5.0 )
                ( phi );

    // print current state:
    // print_field( phi, std::cout, true );
  }

  std::ofstream fout(fname);
  print_field( phi, fout, true );

  return 0;
}


#include <Eigen/Core>
#include <iostream>

#include <Spectra/GenEigsComplexShiftSolver.h>
#include <Spectra/MatOp/DenseGenComplexShiftSolve.h>

using namespace Spectra;

#define CATCH_CONFIG_MAIN
#include "catch.hpp"

typedef Eigen::MatrixXd Matrix;
typedef Eigen::VectorXd Vector;
typedef Eigen::MatrixXcd ComplexMatrix;
typedef Eigen::VectorXcd ComplexVector;

template <int SelectionRule>
void run_test(const Matrix &mat, int k, int m, double sigmar, double sigmai, bool allow_fail = false)
{
    DenseGenComplexShiftSolve<double> op(mat);
    GenEigsComplexShiftSolver<double, SelectionRule, DenseGenComplexShiftSolve<double>> eigs(&op, k, m, sigmar, sigmai);
    eigs.init();
    // maxit = 100 to reduce running time for failed cases
    int nconv = eigs.compute(100);
    int niter = eigs.num_iterations();
    int nops = eigs.num_operations();

    if (allow_fail)
    {
        if (eigs.info() != SUCCESSFUL)
        {
            WARN("FAILED on this test");
            std::cout << "nconv = " << nconv << std::endl;
            std::cout << "niter = " << niter << std::endl;
            std::cout << "nops  = " << nops << std::endl;
            return;
        }
    }
    else
    {
        INFO("nconv = " << nconv);
        INFO("niter = " << niter);
        INFO("nops  = " << nops);
        REQUIRE(eigs.info() == SUCCESSFUL);
    }

    ComplexVector evals = eigs.eigenvalues();
    ComplexMatrix evecs = eigs.eigenvectors();

    ComplexMatrix resid = mat * evecs - evecs * evals.asDiagonal();
    const double err = resid.array().abs().maxCoeff();

    INFO("||AU - UD||_inf = " << err);
    INFO("||AU - UD||_2 colwise =" << resid.colwise().norm());
    REQUIRE(err == Approx(0.0).margin(1e-8));
}

void run_test_sets(const Matrix &A, int k, int m, double sigmar, double sigmai)
{
    SECTION("Largest Magnitude")
    {
        run_test<LARGEST_MAGN>(A, k, m, sigmar, sigmai);
    }
    SECTION("Largest Real Part")
    {
        run_test<LARGEST_REAL>(A, k, m, sigmar, sigmai);
    }
    SECTION("Largest Imaginary Part")
    {
        run_test<LARGEST_IMAG>(A, k, m, sigmar, sigmai);
    }
    SECTION("Smallest Magnitude")
    {
        run_test<SMALLEST_MAGN>(A, k, m, sigmar, sigmai, true);
    }
    SECTION("Smallest Real Part")
    {
        run_test<SMALLEST_REAL>(A, k, m, sigmar, sigmai);
    }
    SECTION("Smallest Imaginary Part")
    {
        run_test<SMALLEST_IMAG>(A, k, m, sigmar, sigmai, true);
    }
}

TEST_CASE("Eigensolver of general real matrix [10x10]", "[eigs_gen]")
{
    std::srand(123);

    Matrix A = Eigen::MatrixXd::Random(10, 10);
    int k = 3;
    int m = 8;
    double sigmar = 2.0;
    double sigmai = 1.0;

    run_test_sets(A, k, m, sigmar, sigmai);
}

TEST_CASE("Eigensolver of general real matrix [100x100]", "[eigs_gen]")
{
    std::srand(123);

    Matrix A = Eigen::MatrixXd::Random(100, 100);
    int k = 10;
    int m = 20;
    double sigmar = 20.0;
    double sigmai = 10.0;

    run_test_sets(A, k, m, sigmar, sigmai);
}

TEST_CASE("Eigensolver of general real matrix [1000x1000]", "[eigs_gen]")
{
    std::srand(123);

    Matrix A = Eigen::MatrixXd::Random(1000, 1000);
    int k = 20;
    int m = 50;
    double sigmar = 200.0;
    double sigmai = 100.0;

    run_test_sets(A, k, m, sigmar, sigmai);
}


//=======================================================================
// Copyright 2007 Aaron Windsor
//
// 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 <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map/property_map.hpp>
#include <vector>

#include <boost/graph/connected_components.hpp>
#include <boost/graph/make_connected.hpp>

using namespace boost;

int main(int argc, char** argv)
{

  typedef adjacency_list
    < vecS,
      vecS,
      undirectedS,
      property<vertex_index_t, int>
    >
    graph;

  graph g(11);
  add_edge(0,1,g);
  add_edge(2,3,g);
  add_edge(3,4,g);
  add_edge(5,6,g);
  add_edge(6,7,g);
  add_edge(8,9,g);
  add_edge(9,10,g);
  add_edge(10,8,g);

  std::vector< graph_traits<graph>::vertices_size_type >
    component(num_vertices(g));

  std::cout << "Before calling make_connected, the graph has "
            << connected_components(g, &component[0])
            << " connected components" << std::endl;

  make_connected(g);

  std::cout << "After calling make_connected, the graph has "
            << connected_components(g, &component[0])
            << " connected components" << std::endl;

  return 0;
}


// (C) Copyright Jeremy Siek 2001.
// 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)

// Sample output:
//  1
//  1
//  0
//  0
//  1

#include <iostream>
#include <boost/dynamic_bitset.hpp>
int main() {
  boost::dynamic_bitset<> x(5); // all 0's by default
  x[0] = 1;
  x[1] = 1;
  x[4] = 1;
  for (boost::dynamic_bitset<>::size_type i = 0; i < x.size(); ++i)
    std::cout << x[i];
  std::cout << "\n";
  std::cout << x << "\n";
  return EXIT_SUCCESS;
}


/*
 * GraphWeigher.cpp
 *
 *  Created on: Nov 29, 2016
 *      Author: cochez
 */

#include "GraphWeigher.h"

#include <iostream>

#include <string>
#include <unordered_map>
#include <fstream>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/lexical_cast.hpp>

#include "graph/LabeledGraph.h"
#include "utils.h"
using namespace std;

typedef boost::flyweight<std::string> flyString;

namespace { //helpers

//count freq of each property:
unordered_map<flyString, double> absolute_predicate_freq(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) {
	unordered_map<flyString, double> absolute_freq;
	std::vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	for (auto nodeI = nodes.begin(); nodeI != nodes.end(); nodeI++) {
		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
		for (auto edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			flyString predictae = edgeI->label;
			auto oldValI = absolute_freq.find(predictae);
			if (oldValI == absolute_freq.end()) {
				absolute_freq[predictae] = 1;
			} else {
				absolute_freq[predictae] = oldValI->second + 1;
			}
		}
	}
	return absolute_freq;
}

////counts the absolute frequence of a the objects
//unordered_map<flyString, double> absolute_object_freq(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) {
//	unordered_map<flyString, double> absolute_freq;
//	std::vector<QuickGraph::Node> & nodes = baseGraph->nodes;
//	for (auto nodeI = nodes.begin(); nodeI != nodes.end(); nodeI++) {
//		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
//		for (auto edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
//			flyString object = edgeI->target->label;
//			auto oldValI = absolute_freq.find(object);
//			if (oldValI == absolute_freq.end()) {
//				absolute_freq[object] = 1;
//			} else {
//				absolute_freq[object] = oldValI->second + 1;
//			}
//		}
//	}
//	return absolute_freq;
//}

//counts the absolute frequence of a the objects - adapted form the above as it seems more reasonable to keep an iterator, but then iteratrs cannot be hashed -> index
vector<double> absolute_object_freq(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) {
	vector<double> absolute_freq;
	absolute_freq.resize(baseGraph->nodes.size(), 0);
	std::vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	for (auto nodeI = nodes.begin(); nodeI != nodes.end(); nodeI++) {
		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
		for (auto edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			int objectIndex = edgeI->targetIndex;
			absolute_freq[objectIndex]++;
		}
	}
	return absolute_freq;
}

unordered_map<pair<flyString, flyString>, double, pairhash> absolute_predicate_object_freq(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) {
	unordered_map<pair<flyString, flyString>, double, pairhash> absolute_freq;
	std::vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	for (auto nodeI = nodes.begin(); nodeI != nodes.end(); nodeI++) {
		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
		for (auto edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			flyString predicate = edgeI->label;
			flyString object = nodes[edgeI->targetIndex].label;
			pair<flyString, flyString> pair(predicate, object);
			auto oldValI = absolute_freq.find(pair);
			if (oldValI == absolute_freq.end()) {
				absolute_freq[pair] = 1;
			} else {
				absolute_freq[pair] = oldValI->second + 1;
			}
		}
	}
	return absolute_freq;
}

//inverse all freq
template<class type, class Hash = std::hash<type>> void inverse_the_frequency_map(unordered_map<type, double, Hash> & absolute_freq) {
	for (typename unordered_map<type, double, Hash>::iterator iter = absolute_freq.begin(); iter != absolute_freq.end(); iter++) {
		absolute_freq[iter->first] = 1.0 / iter->second;
	}
}

//inverse all freq
void inverse_the_frequency_vec(vector<double> & absolute_freq) {
	for (typename vector<double>::iterator iter = absolute_freq.begin(); iter != absolute_freq.end(); iter++) {
		*iter = 1.0 / *iter;
	}
}

//normalize all weights in unbalanced (sum weight on outedges == 1)
void normalize(std::shared_ptr<QuickGraph::LabeledGraph> unbalanced) {

	for (vector<QuickGraph::Node>::iterator nodeI = unbalanced->nodes.begin(); nodeI != unbalanced->nodes.end(); nodeI++) {
		//normalize outgoing from this node
		double totalWeight = 0;
		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
		for (vector<QuickGraph::Edge>::const_iterator edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			totalWeight += edgeI->weight;
		}
		double totalWeightInverse = 1.0 / totalWeight;
		for (vector<QuickGraph::Edge>::iterator edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			double normalizedWeight = edgeI->weight * totalWeightInverse;
			edgeI->weight = normalizedWeight;
		}
	}

}

void setValueToOne(QuickGraph::LabeledGraph & /* baseGraph */, QuickGraph::Edge & edge) {
	edge.weight = 1;
}

} //and anonymous namespace helpers

namespace weigher{

unordered_map<string, double> readDBPediaPageRanks(string tsvFile) {
	unordered_map<string, double> ranks;

	ifstream infile(tsvFile);

	if (!infile.is_open()) {
		// error! maybe the file doesn't exist.
		cerr << "Input file " << tsvFile << " not found, exiting!!" << endl;
		exit(7);
	}

	string line;

	while (std::getline(infile, line)) {
		if (line.find_first_not_of(' ') == std::string::npos) {
			continue;
		}
		if (line.find_first_of('#') == 0) {
			//comment
			continue;
		}

		vector<string> SplitVec; // #2: Search for tokens
		boost::split(SplitVec, line, boost::is_any_of("\t"), boost::token_compress_off);
		string resource = "<" + SplitVec[0] + ">";
		double rank = boost::lexical_cast<double>(SplitVec[1]);

		ranks[resource] = rank;
	}
	infile.close();
	return ranks;
}

void UniformWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {

	baseGraph->forEachEdge(setValueToOne);
	normalize(baseGraph);

}

namespace {
class PredicateToWeight {
	const unordered_map<flyString, double> weights;
public:

	PredicateToWeight(const unordered_map<flyString, double> & weights) :
			weights(weights) {

	}
	void operator()(QuickGraph::LabeledGraph & /*baseGraph*/, QuickGraph::Edge & edge) {
		double newWeight = this->weights.at(edge.label);
		edge.weight = newWeight;
	}

};
}

void PredicateFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {

	unordered_map<flyString, double> absolute_freq = absolute_predicate_freq(baseGraph);
	baseGraph->forEachEdge(PredicateToWeight(absolute_freq));
	normalize(baseGraph);

}

void InversePredicateFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {

//count freq of each property:
	unordered_map<flyString, double> absolute_freq = absolute_predicate_freq(baseGraph);
	inverse_the_frequency_map(absolute_freq);
	baseGraph->forEachEdge(PredicateToWeight(absolute_freq));
	normalize(baseGraph);

}

void ObjectFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<double> absolute_freq = absolute_object_freq(baseGraph);
	PushDownWeigher subWeigher(absolute_freq);
	return subWeigher.weigh(baseGraph);
}

void InverseObjectFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<double> absolute_freq = absolute_object_freq(baseGraph);
	inverse_the_frequency_vec(absolute_freq);
	PushDownWeigher subWeigher(absolute_freq);
	return subWeigher.weigh(baseGraph);
}

void InverseObjectFrequencyWeigherSplitDown::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<double> absolute_freq = absolute_object_freq(baseGraph);
	inverse_the_frequency_vec(absolute_freq);
	SplitDownWeigher subWeigher(absolute_freq);
	return subWeigher.weigh(baseGraph);
}

namespace {
void predObWeigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph, std::unordered_map<std::pair<flyString, flyString>, double, pairhash> & freqs) {
	std::vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	for (std::vector<QuickGraph::Node>::iterator nodeI = nodes.begin(); nodeI != nodes.end(); nodeI++) {
		std::vector<QuickGraph::Edge> & edges = nodeI->edges;
		for (std::vector<QuickGraph::Edge>::iterator edgeI = edges.begin(); edgeI != edges.end(); edgeI++) {
			flyString pred = edgeI->label;
			flyString obj = nodes[edgeI->targetIndex].label;
			std::pair<flyString, flyString> pair(pred, obj);
			double weight = freqs.at(pair);
			edgeI->weight = weight;
		}
	}
	normalize(baseGraph);
}
}

void PredicateObjectFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	std::unordered_map<std::pair<flyString, flyString>, double, pairhash> absolute_freq = absolute_predicate_object_freq(baseGraph);
	predObWeigh(baseGraph, absolute_freq);
}

void InversePredicateObjectFrequencyWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	std::unordered_map<std::pair<flyString, flyString>, double, pairhash> absolute_freq = absolute_predicate_object_freq(baseGraph);
	inverse_the_frequency_map(absolute_freq);
	predObWeigh(baseGraph, absolute_freq);
}

void PushDownWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	assert(nodes.size() == this->nodeWeights.size());

	for (unsigned int i = 0; i < this->nodeWeights.size(); i++) {
		std::vector<QuickGraph::Edge> & edges = nodes[i].edges;

		for (std::vector<QuickGraph::Edge>::iterator edge = edges.begin(); edge != edges.end(); edge++) {

			int targetIndex = edge->targetIndex;
			double weight = this->nodeWeights[targetIndex];
			edge->weight = weight;
		}
	}
	normalize(baseGraph);
}

void PushDownWeigherMap::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<QuickGraph::Node> & nodes = baseGraph->nodes;

	for (std::vector<QuickGraph::Node>::iterator iter = nodes.begin(); iter != nodes.end(); iter++) {
		QuickGraph::Node & theNode = *iter;
		std::unordered_map<string, double>::const_iterator findIter = this->nodeWeights.find(theNode.label);
		double weight;
		if (findIter == this->nodeWeights.cend()) {
			weight = this->defaultWeight;
		} else {
			weight = findIter->second;
		}
		std::vector<QuickGraph::Edge> & edges = theNode.edges;
		for (std::vector<QuickGraph::Edge>::iterator edge = edges.begin(); edge != edges.end(); edge++) {
			edge->weight = weight;
		}
	}
	normalize(baseGraph);
}

void SplitDownWeigher::weigh(std::shared_ptr<QuickGraph::LabeledGraph> baseGraph) const {
	vector<QuickGraph::Node> & nodes = baseGraph->nodes;
	assert(nodes.size() == this->nodeWeights.size());

	//determine all in degrees
	vector<int> indegrees;
	indegrees.resize(nodes.size(), 0);
	for (unsigned int i = 0; i < this->nodeWeights.size(); i++) {
		std::vector<QuickGraph::Edge> & edges = nodes[i].edges;
		for (std::vector<QuickGraph::Edge>::iterator edge = edges.begin(); edge != edges.end(); edge++) {
			int targetIndex = edge->targetIndex;
			indegrees[targetIndex]++;
		}
	}

	for (unsigned int i = 0; i < this->nodeWeights.size(); i++) {
		std::vector<QuickGraph::Edge> & edges = nodes[i].edges;

		for (std::vector<QuickGraph::Edge>::iterator edge = edges.begin(); edge != edges.end(); edge++) {
			int targetIndex = edge->targetIndex;
			double weight = this->nodeWeights[targetIndex] / indegrees[targetIndex];
			edge->weight = weight;
		}
	}
	normalize(baseGraph);

}

}


#define BOOST_TEST_MODULE Stream Generator Unit Test

#include <cstdint>
#include <stdexcept>
#include <string>
#include <unordered_map>

#include <boost/test/included/unit_test.hpp>

#include "stream.hpp"

namespace stream
{

struct int_sparse_stream_tester final
{
    template<stream_type ST>
    void test_generation(uint64_t const pn, uint64_t const nn)
    {
        auto g = int_sparse_stream<ST>(pn, nn);
        std::unordered_map<uint16_t, int64_t> record;
        while (true)
        {
            std::pair<bool, std::pair<uint16_t, int8_t>> o = g;
            if (!o.first)
                break;
            auto const & [item, update] = o.second;
            BOOST_REQUIRE_NE(update, 0);
            if constexpr (ST == stream_type::turnstile)
                if (update < 0)
                    BOOST_REQUIRE_GE(record[item], -update);
            record[item] += update;
        }
        BOOST_REQUIRE_EQUAL(g.history.size(), pn);
        uint64_t count = 0;
        for (auto const & [k, v] : record)
            if constexpr (ST == stream_type::turnstile)
                count += v > 0;
            else
                count += v != 0;
        BOOST_REQUIRE_EQUAL(count, pn - nn);
        BOOST_REQUIRE_EQUAL(g.left.size(), pn - nn);
    }
};

}

struct int_sparse_stream_test_fixture
{
    stream::int_sparse_stream_tester tester;
};

bool catch_invalid(std::invalid_argument const & e) noexcept
{
    using namespace std::string_literals;
    return "invalid sparse stream"s == e.what();
}

BOOST_FIXTURE_TEST_SUITE(IntSparseStream, int_sparse_stream_test_fixture)

BOOST_AUTO_TEST_CASE(Turnstile)
{
    BOOST_REQUIRE_EXCEPTION(tester.test_generation<stream::stream_type::turnstile>(2, 3), std::invalid_argument, catch_invalid);
    BOOST_TEST_MESSAGE("0 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::turnstile>(100, 100);
    BOOST_TEST_MESSAGE("1 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::turnstile>(100, 99);
    BOOST_TEST_MESSAGE("2 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::turnstile>(100, 98);
    BOOST_TEST_MESSAGE("3 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::turnstile>(100, 97);
    BOOST_TEST_MESSAGE("4 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::turnstile>(100, 96);
}

BOOST_AUTO_TEST_CASE(General)
{
    BOOST_REQUIRE_EXCEPTION(tester.test_generation<stream::stream_type::general>(2, 3), std::invalid_argument, catch_invalid);
    BOOST_TEST_MESSAGE("0 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::general>(100, 100);
    BOOST_TEST_MESSAGE("1 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::general>(100, 99);
    BOOST_TEST_MESSAGE("2 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::general>(100, 98);
    BOOST_TEST_MESSAGE("3 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::general>(100, 97);
    BOOST_TEST_MESSAGE("4 sparse");
    for (uint8_t i = 0; i < 10; ++i)
        tester.test_generation<stream::stream_type::general>(100, 96);
}

BOOST_AUTO_TEST_SUITE_END()


/*
 * File:   runLSH_all.cc
 * Author: chteflio
 *
 * Created on March 30, 2015, 6:12 PM
 */

#include <boost/program_options.hpp>

#include <iostream>
#include <mips/mips.h>

using namespace std;
using namespace mips;
using namespace boost::program_options;

int main(int argc, char *argv[]) {
    string usersFile;
    string itemsFile;
    string logFile, resultsFile;

    bool querySideLeft = true;
    bool isTransformed = false;
    int k, r, m, n, threads;

    // read command line
    options_description desc("Options");
    desc.add_options()
            ("help", "produce help message")
            ("Q^T", value<string>(&usersFile), "file containing the query matrix (left side)")
            ("P", value<string>(&itemsFile), "file containing the probe matrix (right side)")
            ("querySideLeft", value<bool>(&querySideLeft)->default_value(true), "1 if Q^T contains the queries (default). Interesting for Row-Top-k")
            ("isTransformed", value<bool>(&isTransformed)->default_value(false), "1 if the input files contain the tranformed vectors. If 0 the program will transform them")
            ("k", value<int>(&k), "top k ")
            ("logFile", value<string>(&logFile)->default_value(""), "output File (contains runtime information)")
            ("resultsFile", value<string>(&resultsFile)->default_value(""), "output File (contains the results)")
            ("t", value<int>(&threads)->default_value(1), "num of threads (default 1)")
            ("r", value<int>(&r)->default_value(0), "num of coordinates in each vector (needed when reading from csv files)")
            ("m", value<int>(&m)->default_value(0), "num of vectors in Q^T (needed when reading from csv files)")
            ("n", value<int>(&n)->default_value(0), "num of vectors in P (needed when reading from csv files)")
            ;

    positional_options_description pdesc;
    pdesc.add("Q^T", 1);
    pdesc.add("P", 2);

    variables_map vm;
    store(command_line_parser(argc, argv).options(desc).positional(pdesc).run(), vm);
    notify(vm);

    if (vm.count("help") || vm.count("Q^T") == 0 || vm.count("P") == 0) {
        cout << "runSimpleLsh [options] <Q^T> <P>" << endl << endl;
        cout << desc << endl;
        return 1;
    }

    InputArguments args;
    args.logFile = logFile;
    args.k = k;
    args.threads = threads;

    VectorMatrix leftMatrix, rightMatrix;

    if (querySideLeft) {
        leftMatrix.readFromFile(usersFile, r, m, true);
        rightMatrix.readFromFile(itemsFile, r, n, false);
    } else {
        leftMatrix.readFromFile(itemsFile, r, n, false);
        rightMatrix.readFromFile(usersFile, r, m, true);
    }

    mips::SimpleLsh algo(args, isTransformed);

    algo.initialize(rightMatrix);

    Results results;
    algo.runTopK(leftMatrix, results);

    if (resultsFile != "") {
        results.writeToFile(resultsFile);
    }

    return 0;
}


//
// Created by Linyuan Shi on 10/19/19.
// Copyright (c) 2019 University of Florida. All rights reserved.
//

#include <algorithm>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graph_traits.hpp>
using boost::adjacency_list;
using boost::property;
using boost::property_map;
using boost::vertex_name;
using boost::vertex_name_t;
using boost::graph_traits;

#include <memory>
using std::unique_ptr;

#include <iostream>
using std::cout;
using std::endl;

#include "functions.h"

#ifdef DEBUG
#include "timer.h"
#endif

//typedef adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
//        property<vertex_name_t, const bin *>> Graph;
typedef adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> Graph;
typedef graph_traits<Graph>::vertex_descriptor Vertex;
typedef graph_traits<Graph>::vertex_iterator Vertex_iterator;
typedef std::pair<int, int> Edge;

auto find_voids(const vector<const bin*>& empty_bins, const grid_info& gr_nf, const settings& config) ->unique_ptr<vector<vector<const bin*>>>
{
#ifdef DEBUG
	const timer timer_voids(std::cout, "Finding voids");
#endif
	const int empty_bins_size = empty_bins.size();
	const bool flag_periodic = config.isPeriodic();
#ifdef DEBUG
	const timer timer_edges(std::cout, "Constructing edges");
#endif
	vector<Edge> edges;
	for (int i = 0; i < empty_bins_size; ++i)
		for (int j = i + 1; j < empty_bins_size; ++j) {
			if (bin_if_adjacent(*empty_bins[i], *empty_bins[j], gr_nf, flag_periodic)) {
				edges.emplace_back(i, j);
			}
		}
	const Graph g_bin(edges.begin(), edges.end(), empty_bins_size);
#ifdef DEBUG
	timer_edges.span();
#endif
	std::vector<int> component(boost::num_vertices(g_bin));
	int n = boost::connected_components(g_bin, &component[0]);
	auto bin_voids_ptr = std::make_unique<vector<vector<const bin*>>>(n);
	for (size_t i = 0; i != component.size(); ++i) {
		(*bin_voids_ptr)[component[i]].push_back(empty_bins[i]);
	}

#ifdef DEBUG
	timer_voids.span();
#endif
	return bin_voids_ptr;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2010-2012 Barend Gehrels, Amsterdam, the Netherlands.

// Use, modification and distribution is 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 <iostream>
#include <string>

#include <geometry_test_common.hpp>

#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/point_xy.hpp>

#include <boost/geometry/algorithms/buffer.hpp>
#include <boost/geometry/algorithms/num_points.hpp>
#include <boost/geometry/algorithms/unique.hpp>
#include <boost/geometry/extensions/algorithms/connect.hpp>
#include <boost/geometry/multi/io/wkt/read.hpp>
#include <boost/geometry/multi/algorithms/area.hpp>
#include <boost/geometry/multi/algorithms/length.hpp>
#include <boost/geometry/multi/algorithms/num_points.hpp>
#include <boost/geometry/multi/algorithms/unique.hpp>
#include <boost/geometry/multi/geometries/multi_linestring.hpp>
#include <boost/geometry/strategies/strategies.hpp>

#if defined(TEST_WITH_SVG)
#  include <boost/geometry/multi/algorithms/envelope.hpp>
#  include <boost/geometry/extensions/io/svg/svg_mapper.hpp>
#endif

template <typename GeometryOut, typename Geometry>
void test_connect(std::string const& caseid, Geometry const& geometry,
        std::size_t expected_count, std::size_t expected_point_count,
        double expected_length, double limit = -1, double percentage = 0.001)
{
    typedef typename bg::coordinate_type<Geometry>::type coordinate_type;

    std::vector<GeometryOut> connected_vector;
    if (limit > 0)
    {
        bg::connect(geometry, connected_vector, limit);
    }
    else
    {
        bg::connect(geometry, connected_vector);
    }

    typename bg::default_length_result<Geometry>::type length = 0;
    std::size_t count = boost::size(connected_vector);
    std::size_t point_count = 0;

    BOOST_FOREACH(GeometryOut& connected, connected_vector)
    {
        bg::unique(connected);
        length += bg::length(connected);
        point_count += bg::num_points(connected);
    }

    BOOST_CHECK_MESSAGE(count == expected_count,
            "connect: " << caseid
            << " #lines expected: " << expected_count
            << " detected: " << count
            << " type: " << string_from_type<coordinate_type>::name()
            );

    BOOST_CHECK_EQUAL(point_count, expected_point_count);
    BOOST_CHECK_CLOSE(length, expected_length, percentage);

#if defined(TEST_WITH_SVG)
    {
        std::ostringstream filename;
        filename << "connect_"
            << caseid << "_"
            << string_from_type<coordinate_type>::name()
            << ".svg";

        std::ofstream svg(filename.str().c_str());

        bg::svg_mapper
            <
                typename bg::point_type<Geometry>::type
            > mapper(svg, 500, 500);
        mapper.add(geometry);

        bg::model::box<typename bg::point_type<Geometry>::type> extent;
        bg::envelope(geometry, extent);
        bg::buffer(extent, extent, 0.1);
        mapper.add(extent);

        mapper.map(geometry, "opacity:0.6;fill:rgb(0,0,255);stroke:rgb(0,0,0);stroke-width:1");
        BOOST_FOREACH(GeometryOut& connected, connected_vector)
        {
           mapper.map(connected, "opacity:0.6;fill:none;stroke:rgb(255,0,0);stroke-width:5");
        }
    }
#endif
}

template <typename Geometry, typename GeometryOut>
void test_one(std::string const& caseid, std::string const& wkt,
        std::size_t expected_count, std::size_t expected_point_count,
        double expected_length, double limit = -1, double percentage = 0.001)
{
    Geometry geometry;
    bg::read_wkt(wkt, geometry);

    test_connect<GeometryOut>(caseid, geometry,
        expected_count, expected_point_count,
        expected_length, limit, percentage);

#ifdef BOOST_GEOMETRY_TEST_MULTI_PERMUTATIONS
    // Test different combinations of a multi-polygon

    int n = geometry.size();

    // test them in all orders
    std::vector<int> indices;
    for (int i = 0; i < n; i++)
    {
        indices.push_back(i);
    }
    int permutation = 0;
    do
    {
        std::ostringstream out;
        out << caseid;
        Geometry geometry2;
        for (int i = 0; i < n; i++)
        {
            int index = indices[i];
            out << "_" << index;
            geometry2.push_back(geometry[index]);
        }
        test_connect<GeometryOut>(out.str(), geometry2,
                expected_point_count, expected_length, percentage);
    } while (std::next_permutation(indices.begin(), indices.end()));
#endif

}

template <typename P>
void test_all()
{

    typedef bg::model::linestring<P> linestring;
    typedef bg::model::multi_linestring<linestring> multi_linestring;

    test_one<multi_linestring, linestring>("ls_simplex",
        "MULTILINESTRING((0 0,1 1),(1 1,2 2))",
        1, 3, 2 * std::sqrt(2.0));

    // Opposites, forming one line
    test_one<multi_linestring, linestring>("ls_simplex_opposite_to",
        "MULTILINESTRING((0 0,1 1),(2 2,1 1))",
        1, 3, 2 * std::sqrt(2.0));
    test_one<multi_linestring, linestring>("ls_simplex_opposite_from",
        "MULTILINESTRING((1 1,0 0),(1 1,2 2))",
        1, 3, 2 * std::sqrt(2.0));

    // Two output linestrings
    test_one<multi_linestring, linestring>("ls_simplex_two",
        "MULTILINESTRING((0 0,1 1),(1 1,2 2),(3 3,4 4),(4 4,5 5))",
        2, 6, 4 * std::sqrt(2.0));

    // Linestrings forming a ring
    test_one<multi_linestring, linestring>("ls_simplex_ring",
        "MULTILINESTRING((0 0,0 1),(1 1,1 0),(0 1,1 1),(1 0,0 0))",
        1, 5, 4.0);

    // disconnected rings
    test_one<multi_linestring, linestring>("ls_disconnected_ring1",
        "MULTILINESTRING((0 0,0 1.01),(1.02 1.03,0.99 0),(0 0.98,1.001 1),(1.01 0,0 0))",
        1, 8, 4.137147, 0.5);
    test_one<multi_linestring, linestring>("ls_disconnected_ring2",
        "MULTILINESTRING((0 0,0 1.01),(1.02 1.03,0.99 0),(0 0.98,1.001 1),(1.01 0,0 0))",
        1, 8, 4.137147, 0.1);
    test_one<multi_linestring, linestring>("ls_disconnected_ring3",
        "MULTILINESTRING((0 0,0 1.01),(1.02 1.03,0.99 0),(0 0.98,1.001 1),(1.01 0,0 0))",
        3, 7, 4.05163658, 0.01);

    test_one<multi_linestring, linestring>("ls_disconnected_ring4",
        "MULTILINESTRING((0.01 0,0 1.01),(1.02 1.03,0.99 0),(0 0.98,1.001 1),(1.01 0,0.02 0))",
        1, 8, 4.1172, 0.1);
}

int test_main(int, char* [])
{
    test_all<bg::model::d2::point_xy<double> >();

    return 0;
}


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/constant/halfeps.hpp>
#include <boost/simd/constant/eps.hpp>
#include <boost/simd/as.hpp>
#include <simd_test.hpp>

STF_CASE_TPL( "Check halfeps behavior for integral types"
            , (std::uint8_t)(std::uint16_t)(std::uint32_t)(std::uint64_t)
              (std::int8_t)(std::int16_t)(std::int32_t)(std::int64_t)
            )
{
  using boost::simd::as;
  using boost::simd::detail::halfeps;
  using boost::simd::Halfeps;

  STF_TYPE_IS(decltype(Halfeps<T>()), T);
  STF_EQUAL(Halfeps<T>(), T(1));
  STF_EQUAL(halfeps( as(T{}) ),T(1));
}

STF_CASE_TPL( "Check halfeps behavior for floating types"
            , (double)(float)
            )
{
  using boost::simd::as;
  using boost::simd::detail::halfeps;
  using boost::simd::Halfeps;
  using boost::simd::Eps;

  STF_TYPE_IS(decltype(Halfeps<T>()), T);
  STF_IEEE_EQUAL(Halfeps<T>(), Eps<T>()/2);
  STF_IEEE_EQUAL(halfeps( as(T{}) ), Eps<T>()/2);
}


#include <alglib/ap.h>
#include <alglib/interpolation.h>

#include "perceive/utils/md5.hpp"
#include "rbf-field.hpp"

#define This RBFField

namespace perceive
{
// ----------------------------------------------------------------------- Pimpl

enum FieldType : unsigned { FIELD_NONE = 0, RBF, QNN, BICUBIC };

class This::Pimpl
{
 public:
   Pimpl()
   {
      alglib::rbfcreate(2, 2, model); // 2d => 2d field
      src_aabb = AABB::minmax();
      dst_aabb = AABB::minmax();
   }

   ~Pimpl() = default;

   Pimpl(const Pimpl& o)
   {
      src_aabb = o.src_aabb;
      dst_aabb = o.dst_aabb;
      type     = o.type;
      if(type == FieldType::BICUBIC) {
         x_map = o.x_map;
         y_map = o.y_map;
      } else if(type == FieldType::FIELD_NONE) {
         // Do nothing
      } else {
         std::stringstream ss("");
         rbfserialize(const_cast<alglib::rbfmodel&>(o.model), ss);
         std::string s = ss.str();
         std::istringstream os(s);
         rbfunserialize(s, model);
      }
   }

   AABB src_aabb;
   AABB dst_aabb;

   FieldType type{FIELD_NONE};

   alglib::rbfreport rep;
   alglib::rbfmodel model;

   BicubicSpline x_map, y_map;

   void init(const vector<Vector2>& src,
             const vector<Vector2>& dst,
             FieldType type_,
             double rbase,
             unsigned layers,
             double smoothing,
             double qq,
             double zz);
};

// ---------------------------------------------------------------- Construction

This::RBFField()
    : pimpl_(new This::Pimpl())
{}
This::RBFField(const RBFField& o) { *this = o; }
This::RBFField(RBFField&&) = default;
This::~RBFField()          = default;
RBFField& This::operator   =(const RBFField& o)
{
   if(this == &o) return *this;
   pimpl_ = unique_ptr<Pimpl>(new This::Pimpl(*o.pimpl_));
   return *this;
}
RBFField& This::operator=(RBFField&&) = default;

// --------------------------------------------------------------------- Getters

const AABB& This::src_aabb() const { return pimpl_->src_aabb; }
const BicubicSpline& This::x_map() const { return pimpl_->x_map; }
const BicubicSpline& This::y_map() const { return pimpl_->y_map; }

// ------------------------------------------------------------------------ Init

void This::init(const vector<Vector2>& src,
                const vector<Vector2>& dst,
                double rbase,
                unsigned layers,
                double smoothing)
{
   return pimpl_->init(src, dst, RBF, rbase, layers, smoothing, 0.0, 0.0);
}

void This::Pimpl::init(const vector<Vector2>& src,
                       const vector<Vector2>& dst,
                       FieldType type_,
                       double rbase,
                       unsigned layers,
                       double smoothing,
                       double qq,
                       double zz)
{
   if(src.size() == 0 || src.size() != dst.size()) {
      throw std::runtime_error(format("something is wrong with 'src' "
                                      "(size={}) or 'dst' (size={})",
                                      src.size(),
                                      dst.size()));
   }

   type = type_;

   src_aabb = AABB::minmax();
   dst_aabb = AABB::minmax();

   // Fill the points array
   alglib::real_2d_array xy;
   xy.setlength(alglib::ae_int_t(src.size()), 4);
   for(unsigned i = 0; i < src.size(); ++i) {
      xy[i][0] = src[i].x;
      xy[i][1] = src[i].y;
      xy[i][2] = dst[i].x;
      xy[i][3] = dst[i].y;

      src_aabb.union_point(src[i]);
      dst_aabb.union_point(dst[i]);
   }

   { // Add a margin to the src-aabb
      auto mw = 0.005 * src_aabb.width();
      auto mh = 0.005 * src_aabb.height();
      src_aabb.left -= mw;
      src_aabb.right += mw;
      src_aabb.top -= mh;
      src_aabb.bottom += mh;
   }

   if(type == QNN || type == RBF) {
      try {
         // Create the model
         alglib::rbfcreate(2, 2, model);
         // Set the points into the model
         rbfsetpoints(model, xy);
         // Build the model
         if(type == RBF) {
            rbfsetalgohierarchical(model, rbase, layers, smoothing);
         } else if(type == QNN) {
            rbfsetalgoqnn(model, qq, zz);
         } else {
            FATAL("LOGIC ERROR");
         }
         rbfbuildmodel(model, rep);
      } catch(std::exception& e) {
         LOG_ERR(e.what());
      } catch(...) {
         LOG_ERR("unknown exception");
      }
   } else if(type == BICUBIC) {
      FATAL("LOGIC ERROR");

   } else {
      FATAL(format("Logic error: type not supported"));
   }
}

void This::init_qnn(const vector<Vector2>& src,
                    const vector<Vector2>& dst,
                    double q,
                    double z)
{
   pimpl_->init(src, dst, QNN, 0.0, 0, 0.0, q, z);
}

void This::init_bicubic(const BicubicSpline& x_map, const BicubicSpline& y_map)
{
   pimpl_->type  = BICUBIC;
   pimpl_->x_map = x_map;
   pimpl_->y_map = y_map;
}

static std::pair<Vector4r, Vector4r>
calc_f_dx_dy_dxy(const Vector2& p,
                 std::function<Vector2(const Vector2& x)> func,
                 const real dx = 0.01,
                 const real dy = 0.01)
{
   Vector4r U[2];

   auto f00 = func(p);

   // First order derivatives
   auto fx0 = func(Vector2(p.x - dx, p.y));
   auto fx1 = func(Vector2(p.x + dx, p.y));
   auto fy0 = func(Vector2(p.x, p.y - dy));
   auto fy1 = func(Vector2(p.x, p.y + dy));

   // One second order derivative (dxy)
   auto fx0y0 = func(Vector2(p.x - dx, p.y - dy));
   auto fx1y0 = func(Vector2(p.x + dx, p.y - dy));
   auto fx0y1 = func(Vector2(p.x - dx, p.y + dy));
   auto fx1y1 = func(Vector2(p.x + dx, p.y + dy));

   auto fxy0 = (fx1y0 - fx0y0) / (dx + dy);
   auto fxy1 = (fx1y1 - fx0y1) / (dx + dy);

   for(int i = 0; i < 2; ++i) {
      U[i](0) = f00(i);
      U[i](1) = (fx1(i) - fx0(i)) / (dx + dx);
      U[i](2) = (fy1(i) - fy0(i)) / (dy + dy);
      U[i](3) = (fxy1(i) - fxy0(i)) / (dx + dy);
   }

   // cout << format("dxdy_dxy, p = {}", str(p)) << endl;
   // cout << format("mapx = {:9.6f}, {:9.6f}, {:9.6f}, {:9.6f}",
   //                U[0](0), U[0](1), U[0](2), U[0](3)) << endl;
   // cout << format("mapy = {:9.6f}, {:9.6f}, {:9.6f}, {:9.6f}",
   //                U[1](0), U[1](1), U[1](2), U[1](3)) << endl;
   // cout << endl << endl;

   return std::pair(U[0], U[1]);
}

void This::init_bicubic(const AABB region,
                        const real grid_sz,
                        std::function<Vector2(const Vector2& x)> func,
                        const real dxy_step)
{
   pimpl_->src_aabb = region;
   pimpl_->dst_aabb = AABB::minmax();

   const unsigned spline_cols = unsigned(std::ceil(region.w() / grid_sz));
   const unsigned spline_rows = unsigned(std::ceil(region.h() / grid_sz));

   BicubicSpline mapx, mapy;
   mapx.init(region, spline_cols, spline_rows);
   mapy.init(region, spline_cols, spline_rows);

   const real dxy = std::isfinite(dxy_step) ? dxy_step : (grid_sz * 1e-6);
   for(int j = 0; j < int(spline_rows); ++j)
      for(int i = 0; i < int(spline_cols); ++i)
         std::tie(mapx.X(i, j), mapy.X(i, j))
             = calc_f_dx_dy_dxy(mapx.position(i, j), func, dxy, dxy);

   mapx.finalize();
   mapy.finalize();

   pimpl_->type  = BICUBIC;
   pimpl_->x_map = mapx;
   pimpl_->y_map = mapy;
}

// ------------------------------------------------------------------ Fill Field

void This::fill_field(EmpiricalField& f, unsigned w, unsigned h) const
{
   const auto& src_aabb = pimpl_->src_aabb;

   // Initialize the field metrics
   f.field.resize(w, h);

   Matrix3r H = rescale_homography(AABB(0, 0, w, h), src_aabb);

   f.fxy.x = H(0, 0);
   f.fxy.y = H(1, 1);
   f.ppt.x = H(0, 2);
   f.ppt.y = H(1, 2);

   if(pimpl_->type == FIELD_NONE) {
      f.field.zero();
      return;
   } else if(pimpl_->type == BICUBIC) {
      for(unsigned y = 0; y < h; ++y) {
         for(unsigned x = 0; x < w; ++x) {
            Vector2 F     = Vector2(x, y);
            Vector2 U     = f.inv_transform(F);
            Vector2 V     = this->evaluate(U);
            f.field(x, y) = V;
         }
      }

   } else {
      alglib::real_1d_array out;
      alglib::real_1d_array x_axis, y_axis;
      x_axis.setlength(w);
      y_axis.setlength(h);

      for(unsigned i = 0; i < w; ++i)
         x_axis[i] = f.inv_transform(Vector2(i, 0)).x;
      for(unsigned i = 0; i < h; ++i)
         y_axis[i] = f.inv_transform(Vector2(0, i)).y;
      alglib::smp_rbfgridcalc2v(pimpl_->model, x_axis, w, y_axis, h, out);

      const auto expected_length = 2 * w * h;
      if(out.length() != expected_length) {
         LOG_ERR(format(
             "Expected {} outputs, but got {}", expected_length, out.length()));
         return;
      }

      unsigned pos = 0;
      for(unsigned y = 0; y < h; ++y)
         for(unsigned x = 0; x < w; ++x)
            for(unsigned i = 0; i < 2; ++i) f.field(x, y)(int(i)) = out[pos++];
   }
}

Vector2 This::evaluate(const Vector2& x) const
{
   static thread_local alglib::real_1d_array xx, xy;

   switch(pimpl_->type) {
   case FIELD_NONE: return Vector2::nan();
   case QNN:
   case RBF:
      if(xx.length() != 2) {
         xx.setlength(2);
         xy.setlength(2);
      }
      xx[0] = x.x;
      xx[1] = x.y;
      alglib::rbfcalc(pimpl_->model, xx, xy);
      return Vector2(xy[0], xy[1]);
   case BICUBIC:
      return Vector2(pimpl_->x_map.evaluate(x.x, x.y),
                     pimpl_->y_map.evaluate(x.x, x.y));
   default: FATAL("LOGIC ERROR");
   }

   return Vector2::nan();
}

void This::serialize(std::ostream& s_out) const
{
   const auto& src_aabb = pimpl_->src_aabb;
   const auto& dst_aabb = pimpl_->dst_aabb;

   s_out << format("{} {} {} {}",
                   src_aabb.left,
                   src_aabb.top,
                   src_aabb.right,
                   src_aabb.bottom)
         << endl;
   s_out << format("{} {} {} {}",
                   dst_aabb.left,
                   dst_aabb.top,
                   dst_aabb.right,
                   dst_aabb.bottom)
         << endl;
   s_out << int(pimpl_->type) << endl;

   if(pimpl_->type == FIELD_NONE) {
      // Do nothing
   } else if(pimpl_->type == QNN || pimpl_->type == RBF) {
      rbfserialize(pimpl_->model, s_out);
   } else if(pimpl_->type == BICUBIC) {
      pimpl_->x_map.serialize(s_out);
      pimpl_->y_map.serialize(s_out);
   }
}

void This::unserialize(std::istream& s_in)
{
   auto& src_aabb = pimpl_->src_aabb;
   auto& dst_aabb = pimpl_->dst_aabb;

   s_in >> src_aabb.left;
   s_in >> src_aabb.top;
   s_in >> src_aabb.right;
   s_in >> src_aabb.bottom;
   s_in >> dst_aabb.left;
   s_in >> dst_aabb.top;
   s_in >> dst_aabb.right;
   s_in >> dst_aabb.bottom;

   int val = 0;
   s_in >> val;
   pimpl_->type = FieldType(val);

   if(pimpl_->type == FIELD_NONE) {
      // Do nothing
   } else if(pimpl_->type == QNN || pimpl_->type == RBF) {
      rbfunserialize(s_in, pimpl_->model);
   } else if(pimpl_->type == BICUBIC) {
      pimpl_->x_map.unserialize(s_in);
      pimpl_->y_map.unserialize(s_in);
   }
}

std::string This::to_string() const
{
   const auto& src_aabb = pimpl_->src_aabb;
   const auto& dst_aabb = pimpl_->dst_aabb;

   const auto& model                = pimpl_->model;
   const alglib_impl::ae_vector& cw = model.c_ptr()->model2.cw;
   unsigned cww                     = unsigned(cw.cnt);
   double cwh                       = cww / 4.0;

   auto type_s = "NONE";
   switch(pimpl_->type) {
   case FIELD_NONE: type_s = "NONE"; break;
   case QNN: type_s = "QNN"; break;
   case RBF: type_s = "RBF"; break;
   case BICUBIC: type_s = "BICUBIC"; break;
   default: FATAL("Logic Error");
   }

   std::stringstream ss("");
   serialize(ss);
   MD5 md5(ss.str());

   return format(R"V0G0N(RBF-Field
   type:     {}
   src-aabb: {}
   dst-aabb: {}
   checksum: {}
   cw:       {}x{}
-----
)V0G0N",
                 type_s,
                 src_aabb.to_string(),
                 dst_aabb.to_string(),
                 md5.hexdigest(),
                 cww,
                 cwh);
}

} // namespace perceive


/*! \file demo_2d_tick_values.cpp
    \brief Example of changing tick values color, etc
    \details Creates file demo_2d_tick_values.svg
    \author Paul A. Bristow
    \date Jul 2009
  */

// Copyright Paul A. Bristow 2009

// Distributed under the Boost Software License, Version 1.0.
// For more information, see http://www.boost.org

// An example to demonstrate options to change the way tick values are labelled.

// This file is written to be included from a Quickbook .qbk document.
// It can be compiled by the C++ compiler, and run. Any output can
// also be added here as comment or included or pasted in elsewhere.

// Caution: this file contains Quickbook markup as well as code
// and comments: don't change any of the special comment markups!

//[demo_2d_tick_values_1
/*`
An example to demonstrate some options for controlling the layout and color of tick values.
*/
//] [/demo_2d_tick_values_1]

#include <boost/svg_plot/svg_2d_plot.hpp>
using namespace boost::svg;
#include <boost/svg_plot/detail/pair.hpp>
//  using boost::svg::detail::operator<<; // Output pair as, for example: 1.23, 4.56

#include <iostream>
 using std::cout;
  using std::endl;
  using std::ios_base;
  using std::dec;
  using std::hex;
#include <vector>
  using std::vector; // 1D data container.
#include <map>
  using std::map; // 2D data container.
#include <fstream> // for file streaming, ifstream & ofstream.
  using std::ifstream;
#include <string>
  using std::string;
  using std::getline;
#include <sstream>
  using std::istringstream;
#include <limits>
  using std::numeric_limits;
#include <algorithm>
  using std::min;
  using std::max;
#include <utility>
  using std::pair;
#include <ios>
  using std::noskipws;

double f(double x)
{ // A sample function to generate some X, Y pairs.
  return sqrt(x);
}

int main()
{ // Construct one STL containers for the data series to plot.

  map<double, double> data1; // record number and value.

  for(double i = 0.; i <= 20.; i += 1.)
  {
    data1[i] = f(i);
  }

  try
  { // try'n'catch blocks are needed to ensure error messages from any exceptions are shown.
//[demo_2d_tick_values_2

/*`
The code below shows plotting the sqrt function
selecting the range of the axis by a user choice.

[note Unicode symbols can be found at http://en.wikipedia.org/wiki/Unicode_symbols.
The 4 hex digit value needs to be wrapped with prefix &#x and suffix ; like �]
*/
    svg_2d_plot my_plot; // Construct a 2D plot.

    my_plot.legend_on(true) // Set title and legend, and X and Y axis range.
           .title("√ Function ") // Unicode sqrt symbol.
           .x_range(0, +20.)
           .x_major_interval(2.)

           .x_axis_label_color(green)
           .x_label_font_family("helvetica")
           .x_label_font_size(30)

           .x_num_minor_ticks(4) // MAJOR, minor, minor, minor, minor, MAJOR
           .x_ticks_values_color(red) //
           .x_ticks_values_font_family("Times New Roman")
           .x_ticks_values_font_size(20)
           .x_ticks_values_precision(0)
           .x_ticks_values_ioflags(ios_base::fixed)

           .y_range(0., 5.)
           .y_ticks_values_color(magenta)
           .y_ticks_values_precision(1)
           .y_ticks_values_ioflags(ios_base::scientific | ios_base::showpos)

            // "arial", "impact", "courier", "lucida console",  "Lucida sans unicode", "verdana", "calibri", "century", "lucida calligraphy", "tahoma", "vivaldi"
            // "informal roman", "lucida handwriting", "lucida bright", "helvetica"
            // "arial narrow" is narrow, so may be useful.
            // "arial black" is black!
            // "Times New Roman Bold" "Times New Roman italic" = Times New Roman
            // "comic sans", "sans" "bauhaus" "brush script" "segeo condensed" = Serif

            .y_ticks_values_font_family("lucida console")
            .y_ticks_values_font_size(10)

            .y_label_font_family("Times New Roman")
            .y_label_font_size(30)
            .y_axis_label_color(blue)
          ;

    my_plot.x_label("x abcd1234(√)").y_label("sqrt(x) "); // Note chaining.

    // Add a container of data to the plot, choosing a color.
    my_plot.plot(data1, "Function (√)").stroke_color(red).shape(circlet).size(3).bezier_on(true).line_color(pink);

    my_plot.write("./demo_2d_tick_values.svg"); // Write the plot to another file.

    // Show the ticks styling:
    // X ticks.
    cout << "my_plot.x_ticks_values_color() " << my_plot.x_ticks_values_color() << endl;
    cout << "my_plot.x_ticks_values_font_family() " << my_plot.x_ticks_values_font_family() << endl;
    cout << "my_plot.x_ticks_values_font_size() " << my_plot.x_ticks_values_font_size() << endl;
    cout << "my_plot.x_ticks_values_precision() " << my_plot.x_ticks_values_precision() << endl;
    cout << "my_plot.x_ticks_values_ioflags() 0x" << hex << my_plot.x_ticks_values_ioflags() << dec << endl;
    // Y ticks.
    cout << "my_plot.y_ticks_values_color() " << my_plot.y_ticks_values_color() << endl;
    cout << "my_plot.y_ticks_values_font_family() " << my_plot.y_ticks_values_font_family() << endl;
    cout << "my_plot.y_ticks_values_font_size() " << my_plot.y_ticks_values_font_size() << endl;
    cout << "my_plot.y_ticks_values_color() " << my_plot.y_ticks_values_color() << endl;
    cout << "my_plot.y_ticks_values_precision() " << my_plot.y_ticks_values_precision() << endl;
    cout << "my_plot.y_ticks_values_ioflags() 0x" << hex << my_plot.y_ticks_values_ioflags() << dec << endl;
  }
//] [/demo_2d_tick_values_2]
  catch(const std::exception& e)
  {
    std::cout <<
      "\n""Message from thrown exception was:\n   " << e.what() << std::endl;
  }

  return 0;
} // int main()


#include <iostream>
#include <cmath>

#include <boost/numeric/linear_algebra/operators.hpp>
#include <boost/numeric/linear_algebra/concepts.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>
#include <boost/numeric/linear_algebra/inverse.hpp>
#include <boost/numeric/linear_algebra/is_invertible.hpp>

#include <libs/numeric/linear_algebra/test/power_explicit.hpp>
#include <libs/numeric/linear_algebra/test/positive_real.hpp>
#include <libs/numeric/linear_algebra/test/positive_real_power.hpp>

// User defined data types and operators

using mtl::positive_real;

// We assume that 0 and infinity is not to guarantee invertibility

# ifdef __GXX_CONCEPTS__
  namespace algebra {
      concept_map SemiGroup< semigroup_mult, positive_real > {};
      concept_map Monoid< monoid_mult, positive_real > {};
      concept_map Group< group_mult, positive_real > {};
  }
# endif

int main(int, char* [])
{
    using mtl::power;
    using math::mult;

    positive_real          value(1.1), zero(0.0);

    compute_power(value, 777, mult<positive_real>(), "Magma");
    std::cout << '\n';

    compute_power(value, 777, semigroup_mult(), "SemiGroup");
    std::cout << '\n';

    compute_power(value, 777, monoid_mult(), "Monoid");
    compute_power(value, -777, monoid_mult(), "Monoid");
    std::cout << '\n';

    compute_power(value, 777, group_mult(), "Group");
    compute_power(value, -777, group_mult(), "Group");
    compute_power(zero, 777, group_mult(), "Group");
    compute_power(zero, -777, group_mult(), "Group");
    std::cout << '\n';

    return 0;
}


/*
 * Copyright (C) 2005-2020 Centre National d'Etudes Spatiales (CNES)
 *
 * This file is part of Orfeo Toolbox
 *
 *     https://www.orfeo-toolbox.org/
 *
 * 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.
 */

#include "itkNumericTraits.h"

#include "otbProspectModel.h"
#include "otb_boost_expint_header.h"
#include <boost/shared_ptr.hpp>
#include "otbMath.h"

// TODO check EPSILON matlab
#define EPSILON 0.0000000000000000000000001

namespace otb
{

/** Constructor */
ProspectModel::ProspectModel()
{
  this->ProcessObject::SetNumberOfRequiredInputs(1);
  this->ProcessObject::SetNumberOfRequiredOutputs(2);

  SpectralResponseType::Pointer outputRefl = static_cast<SpectralResponseType*>(this->MakeOutput(0).GetPointer());
  this->itk::ProcessObject::SetNthOutput(0, outputRefl.GetPointer());

  SpectralResponseType::Pointer outputTrans = static_cast<SpectralResponseType*>(this->MakeOutput(1).GetPointer());
  this->itk::ProcessObject::SetNthOutput(1, outputTrans.GetPointer());
}

/** Destructor */
ProspectModel::~ProspectModel()
{
}

/** Set Input */
void ProspectModel::SetInput(const LeafParametersType* object)
{
  this->itk::ProcessObject::SetNthInput(0, const_cast<LeafParametersType*>(object));
}

/** Get Input */
ProspectModel::LeafParametersType* ProspectModel::GetInput()
{
  if (this->GetNumberOfInputs() != 1)
  {
    // exit
    return nullptr;
  }
  return static_cast<LeafParametersType*>(this->itk::ProcessObject::GetInput(0));
}

/** Make outputs */
ProspectModel::DataObjectPointer ProspectModel::MakeOutput(DataObjectPointerArraySizeType)
{
  return static_cast<itk::DataObject*>(SpectralResponseType::New().GetPointer());
}

/** Get Reflectance */
ProspectModel::SpectralResponseType* ProspectModel::GetReflectance()
{
  if (this->GetNumberOfOutputs() < 2)
  {
    // exit
    return nullptr;
  }
  return static_cast<SpectralResponseType*>(this->itk::ProcessObject::GetOutput(0));
}

/** Get Transmittance */
ProspectModel::SpectralResponseType* ProspectModel::GetTransmittance()
{
  if (this->GetNumberOfOutputs() < 2)
  {
    // exit
    return nullptr;
  }
  return static_cast<SpectralResponseType*>(this->itk::ProcessObject::GetOutput(1));
}

/** Set Parameters */
void ProspectModel::SetInput(const ParametersType& params)
{
  //    m_Parameters = params;
  if (params.Size() != 6)
    itkExceptionMacro(<< "Must have 6 parameters in that order : Cab, Car, CBrown, Cw, Cm, N");
  LeafParametersType::Pointer leafParams = LeafParametersType::New();
  leafParams->SetCab(params[0]);
  leafParams->SetCar(params[1]);
  leafParams->SetCBrown(params[2]);
  leafParams->SetCw(params[3]);
  leafParams->SetCm(params[4]);
  leafParams->SetN(params[5]);

  this->itk::ProcessObject::SetNthInput(0, leafParams);
}

/** Plant Leaf Reflectance and Transmittance computation from 400nm to 2500 nm*/
void ProspectModel::GenerateData()
{

  LeafParametersType::Pointer   leafParameters = this->GetInput();
  SpectralResponseType::Pointer outRefl        = this->GetReflectance();
  SpectralResponseType::Pointer outTrans       = this->GetTransmittance();

  unsigned int alpha = 40;
  double       lambda, n, k, trans, t12, temp, t21, r12, r21, x, y, ra, ta, r90, t90;
  double       delta, beta, va, vb, vbNN, vbNNinv, vainv, s1, s2, s3, RN, TN;
  double       N, Cab, Car, CBrown, Cw, Cm;

  N      = leafParameters->GetN();
  Cab    = leafParameters->GetCab();
  Car    = leafParameters->GetCar();
  CBrown = leafParameters->GetCBrown();
  Cw     = leafParameters->GetCw();
  Cm     = leafParameters->GetCm();

  int nbdata = sizeof(DataSpecP5B) / sizeof(DataSpec);
  for (int i = 0; i < nbdata; ++i)
  {
    lambda = DataSpecP5B[i].lambda;
    n      = DataSpecP5B[i].refLeafMatInd;

    k = Cab * DataSpecP5B[i].chlAbsCoef + Car * DataSpecP5B[i].carAbsCoef + CBrown * DataSpecP5B[i].brownAbsCoef + Cw * DataSpecP5B[i].waterAbsCoef;
    k = k + Cm * DataSpecP5B[i].dryAbsCoef;
    k = k / N;
    if (k == itk::NumericTraits<double>::ZeroValue())
      k = EPSILON;

    trans = (1. - k) * exp(-k) + k * k * boost::math::expint(1, k);

    t12  = this->Tav(alpha, n);
    temp = this->Tav(90, n);

    t21 = temp / (n * n);
    r12 = 1. - t12;
    r21 = 1. - t21;
    x   = t12 / temp;
    y   = x * (temp - 1) + 1 - t12;

    ra  = r12 + (t12 * t21 * r21 * (trans * trans)) / (1. - r21 * r21 * trans * trans);
    ta  = (t12 * t21 * trans) / (1. - r21 * r21 * trans * trans);
    r90 = (ra - y) / x;
    t90 = ta / x;

    delta = (t90 * t90 - r90 * r90 - 1.) * (t90 * t90 - r90 * r90 - 1.) - 4. * r90 * r90;
    if (delta < 0)
      delta = EPSILON;
    else
      delta = std::sqrt(delta);

    beta = (1. + r90 * r90 - t90 * t90 - delta) / (2. * r90);
    va   = (1. + r90 * r90 - t90 * t90 + delta) / (2. * r90);
    if ((beta - r90) <= 0)
      vb = std::sqrt(beta * (va - r90) / (va * EPSILON));
    else
      vb = std::sqrt(beta * (va - r90) / (va * (beta - r90)));

    vbNN    = std::pow(vb, N - 1.);
    vbNNinv = 1. / vbNN;
    vainv   = 1. / va;
    s1      = ta * t90 * (vbNN - vbNNinv);
    s2      = ta * (va - vainv);
    s3      = va * vbNN - vainv * vbNNinv - r90 * (vbNN - vbNNinv);

    RN = ra + s1 / s3;
    TN = s2 / s3;

    SpectralResponseType::PairType rrefl;
    SpectralResponseType::PairType ttrans;
    rrefl.first   = lambda / 1000.0;
    rrefl.second  = RN;
    ttrans.first  = lambda / 1000.0;
    ttrans.second = TN;
    outRefl->GetResponse().push_back(rrefl);
    outTrans->GetResponse().push_back(ttrans);
  }
}

double ProspectModel::Tav(const int theta, double ref)
{

  double theta_rad = theta * CONST_PI / 180;
  double r2, rp, rm, a, k, ds, k2, rm2, res, b1, b2, b;
  double ts, tp1, tp2, tp3, tp4, tp5, tp;

  r2 = ref * ref;
  rp = r2 + 1;
  rm = r2 - 1;
  a  = (ref + 1) * (ref + 1) / 2;
  k  = -(r2 - 1) * (r2 - 1) / 4;
  ds = sin(theta_rad);

  k2  = k * k;
  rm2 = rm * rm;

  if (theta_rad == 0)
    res = 4 * ref / ((ref + 1) * (ref + 1));
  else
  {
    if (theta_rad == CONST_PI / 2)
      b1 = itk::NumericTraits<double>::ZeroValue();
    else
      b1 = std::sqrt((ds * ds - rp / 2) * (ds * ds - rp / 2) + k);

    b2  = ds * ds - rp / 2;
    b   = b1 - b2;
    ts  = (k2 / (6 * std::pow(b, 3)) + k / b - b / 2) - (k2 / (6 * std::pow(a, 3)) + k / a - a / 2);
    tp1 = -2 * r2 * (b - a) / (rp * rp);
    tp2 = -2 * r2 * rp * log(b / a) / rm2;
    tp3 = r2 * (1. / b - 1. / a) / 2;
    tp4 = 16 * r2 * r2 * (r2 * r2 + 1) * log((2 * rp * b - rm2) / (2 * rp * a - rm2)) / (std::pow(rp, 3) * rm2);
    tp5 = 16 * std::pow(r2, 3) * (1. / (2 * rp * b - rm2) - 1. / (2 * rp * a - rm2)) / std::pow(rp, 3);
    tp  = tp1 + tp2 + tp3 + tp4 + tp5;
    res = (ts + tp) / (2 * ds * ds);
  }
  return res;
}

void ProspectModel::PrintSelf(std::ostream& os, itk::Indent indent) const
{
  Superclass::PrintSelf(os, indent);
}
} // end namespace otb


#pragma once

#include <Eigen/Core>
#include <Eigen/Sparse>
#include <algorithm>
#include <cassert>
#include <iostream>
#include <limits>
#include "base/exceptions.hpp"
#include "base/pow2p.hpp"
#include "ridgelet_functions.hpp"

template <template <typename> class PSI_RADIAL = PsiRadial1,
          template <typename> class PSI_SPHERICAL = PsiSpherical1,
          template <typename> class PSI_SCALING = PsiScaling1,
          typename TRANSITION_FUNCTION = TransitionFunction,
          typename NUMERIC = double>
class ridge_ft
{
 public:
  typedef NUMERIC numeric_t;
  typedef Eigen::SparseMatrix<numeric_t, Eigen::RowMajor> sparse_mat_t;
  typedef Eigen::Array<numeric_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> mat_t;

 protected:
  typedef TRANSITION_FUNCTION tfct_t;
  typedef PSI_RADIAL<tfct_t> psi_radial_t;
  typedef PSI_SPHERICAL<tfct_t> psi_spherical_t;
  typedef PSI_SCALING<tfct_t> psi_scaling_t;

 public:
  static void d_ridgelet_ft(sparse_mat_t &ft,
                            const sparse_mat_t &ft_x,
                            const sparse_mat_t &ft_y,
                            unsigned int j,
                            unsigned int rho_x,
                            unsigned int rho_y);

  static void x_ridgelet_ft(
      sparse_mat_t &ft, unsigned int j, int k, unsigned int rho_x, unsigned int rho_y);

  static void y_ridgelet_ft(
      sparse_mat_t &ft, unsigned int j, int k, unsigned int rho_x, unsigned int rho_y);

  static void s_ridgelet_ft(mat_t &ft, unsigned int rho_x, unsigned int rho_y);
};

// --------------------------------------------------------------------------------
/**
 * @brief
 *
 * @param ft
 * @param ft_x   \f$\Psi_{j,x,k}\f$
 * @param ft_y   \f$\Psi_{j,y,k}\f$
 * @param rho_x
 * @param rho_y
 */
template <template <class> class PSI_RADIAL,
          template <class> class PSI_SPHERICAL,
          template <class> class PSI_SCALING,
          typename TRANSITION_FUNCTION,
          typename NUMERIC>
void
ridge_ft<PSI_RADIAL, PSI_SPHERICAL, PSI_SCALING, TRANSITION_FUNCTION, NUMERIC>::d_ridgelet_ft(
    sparse_mat_t &ft,
    const sparse_mat_t &ft_x,
    const sparse_mat_t &ft_y,
    unsigned int j,
    unsigned int rho_x,
    unsigned int rho_y)
{
  auto freq = [](int x, int nx) { return x - nx; };
  // output size

  double rhoyox = double(rho_y) / rho_x;
  unsigned int new_rows = pow2p(j + 2) * rho_y;
  unsigned int new_cols = pow2p(j + 2) * rho_x;
  int fcutx = new_cols / 2;
  int fcuty = new_rows / 2;

  ft.resize(new_rows, new_cols);

  // nrows <-> y
  // ncols <-> x
  typedef Eigen::Triplet<double, int> T;
  std::vector<T> entries;
  entries.reserve(ft_x.nonZeros());

  unsigned int nrowsh = ft_x.rows() / 2;
  unsigned int ncolsh = ft_x.cols() / 2;
  for (int ii = 0; ii < ft_x.outerSize(); ++ii) {
    for (typename sparse_mat_t::InnerIterator it(ft_x, ii); it; ++it) {
      int y = freq(it.row(), nrowsh);
      int x = freq(it.col(), ncolsh);
      int posx = x + fcutx;
      int posy = y + fcuty;
      int ax = std::abs(x);
      int ay = std::abs(y);
      if (rhoyox * ax >= ay && posx < new_cols && posx >= 0 && y + fcuty < new_rows && posy >= 0)
        entries.push_back(T(posy, posx, it.value()));
    }
  }

  nrowsh = ft_y.rows() / 2;
  ncolsh = ft_y.cols() / 2;
  for (int ii = 0; ii < ft_y.outerSize(); ++ii) {
    for (typename sparse_mat_t::InnerIterator it(ft_y, ii); it; ++it) {
      int y = freq(it.row(), nrowsh);
      int x = freq(it.col(), ncolsh);
      int posx = x + fcutx;
      int posy = y + fcuty;
      int ax = std::abs(x);
      int ay = std::abs(y);
      if (rhoyox * ax < ay && posx < new_cols && posx >= 0 && y + fcuty < new_rows && posy >= 0)
        entries.push_back(T(y + fcuty, x + fcutx, it.value()));
    }
  }

  // duplicates are *SUMMED* up
  ft.setFromTriplets(entries.begin(), entries.end());
  ft.makeCompressed();
}

// --------------------------------------------------------------------------------
template <template <class> class PSI_RADIAL,
          template <class> class PSI_SPHERICAL,
          template <class> class PSI_SCALING,
          typename TRANSITION_FUNCTION,
          typename NUMERIC>
void
ridge_ft<PSI_RADIAL, PSI_SPHERICAL, PSI_SCALING, TRANSITION_FUNCTION, NUMERIC>::x_ridgelet_ft(
    sparse_mat_t &ft, unsigned int j, int k, unsigned int rho_x, unsigned int rho_y)
{
  psi_radial_t psi_radial;
  psi_spherical_t psi_spherical;

  assert(j > 0);
  assert(k == 0 || std::abs(k) <= pow2p(j - 1));

  typedef Eigen::VectorXd vec_t;
  std::vector<unsigned int> vecd_t;
  typedef Eigen::Triplet<numeric_t, int> T;

  // rho_y/float(rho_x)/2**(j-1)*(1+abs(k))*xmax
  int Nrows = 4 * rho_y * (std::abs(k) + 1);
  int Ncols = pow2p(j + 1) * rho_x;

  // upper bound for number of nonzero elements found in  {(x,y) : x > 0}
  // 2*2**(-1-j)*(float(rho_y)/rho_x)*(2+3*2**j*rho_x)*(5*2**j*rho_x-4)
  // unsigned int nnz = 1./pow2p(j+1) * double(rho_y) / rho_x * (2 +
  // 3*pow2p(j)*rho_x) *
  // (5*pow2p(j)*rho_x-4);
  const unsigned int nnz = 5 * (2 + 3 * pow2p(j) * rho_x) * rho_y;
  std::vector<T> triplets;
  triplets.reserve(2 * nnz);
  ft.resize(2 * Nrows, 2 * Ncols);

  /// initialize helper
  // x' \in [2^(j+1) rho_x,..., 2^(j+1) rho_x]
  const unsigned int x_begin = pow2p(j - 1) * rho_x;
  //  std::cout << "xbegin: " << x_begin << "\n";
  const unsigned int x_end = pow2p(j + 1) * rho_x - 1;
  //  std::cout << "xend: " << x_end << "\n";
  const unsigned int nx = x_end - x_begin + 1;
  std::vector<double> wr(nx);
  unsigned int n = x_begin;
  const double d = (1. / pow2p(j - 1)) / rho_x;
  std::generate(wr.begin(), wr.end(), [&n, &psi_radial, d]() { return psi_radial((n++) * d); });

  unsigned int count = 0;

  const double c1p2_jm1 = 1. / pow2p(j - 1);
  const double rho_yox = rho_y / double(rho_x);
  /// assemble
  for (int x = x_begin, i = 0; x <= x_end; ++x, ++i) {
    // x coordinate on reference frame
    // int xr = x>>(j-1);
    int yl = (k - 1) * rho_yox * c1p2_jm1 * x;
    int yh = (k + 1) * rho_yox * c1p2_jm1 * x;
    //    std::cout << "x: " << x << ", y in " << yl << ".." << yh << "\n";
    for (int y = yl; y <= yh; ++y) {
      // -((k rx)/ry)+(2^(-1+j) rx y)/(ry x)
      double z = rho_x / double(rho_y) * pow2p(j - 1) * y / double(x) - k;
      double ws = psi_spherical(z);
      double val = wr[i] * ws;
      // double val = 1;

      // debug
      assert(y + Nrows < 2 * Nrows);
      assert(-y + Nrows < 2 * Nrows && -y + Nrows >= 0);

      triplets.push_back(T(y + Nrows, x + Ncols, val));
      triplets.push_back(T(-y + Nrows, -x + Ncols, val));
      count++;
    }
  }

  // // std::cout << "computed: " << count << " entries\n";
  // // debug
  // int cmax = std::numeric_limits<int>::min();
  // int rmax = std::numeric_limits<int>::min();
  // int cmin = std::numeric_limits<int>::max();
  // int rmin = std::numeric_limits<int>::max();
  // for (auto& t : triplets) {
  //   int col = t.col();
  //   int row = t.row();
  //   assert(col < 2*Ncols);
  //   assert(row < 2*Nrows);
  //   cmax = std::max(cmax, col);
  //   rmax = std::max(rmax, row);
  //   cmin = std::min(cmin, col);
  //   rmin = std::min(rmin, row);
  //   //    std::cout << col << " " << row << " " << t.value() << std::endl;
  // }
  // if(triplets.size() > 0) {
  //   std::cout << "x in " << cmin << "\t" << cmax
  //             << std::endl;
  //   std::cout << "y in " << rmin << "\t" << rmax
  //             << std::endl;
  //   std::cout << "nnz: "  << nnz << "\n";
  //   std::cout << "triplets.size: "  << triplets.size() << "\n";
  // } else {
  //   std::cout << "Ooops, triplets were empty!!" << "\n";
  // }

  // assert(triplets.size() <= nnz);
  ft.setFromTriplets(triplets.begin(), triplets.end());
  ft.makeCompressed();
}

// --------------------------------------------------------------------------------
template <template <class> class PSI_RADIAL,
          template <class> class PSI_SPHERICAL,
          template <class> class PSI_SCALING,
          typename TRANSITION_FUNCTION,
          typename NUMERIC>
void
ridge_ft<PSI_RADIAL, PSI_SPHERICAL, PSI_SCALING, TRANSITION_FUNCTION, NUMERIC>::y_ridgelet_ft(
    sparse_mat_t &ft, unsigned int j, int k, unsigned int rho_x, unsigned int rho_y)
{
  x_ridgelet_ft(ft, j, k, rho_y, rho_x);
  ft = ft.transpose();
  ft.makeCompressed();
}

// --------------------------------------------------------------------------------
template <template <class> class PSI_RADIAL,
          template <class> class PSI_SPHERICAL,
          template <class> class PSI_SCALING,
          typename TRANSITION_FUNCTION,
          typename NUMERIC>
void
ridge_ft<PSI_RADIAL, PSI_SPHERICAL, PSI_SCALING, TRANSITION_FUNCTION, NUMERIC>::s_ridgelet_ft(
    mat_t &ft, unsigned int rho_x, unsigned int rho_y)
{
  psi_scaling_t psi_scaling;
  const double dx = 1. / rho_x;
  const double dy = 1. / rho_y;

  ft.resize(4 * rho_y, 4 * rho_x);

  Eigen::RowVectorXd x = Eigen::RowVectorXd::LinSpaced(2 * rho_x - 1, dx, 2 - dx);
  Eigen::RowVectorXd y = Eigen::RowVectorXd::LinSpaced(2 * rho_y - 1, dy, 2 - dy);

  // // debug
  // std::cout << "ridge_ft::s_ridgelet_ft: x= \n" << "\n";
  // std::cout << x << "\n";

  Eigen::RowVectorXd sx(x.size());
  std::transform(x.data(), x.data() + x.size(), sx.data(), psi_scaling);

  Eigen::RowVectorXd sy(y.size());
  std::transform(y.data(), y.data() + y.size(), sy.data(), psi_scaling);

  Eigen::MatrixXd F =
      (y.transpose().replicate(1, x.size()).array() > x.replicate(y.size(), 1).array())
          .select(sy.transpose().replicate(1, x.size()), sx.replicate(y.size(), 1));

  /*
   *
   *     |---+-----------------+-----------+----------|
   *     | 0 | 0               |         0 | 0        |
   *     |---+-----------------+-----------+----------|
   *     | 0 | flipud fliplr F | flip(sy)' | flipud F |
   *     | 0 | flip(sx)        |         1 | sx       |
   *     | 0 | fliplr(F)       |       sy' | F        |
   *     |---+-----------------+-----------+----------|
   *     MATLAB syntax: fliplr -> left-right
   *                    flipud -> up-down
   */
  // std::cout << "F.size : "  << F.rows() << "x" << F.cols() << "\n";
  // std::cout << "ft.rows: "<< ft.rows() << "\n"
  //           << "ft.cols: " << ft.cols() << "\n";

  ft << Eigen::MatrixXd::Zero(1, 4 * rho_x), Eigen::MatrixXd::Zero(y.size(), 1),
      F.rowwise().reverse().colwise().reverse(), sy.reverse().transpose(), F.colwise().reverse(), 0,
      sx.reverse(), 1, sx, Eigen::MatrixXd::Zero(y.size(), 1), F.rowwise().reverse(),
      sy.transpose(), F;
}