/*
Author: Jason Bunk
Jan. 2017
*/
#include <iostream>
#include <stdint.h>
#include <boost/python.hpp>
#include <thread>
#include "PythonCVMatConvert.h"
#include "utils_RNG.h"

#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>

namespace bp = boost::python;
using std::cout; using std::endl;

#define DEBUG_NO_THREADING 1

#define MAX_N_THREADS 6

//static RNG_rand_r myrng; //no longer used due to parallelization (each thread needs its own RNG)

template <class T>
inline std::string to_istring(const T& t)
{
	std::stringstream ss;
	ss << static_cast<int>(t);
	return ss.str();
}
std::string describemat(const cv::Mat & inmat) {
	return std::string("(rows,cols,channels,depth) == (")
		+to_istring(inmat.rows)+std::string(", ")
		+to_istring(inmat.cols)+std::string(", ")
		+to_istring(inmat.channels())+std::string(", ")
		+to_istring(inmat.depth())
		+std::string(")");
}

/*
scX*cos(rth) + shB*sin(rth),
shA*cos(rth) + scY*sin(rth),
-(scX*cos(rth) + shB*sin(rth))*cX - (shA*cos(rth) + scY*sin(rth))*cY + cX + trX,

shB*cos(rth) - scX*sin(rth),
scY*cos(rth) - shA*sin(rth),
-(shB*cos(rth) - scX*sin(rth))*cX - (scY*cos(rth) - shA*sin(rth))*cY + cY + trY,
*/

#define ROTATION_BASE_AMT  20.0f
#define SHEAR_BASE_AMT      3.5f
#define TRANSLATE_BASE_AMT 0.03f

void processbatch_randcrop(RNG* myRNG, std::vector<cv::Mat>* batch, int extraarg, cv::Mat * optionalMat)
{
	// extraarg == "crop width" and "crop height" (returns square crops)
	const int numimgs = ((int)batch->size());
	int cropR, cropC;
	int marginR, marginC;

	if(extraarg <= 0) {
		std::cout<<"Crop size "<<extraarg<<" must be > 0 !!!!!!!!!"<<std::endl<<std::flush;
		return;
	}

	for(int ii=0; ii<numimgs; ii++) {
		marginR = ((*batch)[ii].rows - extraarg);
		marginC = ((*batch)[ii].cols - extraarg);
		if(marginR >= 0 && marginC >= 0) {
			cropR = myRNG->rand_int(0, marginR);
			cropC = myRNG->rand_int(0, marginC);
			(*batch)[ii] = (*batch)[ii](cv::Rect(cropC,cropR,extraarg,extraarg)).clone();
		} else {
			std::cout<<"IMAGE WAS SMALLER THAN CROP SIZE: "<<(*batch)[ii].cols<<"x"<<(*batch)[ii].rows<<" > "<<extraarg<<"x"<<extraarg<<std::endl<<std::flush;
		}
	}
}

void processbatch_randaffinetransf(RNG* myRNG, std::vector<cv::Mat>* batch, int extraarg, cv::Mat * optionalMat)
{
	//extraarg has three bits:
	// 0 i.e. 0001: "do scaling?" if false, does rotations, shears, and translations only
	// 1 i.e. 0010: "flip?" if true, will randomly mirror across X axis
	// 2 i.e. 0100: "fill border with 0s?" if true, will fill border with 0s, else will use REFLECT_101
	// 3 i.e. 1000: translations?
	const int numimgs = ((int)batch->size());
	float cX, cY, trX, trY, rth, shA, shB, scX, scY;
	cv::Mat transfmat(2, 3, CV_32F);

	for(int ii=0; ii<numimgs; ii++) {

		cX = ((float)(*batch)[ii].cols) * 0.5f;
		cY = ((float)(*batch)[ii].rows) * 0.5f;

		if(optionalMat == NULL) {
			//translation
			if(extraarg & 8) {
				cout<<"RANDOM TRANSLATIONS!!!!!!!!!!!!!!!!!!!!!!!!"<<endl;
				trX = myRNG->rand_float(-TRANSLATE_BASE_AMT*cX, TRANSLATE_BASE_AMT*cX);
				trY = myRNG->rand_float(-TRANSLATE_BASE_AMT*cY, TRANSLATE_BASE_AMT*cY);
			} else {
				trX = trY = 0.0f;
			}
			//rotation; negative angles make it look more italicized: (-6.8, 8.0) approximately centers
			rth = 0.0f;
			while(fabs(rth) < 1.0f) {
				rth = myRNG->rand_float(-ROTATION_BASE_AMT, ROTATION_BASE_AMT); // degrees to radians: multiply by pi/180
			}
			rth *= 0.01745329252f;

			//shear
			shA = myRNG->rand_float(-SHEAR_BASE_AMT, SHEAR_BASE_AMT)/cX; //negative A makes it look more italicized: (-3.0,3.7) approximately centers
			shB = myRNG->rand_float(-SHEAR_BASE_AMT, SHEAR_BASE_AMT)/cY; //positive B makes it look more italicized: (-3.7,3.0) approximately centers

			//scaling?
			if(extraarg & 1) {
				scX = 1.0f; scY = 1.0f;
				while(fabs(scX-1.0f) < 0.02f && fabs(scY-1.0f) < 0.02f) {
					scX = myRNG->rand_float(0.66f, 1.5f); //if scaled slightly too big, that's OK
					scY = scX * myRNG->rand_float(0.95f, 1.0f/0.95f); //different scaling between X and Y
				}
			} else {
				cout<<"NO SCALING!!!!!!!!!!!!!!!!!!!!!!!!"<<endl;
				scX = myRNG->rand_float(0.96f, 1.0f/0.96f);
				scY = myRNG->rand_float(0.96f, 1.0f/0.96f);
			}

			// now create 3 affine transformation matrices, one for each warp type (translate, rotate, shear), and then combine them
			// order: translate to centered about origin; rotate; rescale; shear; translate back and offset

			transfmat.at<float>(0,0) = scX*cos(rth) + shB*sin(rth);
			transfmat.at<float>(0,1) = shA*cos(rth) + scY*sin(rth);
			transfmat.at<float>(0,2) = -(scX*cos(rth) + shB*sin(rth))*cX - (shA*cos(rth) + scY*sin(rth))*cY + cX + trX;

			transfmat.at<float>(1,0) = shB*cos(rth) - scX*sin(rth);
			transfmat.at<float>(1,1) = scY*cos(rth) - shA*sin(rth);
			transfmat.at<float>(1,2) = -(shB*cos(rth) - scX*sin(rth))*cX - (scY*cos(rth) - shA*sin(rth))*cY + cY + trY;
			cout<<"USING AUTO-GENERATED TRANSFORMATION!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"<<endl<<std::flush;
		} else {
			transfmat = (*optionalMat);
		}

		//transfmat.at<float>(0,0) = ; transfmat.at<float>(0,1) = ; transfmat.at<float>(0,2) = ;
		//transfmat.at<float>(1,0) = ; transfmat.at<float>(1,1) = ; transfmat.at<float>(1,2) = ;

		//cout<<"tmat "<<transfmat<<endl<<std::flush;
		//cout<<"s"<<ii<<" to "<<(*batch)[ii].size().width<<"x"<<(*batch)[ii].size().height<<endl<<std::flush;

		cv::Mat temp;
		if((extraarg & 2) && myRNG->rand_float(0.0f, 1.0f) < 0.5f) {
			cv::flip((*batch)[ii], temp, 1);
		} else {
			(*batch)[ii].copyTo(temp);
		}

		//cout<<"s"<<ii<<" to "<<temp.size().width<<"x"<<temp.size().height<<endl<<std::flush;
		//cv::imshow("input", (*batch)[ii] / 255.0f);
		//cv::imshow("temp", temp / 255.0f);
		//cv::waitKey(0);

		cv::warpAffine(temp, (*batch)[ii], transfmat, (*batch)[ii].size(), cv::INTER_LINEAR,
						(extraarg & 4) ? cv::BORDER_CONSTANT : cv::BORDER_REFLECT_101);

		//cout<<"f"<<ii<<endl<<std::flush;
	}
}

void processbatch_jpeg(RNG* myRNG, std::vector<cv::Mat>* batch, int extraarg, cv::Mat * optionalMat)
{
	const int numimgs = ((int)batch->size());
	std::vector<int> params(3,0);
	params[0] = CV_IMWRITE_JPEG_QUALITY;
	std::vector<uint8_t> tmp;
	for(int ii=0; ii<numimgs; ii++) {
		if(extraarg > 0) {
			params[1] = extraarg; // JPEG quality factor
		} else {
			params[1] = myRNG->rand_int(60, 97);
		}
		cv::imencode(std::string(".jpg"), (*batch)[ii], tmp, params);
		(*batch)[ii] = cv::imdecode(tmp, cv::IMREAD_ANYCOLOR);
	}
}

void processbatch_affinetransf_thencrop(RNG* myRNG, std::vector<cv::Mat>* batch, int extraarg, cv::Mat * optionalMat)
{
	// extraarg:
	// 1: "do scaling?" if extraarg > 1,000,000 then will do scaling, else will not
	// 2: "crop width" and "crop height" (returns square crops)
	// to do both scaling and specify crop width (e.g. to 28), feed 1,000,028 == 1000028

	int cropsize = (extraarg & 1048575); // bits 0-19 are reserved for the crop size
	int transfargs = ((((uint32_t)extraarg) & 15728640) >> 20) & 15; // bits 20,21,22,23 are for the transformation args

	processbatch_randaffinetransf(myRNG, batch, transfargs, optionalMat);
	processbatch_randcrop(myRNG, batch, cropsize, NULL);
}

bp::object OnlineBatchProcessGeneric(bp::list* pyImagesBatch,
																		void (*processorFuncPtr)(RNG*, std::vector<cv::Mat>*, int, cv::Mat*),
																		int extraarg,
																		PyObject *optionalPythonMat = NULL)
{
	int ii, jj;
	const int numimgs = bp::len(*pyImagesBatch);

	if(numimgs <= 0) {
		cout<<"OnlineBatchRandomAffineTransf: warning: no images in batch"<<endl<<std::flush;
		return bp::object();
	}

	NDArrayConverter cvt;

	cv::Mat optionalMat;
	bool got_optional_mat = false;
	if(optionalPythonMat != NULL) {
		if(optionalPythonMat != Py_None) {
			optionalMat = cvt.toMat(optionalPythonMat);
			got_optional_mat = (!optionalMat.empty() && optionalMat.rows > 0 && optionalMat.cols > 0);
		}
	}

	// imgsperthread is ceil(numimgs / numthreads)
	const int numthreads = numimgs < MAX_N_THREADS ? numimgs : MAX_N_THREADS;
	const int imgsperthread = 1 + ((numimgs - 1) / numthreads);

	std::vector< std::vector<cv::Mat>* > vecImagesBatches(numthreads); // outer vector indexes threads, inner vector indexes images per thread
	std::vector<std::thread*> threads(numthreads);
	std::vector<RNG_rand_r*> threadRNGs(numthreads); // RNG for each thread
	bp::list returnedPyImages; //will be a list of cv2 numpy images

	int idx = -1;
	for(ii=0; ii < numthreads; ii++) {
		vecImagesBatches[ii] = new std::vector<cv::Mat>();
		for(jj=0; jj < imgsperthread; jj++) {
			idx++;
			if(idx < numimgs) {
				bp::object imgobject = bp::extract<bp::object>((*pyImagesBatch)[idx]);
				vecImagesBatches[ii]->push_back(cvt.toMat(imgobject.ptr()));
				if((*vecImagesBatches[ii])[jj].depth() != CV_32F) {
					(*vecImagesBatches[ii])[jj].convertTo((*vecImagesBatches[ii])[jj], CV_MAKETYPE(CV_32F, (*vecImagesBatches[ii])[jj].channels()));
				}
			}
		}
		//cout<<"launching thread with "<<vecImagesBatches[ii]->size()<<" images; numthreads "<<numthreads<<", imgsperthread "<<imgsperthread<<endl<<std::flush;

		threadRNGs[ii] = new RNG_rand_r(rand());//ii);
#if DEBUG_NO_THREADING
		//cout<<"debug: no threading"<<endl<<std::flush;
		(*processorFuncPtr)(threadRNGs[ii], vecImagesBatches[ii], extraarg,
																	got_optional_mat ? &optionalMat : NULL);
#else
		threads[ii] = new std::thread(processorFuncPtr, threadRNGs[ii], vecImagesBatches[ii], extraarg,
																	got_optional_mat ? &optionalMat : NULL);
		//cout<<"waiting for thread "<<(ii+1)<<" / "<<numthreads<<" to join"<<endl<<std::flush;
		//threads[ii]->join();
#endif
	}

	for(ii=0; ii < numthreads; ii++) {
#if DEBUG_NO_THREADING
#else
		cout<<"waiting for thread "<<(ii+1)<<" / "<<numthreads<<" to join"<<endl<<std::flush;
		threads[ii]->join();
#endif
		idx = (int)vecImagesBatches[ii]->size();
		for(jj=0; jj<idx; jj++) {
			PyObject* thisImgCPP = cvt.toNDArray((*vecImagesBatches[ii])[jj]);
			//returnedPyImages.append(bp::object(bp::handle<>(bp::borrowed(thisImgCPP)))); // borrowed() increments the reference counter, causing a memory leak when we return to Python
			returnedPyImages.append(bp::object(bp::handle<>(thisImgCPP)));
		}
		delete vecImagesBatches[ii];
		delete threadRNGs[ii];
#if DEBUG_NO_THREADING
#else
		delete threads[ii];
#endif
	}

	return returnedPyImages;
}

bp::object OnlineBatchRandomAffineTransf(bp::list pyImagesBatch, int doresize, PyObject *optionalTransfMat = NULL) {
	return OnlineBatchProcessGeneric(&pyImagesBatch, &processbatch_randaffinetransf, doresize, optionalTransfMat);
}
bp::object OnlineBatchRandomCrop(bp::list pyImagesBatch, int cropsize) {
	return OnlineBatchProcessGeneric(&pyImagesBatch, &processbatch_randcrop, cropsize);
}
bp::object OnlineBatchRandomAffineThenCrop(bp::list pyImagesBatch, int extraarg, PyObject *optionalTransfMat = NULL) {
	return OnlineBatchProcessGeneric(&pyImagesBatch, &processbatch_affinetransf_thencrop, extraarg, optionalTransfMat);
}
bp::object OnlineBatchJPEG(bp::list pyImagesBatch, int extraarg) {
	return OnlineBatchProcessGeneric(&pyImagesBatch, &processbatch_jpeg, extraarg);
}

static void init()
{
	Py_Initialize();
	import_array();
}

BOOST_PYTHON_MODULE(pymyonlineaffinecpplib)
{
	init();
	bp::def("OnlineBatchRandomAffineTransf", OnlineBatchRandomAffineTransf);
	bp::def("OnlineBatchRandomCrop", OnlineBatchRandomCrop);
	bp::def("OnlineBatchRandomAffineThenCrop", OnlineBatchRandomAffineThenCrop);
	bp::def("OnlineBatchJPEG", OnlineBatchJPEG);
}


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// 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.

#include "bempp/common/config_trilinos.hpp"

#ifdef WITH_TRILINOS

#include "default_iterative_solver.hpp"

#include "belos_solver_wrapper.hpp"
#include "solution.hpp"
#include "blocked_solution.hpp"
#include "../assembly/abstract_boundary_operator.hpp"
#include "../assembly/abstract_boundary_operator_pseudoinverse.hpp"
#include "../assembly/blocked_boundary_operator.hpp"
#include "../assembly/boundary_operator.hpp"
#include "../assembly/context.hpp"
#include "../assembly/discrete_boundary_operator.hpp"
#include "../assembly/discrete_boundary_operator_composition.hpp"
#include "../assembly/identity_operator.hpp"
#include "../assembly/vector.hpp"
#include "../fiber/explicit_instantiation.hpp"
#include "../space/space.hpp"

#include <Teuchos_RCPBoostSharedPtrConversions.hpp>
#include <Thyra_DefaultSpmdVectorSpace.hpp>

#include <boost/make_shared.hpp>
#include <boost/variant.hpp>

#include <tbb/task_scheduler_init.h>

namespace Bempp {

template <typename ValueType>
Teuchos::RCP<Thyra::DefaultSpmdVector<ValueType>>
wrapInTrilinosVector(arma::Col<ValueType> &col) {
  size_t size = col.n_rows;
  Teuchos::ArrayRCP<ValueType> trilinosArray = Teuchos::arcp(
      col.memptr(), 0 /* lowerOffset */, size, false /* doesn't own memory */);
  typedef Thyra::DefaultSpmdVector<ValueType> TrilinosVector;
  return Teuchos::RCP<TrilinosVector>(
      new TrilinosVector(Thyra::defaultSpmdVectorSpace<ValueType>(size),
                         trilinosArray, 1 /* stride */));
}

/** \cond HIDDEN_INTERNAL */

template <typename BasisFunctionType, typename ResultType>
struct DefaultIterativeSolver<BasisFunctionType, ResultType>::Impl {

  // Constructor for non-blocked operators
  Impl(const BoundaryOperator<BasisFunctionType, ResultType> &op_,
       ConvergenceTestMode::Mode mode_)
      : op(op_), mode(mode_) {
    typedef BoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
    typedef Solver<BasisFunctionType, ResultType> Solver_;
    const BoundaryOp &boundaryOp = boost::get<BoundaryOp>(op);
    if (!boundaryOp.isInitialized())
      throw std::invalid_argument("DefaultIterativeSolver::Impl::Impl(): "
                                  "boundary operator must be initialized");

    if (mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_DUAL_TO_RANGE) {
      if (boundaryOp.domain()->globalDofCount() !=
          boundaryOp.dualToRange()->globalDofCount())
        throw std::invalid_argument("DefaultIterativeSolver::Impl::Impl(): "
                                    "non-square system provided");

      solverWrapper.reset(new BelosSolverWrapper<ResultType>(
          Teuchos::rcp<const Thyra::LinearOpBase<ResultType>>(
              boundaryOp.weakForm())));
    } else if (mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_RANGE) {
      if (boundaryOp.domain()->globalDofCount() !=
          boundaryOp.range()->globalDofCount())
        throw std::invalid_argument("DefaultIterativeSolver::Impl::Impl(): "
                                    "non-square system provided");

      BoundaryOp id =
          identityOperator(boundaryOp.context(), boundaryOp.range(),
                           boundaryOp.range(), boundaryOp.dualToRange());
      pinvId = pseudoinverse(id, boundaryOp.dualToRange());
      // dualToRange could be anything here.
      shared_ptr<DiscreteBoundaryOperator<ResultType>> totalBoundaryOp =
          boost::make_shared<DiscreteBoundaryOperatorComposition<ResultType>>(
              boost::get<BoundaryOp>(pinvId).weakForm(), boundaryOp.weakForm());
      solverWrapper.reset(new BelosSolverWrapper<ResultType>(
          Teuchos::rcp<const Thyra::LinearOpBase<ResultType>>(
              totalBoundaryOp)));
    } else
      throw std::invalid_argument(
          "DefaultIterativeSolver::DefaultIterativeSolver(): "
          "invalid convergence test mode");
  }

  // Constructor for blocked operators
  Impl(const BlockedBoundaryOperator<BasisFunctionType, ResultType> &op_,
       ConvergenceTestMode::Mode mode_)
      : op(op_), mode(mode_) {
    typedef BlockedBoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
    typedef Solver<BasisFunctionType, ResultType> Solver_;
    const BoundaryOp &boundaryOp = boost::get<BoundaryOp>(op);

    if (mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_DUAL_TO_RANGE) {
      if (boundaryOp.totalGlobalDofCountInDomains() !=
          boundaryOp.totalGlobalDofCountInDualsToRanges())
        throw std::invalid_argument("DefaultIterativeSolver::Impl::Impl(): "
                                    "non-square system provided");
      solverWrapper.reset(new BelosSolverWrapper<ResultType>(
          Teuchos::rcp<const Thyra::LinearOpBase<ResultType>>(
              boundaryOp.weakForm())));
    } else if (mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_RANGE) {
      if (boundaryOp.totalGlobalDofCountInDomains() !=
          boundaryOp.totalGlobalDofCountInRanges())
        throw std::invalid_argument("DefaultIterativeSolver::Impl::Impl(): "
                                    "non-square system provided");

      // Construct a block-diagonal operator composed of pseudoinverses
      // for appropriate spaces
      BlockedOperatorStructure<BasisFunctionType, ResultType> pinvIdStructure;
      size_t rowCount = boundaryOp.rowCount();
      size_t columnCount = boundaryOp.columnCount();
      for (size_t row = 0; row < rowCount; ++row) {
        // Find the first non-zero block in row #row and retrieve its context
        shared_ptr<const Context<BasisFunctionType, ResultType>> context;
        for (int col = 0; col < columnCount; ++col)
          if (boundaryOp.block(row, col).context()) {
            context = boundaryOp.block(row, col).context();
            break;
          }
        assert(context);

        BoundaryOperator<BasisFunctionType, ResultType> id = identityOperator(
            context, boundaryOp.range(row), boundaryOp.range(row),
            boundaryOp.dualToRange(row));
        pinvIdStructure.setBlock(row, row, pseudoinverse(id));
      }
      pinvId = BlockedBoundaryOperator<BasisFunctionType, ResultType>(
          pinvIdStructure);

      shared_ptr<DiscreteBoundaryOperator<ResultType>> totalBoundaryOp =
          boost::make_shared<DiscreteBoundaryOperatorComposition<ResultType>>(
              boost::get<BoundaryOp>(pinvId).weakForm(), boundaryOp.weakForm());
      solverWrapper.reset(new BelosSolverWrapper<ResultType>(
          Teuchos::rcp<const Thyra::LinearOpBase<ResultType>>(
              totalBoundaryOp)));
    } else
      throw std::invalid_argument(
          "DefaultIterativeSolver::DefaultIterativeSolver(): "
          "invalid convergence test mode");
  }

  boost::variant<BoundaryOperator<BasisFunctionType, ResultType>,
                 BlockedBoundaryOperator<BasisFunctionType, ResultType>> op;
  ConvergenceTestMode::Mode mode;
  boost::scoped_ptr<BelosSolverWrapper<ResultType>> solverWrapper;
  boost::variant<BoundaryOperator<BasisFunctionType, ResultType>,
                 BlockedBoundaryOperator<BasisFunctionType, ResultType>> pinvId;
};

/** \endcond */

template <typename BasisFunctionType, typename ResultType>
DefaultIterativeSolver<BasisFunctionType, ResultType>::DefaultIterativeSolver(
    const BoundaryOperator<BasisFunctionType, ResultType> &boundaryOp,
    ConvergenceTestMode::Mode mode)
    : m_impl(new Impl(boundaryOp, mode)) {}

template <typename BasisFunctionType, typename ResultType>
DefaultIterativeSolver<BasisFunctionType, ResultType>::DefaultIterativeSolver(
    const BlockedBoundaryOperator<BasisFunctionType, ResultType> &boundaryOp,
    ConvergenceTestMode::Mode mode)
    : m_impl(new Impl(boundaryOp, mode)) {}

template <typename BasisFunctionType, typename ResultType>
DefaultIterativeSolver<BasisFunctionType,
                       ResultType>::~DefaultIterativeSolver() {}

template <typename BasisFunctionType, typename ResultType>
void DefaultIterativeSolver<BasisFunctionType, ResultType>::setPreconditioner(
    const Preconditioner<ResultType> &preconditioner) {
  m_impl->solverWrapper->setPreconditioner(preconditioner.get());
}

template <typename BasisFunctionType, typename ResultType>
void DefaultIterativeSolver<BasisFunctionType, ResultType>::initializeSolver(
    const Teuchos::RCP<Teuchos::ParameterList> &paramList) {
  m_impl->solverWrapper->initializeSolver(paramList);
}

template <typename BasisFunctionType, typename ResultType>
void DefaultIterativeSolver<BasisFunctionType, ResultType>::initializeSolver(
    const Teuchos::RCP<Teuchos::ParameterList> &paramList,
    const Preconditioner<ResultType> &preconditioner) {
  m_impl->solverWrapper->setPreconditioner(preconditioner.get());
  m_impl->solverWrapper->initializeSolver(paramList);
}

template <typename BasisFunctionType, typename ResultType>
Solution<BasisFunctionType, ResultType>
DefaultIterativeSolver<BasisFunctionType, ResultType>::solveImplNonblocked(
    const GridFunction<BasisFunctionType, ResultType> &rhs) const {
  typedef BoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
  typedef typename ScalarTraits<ResultType>::RealType MagnitudeType;
  typedef Thyra::MultiVectorBase<ResultType> TrilinosVector;

  const BoundaryOp *boundaryOp = boost::get<BoundaryOp>(&m_impl->op);
  if (!boundaryOp)
    throw std::logic_error(
        "DefaultIterativeSolver::solve(): for solvers constructed "
        "from a BlockedBoundaryOperator the other solve() overload "
        "must be used");
  Solver<BasisFunctionType, ResultType>::checkConsistency(*boundaryOp, rhs,
                                                          m_impl->mode);

  // Construct rhs vector
  Vector<ResultType> projectionsVector(
      rhs.projections(boundaryOp->dualToRange()));
  Teuchos::RCP<TrilinosVector> rhsVector;
  if (m_impl->mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_DUAL_TO_RANGE)
    rhsVector = Teuchos::rcpFromRef(projectionsVector);
  else {
    const size_t size = boundaryOp->range()->globalDofCount();
    rhsVector.reset(new Vector<ResultType>(size));
    boost::get<BoundaryOp>(m_impl->pinvId).weakForm()->apply(
        Thyra::NOTRANS, projectionsVector, rhsVector.ptr(), 1., 0.);
  }

  // Construct solution vector
  arma::Col<ResultType> armaSolution(rhsVector->range()->dim());
  armaSolution.fill(static_cast<ResultType>(0.));
  Teuchos::RCP<TrilinosVector> solutionVector =
      wrapInTrilinosVector(armaSolution);

  // Get number of threads
  Fiber::ParallelizationOptions parallelOptions =
      boundaryOp->context()->assemblyOptions().parallelizationOptions();
  int maxThreadCount = 1;
  if (!parallelOptions.isOpenClEnabled()) {
    if (parallelOptions.maxThreadCount() == ParallelizationOptions::AUTO)
      maxThreadCount = tbb::task_scheduler_init::automatic;
    else
      maxThreadCount = parallelOptions.maxThreadCount();
  }

  // Solve
  Thyra::SolveStatus<MagnitudeType> status;
  {
    // Initialize TBB threads here (to prevent their construction and
    // destruction on every matrix-vector multiplication)
    tbb::task_scheduler_init scheduler(maxThreadCount);
    status = m_impl->solverWrapper->solve(Thyra::NOTRANS, *rhsVector,
                                          solutionVector.ptr());
  }

  // Construct grid function and return
  return Solution<BasisFunctionType, ResultType>(
      GridFunction<BasisFunctionType, ResultType>(
          boundaryOp->context(), boundaryOp->domain(), armaSolution),
      status);
}

template <typename BasisFunctionType, typename ResultType>
BlockedSolution<BasisFunctionType, ResultType>
DefaultIterativeSolver<BasisFunctionType, ResultType>::solveImplBlocked(
    const std::vector<GridFunction<BasisFunctionType, ResultType>> &rhs) const {
  typedef BlockedBoundaryOperator<BasisFunctionType, ResultType> BoundaryOp;
  typedef typename ScalarTraits<ResultType>::RealType MagnitudeType;
  typedef Thyra::MultiVectorBase<ResultType> TrilinosVector;

  const BoundaryOp *boundaryOp = boost::get<BoundaryOp>(&m_impl->op);
  if (!boundaryOp)
    throw std::logic_error(
        "DefaultIterativeSolver::solve(): for solvers constructed "
        "from a (non-blocked) BoundaryOperator the other solve() overload "
        "must be used");
  std::vector<GridFunction<BasisFunctionType, ResultType>> canonicalRhs =
      Solver<BasisFunctionType, ResultType>::canonicalizeBlockedRhs(
          *boundaryOp, rhs, m_impl->mode);
  // Shouldn't be needed, but better safe than sorry...
  Solver<BasisFunctionType, ResultType>::checkConsistency(
      *boundaryOp, canonicalRhs, m_impl->mode);

  // Currently we only support convergence testing in space dual to range.

  // Construct the right-hand-side vector
  arma::Col<ResultType> armaProjections(
      boundaryOp->totalGlobalDofCountInDualsToRanges());
  for (size_t i = 0, start = 0; i < canonicalRhs.size(); ++i) {
    const arma::Col<ResultType> &chunkProjections =
        canonicalRhs[i].projections(boundaryOp->dualToRange(i));
    size_t chunkSize = chunkProjections.n_rows;
    armaProjections.rows(start, start + chunkSize - 1) = chunkProjections;
    start += chunkSize;
  }

  Vector<ResultType> projectionsVector(armaProjections);
  Teuchos::RCP<TrilinosVector> rhsVector;
  if (m_impl->mode == ConvergenceTestMode::TEST_CONVERGENCE_IN_DUAL_TO_RANGE)
    rhsVector = Teuchos::rcpFromRef(projectionsVector);
  else {
    const size_t rhsSize = boundaryOp->totalGlobalDofCountInRanges();
    rhsVector.reset(new Vector<ResultType>(rhsSize));
    boost::get<BoundaryOp>(m_impl->pinvId).weakForm()->apply(
        Thyra::NOTRANS, projectionsVector, rhsVector.ptr(), 1., 0.);
  }

  // Initialize the solution vector
  size_t solutionSize = 0;
  for (size_t i = 0; i < canonicalRhs.size(); ++i)
    solutionSize += boundaryOp->domain(i)->globalDofCount();
  arma::Col<ResultType> armaSolution(solutionSize);
  armaSolution.fill(static_cast<ResultType>(0.));
  Teuchos::RCP<TrilinosVector> solutionVector =
      wrapInTrilinosVector(armaSolution);

  // Get context of the first non-empty operator
  size_t rowCount = boundaryOp->rowCount();
  shared_ptr<const Context<BasisFunctionType, ResultType>> context;
  for (size_t row = 0; row < rowCount; ++row)
    if (boundaryOp->block(row, 0).context()) {
      context = boundaryOp->block(row, 0).context();
      break;
    }
  assert(context);

  // Get number of threads
  Fiber::ParallelizationOptions parallelOptions =
      context->assemblyOptions().parallelizationOptions();
  int maxThreadCount = 1;
  if (!parallelOptions.isOpenClEnabled()) {
    if (parallelOptions.maxThreadCount() == ParallelizationOptions::AUTO)
      maxThreadCount = tbb::task_scheduler_init::automatic;
    else
      maxThreadCount = parallelOptions.maxThreadCount();
  }

  // Solve
  Thyra::SolveStatus<MagnitudeType> status;
  {
    // Initialize TBB threads here (to prevent their construction and
    // destruction on every matrix-vector multiplication)
    tbb::task_scheduler_init scheduler(maxThreadCount);
    status = m_impl->solverWrapper->solve(Thyra::NOTRANS, *rhsVector,
                                          solutionVector.ptr());
  }

  // Convert chunks of the solution vector into grid functions
  std::vector<GridFunction<BasisFunctionType, ResultType>> solutionFunctions;
  Solver<BasisFunctionType, ResultType>::constructBlockedGridFunction(
      armaSolution, *boundaryOp, solutionFunctions);

  // Return solution
  return BlockedSolution<BasisFunctionType, ResultType>(solutionFunctions,
                                                        status);
}

FIBER_INSTANTIATE_CLASS_TEMPLATED_ON_BASIS_AND_RESULT(DefaultIterativeSolver);

} // namespace Bempp

#endif // WITH_TRILINOS


#ifndef _AVERAGER_HH_
#define _AVERAGER_HH_

/** \file
    Definition of the interfaces and implementation of Average and
    Averager. */

#include "covertree.hh"
#include "state.hh"
#include <boost/weak_ptr.hpp>
#include <ext/hash_map>
#include <ext/hash_set>
#include <map>
#include <set>

class Average;
typedef boost::shared_ptr<Average> AverageRef;

/** Interface for objects that approximate a given state as a weighted
    average of other states.  Such averages are used to approximate
    the model of a given state using data averaged from visited
    states, as well as to approximate the value of a given state using
    the values of a fixed finite set of states. */
class Average
{
public:
  /** Interface for objects that wish to be notified when an Average
      changes how it approximates its query state. */
  class Observer
  {
  public:
    virtual ~Observer();

    /** Delivers a message to this Observer that its observed Average
	has changed.  In particular, the Average now returns a
	different value for norm or bases. */
    virtual void observe_average_change() = 0;
  };

  typedef boost::shared_ptr<Observer> ObserverRef;
  typedef boost::weak_ptr<Observer> ObserverWeakRef;

  virtual ~Average();

  /** \return The state that this average approximates. */
  virtual const StateVectorRef &state() const = 0;

  /** \return A mapping of states to UNNORMALIZED weights.  Each state
              reference in the mapping is non-NULL. */
  virtual const StateDistribution &basis_weights() const = 0;

  /** Informs an Average that an Average::Observer would like to be
      notified of any changes in the return value of
      basis_probabilities().
      \param o The observer to add. */
  virtual void add_observer(const ObserverWeakRef &o);

  /** Informs an Average that an Average::Observer would like to stop
      receiving notifications.
      \param o The Average::Observer that would like to stop receiving
               notifications messages. */
  virtual void remove_observer(const ObserverWeakRef &o);

protected:
  /** Sends a notification message to all Average::Observers. */
  virtual void notify_average_observers();

private:
  /** Container for all the Average::Observers subscribed to this
      Average. */
  std::vector<ObserverWeakRef> m_observers;
};

/** Interface for objects that construct Average objects.  Averager
    objects have the property that all the StateVectorRef objects returned
    by any of their components are canonical: if *p1 == *p2, then p1
    == p2. */
class Averager
{
public:
  virtual ~Averager();

  /** Specifies the importance of each state dimension to this Averager.
      \return A mapping whose keys comprise all the state dimensions
              that this Averager uses in its approximation.  The data
              give the scaling coefficient for each of these
              dimensions. */
  virtual const StateVariables &dimensions() const = 0;

  /** Approximates a given state using an Average.
      \param s The state to approximate.
      \return An Average approximating s. */
  virtual AverageRef approximate(const StateVectorRef &s) = 0;
};

typedef boost::shared_ptr<Averager> AveragerRef;

/** Interface for Averagers that permit the dynamic (user-directed)
    growth of the set of states used to approximate query states. */
class DynamicAverager: public Averager
{
public:
  virtual ~DynamicAverager();

  /** Adds a state to the set of bases used to approximate query
      states.
      \param s The state to add.
      \return Pointer to the averager's canonical of s. */
  virtual StateVectorRef add_basis(const StateVectorRef &s) = 0;
};

typedef boost::shared_ptr<DynamicAverager> DynamicAveragerRef;

class KernelAverager;
typedef boost::shared_ptr<KernelAverager> KernelAveragerRef;

/** An Averager that uses Gaussian kernels to approximate states.  In
    particular, for any given query state s, it assigns to each basis
    state x the weight e^(-d(x,s)^2 / b^2), where b is a
    generalization breadth and d is a scaled Euclidean distance
    function.  Note that all weights therefore lie in the interval
    (0,1].  For efficiency reasons, a weight is set to zero if it
    falls below a certain threshold or if it is smaller than a certain
    fraction of the sum of the larger weights.  (The tail of the
    distribution is truncated, since it has a small effect on the
    approximation.)  */
class KernelAverager: public DynamicAverager
{
public:
  /** Constructs a new KernelAverager that begins with no basis states.
      \param breadth The generalization breadth b
      \param minweight The minimum nonzero value of a weight
      \param minfraction Weights smaller than this fraction of the sum
                         of larger weights are set to zero.
      \param scale The scale parameters of the DistanceFunction d. */
  static DynamicAveragerRef create(double breadth,
				   double minweight,
				   double minfraction,
				   const StateVariables &scale);

  virtual ~KernelAverager();

  virtual const StateVariables &dimensions() const;
  virtual AverageRef approximate(const StateVectorRef &s);
  virtual StateVectorRef add_basis(const StateVectorRef &s);

protected:
  class KernelAverage;
  typedef boost::shared_ptr<KernelAverage> KernelAverageRef;
  typedef boost::weak_ptr<KernelAverage> KernelAverageWeakRef;

  /** Implementation of Average used by KernelAverager */
  class KernelAverage: public Average
  {
  public:
    /** Creates a new KernelAverage
	\param averager The KernelAverager asked to approximate s
	\param s The state to approximate.
	\param ref A weak reference that should point to the new
	           average */
    static KernelAverageRef create(const KernelAveragerRef &averager,
				   const StateVectorRef &s,
				   KernelAverageWeakRef &ref);

    virtual ~KernelAverage();

    virtual const StateVectorRef &state() const;
    virtual const StateDistribution &basis_weights() const;

    /** Computes the approximation weights from the set of basis
	neighbors, which are pruned according to the KernelAverager's
	parameters. */
    void compute_weights();

    /** Potentially updates this KernelAverage to include a new basis.
	\param d The distance from s to this KernelAverage's state
	         according to the KernelAverager's DistanceFunction
	\param s The new basis state */
    void include(double d, const StateVectorRef &s);

  private:
    KernelAverage(const KernelAveragerRef &averager, const StateVectorRef &s);

    typedef std::vector<std::pair<double, StateVectorRef> > NeighborContainer;

    const KernelAveragerRef m_averager;
    const StateVectorRef m_s;

    /** A sorted array of the basis states near the given state,
	sorted in ascending order of distance.  Should contain all the
	states within m_averager->m_maxd of the given state, except
	the ones pruned according to m_averager->m_minfraction. */
    NeighborContainer m_neighbors;

    StateDistribution m_weights;
    double m_sum;
  };

  friend class KernelAverage;

private:
  KernelAverager(double breadth, double minweight, double minfraction,
		 const StateVariables &scale);

  const double m_bb;
  const double m_minfraction;
  const double m_maxd;
  const StateVariables m_scale;

  /** Invariant: contains all the keys in the queries hash. */
  CoverTree<StateVectorRef, DistanceFunction> m_querytree;

  /** Invariant: for each state given to add_basis as an argument,
      contains exactly one reference to a state equivalent on the
      dimensions used by this Averager. */
  CoverTree<StateVectorRef, DistanceFunction> m_basistree;

  typedef __gnu_cxx::hash_map<StateVectorRef,
			      KernelAverageWeakRef,
			      StateHasher> QueryContainer;

  /** Invariant: contains all the existing KernelAverages created by
      this KernelAverager.  The keys serve as both queries and bases
      (queries are a superset of bases), so a datum may be NULL if the
      associated key is just a basis, not a query. */
  QueryContainer m_queries;

  /** The subset of the keys of queries that are also bases.  This
      container does not own any of its keys. */
  __gnu_cxx::hash_set<StateVectorRef, StateHasher> m_bases;

  /** Weak pointer to self, passed to KernelAverage constructor. */
  boost::weak_ptr<KernelAverager> m_this;
};

class InterpolationAverager;
typedef boost::shared_ptr<InterpolationAverager> InterpolationAveragerRef;

/** An Averager that uses multilinear interpolation to approximate
    states.  It maps each query state to a scaled Euclidean space,
    where the query is approximated using a uniform grid with spacing
    equal to 2^resolutionfactor points per unit distance.  Where d is
    the number of dimensions, each query is approximated as the
    Average of exactly 2^d points in this grid.  Note that each of
    these points has one of exactly two values for each dimension.
    For each of these points, the weight is the product of the weights
    for the d linear interpolations of each dimension of the query
    using the two grid values in that dimension.

    Note that this Averager generates and maintains its own set of
    basis vectors.  It allocates the vectors lazily.  These vectors
    will only have nonzero values for those indices specified by the
    dimensions parameter to the constructor.

    This Averager generates Average objects with the property that all
    the weights sum to 1. */
class InterpolationAverager: public Averager
{
public:
  /** Creates a new InterpolationAverager.
      \param resolutionfactor Controls the spacing of the uniform grid
                              used as the basis set
      \param scale The weights used to obtain the scaled Euclidean
	           space.  Unused dimensions are set to 0 in the basis
	           vectors. */
  static AveragerRef create(int resolutionfactor, const StateVariables &scale);

  virtual ~InterpolationAverager();

  virtual const StateVariables &dimensions() const;
  virtual AverageRef approximate(const StateVectorRef &s);

protected:
  class InterpolationAverage;
  typedef boost::shared_ptr<InterpolationAverage> InterpolationAverageRef;
  typedef boost::weak_ptr<InterpolationAverage> InterpolationAverageWeakRef;

  /** Implementation of Average used by InterpolationAverager */
  class InterpolationAverage: public Average
  {
  public:
    /** Creates a new InterpolationAverage
	\param averager The InterpolationAverager asked to approximate s
	\param s The state to approximate.
	\param is_basis Whether s is a basis state
	\param ref A pointer that should be assigned the new average */
    static InterpolationAverageRef
    create(const InterpolationAveragerRef &averager,
	   const StateVectorRef &s,
	   bool is_basis,
	   InterpolationAverageWeakRef &ref);

    virtual ~InterpolationAverage();

    virtual const StateVectorRef &state() const;
    virtual const StateDistribution &basis_weights() const;

    void set_basis() {
      m_basis = true;
    }

  private:
    InterpolationAverage(const InterpolationAveragerRef &averager,
			 const StateVectorRef &s,
			 bool is_basis);

    const InterpolationAveragerRef m_averager;
    const StateVectorRef m_s;
    StateDistribution m_weights;
    bool m_basis; // True if this query is also a basis
  };

  friend class InterpolationAverage;

private:
  InterpolationAverager(int resolutionfactor, const StateVariables &scale);

  const int m_res;
  const StateVariables m_scale;

  typedef std::map<StateVectorRef,
		   InterpolationAverageWeakRef,
		   AbstractStateComparator> QueryContainer;

  /** Invariant: contains all the existing InterpolationAverages that
      this InterpolationAverager has created.  The keys include both
      bases and queries, so some keys have a NULL value. */
  QueryContainer m_queries;

  /** Weak reference to self, passed to Average constructors. */
  boost::weak_ptr<InterpolationAverager> m_this;
};

class ProjectionAverager;
typedef boost::shared_ptr<ProjectionAverager> ProjectionAveragerRef;

/** An Averager that projects a given state onto the state subspace
    obtained by ignoring certain state dimensions.  Since each Average
    only uses a single basis state, this approximation corresponds
    exactly to state abstraction.  Each Average object created by this
    averager returns a value for basis_weights() that has exactly one
    basis with weight 1.0. */
class ProjectionAverager: public DynamicAverager {
public:
  /** \param dims The state variables than span the desired subspace.
                  Note that the weights attached to the included
                  variables are irrelevant. */
  static DynamicAveragerRef create(const StateVariables &dims);

  virtual ~ProjectionAverager();

  virtual const StateVariables &dimensions() const;
  virtual AverageRef approximate(const StateVectorRef &s);
  virtual StateVectorRef add_basis(const StateVectorRef &s);

protected:
  class ProjectionAverage;
  typedef boost::shared_ptr<ProjectionAverage> ProjectionAverageRef;
  typedef boost::weak_ptr<ProjectionAverage> ProjectionAverageWeakRef;

  class ProjectionAverage: public Average {
  public:
    static ProjectionAverageRef
    create(const ProjectionAveragerRef &averager,
	   const StateVectorRef &s,
	   ProjectionAverageWeakRef &ref);

    virtual ~ProjectionAverage();

    virtual const StateVectorRef &state() const;
    virtual const StateDistribution &basis_weights() const;

  private:
    ProjectionAverage(const ProjectionAveragerRef &averager,
		      const StateVectorRef &s);

    const ProjectionAveragerRef m_averager;
    StateDistribution m_base;
  };

  friend class ProjectionAverage;

  typedef std::map<StateVectorRef,
		   ProjectionAverageWeakRef,
		   AbstractStateComparator> AveragesContainer;

private:
  ProjectionAverager(const StateVariables &dims);

  const StateVariables m_dims;

  /** The keys of this map are the canonical forms of all the states
      seen by this averager.  The data of this map are the averages
      that this averager has create. */
  AveragesContainer m_averages;

  /** Weak reference to self, passed to Average constructors. */
  boost::weak_ptr<ProjectionAverager> m_this;
};

class DiscretizationAverager;
typedef boost::shared_ptr<DiscretizationAverager> DiscretizationAveragerRef;

class DiscretizationAverager: public Averager
{
public:
  /** Creates a new DiscretizationAverager.
      \param resolutionfactor Controls the spacing of the uniform grid
                              used as the basis set
      \param scale The weights used to obtain the scaled Euclidean
	           space.  Unused dimensions are set to 0 in the basis
	           vectors. */
  static AveragerRef create(int resolutionfactor, const StateVariables &scale);

  virtual ~DiscretizationAverager();

  virtual const StateVariables &dimensions() const;
  virtual AverageRef approximate(const StateVectorRef &s);

protected:
  class DiscretizationAverage;
  typedef boost::shared_ptr<DiscretizationAverage> DiscretizationAverageRef;
  typedef boost::weak_ptr<DiscretizationAverage> DiscretizationAverageWeakRef;

  /** Implementation of Average used by DiscretizationAverager */
  class DiscretizationAverage: public Average
  {
  public:
    /** Creates a new DiscretizationAverage
	\param averager The DiscretizationAverager asked to approximate s
	\param s The state to approximate.
	\param is_basis Whether s is a basis state
	\param ref A weak pointer that should be assigned the new
	           average */
    static DiscretizationAverageRef
    create(const DiscretizationAveragerRef &averager,
	   const StateVectorRef &s,
	   bool is_basis,
	   DiscretizationAverageWeakRef &ref);

    virtual ~DiscretizationAverage();

    virtual const StateVectorRef &state() const;
    virtual const StateDistribution &basis_weights() const;

    void set_basis() {
      m_basis = true;
    }

  private:
    DiscretizationAverage(const DiscretizationAveragerRef &averager,
			  const StateVectorRef &s,
			  bool is_basis);

    const DiscretizationAveragerRef m_averager;
    const StateVectorRef m_s;
    StateDistribution m_weights;
    bool m_basis; // True if this query is also a basis
  };

  friend class DiscretizationAverage;

private:
  DiscretizationAverager(int resolutionfactor, const StateVariables &scale);

  const int m_res;
  const StateVariables m_scale;

  typedef std::map<StateVectorRef,
		   DiscretizationAverageWeakRef,
		   AbstractStateComparator> QueryContainer;

  /** Invariant: contains all the existing DiscretizationAverages that
      this DiscretizationAverager has created.  The keys include both
      bases and queries, so the data pointer may be NULL. */
  QueryContainer m_queries;

  /** Weak reference to self, passed to Average constructors. */
  boost::weak_ptr<DiscretizationAverager> m_this;
};

#endif


/* decision_tree.cc                                                -*- C++ -*-
   Jeremy Barnes, 22 March 2004
   Copyright (c) 2004 Jeremy Barnes.  All rights reserved.
   $Source$

   Implementation of the decision tree.
*/

#include "decision_tree.h"
#include "classifier_persist_impl.h"
#include <boost/progress.hpp>
#include <boost/timer.hpp>
#include <functional>
#include "jml/utils/vector_utils.h"
#include "config_impl.h"
#include "jml/utils/exc_assert.h"
#include "jml/utils/smart_ptr_utils.h"

using namespace std;
using namespace DB;

namespace ML {

/*****************************************************************************/
/* DECISION_TREE                                                             */
/*****************************************************************************/

Decision_Tree::Decision_Tree()
    : encoding(OE_PROB), optimized_(false)
{
}

Decision_Tree::
Decision_Tree(DB::Store_Reader & store,
              const std::shared_ptr<const Feature_Space> & fs)
    : optimized_(false)
{
    throw Exception("Decision_Tree constructor(reconst): not implemented");
}

Decision_Tree::
Decision_Tree(std::shared_ptr<const Feature_Space> feature_space,
              const Feature & predicted)
    : Classifier_Impl(feature_space, predicted),
      encoding(OE_PROB),
      optimized_(false)
{
}

Decision_Tree::
~Decision_Tree()
{
}

void
Decision_Tree::
swap(Decision_Tree & other)
{
    Classifier_Impl::swap(other);
    std::swap(tree, other.tree);
    std::swap(encoding, other.encoding);
    std::swap(optimized_, other.optimized_);
}

namespace {

struct StandardGetFeatures {
    StandardGetFeatures(const Feature_Set & features)
        : features(features)
    {
    }

    const Feature_Set & features;

    Split::Weights operator () (const Split & split) const
    {
        return split.apply(features);
    }
};

struct OptimizedGetFeatures {
    OptimizedGetFeatures(const float * features)
        : features(features)
    {
    }

    const float * features;

    JML_ALWAYS_INLINE Split::Weights operator () (const Split & split) const
    {
        return split.apply(features);
    }

};

struct AccumResults {
    explicit AccumResults(double * accum, int nl, double weight)
        : accum(accum), nl(nl), weight(weight)
    {
    }

    double * accum;
    int nl;
    double weight;

    JML_ALWAYS_INLINE
    void operator () (const Label_Dist & dist, float weight1)
    {
        if (JML_LIKELY(nl == 2)) {
            double factor = weight1 * weight;
            accum[0] += dist[0] * factor;
            accum[1] += dist[1] * factor;
            return;
        }

        for (unsigned i = 0;  i < nl;  ++i)
            accum[i] += dist[i] * weight1 * weight;
    }
};

struct DistResults {
    explicit DistResults(double * accum, int nl)
        : accum(accum), nl(nl)
    {
        std::fill(accum, accum + nl, 0.0);
    }

    double * accum;
    int nl;

    JML_ALWAYS_INLINE
    void operator () (const Label_Dist & dist, float weight)
    {
        for (unsigned i = 0;  i < nl;  ++i)
            accum[i] += dist[i] * weight;
    }

    operator Label_Dist () const { return Label_Dist(accum, accum + nl); }
};

struct LabelResults {
    explicit LabelResults(int label)
        : label(label), result(0.0)
    {
    }

    JML_ALWAYS_INLINE
    void operator () (const Label_Dist & dist, float weight)
    {
        result += weight * dist[label];
    }

    int label;
    double result;

    operator double () const { return result; }
};

} // file scope

float
Decision_Tree::
predict(int label, const Feature_Set & features,
        PredictionContext * context) const
{
    StandardGetFeatures get_features(features);
    LabelResults results(label);

    predict_recursive_impl(get_features, results, tree.root);
    return results;
}

Label_Dist
Decision_Tree::
predict(const Feature_Set & features,
        PredictionContext * context) const
{
    StandardGetFeatures get_features(features);
    int nl = label_count();
    double accum[nl];
    DistResults results(accum, nl);

    predict_recursive_impl(get_features, results, tree.root);
    return results;
}

bool
Decision_Tree::
optimization_supported() const
{
    return true;
}

bool
Decision_Tree::
predict_is_optimized() const
{
    return optimized_;
}

bool
Decision_Tree::
optimize_impl(Optimization_Info & info)
{
    optimize_recursive(info, tree.root);
    optimized_ = true;
    return true;
}

void
Decision_Tree::
optimize_recursive(Optimization_Info & info,
                   const Tree::Ptr & ptr)
{
    if (!ptr) return;
    if (!ptr.node()) return;

    Tree::Node & node = *ptr.node();

    node.split.optimize(info);
    optimize_recursive(info, node.child_true);
    optimize_recursive(info, node.child_false);
    optimize_recursive(info, node.child_missing);
}

Label_Dist
Decision_Tree::
optimized_predict_impl(const float * features,
                       const Optimization_Info & info,
                       PredictionContext * context) const
{
    OptimizedGetFeatures get_features(features);

    int nl = label_count();
    double accum[nl];
    DistResults results(accum, nl);

    predict_recursive_impl(get_features, results, tree.root);
    return results;
}

void
Decision_Tree::
optimized_predict_impl(const float * features,
                       const Optimization_Info & info,
                       double * accum,
                       double weight,
                       PredictionContext * context) const
{
    OptimizedGetFeatures get_features(features);
    AccumResults results(accum, label_count(), weight);

    predict_recursive_impl(get_features, results, tree.root);
}

float
Decision_Tree::
optimized_predict_impl(int label,
                       const float * features,
                       const Optimization_Info & info,
                       PredictionContext * context) const
{
    OptimizedGetFeatures get_features(features);
    LabelResults results(label);

    predict_recursive_impl(get_features, results, tree.root);
    return results;
}

template<class GetFeatures, class Results>
void
Decision_Tree::
predict_recursive_impl(const GetFeatures & get_features,
                       Results & results,
                       const Tree::Ptr & ptr,
                       double weight) const
{
    if (!ptr) return;

    if (!ptr.node()) {
        results(ptr.leaf()->pred, weight);
        return;
    }

    const Tree::Node & node = *ptr.node();

    Split::Weights weights = get_features(node.split);

    /* Go down all of the edges that we need to for this example. */
    if (weights[true] > 0.0)
        predict_recursive_impl(get_features, results, node.child_true,
                               weights[true]);
    if (weights[false] > 0.0)
        predict_recursive_impl(get_features, results, node.child_false,
                               weights[false]);
    if (weights[MISSING] > 0.0)
        predict_recursive_impl(get_features, results, node.child_missing,
                               weights[MISSING]);
}

std::string
printLabels(const distribution<float> & dist)
{
    string result = "";
    for (unsigned i = 0;  i < dist.size();  ++i)
        if (dist[i] != 0.0) result += format(" %d/%.3f", i, dist[i]);
    return result;
}

std::string
printLabels(const Tree::Ptr & ptr)
{
    return printLabels(ptr.pred());
}

std::string
Decision_Tree::
print() const
{
    string result = "Decision Tree: ";
    float total_weight = tree.root.examples();
    if (tree.root.node()) total_weight = tree.root.examples();
    result = format("Decision Tree: (weight = %.2f, cov = %.2f%%) ",
                    total_weight, 100.0);
    result += printLabels(tree.root) + "\n";
    result += print_recursive(0, tree.root, total_weight);
    return result;
}

std::string
Decision_Tree::
summary() const
{
    if (!tree.root)
        return "NULL";

    float total_weight = 0.0;
    if (tree.root.node()) total_weight = tree.root.node()->examples;

    if (tree.root.node()) {
        Tree::Node & n = *tree.root.node();
        float cov = n.examples / total_weight;
        float z_adj = n.z / cov;
        return "Root: " + n.split.print(*feature_space())
            + format(" (z = %.4f)", z_adj);
    }
    else {
        string result = "leaf: ";
        Tree::Leaf & l = *tree.root.leaf();
        const distribution<float> & dist = l.pred;
        for (unsigned i = 0;  i < dist.size();  ++i)
            if (dist[i] != 0.0) result += format(" %d/%.3f", i, dist[i]);
        return result;
    }
}

string
Decision_Tree::
print_recursive(int level, const Tree::Ptr & ptr,
                float total_weight) const
{
    string spaces(level * 4, ' ');
    if (ptr.node()) {
        Tree::Node & n = *ptr.node();
        string result;
        float z_cov = n.examples / total_weight;
        float z_adj = n.z / z_cov;

        if (n.child_false && n.child_false.examples() > 0) {
            float cov = n.child_false.examples() / total_weight;
            result += spaces
                + format(" %s (z = %.4f, weight = %.2f, cov = %.2f%%) ",
                         n.split.print(*feature_space(), false).c_str(),
                         z_adj, n.child_false.examples(), cov * 100.0);
            result += printLabels(n.child_false) + "\n";
            result += print_recursive(level + 1, n.child_false, total_weight);
        }

        if (n.child_true && n.child_true.examples() > 0) {
            float cov = n.child_true.examples() / total_weight;
            result += spaces
                + format(" %s (z = %.4f, weight = %.2f, cov = %.2f%%) ",
                         n.split.print(*feature_space(), true).c_str(),
                         z_adj, n.child_true.examples(), cov * 100.0);
            result += printLabels(n.child_true) + "\n";
            result += print_recursive(level + 1, n.child_true, total_weight);
        }

        if (n.child_missing && n.child_missing.examples() > 0) {
            float cov = n.child_missing.examples() / total_weight;
            result += spaces
                + format(" %s (z = %.4f, weight = %.2f, cov = %.2f%%) ",
                         n.split.print(*feature_space(), MISSING).c_str(),
                         z_adj, n.child_missing.examples(), cov * 100.0);
            result += printLabels(n.child_missing) + "\n";
            result += print_recursive(level + 1, n.child_missing, total_weight);
        }
        return result;
    }
    else if (ptr.leaf()) {
        return "";
        string result = spaces + "leaf ";
        Tree::Leaf & l = *ptr.leaf();
        const distribution<float> & dist = l.pred;
        float cov = l.examples / total_weight;
        result += format(" (weight = %.2f, cov = %.2f%%) ",
                         l.examples, cov * 100.0);
        result += printLabels(dist);
        result += "\n";
        return result;
    }
    else return spaces + "NULL";
}

Explanation
Decision_Tree::
explain(const Feature_Set & feature_set,
        int label,
        double weight,
        PredictionContext * context) const
{
    Explanation result(feature_space(), weight);

    explain_recursive(result, feature_set, label, weight, tree.root, 0);

    return result;
}

void
Decision_Tree::
explain_recursive(Explanation & explanation,
                  const Feature_Set & feature_set,
                  int label,
                  double weight,
                  const Tree::Ptr & ptr,
                  const Tree::Node * parent) const
{
    StandardGetFeatures get_features(feature_set);
    int nl = label_count();

    if (label < 0 || label >= nl)
        throw Exception("Decision_Tree::explain(): no label");

    if (!ptr) return;

    if (!ptr.node()) {
        // It's a leaf; we give the difference to the parent's feature
        if (parent) {
            explanation.feature_weights[parent->split.feature()]
                += weight
                * (ptr.leaf()->pred.at(label) - parent->pred.at(label));
        }
        else {
            // No parent, therefore it's all bias
            explanation.bias += weight * (ptr.leaf()->pred.at(label));
        }

        return;
    }

    const Tree::Node & node = *ptr.node();

    Split::Weights weights = get_features(node.split);

    // Accumulate the weight for this split
    if (!parent)
        explanation.bias += weight * node.pred.at(label);
    else
        explanation.feature_weights[parent->split.feature()]
            += weight
            * (node.pred.at(label) - parent->pred.at(label));

    /* Go down all of the edges that we need to for this example. */
    if (weights[true] > 0.0)
        explain_recursive(explanation, feature_set, label,
                          weight * weights[true],
                          node.child_true, &node);

    if (weights[false] > 0.0)
        explain_recursive(explanation, feature_set, label,
                          weight * weights[false],
                          node.child_false, &node);

    if (weights[MISSING] > 0.0)
        explain_recursive(explanation, feature_set, label,
                          weight * weights[MISSING],
                          node.child_missing, &node);
}

Disjunction<Tree::Leaf>
Decision_Tree::
to_rules() const
{
    Disjunction<Tree::Leaf> result;
    result.feature_space = feature_space();
    std::vector<std::shared_ptr<Predicate> > path;

    to_rules_recursive(result, path, tree.root);

    ExcAssert(path.empty());

    return result;
}

void
Decision_Tree::
to_rules_recursive(Disjunction<Tree::Leaf> & result,
                   std::vector<std::shared_ptr<Predicate> > & path,
                   const Tree::Ptr & ptr) const
{
    if (ptr.examples() == 0.0) return;

    if (ptr.node()) {
        const Tree::Node & node = *ptr.node();

        {
            path.push_back(make_sp(new Predicate(node.split, false)));
            to_rules_recursive(result, path, node.child_false);
            path.pop_back();
        }

        {
            path.push_back(make_sp(new Predicate(node.split, true)));
            to_rules_recursive(result, path, node.child_true);
            path.pop_back();
        }

        {
            path.push_back(make_sp(new Predicate(node.split, MISSING)));
            to_rules_recursive(result, path, node.child_missing);
            path.pop_back();
        }
    }
    else if (ptr.leaf()) {
        std::shared_ptr<Conjunction<Tree::Leaf> > c
            (new Conjunction<Tree::Leaf>());
        c->predicates = path;
        c->outcome = *ptr.leaf();
        result.predicates.push_back(c);
    }
}

namespace {

void all_features_recursive(const Tree::Ptr & ptr,
                            vector<Feature> & result)
{
    if (ptr.node()) {
        Tree::Node & n = *ptr.node();
        result.push_back(n.split.feature());

        all_features_recursive(n.child_true,    result);
        all_features_recursive(n.child_false,   result);
        all_features_recursive(n.child_missing, result);
    }
}

} // file scope

std::vector<ML::Feature>
Decision_Tree::
all_features() const
{
    std::vector<ML::Feature> result;
    all_features_recursive(tree.root, result);
    make_vector_set(result);
    return result;
}

Output_Encoding
Decision_Tree::
output_encoding() const
{
    return encoding;
}

void
Decision_Tree::
serialize(DB::Store_Writer & store) const
{
    store << string("DECISION_TREE");
    store << compact_size_t(3);  // version
    store << compact_size_t(label_count());
    feature_space_->serialize(store, predicted_);
    tree.serialize(store, *feature_space());
    store << encoding;
    store << compact_size_t(12345);  // end marker
}

void
Decision_Tree::
reconstitute(DB::Store_Reader & store,
             const std::shared_ptr<const Feature_Space> & feature_space)
{
    string id;
    store >> id;

    if (id != "DECISION_TREE")
        throw Exception("Decision_Tree::reconstitute: read bad ID '"
                        + id + "'");

    compact_size_t version(store);

    switch (version) {
    case 1: {
        compact_size_t label_count(store);
        Classifier_Impl::init(feature_space, MISSING_FEATURE, label_count);
        tree.reconstitute(store, *feature_space);
        break;
    }
    case 2:
    case 3: {
        compact_size_t label_count(store);
        feature_space->reconstitute(store, predicted_);
        Classifier_Impl::init(feature_space, predicted_);
        tree.reconstitute(store, *feature_space);
        if (version >= 3)
            store >> encoding;
        else encoding = OE_PROB;
        break;
    }
    default:
        throw Exception("Decision tree: Attempt to reconstitute tree of "
                        "unknown version " + ostream_format(version.size_));
    }

    compact_size_t marker(store);
    if (marker != 12345)
        throw Exception("Decision_Tree::reconstitute: read bad marker at end");

    optimized_ = false;
}

std::string
Decision_Tree::
class_id() const
{
    return "DECISION_TREE";
}

Decision_Tree *
Decision_Tree::
make_copy() const
{
    return new Decision_Tree(*this);
}

/*****************************************************************************/
/* REGISTRATION                                                              */
/*****************************************************************************/

namespace {

Register_Factory<Classifier_Impl, Decision_Tree> REGISTER("DECISION_TREE");

} // file scope

} // namespace ML


#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/min.hpp>
#include <boost/accumulators/statistics/max.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/program_options.hpp>
#include "picpac-cv.h"

using namespace std;
using namespace picpac;
namespace ba = boost::accumulators;

class Splitter {

public:
    struct Config {
        string path;
#if 0
        string bg_path;
#endif
        int size;
        int width;
        int height;
        bool no_scale;
        // for grid splitting
        float grid_size;
        float grid_step;
        float grid_scale;
        bool image_annotation;
        Config (): size(50), width(100), height(100), no_scale(false), grid_size(240), grid_step(200), grid_scale(1), image_annotation(false) {
        }
    };
private:
    Config config;
    ba::accumulator_set<double, ba::stats<ba::tag::mean, ba::tag::min, ba::tag::max, ba::tag::variance > > acc;
    ImageEncoder encoder;
    ImageEncoder anno_encoder;
    FileWriter db;
#if 0
    FileWriter *bg;
#endif
    void add_roi (cv::Mat image,
                  cv::Rect roi,
                  Annotation const &anno) {
        cv::Rect_<float> zoom(1.0 * roi.x / image.cols,
                      1.0 * roi.y / image.rows,
                      1.0 * roi.width / image.cols,
                      1.0 * roi.height / image.rows);
        cv::Mat out = image(roi);
        Annotation anno_out;
        int label = 0;
        for (unsigned j = 0; j < anno.shapes.size(); ++j) {
            auto shapex = anno.shapes[j];
            cv::Rect_<float> bbx;
            shapex->bbox(&bbx);
            cv::Rect_<float> sect = bbx & zoom;
            if (sect.area() > 0) {
                anno_out.shapes.push_back(shapex->clone());
                //label = 1;
            }
        }
        string f0;
        string f1;
        anno_out.zoom(zoom);
        encoder.encode(out, &f0);
        anno_out.dump(&f1);
        Record rec(label, f0, f1);
        db.append(rec);
    }

    void add_roi_image_anno (cv::Mat image,
                  cv::Rect roi,
                  cv::Mat anno) {
        cv::Rect_<float> zoom(1.0 * roi.x / image.cols,
                      1.0 * roi.y / image.rows,
                      1.0 * roi.width / image.cols,
                      1.0 * roi.height / image.rows);
        cv::Mat out = image(roi);
        cv::Mat out_anno = anno(roi);
        string f0;
        string f1;
        encoder.encode(out, &f0);
        anno_encoder.encode(out_anno, &f1);
        int label = 0;
        Record rec(label, f0, f1);
        db.append(rec);
    }

    static void adjust_grid_size(int size, int *patch, int *step, int *nsteps) {
        if (*patch >= size) {
            *patch = size;
            *step = size;
            *nsteps = 1;
            return;
        }
        int s_size = size - *patch;
        int n = (s_size + *step - 1) / *step;
        int miss = (n * (*step) - s_size) / n;
        *nsteps = n;
        *step -= miss;
    }
public:
    Splitter (Config const &c): config(c), encoder(".jpg"), anno_encoder(".png"), db(config.path)
#if 0
                                , bg(nullptr)
#endif
    {
#if 0
        if (config.bg_path.size()) {
            bg = new FileWriter(config.bg_path);
        }
#endif
    }
    ~Splitter () {
        cout << "min: " << ba::min(acc) << endl;
        cout << "mean: " << ba::mean(acc) << endl;
        cout << "max: " << ba::max(acc) << endl;
#if 0
        delete bg;
#endif
    }

    void add (Record const &rec) {
        CHECK(!config.image_annotation);
        if ((rec.meta().width < 2) || (rec.meta().fields[1].size == 0)) {
#if 0
            if (bg) {
                bg->append(rec);
            }
#endif
            return;
        }
        Annotation anno(rec.field_string(1));
        if (anno.shapes.size() == 0) {
#if 0
            if (bg) {
                bg->append(rec);
            }
#endif
            return;
        }
        cv::Mat image = decode_buffer(rec.field(0), -1);
#if 0
        cv::Mat image_bg;
        if (bg) image_bg = image.clone();
#endif
        // TODO: add support for multiple bounding boxes
        //CHECK(anno.shapes.size() == 1);
        for (unsigned i = 0; i < anno.shapes.size(); ++i) {
            auto shape = anno.shapes[i];
            cv::Rect_<float> bb;
            shape->bbox(&bb);
#if 0
            if (bg) {
                shape->draw(&image_bg, cv::Scalar(0,0,0), CV_FILLED);
            }
#endif
            float scale = config.size / std::sqrt(bb.width * bb.height * image.rows * image.cols);
            acc(scale);
            cv::Mat scaled;
            cv::Rect roi;
            int dw = 0, dh = 0;
            if (config.no_scale) {
                scaled = image;
                roi = cv::Rect(bb.x * scaled.cols,
                             bb.y * scaled.rows,
                             bb.width * scaled.cols,
                             bb.height * scaled.rows);
                float factor = 1.0 * std::sqrt(1.0 * roi.width * roi.height) / config.size;
                int w = factor * config.width;
                int h = factor * config.height;
                if (w > roi.width) dw = w - roi.width;
                if (h > roi.height) dh = h - roi.height;
            }
            else {
                cv::resize(image, scaled, cv::Size(), scale, scale);
                //    |       |- width -|    |
                //    |----- cols -----------|
                roi = cv::Rect(bb.x * scaled.cols,
                             bb.y * scaled.rows,
                             bb.width * scaled.cols,
                             bb.height * scaled.rows);
                CHECK(roi.width > 0);
                CHECK(roi.height > 0);
                /*
                CHECK(roi.width <= config.width);
                CHECK(roi.height <= config.height);
                dw = config.width - roi.width;
                dh = config.height - roi.height;
                */
                dw = 0;
                if (roi.width <= config.width) {
                    dw = config.width - roi.width;
                }
                dh = 0;
                if (roi.height <= config.height) {
                    dh = config.height - roi.height;
                }
            }
            roi.x -= dw / 2;
            roi.width += dw;
            roi.y -= dh / 2;
            roi.height += dh;
            if (roi.width > scaled.cols) roi.width = scaled.cols;
            if (roi.height > scaled.rows) roi.height = scaled.rows;
            if (roi.x < 0) roi.x = 0;
            if (roi.y < 0) roi.y = 0;
            if (roi.x + roi.width > scaled.cols) roi.x = scaled.cols - roi.width;
            if (roi.y + roi.height > scaled.rows) roi.y = scaled.rows - roi.height;
            CHECK(roi.x >= 0);
            CHECK(roi.y >= 0);

            CHECK(scaled.cols > 0);
            CHECK(scaled.rows > 0);
            add_roi(scaled, roi, anno);
        }
#if 0
        if (bg) {
            encoder.encode(image_bg, &f0);
            Record rec(0, f0);
            bg->append(rec);
        }
#endif
    }

    void add_grid (Record const &rec) {
        Annotation anno;
        if ((rec.meta().width >= 2) && (rec.meta().fields[1].size > 0) && !config.image_annotation) {
            Annotation annox(rec.field_string(1));
            anno.shapes.swap(annox.shapes);
        }
        cv::Mat image = decode_buffer(rec.field(0), -1);
        cv::Mat anno_image;
        if (config.image_annotation) {
            anno_image = decode_buffer(rec.field(1), -1);
        }
        if (config.grid_scale != 1) {
            cv::Mat scaled;
            cv::resize(image, scaled, cv::Size(), config.grid_scale, config.grid_scale);
            image = scaled;
            if (config.image_annotation) {
                scaled = cv::Mat();
                cv::resize(anno_image, scaled, cv::Size(), config.grid_scale, config.grid_scale);
                anno_image = scaled;
            }
        }
        int sx = config.grid_size;  // patch size
        int dx = config.grid_step;  // step
        int nx;                     // # steps
        int sy = config.grid_size;
        int dy = config.grid_step;
        int ny;
        adjust_grid_size(image.rows, &sy, &dy, &ny);
        adjust_grid_size(image.cols, &sx, &dx, &nx);
        cv::Rect roi;
        roi.width = sx;
        roi.height = sy;
        for (int y = 0; y < ny; ++y) {
            roi.y = y * dy;
            if (roi.y + sy > image.rows) {
                roi.y = image.rows - sy;
            }
            for (int x = 0; x < nx; ++x) {
                roi.x = x * dx;
                if (roi.x + sx > image.cols) {
                    roi.x = image.cols - sx;
                }
                if (config.image_annotation) {
                    add_roi_image_anno(image, roi, anno_image);
                }
                else {
                    add_roi(image, roi, anno);
                }
            }
        }
    }
};

int main(int argc, char const* argv[]) {
    Splitter::Config config;
    fs::path input_path;

    namespace po = boost::program_options;
    po::options_description desc("Allowed options");
    desc.add_options()
        ("help,h", "produce help message.")
        ("input", po::value(&input_path), "")
        ("output", po::value(&config.path), "")
#if 0
        ("bg", po::value(&config.bg_path), "")
#endif
        ("no-scale", po::value(&config.no_scale), "")
        ("width", po::value(&config.width), "")
        ("height", po::value(&config.height), "")
        ("size", po::value(&config.size), "")
        ("grid-size", po::value(&config.grid_size), "")
        ("grid-step", po::value(&config.grid_step), "")
        ("grid-scale", po::value(&config.grid_scale), "")
        ("grid", "")
        ("image-annotation", "")
        ;

    po::positional_options_description p;
    p.add("input", 1);
    p.add("output", 1);
    p.add("bg", 1);

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

    if (vm.count("help") || input_path.empty() || config.path.empty()) {
        cout << "Usage:" << endl;
        cout << "\tpicpac-stat ... <db>" << endl;
        cout << desc;
        cout << endl;
        return 0;
    }
    if (vm.count("image-annotation")) config.image_annotation = true;

    Splitter splitter(config);
    picpac::IndexedFileReader db(input_path);
    if (vm.count("grid")) {
        db.loop(std::bind(&Splitter::add_grid, &splitter, placeholders::_1));
    }
    else {
        db.loop(std::bind(&Splitter::add, &splitter, placeholders::_1));
    }
    return 0;
}


/*
 * Copyright (c) 2014-2016, Humanoid Lab, Georgia Tech Research Corporation
 * Copyright (c) 2014-2017, Graphics Lab, Georgia Tech Research Corporation
 * Copyright (c) 2016-2017, Personal Robotics Lab, Carnegie Mellon University
 * All rights reserved.
 *
 * This file is provided under the following "BSD-style" License:
 *   Redistribution and use in source and binary forms, with or
 *   without modification, are permitted provided that the following
 *   conditions are met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above
 *     copyright notice, this list of conditions and the following
 *     disclaimer in the documentation and/or other materials provided
 *     with the distribution.
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 *   CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 *   INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *   MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 *   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 *   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 *   USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *   AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef DART_CONSTRAINT_WELDJOINTCONSTRAINT_HPP_
#define DART_CONSTRAINT_WELDJOINTCONSTRAINT_HPP_

#include <Eigen/Dense>

#include "dart/math/MathTypes.hpp"
#include "dart/constraint/JointConstraint.hpp"

namespace dart {
namespace constraint {

/// WeldJointConstraint represents weld joint constraint between a body and the
/// world or between two bodies
class WeldJointConstraint : public JointConstraint
{
public:
  /// Constructor that takes one body
  explicit WeldJointConstraint(dynamics::BodyNode* _body);

  /// Constructor that takes two bodies
  WeldJointConstraint(dynamics::BodyNode* _body1, dynamics::BodyNode* _body2);

  /// Set the relative transform that this WeldJointConstraint will enforce
  void setRelativeTransform(const Eigen::Isometry3d& _tf);

  /// Get the relative transform that this WeldJointConstraint will enforce
  const Eigen::Isometry3d& getRelativeTransform() const;

  /// Destructor
  virtual ~WeldJointConstraint();

protected:
  //----------------------------------------------------------------------------
  // Constraint virtual functions
  //----------------------------------------------------------------------------

  // Documentation inherited
  void update() override;

  // Documentation inherited
  void getInformation(ConstraintInfo* _lcp) override;

  // Documentation inherited
  void applyUnitImpulse(std::size_t _index) override;

  // Documentation inherited
  void getVelocityChange(double* _vel, bool _withCfm) override;

  // Documentation inherited
  void excite() override;

  // Documentation inherited
  void unexcite() override;

  // Documentation inherited
  void applyImpulse(double* _lambda) override;

  // Documentation inherited
  bool isActive() const override;

  // Documentation inherited
  dynamics::SkeletonPtr getRootSkeleton() const override;

  // Documentation inherited
  void uniteSkeletons() override;

private:
  ///
  Eigen::Isometry3d mRelativeTransform;

  ///
  Eigen::Vector6d mViolation;

  ///
  const Eigen::Matrix6d mJacobian1;

  ///
  Eigen::Matrix6d mJacobian2;

  ///
  double mOldX[6];

  /// Index of applied impulse
  std::size_t mAppliedImpulseIndex;

public:
  // To get byte-aligned Eigen vectors
  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
};

}  // namespace constraint
}  // namespace dart

#endif  // DART_CONSTRAINT_WELDJOINTCONSTRAINT_HPP_


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018.
// Modifications copyright (c) 2017-2018, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

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

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// 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.

#ifndef BOOST_GEOMETRY_PROJECTIONS_FOUC_S_HPP
#define BOOST_GEOMETRY_PROJECTIONS_FOUC_S_HPP

#include <boost/geometry/srs/projections/impl/aasincos.hpp>
#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>
#include <boost/geometry/srs/projections/impl/pj_param.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>

#include <boost/geometry/util/math.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace fouc_s
    {

            static const int max_iter = 10;
            static const double loop_tol = 1e-7;

            template <typename T>
            struct par_fouc_s
            {
                T n, n1;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename T, typename Parameters>
            struct base_fouc_s_spheroid
                : public base_t_fi<base_fouc_s_spheroid<T, Parameters>, T, Parameters>
            {
                par_fouc_s<T> m_proj_parm;

                inline base_fouc_s_spheroid(const Parameters& par)
                    : base_t_fi<base_fouc_s_spheroid<T, Parameters>, T, Parameters>(*this, par)
                {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    T t;

                    t = cos(lp_lat);
                    xy_x = lp_lon * t / (this->m_proj_parm.n + this->m_proj_parm.n1 * t);
                    xy_y = this->m_proj_parm.n * lp_lat + this->m_proj_parm.n1 * sin(lp_lat);
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    static T const half_pi = detail::half_pi<T>();

                    T V;
                    int i;

                    if (this->m_proj_parm.n != 0.0) {
                        lp_lat = xy_y;
                        for (i = max_iter; i ; --i) {
                            lp_lat -= V = (this->m_proj_parm.n * lp_lat + this->m_proj_parm.n1 * sin(lp_lat) - xy_y ) /
                                (this->m_proj_parm.n + this->m_proj_parm.n1 * cos(lp_lat));
                            if (fabs(V) < loop_tol)
                                break;
                        }
                        if (!i)
                            lp_lat = xy_y < 0. ? -half_pi : half_pi;
                    } else
                        lp_lat = aasin(xy_y);
                    V = cos(lp_lat);
                    lp_lon = xy_x * (this->m_proj_parm.n + this->m_proj_parm.n1 * V) / V;
                }

                static inline std::string get_name()
                {
                    return "fouc_s_spheroid";
                }

            };

            // Foucaut Sinusoidal
            template <typename Params, typename Parameters, typename T>
            inline void setup_fouc_s(Params const& params, Parameters& par, par_fouc_s<T>& proj_parm)
            {
                proj_parm.n = pj_get_param_f<T, srs::spar::n>(params, "n", srs::dpar::n);
                if ((proj_parm.n < 0.) || (proj_parm.n > 1.))
                    BOOST_THROW_EXCEPTION( projection_exception(error_n_out_of_range) );

                proj_parm.n1 = 1. - proj_parm.n;
                par.es = 0;
            }

    }} // namespace detail::fouc_s
    #endif // doxygen

    /*!
        \brief Foucaut Sinusoidal projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Pseudocylindrical
         - Spheroid
        \par Projection parameters
         - n (real)
        \par Example
        \image html ex_fouc_s.gif
    */
    template <typename T, typename Parameters>
    struct fouc_s_spheroid : public detail::fouc_s::base_fouc_s_spheroid<T, Parameters>
    {
        template <typename Params>
        inline fouc_s_spheroid(Params const& params, Parameters const& par)
            : detail::fouc_s::base_fouc_s_spheroid<T, Parameters>(par)
        {
            detail::fouc_s::setup_fouc_s(params, this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::spar::proj_fouc_s, fouc_s_spheroid, fouc_s_spheroid)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(fouc_s_entry, fouc_s_spheroid)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(fouc_s_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(fouc_s, fouc_s_entry);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_FOUC_S_HPP


/*
 * Copyright (c) 2015 Cryptonomex, Inc., and contributors.
 *
 * The MIT License
 *
 * 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.
 */
#include <graphene/chain/protocol/asset.hpp>
#include <boost/rational.hpp>
#include <boost/multiprecision/cpp_int.hpp>

namespace graphene { namespace chain {
      typedef boost::multiprecision::uint128_t uint128_t;
      typedef boost::multiprecision::int128_t  int128_t;

      bool operator == ( const price& a, const price& b )
      {
         if( std::tie( a.base.asset_id, a.quote.asset_id ) != std::tie( b.base.asset_id, b.quote.asset_id ) )
            return false;

         const auto amult = uint128_t( b.quote.amount.value ) * a.base.amount.value;
         const auto bmult = uint128_t( a.quote.amount.value ) * b.base.amount.value;

         return amult == bmult;
      }

      bool operator < ( const price& a, const price& b )
      {
         if( a.base.asset_id < b.base.asset_id ) return true;
         if( a.base.asset_id > b.base.asset_id ) return false;
         if( a.quote.asset_id < b.quote.asset_id ) return true;
         if( a.quote.asset_id > b.quote.asset_id ) return false;

         const auto amult = uint128_t( b.quote.amount.value ) * a.base.amount.value;
         const auto bmult = uint128_t( a.quote.amount.value ) * b.base.amount.value;

         return amult < bmult;
      }

      asset operator * ( const asset& a, const price& b )
      {
         if( a.asset_id == b.base.asset_id )
         {
            FC_ASSERT( b.base.amount.value > 0 );
            uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value)/b.base.amount.value;
            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
            return asset( result.convert_to<int64_t>(), b.quote.asset_id );
         }
         else if( a.asset_id == b.quote.asset_id )
         {
            FC_ASSERT( b.quote.amount.value > 0 );
            uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value)/b.quote.amount.value;
            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
            return asset( result.convert_to<int64_t>(), b.base.asset_id );
         }
         FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset * price", ("asset",a)("price",b) );
      }

      asset asset::multiply_and_round_up( const price& b )const
      {
         const asset& a = *this;
         if( a.asset_id == b.base.asset_id )
         {
            FC_ASSERT( b.base.amount.value > 0 );
            uint128_t result = (uint128_t(a.amount.value) * b.quote.amount.value + b.base.amount.value - 1)/b.base.amount.value;
            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
            return asset( result.convert_to<int64_t>(), b.quote.asset_id );
         }
         else if( a.asset_id == b.quote.asset_id )
         {
            FC_ASSERT( b.quote.amount.value > 0 );
            uint128_t result = (uint128_t(a.amount.value) * b.base.amount.value + b.quote.amount.value - 1)/b.quote.amount.value;
            FC_ASSERT( result <= GRAPHENE_MAX_SHARE_SUPPLY );
            return asset( result.convert_to<int64_t>(), b.base.asset_id );
         }
         FC_THROW_EXCEPTION( fc::assert_exception, "invalid asset::multiply_and_round_up(price)", ("asset",a)("price",b) );
      }

      price operator / ( const asset& base, const asset& quote )
      { try {
         FC_ASSERT( base.asset_id != quote.asset_id );
         return price{base,quote};
      } FC_CAPTURE_AND_RETHROW( (base)(quote) ) }

      price price::max( asset_id_type base, asset_id_type quote ) { return asset( share_type(GRAPHENE_MAX_SHARE_SUPPLY), base ) / asset( share_type(1), quote); }
      price price::min( asset_id_type base, asset_id_type quote ) { return asset( 1, base ) / asset( GRAPHENE_MAX_SHARE_SUPPLY, quote); }

      price operator *  ( const price& p, const ratio_type& r )
      { try {
         p.validate();

         FC_ASSERT( r.numerator() > 0 && r.denominator() > 0 );

         if( r.numerator() == r.denominator() ) return p;

         boost::rational<int128_t> p128( p.base.amount.value, p.quote.amount.value );
         boost::rational<int128_t> r128( r.numerator(), r.denominator() );
         auto cp = p128 * r128;
         auto ocp = cp;

         bool shrinked = false;
         bool using_max = false;
         static const int128_t max( GRAPHENE_MAX_SHARE_SUPPLY );
         while( cp.numerator() > max || cp.denominator() > max )
         {
            if( cp.numerator() == 1 )
            {
               cp = boost::rational<int128_t>( 1, max );
               using_max = true;
               break;
            }
            else if( cp.denominator() == 1 )
            {
               cp = boost::rational<int128_t>( max, 1 );
               using_max = true;
               break;
            }
            else
            {
               cp = boost::rational<int128_t>( cp.numerator() >> 1, cp.denominator() >> 1 );
               shrinked = true;
            }
         }
         if( shrinked ) // maybe not accurate enough due to rounding, do additional checks here
         {
            int128_t num = ocp.numerator();
            int128_t den = ocp.denominator();
            if( num > den )
            {
               num /= den;
               if( num > max )
                  num = max;
               den = 1;
            }
            else
            {
               den /= num;
               if( den > max )
                  den = max;
               num = 1;
            }
            boost::rational<int128_t> ncp( num, den );
            if( num == max || den == max ) // it's on the edge, we know it's accurate enough
               cp = ncp;
            else
            {
               // from the accurate ocp, now we have ncp and cp. use the one which is closer to ocp.
               // TODO improve performance
               auto diff1 = abs( ncp - ocp );
               auto diff2 = abs( cp - ocp );
               if( diff1 < diff2 ) cp = ncp;
            }
         }

         price np = asset( cp.numerator().convert_to<int64_t>(), p.base.asset_id )
                  / asset( cp.denominator().convert_to<int64_t>(), p.quote.asset_id );

         if( shrinked || using_max )
         {
            if( ( r.numerator() > r.denominator() && np < p )
                  || ( r.numerator() < r.denominator() && np > p ) )
               // even with an accurate result, if p is out of valid range, return it
               np = p;
         }

         np.validate();
         return np;
      } FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }

      price operator /  ( const price& p, const ratio_type& r )
      { try {
         return p * ratio_type( r.denominator(), r.numerator() );
      } FC_CAPTURE_AND_RETHROW( (p)(r.numerator())(r.denominator()) ) }

      /**
       *  The black swan price is defined as debt/collateral, we want to perform a margin call
       *  before debt == collateral.   Given a debt/collateral ratio of 1 USD / CORE and
       *  a maintenance collateral requirement of 2x we can define the call price to be
       *  2 USD / CORE.
       *
       *  This method divides the collateral by the maintenance collateral ratio to derive
       *  a call price for the given black swan ratio.
       *
       *  There exists some cases where the debt and collateral values are so small that
       *  dividing by the collateral ratio will result in a 0 price or really poor
       *  rounding errors.   No matter what the collateral part of the price ratio can
       *  never go to 0 and the debt can never go more than GRAPHENE_MAX_SHARE_SUPPLY
       *
       *  CR * DEBT/COLLAT or DEBT/(COLLAT/CR)
       *
       *  Note: this function is only used before core-1270 hard fork.
       */
      price price::call_price( const asset& debt, const asset& collateral, uint16_t collateral_ratio)
      { try {
         boost::rational<int128_t> swan(debt.amount.value,collateral.amount.value);
         boost::rational<int128_t> ratio( collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
         auto cp = swan * ratio;

         while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY )
            cp = boost::rational<int128_t>( (cp.numerator() >> 1)+1, (cp.denominator() >> 1)+1 );

         return  (  asset( cp.denominator().convert_to<int64_t>(), collateral.asset_id )
                  / asset( cp.numerator().convert_to<int64_t>(), debt.asset_id ) );
      } FC_CAPTURE_AND_RETHROW( (debt)(collateral)(collateral_ratio) ) }

      bool price::is_null() const
      {
         // Effectively same as "return *this == price();" but perhaps faster
         return ( base.asset_id == asset_id_type() && quote.asset_id == asset_id_type() );
      }

      void price::validate() const
      { try {
         FC_ASSERT( base.amount > share_type(0) );
         FC_ASSERT( quote.amount > share_type(0) );
         FC_ASSERT( base.asset_id != quote.asset_id );
      } FC_CAPTURE_AND_RETHROW( (base)(quote) ) }

      void price_feed::validate() const
      { try {
         if( !settlement_price.is_null() )
            settlement_price.validate();
         FC_ASSERT( maximum_short_squeeze_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );
         FC_ASSERT( maximum_short_squeeze_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
         FC_ASSERT( maintenance_collateral_ratio >= GRAPHENE_MIN_COLLATERAL_RATIO );
         FC_ASSERT( maintenance_collateral_ratio <= GRAPHENE_MAX_COLLATERAL_RATIO );
         // Note: there was code here calling `max_short_squeeze_price();` before core-1270 hard fork,
         //       in order to make sure that it doesn't overflow,
         //       but the code doesn't actually check overflow, and it won't overflow, so the code is removed.

         // Note: not checking `maintenance_collateral_ratio >= maximum_short_squeeze_ratio` since launch
      } FC_CAPTURE_AND_RETHROW( (*this) ) }

      bool price_feed::is_for( asset_id_type asset_id ) const
      {
         try
         {
            if( !settlement_price.is_null() )
               return (settlement_price.base.asset_id == asset_id);
            if( !core_exchange_rate.is_null() )
               return (core_exchange_rate.base.asset_id == asset_id);
            // (null, null) is valid for any feed
            return true;
         }
         FC_CAPTURE_AND_RETHROW( (*this) )
      }

      // This function is kept here due to potential different behavior in edge cases.
      // TODO check after core-1270 hard fork to see if we can safely remove it
      price price_feed::max_short_squeeze_price_before_hf_1270()const
      {
         // settlement price is in debt/collateral
         boost::rational<int128_t> sp( settlement_price.base.amount.value, settlement_price.quote.amount.value );
         boost::rational<int128_t> ratio( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );
         auto cp = sp * ratio;

         while( cp.numerator() > GRAPHENE_MAX_SHARE_SUPPLY || cp.denominator() > GRAPHENE_MAX_SHARE_SUPPLY )
            cp = boost::rational<int128_t>( (cp.numerator() >> 1)+(cp.numerator()&1),
                                            (cp.denominator() >> 1)+(cp.denominator()&1) );

         return (  asset( cp.numerator().convert_to<int64_t>(), settlement_price.base.asset_id )
                 / asset( cp.denominator().convert_to<int64_t>(), settlement_price.quote.asset_id ) );
      }

      price price_feed::max_short_squeeze_price()const
      {
         // settlement price is in debt/collateral
         return settlement_price * ratio_type( GRAPHENE_COLLATERAL_RATIO_DENOM, maximum_short_squeeze_ratio );
      }

      price price_feed::maintenance_collateralization()const
      {
         if( settlement_price.is_null() )
            return price();
         return ~settlement_price * ratio_type( maintenance_collateral_ratio, GRAPHENE_COLLATERAL_RATIO_DENOM );
      }

// compile-time table of powers of 10 using template metaprogramming

template< int N >
struct p10
{
   static const int64_t v = 10 * p10<N-1>::v;
};

template<>
struct p10<0>
{
   static const int64_t v = 1;
};

const int64_t scaled_precision_lut[19] =
{
   p10<  0 >::v, p10<  1 >::v, p10<  2 >::v, p10<  3 >::v,
   p10<  4 >::v, p10<  5 >::v, p10<  6 >::v, p10<  7 >::v,
   p10<  8 >::v, p10<  9 >::v, p10< 10 >::v, p10< 11 >::v,
   p10< 12 >::v, p10< 13 >::v, p10< 14 >::v, p10< 15 >::v,
   p10< 16 >::v, p10< 17 >::v, p10< 18 >::v
};

} } // graphene::chain