/*
    Copyright (c) 2014, Philipp Krähenbühl
    All rights reserved.

    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.
        * Neither the name of the Stanford University nor the
        names of its contributors may be used to endorse or promote products
        derived from this software without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY Philipp Krähenbühl ''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 Philipp Krähenbühl 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.
*/
#include "covering.h"
#include "python/map.h"
#include <Eigen/Sparse>
using namespace Eigen;
#include <iostream>
#include <unordered_set>

const float RECALL_OVERLAP = 0.5;

VectorXf bestOverlap( const std::vector<Polygons> & regions, const RMatrixXs &over_seg, const RMatrixXb &segments, VectorXf * area = NULL ) {
	const int N_sp = over_seg.maxCoeff()+1, N_gt = regions.size();

	// Compute the sparse proposal matrix
	SparseMatrix<float> props( segments.rows(), segments.cols() );
	std::vector< Triplet<float> > t;
	for( int j=0; j<props.rows(); j++ )
		for( int i=0; i<props.cols(); i++ )
			if( segments(j,i) )
				t.push_back( Triplet<float>(j,i,1) );
	props.setFromTriplets( t.begin(), t.end() );

	// Compute the superpixel areas
	VectorXf sp_area = VectorXf::Zero( N_sp );
	for( int j=0; j<over_seg.rows(); j++ )
		for( int i=0; i<over_seg.cols(); i++ )
			sp_area[ over_seg(j,i) ]++;

	if(area)
		*area = VectorXf::Zero( N_gt );
	VectorXf r( N_gt );
	// Compute the best overlap with each GT object
	for( int i=0; i<N_gt; i++ ) {
		float gt_area = 0;
		VectorXf cur_sp_area = sp_area;
		SparseVector<float> intersection( N_sp );
		rasterize( [&](int x,int y,RasterType t){
			if (0<=x && x<over_seg.cols() && 0<=y && y<over_seg.rows()) {
				const int s = over_seg(y,x);
				if( t==INSIDE ){
					gt_area += 1;
					intersection.coeffRef( s ) += 1;
				}
				// Ignore boundary pixels
				else if (t==OUTSIDE_BOUNDARY)
					cur_sp_area[s]-=1;
			}
		}, regions[i] );
		VectorXf prop_area = props * cur_sp_area;
		SparseVector<float> prop_intersection = props * intersection;

		float bo = 0;
		for( SparseVector<float>::InnerIterator it(prop_intersection); it; ++it )
			bo = std::max( bo, it.value() / (gt_area + prop_area[it.row()] - it.value()) );

		r[i] = bo;
		if(area)
			(*area)[i] = gt_area;
	}
	return r;
}

VectorXf bestOverlap( const short * gt_seg, int W, int H, int D, const RMatrixXs &over_seg, const RMatrixXb &segments, VectorXf * area = NULL ) {
	if( W != over_seg.cols() || H != over_seg.rows() )
		throw std::invalid_argument("Ground truth and over segmentation shape does not match!");
	Map<const VectorXs> sp( (const short*)over_seg.data(), W*H );
	Map<const RMatrixXs> gt( gt_seg, D, W*H );

	VectorXs N_sgt = gt.array().rowwise().maxCoeff()+1;
	const int N_gt = N_sgt.array().sum(), N_sp = sp.maxCoeff()+1;
	if( N_sp != segments.cols() )
		throw std::invalid_argument("Number of superpixels does not match segment size!");

	VectorXf gt_area = VectorXf::Zero( N_gt ), sp_area = VectorXf::Zero( N_sp );
	SparseMatrix<float> intersection( N_gt, N_sp );
	for( int i=0; i<W*H; i++ ) {
		const int s = sp[i];
		bool cnt_area = false;
		for( int d=0,o=0; d<D; o+=N_sgt[d++] ) {
			if( !cnt_area )
				cnt_area = (gt(d,i)>=-1);
			if( gt(d,i)>=0 ){
				const int t = o+gt(d,i);
				gt_area[t]++;
				if( s >= 0 ) {
					intersection.coeffRef(t,s) += 1;
				}
			}
		}
		if( cnt_area && s >= 0 )
			sp_area[s]++;
	}
	intersection.makeCompressed();

	SparseMatrix<float> props( segments.rows(), segments.cols() );
	std::vector< Triplet<float> > t;
	for( int j=0; j<props.rows(); j++ )
		for( int i=0; i<props.cols(); i++ )
			if( segments(j,i) )
				t.push_back( Triplet<float>(j,i,1) );
	props.setFromTriplets( t.begin(), t.end() );

	VectorXf seg_area = props * sp_area;
	SparseMatrix<float> seg_intersection = intersection * props.transpose();

	VectorXf r = VectorXf::Zero( N_gt );
	for (int k=0; k<seg_intersection.outerSize(); ++k)
		for (SparseMatrix<float>::InnerIterator it(seg_intersection,k); it; ++it)
			r[it.row()] = std::max( r[it.row()], it.value() / ( seg_area[ it.col() ] + gt_area[ it.row() ] - it.value() ) );
	if( area )
		*area = gt_area;
	return r;
}

ProposalEvaluation::ProposalEvaluation(const std::vector<Polygons> & regions, const std::vector<RMatrixXs> & over_seg, const std::vector<RMatrixXb> & props) {
	if( over_seg.size() != props.size() )
		throw std::invalid_argument("Different number of over segmentations and proposals!");
	if( over_seg.size() < 1 )
		throw std::invalid_argument("At least one proposal required!");

	VectorXf area, bo;
	pool_size_ = 0;
	for( int k=0; k <over_seg.size(); k++ ) {
		VectorXf o = bestOverlap( regions, over_seg[k], props[k], &area );
		if( k )
			bo = bo.array().max( o.array() );
		else
			bo = o;
		pool_size_ += props[k].rows();
	}
	bo_ = bo;
	area_ = area;
}
ProposalEvaluation::ProposalEvaluation(const std::vector<Polygons> & regions, const RMatrixXs &over_seg, const RMatrixXb &props ):ProposalEvaluation( regions, std::vector<RMatrixXs>(1,over_seg), std::vector<RMatrixXb>( 1, props ) ) {
}
ProposalEvaluation::ProposalEvaluation(const short int *gt_seg, int W, int H, int D, const std::vector<RMatrixXs> & over_seg, const std::vector<RMatrixXb> & props) {
	if( over_seg.size() != props.size() )
		throw std::invalid_argument("Different number of over segmentations and proposals!");
	if( over_seg.size() < 1 )
		throw std::invalid_argument("At least one proposal required!");

	VectorXf area, bo;
	pool_size_ = 0;
	for( int k=0; k <over_seg.size(); k++ ) {
		VectorXf o = bestOverlap( gt_seg, W, H, D, over_seg[k], props[k], &area );
		if( k )
			bo = bo.array().max( o.array() );
		else
			bo = o;
		pool_size_ += props[k].rows();
	}
	bo_ = bo;
	area_ = area;
}
ProposalEvaluation::ProposalEvaluation( const short int *gt_seg, int W, int H, int D, const RMatrixXs &over_seg, const RMatrixXb &props ):ProposalEvaluation( gt_seg, W, H, D, std::vector<RMatrixXs>(1,over_seg), std::vector<RMatrixXb>( 1, props )/*, recall_overlap*/ ) {
}
float boxOverlap( const VectorXi & b1, const VectorXi & b2 ) {
	float intersec = (std::max(0,std::min(b1[2],b2[2]) - std::max(b1[0],b2[0]))*std::max(0,std::min(b1[3],b2[3]) - std::max(b1[1],b2[1])));
	float _union   = (std::max(0,std::max(b1[2],b2[2]) - std::min(b1[0],b2[0]))*std::max(0,std::max(b1[3],b2[3]) - std::min(b1[1],b2[1])));
	if( _union > 0 )
		return intersec / _union;
	return 0;
}
ProposalBoxEvaluation::ProposalBoxEvaluation(const RMatrixXi &bbox, const std::vector<RMatrixXi> & prop_boxes) {
	if( prop_boxes.size() < 1 )
		throw std::invalid_argument("At least one proposal required!");

	if (bbox.rows() == 0) {
		bo_ = VectorXf::Zero(0);
		pool_size_ = NAN;
		return;
	}

	VectorXf bo = VectorXf::Zero( bbox.rows() );
	pool_size_ = 0;
	for( int k=0; k<prop_boxes.size(); k++ ) {
		const int nProp = prop_boxes[k].rows();
		// Compute the pool size
		std::unordered_set<uint64_t> ht;
		for( int j=0; j<nProp; j++ ) {
			uint64_t h = 0;
			for( int l=0; l<4; l++ )
				h = (h<<16) + ((unsigned short)prop_boxes[k](j,l));
			if( !ht.count( h ) ) {
				ht.insert( h );
				pool_size_++;
			}
		}

		for( int i=0; i<bo.size(); i++ )
			for( int j=0; j<nProp; j++ )
				bo[i] = std::max( bo[i], boxOverlap( prop_boxes[k].row(j), bbox.row(i) ) );
	}
	bo_ = bo;
}
ProposalBoxEvaluation::ProposalBoxEvaluation( const RMatrixXi &bbox, const RMatrixXi &prop_boxes):ProposalBoxEvaluation( bbox, std::vector<RMatrixXi>(1,prop_boxes) ) {
}


//| This file is a part of the sferes2 framework.
//| Copyright 2009, ISIR / Universite Pierre et Marie Curie (UPMC)
//| Main contributor(s): Jean-Baptiste Mouret, mouret@isir.fr
//|
//| This software is a computer program whose purpose is to facilitate
//| experiments in evolutionary computation and evolutionary robotics.
//|
//| This software is governed by the CeCILL license under French law
//| and abiding by the rules of distribution of free software.  You
//| can use, modify and/ or redistribute the software under the terms
//| of the CeCILL license as circulated by CEA, CNRS and INRIA at the
//| following URL "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and rights to
//| copy, modify and redistribute granted by the license, users are
//| provided only with a limited warranty and the software's author,
//| the holder of the economic rights, and the successive licensors
//| have only limited liability.
//|
//| In this respect, the user's attention is drawn to the risks
//| associated with loading, using, modifying and/or developing or
//| reproducing the software by the user in light of its specific
//| status of free software, that may mean that it is complicated to
//| manipulate, and that also therefore means that it is reserved for
//| developers and experienced professionals having in-depth computer
//| knowledge. Users are therefore encouraged to load and test the
//| software's suitability as regards their requirements in conditions
//| enabling the security of their systems and/or data to be ensured
//| and, more generally, to use and operate it in the same conditions
//| as regards security.
//|
//| The fact that you are presently reading this means that you have
//| had knowledge of the CeCILL license and that you accept its terms.

#ifndef EPSMOEA_HPP_
#define EPSMOEA_HPP_

#include <algorithm>
#include <limits>

#include <boost/foreach.hpp>

#include <sferes/stc.hpp>
#include <sferes/parallel.hpp>
#include <sferes/ea/ea.hpp>
#include <sferes/fit/fitness.hpp>
#include <sferes/ea/common.hpp>
namespace sferes {
  namespace ea {
    // param : eps (array)
    // param : min_fit (array)
    // param : grain
    SFERES_EA(EpsMOEA, Ea) {
    public:
      void random_pop() {
        parallel::init();
        this->_pop.resize(Params::pop::size);
        parallel::p_for(parallel::range_t(0, this->_pop.size()),
                        random<Phen>(this->_pop));
        this->_eval.eval(this->_pop, 0, this->_pop.size());

        // create archive
        add_to_archive(this->_pop.front());
        for (typename pop_t :: const_iterator it = this->_pop.begin();
             it != this->_pop.end(); ++it)
          archive_acceptance(*it);
        sync_archive();
      }

      void epoch() {
        std::vector<indiv_t> indivs;

        for (size_t i = 0; i < Params::pop::grain; ++i) {
          indiv_t i1 = pop_selection();
          indiv_t i2 = archive_selection();
          indiv_t c1, c2;
          i1->cross(i2, c1, c2);
          indivs.push_back(c1);
          indivs.push_back(c2);
        }
        parallel::p_for(parallel::range_t(0, indivs.size()),
                        mutate<Phen>(indivs));

        this->_eval.eval(indivs, 0, indivs.size());

        BOOST_FOREACH(indiv_t i, indivs)
        if (pop_acceptance(i))
          archive_acceptance(i);
        sync_archive();
      }
      const std::vector<boost::shared_ptr<Phen> >& pareto_front() const {
        return _pareto_front;
      }
    protected:
      typedef boost::shared_ptr<Phen> indiv_t;
      typedef std::vector<indiv_t> pop_t;
      typedef std::pair<indiv_t, std::vector<float> > elite_t;
      typedef std::list<elite_t> archive_t;
      typedef std::vector<std::vector<float> > id_t;

      // elite
      archive_t pop_e;

      // identification vectors
      id_t id_b;

      pop_t _pareto_front;

      // keep pareto_front & elite synchronized (for stat reporting)
      void sync_archive() {
        _pareto_front.clear();
        BOOST_FOREACH(elite_t& i, pop_e)
        _pareto_front.push_back(i.first);
      }

      /// return a random + tournament individual in P
      indiv_t pop_selection() {

        indiv_t i1 = this->_pop[misc::rand(this->_pop.size())];
        indiv_t i2 = this->_pop[misc::rand(this->_pop.size())];

        int flag = check_dominance(i1, i2);
        switch (flag) {
        case 1: // a dom b
          return i1;
        case -1:
          return i2;
        case 0:
          if (misc::flip_coin())
            return i1;
          else
            return i2;
        }
        assert(0);
        return indiv_t();
      }

      ///  return a random individual in E
      indiv_t archive_selection() {
        return misc::rand_in_list(pop_e)->first;

      }

      /// try to insert the offspring in population
      /// return true if accepted
      bool pop_acceptance(indiv_t ind) {
        dbg::out(dbg::info, "epsmoea")<<"pop_acceptance :"<<indiv_str(ind)<<std::endl;
        int flag = 0;
        std::vector<int> array;
        int i = 0;

        for (typename pop_t :: const_iterator it = this->_pop.begin();
             it != this->_pop.end(); ++it) {
          flag = check_dominance(ind, *it);
          switch (flag) {
          case 1:
            array.push_back(i);
            break;
          case -1:
            dbg::out(dbg::info, "epsmoea")<<"pop_acceptance -> rejected"<<std::endl;
            return false;
          case 0:
            break;
          default:
            assert(0);
          }
          ++i;
        }

        int k;
        if (array.size())
          k = array[misc::rand(array.size())];
        else
          k = misc::rand(this->_pop.size());
        dbg::out(dbg::info, "epsmoea")<<"pop_acceptance, removing :"
                                      <<indiv_str(this->_pop[k])
                                      <<"  array.size()="<<array.size()<<std::endl;
        this->_pop[k] = ind;
        dbg::out(dbg::info, "epsmoea")<<"pop_acceptance -> accepted (k="<<k<<")"<<std::endl;
        return true;
      }

      ///  try to insert the offspring in pop_e
      /// return true if accepted
      bool archive_acceptance(indiv_t indiv) {
        dbg::out(dbg::info, "epsmoea")<<"archive_acceptance :"<<indiv_str(indiv)<<std::endl;
        elite_t ind = make_identification_vector(indiv);
        typename archive_t :: iterator it = pop_e.begin();
        bool same_box = false;
        do {
          assert(it != pop_e.end());
          int flag = check_box_dominance(ind, *it);
          switch (flag) {
          case 1:
            // if ind eps-dominates *it, we delete *it
            it = pop_e.erase(it);
            break;
          case 2:
            // *it eps-dominates ind, we stop the procedure
            dbg::out(dbg::info, "epsmoea")<<"archive_acceptance -> rejected"<<std::endl;
            return false;
          case 3:
            // both are non-dominated and are in different boxes, we
            // continue
            ++it;
            break;
          case 4:
            // both are non-dominated and are in same hyper-box
            same_box = true;
            break;
          default:
            assert(0);
          }
        } while(!same_box && it != pop_e.end());

        //=> the offspring (indiv) is eps-non-dominated
        // if it isn't in any filled box, we add it to the archive
        if (!same_box) {
          add_to_archive(indiv);
          return true;
        }
        assert(it != pop_e.end());
        // else, they are in the same box and we do a dominance check
        int flag = check_dominance(ind.first, it->first);
        float d1, d2;
        switch (flag) {
        case 1:
          pop_e.erase(it);
          add_to_archive(indiv);
          return true;
        case -1:
          return false;
        case 0:
          //both are non-dominated, we select the closest to the B
          //vector
          //  /!\ -> loss of a archived individual !
          d1 = dist_to_id(ind);
          d2 = dist_to_id(*it);
          if (d1 <= d2) {
            pop_e.erase(it);
            add_to_archive(indiv);
            return true;
          } else
            return false;
        default:
          assert(0);
        }
        assert(0);
        return false;
      }

      /// check dominance using the identification vector
      /// returns the following:
      ///	* 1 if a dominates b
      ///	* 2 if b dominates a
      ///	* 3 if a and b are non-dominated and a!=b (identification arrays unequal)
      ///	* 4 if a and b are non-dominated and a=b
      int check_box_dominance(const elite_t &a, const elite_t &b) const {
        int flag1 = 0, flag2 = 0;

        for (unsigned i = 0; i < Params::pop::eps_size(); ++i)
          if (a.second[i] > b.second[i])
            flag1 = 1;
          else if (b.second[i] > a.second[i])
            flag2 = 1;

        // a dominates b
        if (flag1 && !flag2)
          return 1;
        // b dominates a
        if (!flag1 && flag2)
          return 2;
        // a and b are non-dominated and a!=b (identification arrays unequal)
        if (flag1 && flag2)
          return 3;
        // a and b are non-dominated and a=b
        assert(!flag1 && !flag2);
        return 4;
      }

      /// standard dominance
      /// * 1 if a dominates b
      /// * -1 if b dominates a
      /// * 0 if both a and b are non-dominated
      int check_dominance(const indiv_t a, const indiv_t b) const {
        assert(a->fit().objs().size() == b->fit().objs().size());
        assert(a->fit().objs().size());
        size_t nb_objs = a->fit().objs().size();
        int flag1 = 0, flag2 = 0;
        for (size_t i = 0; i < nb_objs; ++i)
          if (a->fit().obj(i) > b->fit().obj(i))
            flag1 = 1;
          else if (a->fit().obj(i) < b->fit().obj(i))
            flag2 = 1;

        if (flag1 && !flag2)
          return 1;

        if (!flag1 && flag2)
          return -1;

        return 0;
      }

      /// compute an identification vector and return it
      elite_t make_identification_vector(indiv_t indiv) const {
        elite_t e;
        e.first = indiv;
        e.second.resize(Params::pop::eps_size());
        dbg::out(dbg::info, "epsmoea")<<"eps_size="<<Params::pop::eps_size()
                                      <<" fitsize:"<<indiv->fit().objs().size()
                                      <<std::endl;
        assert(e.second.size() == indiv->fit().objs().size());
        for (size_t i = 0; i < Params::pop::eps_size(); ++i)
          e.second[i] =
            ceil((indiv->fit().obj(i) - Params::pop::min_fit(i)) / Params::pop::eps(i));
        return e;
      }

      /// compute a squared euclidean distance between a's fitness and
      /// a's identification vector
      float dist_to_id(const elite_t& a) const {
        float res = 0;
        assert(a.first->fit().objs().size() == a.second.size());
        for (unsigned i = 0; i < a.first->fit().objs().size(); ++i)
          res += powf(a.first->fit().obj(i) - a.second[i], 2.0);
        return res;
      }

      /// make the identification vector and add to the archive / elite
      /// list
      void add_to_archive(indiv_t indiv) {
        dbg::out(dbg::info, "epsmoea")<<"add_to_archive :"<<indiv_str(indiv)<<std::endl;
        pop_e.push_back(make_identification_vector(indiv));
      }

      /// debug function
      std::string indiv_str(indiv_t indiv) {
        std::string s = "";
        for (size_t i = 0; i < indiv->fit().objs().size(); ++i)
          s += boost::lexical_cast<std::string>(indiv->fit().obj(i)) + " ";
        return s;
      }

    };
  }
}
#endif


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_CRTP_BASE_MATRIX_INCLUDE
#define MTL_CRTP_BASE_MATRIX_INCLUDE

#include <iostream>
#include <algorithm>
#include <boost/mpl/bool.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/numeric/mtl/operation/print.hpp>

#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/operation/matrix_bracket.hpp>
#include <boost/numeric/mtl/operation/copy.hpp>
#include <boost/numeric/mtl/operation/mult.hpp>
#include <boost/numeric/mtl/operation/right_scale_inplace.hpp>
#include <boost/numeric/mtl/operation/divide_by_inplace.hpp>
#include <boost/numeric/mtl/matrix/all_mat_expr.hpp>
#include <boost/numeric/mtl/matrix/diagonal_setup.hpp>
#include <boost/numeric/mtl/matrix/inserter.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/ashape.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/exception.hpp>
#include <boost/numeric/mtl/utility/eval_dense.hpp>
#include <boost/numeric/mtl/utility/irange.hpp>
#include <boost/numeric/mtl/utility/iset.hpp>
#include <boost/numeric/mtl/operation/mult_assign_mode.hpp>
#include <boost/numeric/mtl/operation/compute_factors.hpp>
#include <boost/numeric/mtl/operation/column_in_matrix.hpp>
#include <boost/numeric/mtl/operation/row_in_matrix.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>

#ifdef MTL_WITH_INITLIST
# include <initializer_list>
#endif

namespace mtl { namespace matrix {

template <typename Source, typename Matrix>
struct crtp_assign
{
    Matrix& operator()(const Source& source, Matrix& matrix)
    {
	return assign(source, matrix, typename ashape::ashape<Source>::type());
    }
private:
    /// Assign scalar to a matrix by setting the matrix to a multiple of unity matrix
    /** Uses internally \sa diagonal_setup, for details see there. **/
    Matrix& assign(const Source& source, Matrix& matrix, ashape::scal)
    {
	vampir_trace<3055> tracer;
	MTL_DEBUG_THROW_IF(num_rows(matrix) * num_cols(matrix) == 0,
			   range_error("Trying to initialize a 0 by 0 matrix with a value"));
	diagonal_setup(matrix, source);
	return matrix;
    }

    /// Assign matrix expressions by copying except for some special expressions
    Matrix& assign(const Source& source, Matrix& matrix, typename ashape::ashape<Matrix>::type)
    {
	vampir_trace<3056> tracer;
	// Self-assignment between different types shouldn't happen.
	matrix.checked_change_resource(source);
	matrix_copy(source, matrix);
	return matrix;
    }
};

/// Assign sum by assigning first argument and adding second
/*  Note that this is more special then assigning arbitrary expressions including matrices itself
    because mat_mat_plus_expr <E1, E2> is a derived class from mat_expr < MatrixSrc >. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_assign<mat_mat_plus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_plus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3056> tracer;
	matrix.checked_change_resource(src.first);
	matrix= src.first;
	return matrix+= src.second;
    }
};

/// Assign difference by assigning first argument and subtracting second
/*  Note that this is more special then assigning arbitrary expressions including matrices itself
    because mat_mat_minus_expr <E1, E2> is a derived class from mat_expr < MatrixSrc >. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_assign<mat_mat_minus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_minus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3057> tracer;
	matrix.checked_change_resource(src.first);
	matrix= src.first;
	return matrix-= src.second;
    }
};

/// Assign product by calling mult
template <typename E1, typename E2, typename Matrix>
struct crtp_assign<mat_mat_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<4012> tracer;
	operation::compute_factors<Matrix, mat_mat_times_expr<E1, E2> > factors(src);
	matrix.checked_change_resource(factors.first, factors.second);
	mult(factors.first, factors.second, matrix);
	return matrix;
    }
};

/// Assign element-wise product
template <typename E1, typename E2, typename Matrix>
struct crtp_assign<mat_mat_ele_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_ele_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3028> tracer;
	operation::compute_factors<Matrix, mat_mat_ele_times_expr<E1, E2> > factors(src);
	matrix.checked_change_resource(factors.first);
	matrix= factors.first;
	return matrix.ele_rscale(factors.second);
    }
};

/// Assign c-style 2D-array, because it's easier to initialize.
template <typename Value, unsigned Rows, unsigned Cols, typename Matrix>
struct crtp_assign<Value[Rows][Cols], Matrix>
{
    Matrix& operator()(const Value src[Rows][Cols], Matrix& matrix)
    {
	vampir_trace<3059> tracer;
	typedef typename Collection<Matrix>::size_type size_type;

	matrix.checked_change_dim(Rows, Cols);
	inserter<Matrix>  ins(matrix, matrix.dim2());

	for (size_type r= 0; r < Rows; ++r)
	    for (size_type c= 0; c < Cols; ++c)
		ins(r, c) << src[r][c];
	return matrix;
    }
};

#if defined(MTL_WITH_INITLIST) && defined(MTL_WITH_AUTO) && defined(MTL_WITH_RANGEDFOR)
    /// Constructor for initializer list \p values
    template <typename Value2, typename Matrix>
    struct crtp_assign<std::initializer_list<std::initializer_list<Value2> >, Matrix>
    {
	Matrix& operator()(std::initializer_list<std::initializer_list<Value2> > values, Matrix& matrix)
	{
	    typedef typename Collection<Matrix>::size_type size_type;
	    size_type nr= values.size(), nc= nr > 0? values.begin()->size() : 0;
	    matrix.checked_change_dim(nr, nc);
	    inserter<Matrix>  ins(matrix, matrix.dim2());

	    size_t r= 0;
	    for (auto l : values) {
		size_t c= 0;
		MTL_THROW_IF(l.size() != nc, logic_error("All sub-lists must have same size!"));
		for (auto v : l)
		    ins(r, c++) << v;
		r++;
	    }
	    return matrix;
	}
    };
#endif

template <typename Vector, typename Matrix>
struct crtp_assign<multi_vector<Vector>, Matrix>
{
    Matrix& operator()(const multi_vector<Vector>& src, Matrix& matrix)
    {
	vampir_trace<3060> tracer;
	typedef typename Collection<Matrix>::size_type size_type;

	matrix.checked_change_resource(src);
	// del checked_change_dim(num_rows(src), num_cols(src));
	inserter<Matrix>  ins(matrix);

	for (size_type r= 0; r < num_rows(src); ++r)
	    for (size_type c= 0; c < num_cols(src); ++c)
		ins(r, c) << src[r][c];
	return matrix;
    }
};

/// Assign content of a file to the matrix
template <typename IFStream, typename OFStream, typename Matrix>
struct crtp_assign<io::matrix_file<IFStream, OFStream>, Matrix>
{
    Matrix& operator()(const io::matrix_file<IFStream, OFStream>& file, Matrix& matrix)
    {
	vampir_trace<3029> tracer;
	IFStream stream(file.file_name().c_str());
	stream >> matrix;
	return matrix;
    }
};

/// Assign-add matrix expressions by incrementally copying except for some special expressions
template <typename Source, typename Matrix>
struct crtp_plus_assign
{
    Matrix& operator()(const Source& source, Matrix& matrix)
    {
	vampir_trace<3030> tracer;
	return assign(source, matrix, typename ashape::ashape<Source>::type());
    }
  private:
    Matrix& assign(const Source& source, Matrix& matrix, typename ashape::ashape<Matrix>::type)
    {
	matrix_copy_plus(source, matrix);
	return matrix;
    }
};

/// Assign-add sum by adding both arguments
/** Note that this is more special then assigning arbitrary expressions including matrices itself
	because mat_mat_plus_expr <E1, E2> is a derived class from
	mat_expr < MatrixSrc >. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_plus_assign<mat_mat_plus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_plus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3030> tracer;
	matrix+= src.first;
	return matrix+= src.second;
    }
};

template <typename E1, typename E2, typename Matrix>
struct crtp_plus_assign<mat_mat_minus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_minus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3030> tracer;
	matrix+= src.first;
	return matrix-= src.second;
    }
};

template <typename E1, typename E2, typename Matrix>
struct crtp_plus_assign<mat_mat_ele_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_ele_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3030> tracer;
	Matrix Prod(ele_prod(src.first, src.second));
	return matrix+= Prod;
    }
};

template <typename E1, typename E2, typename Matrix>
struct crtp_plus_assign<mat_mat_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3030> tracer;
	operation::compute_factors<Matrix, mat_mat_times_expr<E1, E2> > factors(src);
	gen_mult(factors.first, factors.second, matrix, assign::plus_sum(),
		 tag::flat<tag::matrix>(), tag::flat<tag::matrix>(), tag::flat<tag::matrix>());
	return matrix;
    }
};

/// Assign-subtract matrix expressions by decrementally copying except for some special expressions
template <typename Source, typename Matrix>
struct crtp_minus_assign
{
    Matrix& operator()(const Source& source, Matrix& matrix)
    {
	vampir_trace<3031> tracer;
	return assign(source, matrix, typename ashape::ashape<Source>::type());
    }
private:
    Matrix& assign(const Source& source, Matrix& matrix, typename ashape::ashape<Matrix>::type)
    {
	matrix_copy_minus(source, matrix);
	return matrix;
    }
};

/// Assign-subtract sum by adding both arguments
/** Note that this is more special then assigning arbitrary expressions including matrices itself
	because mat_mat_plus_expr <E1, E2> is a derived class from
	mat_expr < MatrixSrc >. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_minus_assign<mat_mat_plus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_plus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3031> tracer;
	matrix-= src.first;
	return matrix-= src.second;
    }
};

/// Assign-subtracting difference by subtracting first argument and adding the second one
/** Note that this is more special then assigning arbitrary expressions including matrices itself
	because mat_mat_minus_expr <E1, E2> is a derived class from
	mat_expr < MatrixSrc >. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_minus_assign<mat_mat_minus_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_minus_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3031> tracer;
	matrix-= src.first;
	return matrix+= src.second;
    }
};

template <typename E1, typename E2, typename Matrix>
struct crtp_minus_assign<mat_mat_ele_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_ele_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3031> tracer;
	Matrix Prod(ele_prod(src.first, src.second));
	return matrix-= Prod;
    }
};

/// Assign-subtract product by calling gen_mult
/** Note that this does not work for arbitrary expressions. **/
template <typename E1, typename E2, typename Matrix>
struct crtp_minus_assign<mat_mat_times_expr<E1, E2>, Matrix>
{
    Matrix& operator()(const mat_mat_times_expr<E1, E2>& src, Matrix& matrix)
    {
	vampir_trace<3031> tracer;
	operation::compute_factors<Matrix, mat_mat_times_expr<E1, E2> > factors(src);
	gen_mult(factors.first, factors.second, matrix, assign::minus_sum(), tag::flat<tag::matrix>(), tag::flat<tag::matrix>(), tag::flat<tag::matrix>());
	return matrix;
    }
};

/// Base class to provide matrix assignment operators generically
template <typename Matrix, typename ValueType, typename SizeType>
struct crtp_matrix_assign
{
private:

    // For (compatible) dense matrices do a loop over all entries
    template <typename Source>
    Matrix& density_assign(const Source& src, boost::mpl::true_)
    {
	vampir_trace<3032> tracer;
	// typedef typename Collection<Source>::size_type size_type;
	typedef unsigned size_type;

	// std::cout << "Dense assignment\n";
	checked_change_resource(src);

	Matrix& matrix= static_cast<Matrix&>(*this);
	for (size_type r= 0; r < num_rows(matrix); ++r)
	    for (size_type c= 0; c < num_cols(matrix); ++c)
		matrix[r][c]= src[r][c];
	return matrix;
    }

    // If sparse matrices are involved evaluate step-wise (or assignment from scalar)
    template <typename Source>
    Matrix& density_assign(const Source& src, boost::mpl::false_)
    {
	// std::cout << "Sparse assignment\n";
	return crtp_assign<Source, Matrix>()(src, static_cast<Matrix&>(*this));
    }

    // For (compatible) dense matrices do a loop over all entries
    template <typename Source>
    Matrix& density_plus_assign(const Source& src, boost::mpl::true_)
    {
	vampir_trace<3033> tracer;
	// typedef typename Collection<Source>::size_type size_type;
	typedef unsigned size_type;

	// std::cout << "Dense assignment\n";
	checked_change_resource(src);
	// del checked_change_dim(num_rows(src), num_cols(src));

	Matrix& matrix= static_cast<Matrix&>(*this);
	for (size_type r= 0; r < num_rows(matrix); ++r)
	    for (size_type c= 0; c < num_cols(matrix); ++c)
		matrix[r][c]+= src[r][c];
	return matrix;
    }

    // If sparse matrices are involved evaluate step-wise (or assignment from scalar)
    template <typename Source>
    Matrix& density_plus_assign(const Source& src, boost::mpl::false_)
    {
	// std::cout << "Sparse assignment\n";
	return crtp_plus_assign<Source, Matrix>()(src, static_cast<Matrix&>(*this));
    }

    // For (compatible) dense matrices do a loop over all entries
    template <typename Source>
    Matrix& density_minus_assign(const Source& src, boost::mpl::true_)
    {
	vampir_trace<3034> tracer;
	// typedef typename Collection<Source>::size_type size_type;
	typedef unsigned size_type;

	// std::cout << "Dense assignment\n";
	checked_change_resource(src);

	Matrix& matrix= static_cast<Matrix&>(*this);
	for (size_type r= 0; r < num_rows(matrix); ++r)
	    for (size_type c= 0; c < num_cols(matrix); ++c)
		matrix[r][c]-= src[r][c];
	return matrix;
    }

    // If sparse matrices are involved evaluate step-wise (or assignment from scalar)
    template <typename Source>
    Matrix& density_minus_assign(const Source& src, boost::mpl::false_)
    {
	// std::cout << "Sparse assignment\n";
	return crtp_minus_assign<Source, Matrix>()(src, static_cast<Matrix&>(*this));
    }

    // For (compatible) dense matrices do a loop over all entries
    template <typename Source>
    Matrix& density_ele_rscale(const Source& src, boost::mpl::true_)
    {
	vampir_trace<3035> tracer;
	// typedef typename Collection<Source>::size_type size_type;
	typedef unsigned size_type;

	// std::cout << "Dense assignment\n";
	checked_change_resource(src);
	// del checked_change_dim(num_rows(src), num_cols(src));

	Matrix& matrix= static_cast<Matrix&>(*this);
	for (size_type r= 0; r < num_rows(matrix); ++r)
	    for (size_type c= 0; c < num_cols(matrix); ++c)
		matrix[r][c]*= src[r][c];
	return matrix;
    }

    // If sparse matrices are involved evaluate step-wise (or assignment from scalar)
    template <typename Factor>
    Matrix& density_ele_rscale(const Factor& alpha, boost::mpl::false_)
    {
	// std::cout << "Sparse assignment\n";
	matrix_copy_ele_times(alpha, static_cast<Matrix&>(*this));
	return static_cast<Matrix&>(*this);
    }

  public:

    /// Check wether source and target have compatible resources, generalization of check_dim
    /** For expressions like A= B + C, A can be set to the size of B and C if still is 0 by 0. **/
    template <typename Src>
    void check_resource(const Src& src) const
    {	check_resource(src, typename mtl::traits::category<Matrix>::type());    }

    // Default case just check_dim
    template <typename Src>
    void check_resource(const Src& src, tag::universe) const
    {	check_dim(num_rows(src), num_cols(src));    }

    /// Check wether source and target have compatible resources and wether target has already resources
    /** For expressions like A+= B + C, A must be already larger then 0 by 0 and compatible to B and C. **/
    //  Generalization with 2 arguments might be needed (check rows from first and columns from second)
    template <typename Src>
    void check_ready_resource(const Src& src) const
    {
	MTL_DEBUG_THROW_IF(num_rows(src) * num_cols(src) == 0, need_nonempty());
	check_resource(src);
    }

    /// Check wether source and target have compatible resources and adapt empty target
    /** For expressions like A= B + C, A can be set to the size of B and C if still is 0 by 0. **/
    template <typename Src>
    void checked_change_resource(const Src& src)
    {	checked_change_resource(src, src);   }

    /// Check whether source and target have compatible resources and adapt empty target
    /** For expressions like A= B + C, A can be set to the size of B and C if still is 0 by 0. **/
    template <typename Src1, typename Src2>
    void checked_change_resource(const Src1& src1, const Src2& src2)
    {   checked_change_resource_aux(src1, src2, typename mtl::traits::category<Matrix>::type());    }

    template <typename Src1, typename Src2>
    void checked_change_resource_aux(const Src1& src1, const Src2& src2, tag::universe)
    {   checked_change_dim(num_rows(src1), num_cols(src2));  }

    /// Check whether matrix sizes are compatible or if matrix is 0 by 0 change it to r by c.
    /** Deprecated, superseded by checked_change_resource. **/
    void checked_change_dim(SizeType r, SizeType c)
    {
	Matrix& matrix= static_cast<Matrix&>(*this);
	matrix.check_dim(r, c);
	matrix.change_dim(r, c);
    }

    /// Templated assignment implemented by functor to allow for partial specialization
    // Despite there is only an untemplated assignment and despite the disable_if MSVC whines about ambiguity :-!
    // Scalar assignment is also taking out because it has another return type
    template <typename Source>
    typename boost::disable_if_c<boost::is_same<Matrix, Source>::value
                                   || boost::is_same<typename ashape::ashape<Source>::type, ashape::scal>::value,
				 Matrix&>::type
    operator=(const Source& src)
    {
	return density_assign(src, boost::mpl::bool_< boost::is_same<typename ashape::ashape<Matrix>::type,
			                                             typename ashape::ashape<Source>::type>::value
			                              && mtl::traits::eval_dense< mat_mat_asgn_expr<Matrix, Source> >::value >());
    }

    // Helper type for assigning scalars to handle both A= a; and A= a, b, c;
    template <typename Source>
    struct scalar_assign
    {
	scalar_assign(Source src, Matrix& matrix)
	  : src(src), with_comma(false), r(0), c(0), matrix(matrix), ins(matrix, 1) {}

	~scalar_assign()
	{
	    vampir_trace<3047> tracer;
	    if (with_comma) {
		MTL_DEBUG_THROW_IF(r != num_rows(matrix), incompatible_size("Not all matrix entries initialized!"));
	    } else {
		using std::min;
		if (src == math::zero(src)) // it is already set to zero
		    return;
		// Otherwise set diagonal (if square)
		for (SizeType i= 0, n= min(num_rows(matrix), num_cols(matrix)); i < n; i++)
		    ins[i][i] << src;
	    }
	}

	template <typename ValueSource>
	scalar_assign& operator, (ValueSource val)
	{
	    if (!with_comma) {
		with_comma= true;
		assert(r == 0 && c == 0);
		ins[r][c++] << src; // We haven't set v[0] yet
		if (c == num_cols(matrix))
		    c= 0, r++;
	    }
	    ins[r][c++] << val;
	    if (c == num_cols(matrix))
		c= 0, r++;
	    return *this;
	}

	Source         src;
	bool           with_comma;
	SizeType       r, c;
	Matrix&        matrix;
	inserter<Matrix> ins;
    };

    template <typename Source>
    typename boost::enable_if<boost::is_same<typename ashape::ashape<Source>::type, ashape::scal>,
			      scalar_assign<Source> >::type
    operator=(Source src)
    {
	Matrix& matrix= static_cast<Matrix&>(*this);
	MTL_DEBUG_THROW_IF(num_rows(matrix) * num_cols(matrix) == 0,
			   range_error("Trying to initialize a 0 by 0 matrix with a value"));
	set_to_zero(matrix);
	return scalar_assign<Source>(src, static_cast<Matrix&>(*this));
    }

    template <typename Source>
    Matrix& operator+=(const Source& src)
    {
	return density_plus_assign(src, mtl::traits::eval_dense< mat_mat_asgn_expr<Matrix, Source> >());
    }

    template <typename Source>
    Matrix& operator-=(const Source& src)
    {
	return density_minus_assign(src, mtl::traits::eval_dense< mat_mat_asgn_expr<Matrix, Source> >());
    }

    /// Scale matrix (in place) with scalar value or other matrix
    template <typename Factor>
    Matrix& operator*=(const Factor& alpha)
    {
	right_scale_inplace(static_cast<Matrix&>(*this), alpha);
	return static_cast<Matrix&>(*this);
    }

    // Element-wise scaling from right (i.e. like *= as elementwise)
    template <typename Factor>
    Matrix& ele_rscale(const Factor& alpha)
    {
	return density_ele_rscale(alpha, mtl::traits::eval_dense< mat_mat_asgn_expr<Matrix, Factor> >());
    }

    /// Divide matrix (in place) by scalar value
    // added by Hui Li
    template <typename Factor>
    Matrix& operator/=(const Factor& alpha)
    {
	divide_by_inplace(static_cast<Matrix&>(*this), alpha);
	return static_cast<Matrix&>(*this);
    }
};

template <typename Matrix, typename ValueType, typename SizeType>
struct const_crtp_matrix_bracket
{
    template <typename T>
    typename boost::disable_if_c<boost::is_same<T, mtl::irange>::value || boost::is_same<T, mtl::iset>::value,
				 operations::bracket_proxy<Matrix, const Matrix&, ValueType> >::type
    operator[] (const T& row) const
    {
	return operations::bracket_proxy<Matrix, const Matrix&, ValueType>(static_cast<const Matrix&>(*this), row);
    }

    // Compiler error (later) if no sub_matrix function (or row vector resp.) available
    template <typename T>
    typename boost::enable_if<boost::is_same<T, mtl::irange>, operations::range_bracket_proxy<Matrix, const Matrix&, const Matrix> >::type
    operator[] (const T& row_range) const
    {
	return operations::range_bracket_proxy<Matrix, const Matrix&, const Matrix>(static_cast<const Matrix&>(*this), row_range);
    }

    operations::set_bracket_proxy<Matrix, const Matrix&, const Matrix>
    operator[] (const iset& row_set) const
    {
	return operations::set_bracket_proxy<Matrix, const Matrix&, const Matrix>(static_cast<const Matrix&>(*this), row_set);
    }
};

template <typename Matrix, typename ValueType, typename SizeType>
struct crtp_matrix_bracket
{
    operations::bracket_proxy<Matrix, const Matrix&, const ValueType&>
    operator[] (SizeType row) const
    {
        return operations::bracket_proxy<Matrix, const Matrix&, const ValueType&>(static_cast<const Matrix&>(*this), row);
    }

    template <typename T>
    typename boost::disable_if_c<boost::is_same<T, mtl::irange>::value || boost::is_same<T, mtl::iset>::value,
			       operations::bracket_proxy<Matrix, Matrix&, ValueType&> >::type
    // operations::bracket_proxy<Matrix, Matrix&, ValueType&>
    operator[] (const T& row)
    {
        return operations::bracket_proxy<Matrix, Matrix&, ValueType&>(static_cast<Matrix&>(*this), row);
    }

    // Compiler error (later) if no sub_matrix function available
    operations::range_bracket_proxy<Matrix, const Matrix&, const Matrix>
    operator[] (const irange& row_range) const
    {
	return operations::range_bracket_proxy<Matrix, const Matrix&, const Matrix>(static_cast<const Matrix&>(*this), row_range);
    }

    // Compiler error (later) if no sub_matrix function available
    template <typename T>
    typename boost::enable_if<boost::is_same<T, mtl::irange>, operations::range_bracket_proxy<Matrix, Matrix&, Matrix> >::type
    // operations::range_bracket_proxy<Matrix, Matrix&, Matrix>
    operator[] (const T& row_range)
    {
	return operations::range_bracket_proxy<Matrix, Matrix&, Matrix>(static_cast<Matrix&>(*this), row_range);
    }

    operations::set_bracket_proxy<Matrix, const Matrix&, const Matrix>
    operator[] (const iset& row_set) const
    {
	return operations::set_bracket_proxy<Matrix, const Matrix&, const Matrix>(static_cast<const Matrix&>(*this), row_set);
    }
};

template <typename Matrix, typename ValueType, typename SizeType>
struct crtp_matrix_lvalue
{
    // Function must be overwritten by Matrix if m(row, col) does not return a reference
    ValueType& lvalue(SizeType row, SizeType col)
    {
	return static_cast<Matrix&>(*this)(row, col);
    }
};

template <typename Matrix, typename ValueType, typename SizeType>
struct const_crtp_base_matrix
  : public const_crtp_matrix_bracket<Matrix, ValueType, SizeType>
{};

template <typename Matrix, typename ValueType, typename SizeType>
struct mutable_crtp_base_matrix
  : public crtp_matrix_bracket<Matrix, ValueType, SizeType>,
    public crtp_matrix_assign<Matrix, ValueType, SizeType>
{};

template <typename Matrix, typename ValueType, typename SizeType>
struct crtp_base_matrix
  : boost::mpl::if_<boost::is_const<Matrix>,
		    const_crtp_base_matrix<Matrix, ValueType, SizeType>,
		    mutable_crtp_base_matrix<Matrix, ValueType, SizeType>
                   >::type
{};

}} // namespace mtl::matrix

#endif // MTL_CRTP_BASE_MATRIX_INCLUDE


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

#include <exception>
#include <iostream>
#include <limits>
#include <vector>

// Quick hack for definition of 'I' in <complex.h>
#undef I
#include <boost/fusion/include/accumulate.hpp>
#include <boost/fusion/include/for_each.hpp>
#include <boost/fusion/include/vector.hpp>
#include <boost/parameter.hpp>
#define BOOST_NO_SCOPED_ENUMS
#include <boost/filesystem.hpp>

#include <Eigen/Core>

// we need everything to have the defaults
#include <limbo/acqui/ucb.hpp>
#include <limbo/init/random_sampling.hpp>
#include <limbo/kernel/exp.hpp>
#include <limbo/mean/data.hpp>
#include <limbo/model/gp.hpp>
#include <limbo/stat/aggregated_observations.hpp>
#include <limbo/stat/console_summary.hpp>
#include <limbo/stat/samples.hpp>
#include <limbo/stop/chain_criteria.hpp>
#include <limbo/stop/max_iterations.hpp>
#include <limbo/tools/macros.hpp>
#include <limbo/tools/math.hpp>
#include <limbo/tools/sys.hpp>

namespace limbo {
    namespace defaults {
        struct bayes_opt_bobase {
            BO_PARAM(bool, stats_enabled, true);
            BO_PARAM(bool, bounded, true);
        };
    }
    template <typename BO, typename AggregatorFunction>
    struct RefreshStat_f {
        RefreshStat_f(BO& bo, const AggregatorFunction& afun)
            : _bo(bo), _afun(afun) {}

        BO& _bo;
        const AggregatorFunction& _afun;

        template <typename T>
        void operator()(T& x) const { x(_bo, _afun); }
    };

    struct FirstElem {
        using result_type = double;
        double operator()(const Eigen::VectorXd& x) const
        {
            return x(0);
        }
    };
    class EvaluationError : public std::exception {
    };

    // we use optimal named template parameters
    // see:
    // http://www.boost.org/doc/libs/1_55_0/libs/parameter/doc/html/index.html#parameter-enabled-class-templates

    BOOST_PARAMETER_TEMPLATE_KEYWORD(initfun)
    BOOST_PARAMETER_TEMPLATE_KEYWORD(acquifun)
    BOOST_PARAMETER_TEMPLATE_KEYWORD(modelfun)
    BOOST_PARAMETER_TEMPLATE_KEYWORD(statsfun)
    BOOST_PARAMETER_TEMPLATE_KEYWORD(stopcrit)

    namespace bayes_opt {

        using bobase_signature = boost::parameter::parameters<boost::parameter::optional<tag::statsfun>,
            boost::parameter::optional<tag::initfun>,
            boost::parameter::optional<tag::acquifun>,
            boost::parameter::optional<tag::stopcrit>,
            boost::parameter::optional<tag::modelfun>>;

        // clang-format off
        template <class Params,
          class A1 = boost::parameter::void_,
          class A2 = boost::parameter::void_,
          class A3 = boost::parameter::void_,
          class A4 = boost::parameter::void_,
          class A5 = boost::parameter::void_,
          class A6 = boost::parameter::void_>
        // clang-format on
        /**
        \rst

        Base class for Bayesian optimizers

        Parameters:
          - ``bool Params::bayes_opt_bobase::stats_enabled``: activate / deactivate the statistics

        This class is templated by several types with default values (thanks to boost::parameters).

        +----------------+---------+---------+---------------+
        |type            |typedef  | argument| default       |
        +================+=========+=========+===============+
        |init. func.     |init_t   | initfun | RandomSampling|
        +----------------+---------+---------+---------------+
        |model           |model_t  | modelfun| GP<...>       |
        +----------------+---------+---------+---------------+
        |acquisition fun.|aqui_t   | acquifun| GP_UCB        |
        +----------------+---------+---------+---------------+
        |statistics      | stat_t  | statfun | see below     |
        +----------------+---------+---------+---------------+
        |stopping crit.  | stop_t  | stopcrit| MaxIterations |
        +----------------+---------+---------+---------------+

        \endrst

        For GP, the default value is: ``model::GP<Params, kf_t, mean_t, opt_t>>``,
          - with ``kf_t = kernel::SquaredExpARD<Params>``
          - with ``mean_t = mean::Data<Params>``
          - with ``opt_t = model::gp::KernelLFOpt<Params>``

         (meaning: kernel with automatic relevance determination and mean equals to the mean of the input data, that is, center the data automatically)

        For Statistics, the default value is: ``boost::fusion::vector<stat::Samples<Params>, stat::AggregatedObservations<Params>, stat::ConsoleSummary<Params>>``

        Example of customization:
          - ``using Kernel_t = kernel::MaternFiveHalves<Params>;``
          - ``using Mean_t = mean::Data<Params>;``
          - ``using GP_t = model::GP<Params, Kernel_t, Mean_t>;``
          - ``using Acqui_t = acqui::UCB<Params, GP_t>;``
          - ``bayes_opt::BOptimizer<Params, modelfun<GP_t>, acquifun<Acqui_t>> opt;``

        */
        class BoBase {
        public:
            using params_t = Params;
            // defaults
            struct defaults {
                using init_t = init::RandomSampling<Params>; // 1
                using model_t = model::GP<Params>; // 2
                // WARNING: you have to specify the acquisition  function
                // if you use a custom model
                using acqui_t = acqui::UCB<Params, model_t>; // 3
                using stat_t = boost::fusion::vector<stat::Samples<Params>, stat::AggregatedObservations<Params>, stat::ConsoleSummary<Params>>; // 4
                using stop_t = boost::fusion::vector<stop::MaxIterations<Params>>; // 5
            };

            // extract the types
            using args = typename bobase_signature::bind<A1, A2, A3, A4, A5, A6>::type;
            using init_function_t = typename boost::parameter::binding<args, tag::initfun, typename defaults::init_t>::type;
            using acquisition_function_t = typename boost::parameter::binding<args, tag::acquifun, typename defaults::acqui_t>::type;
            using model_t = typename boost::parameter::binding<args, tag::modelfun, typename defaults::model_t>::type;
            using Stat = typename boost::parameter::binding<args, tag::statsfun, typename defaults::stat_t>::type;
            using StoppingCriteria = typename boost::parameter::binding<args, tag::stopcrit, typename defaults::stop_t>::type;

            using stopping_criteria_t = typename boost::mpl::if_<boost::fusion::traits::is_sequence<StoppingCriteria>, StoppingCriteria, boost::fusion::vector<StoppingCriteria>>::type;
            using stat_t = typename boost::mpl::if_<boost::fusion::traits::is_sequence<Stat>, Stat, boost::fusion::vector<Stat>>::type;

            /// default constructor
            BoBase() : _total_iterations(0) { _make_res_dir(); }

            /// copy is disabled (dangerous and useless)
            BoBase(const BoBase& other) = delete;
            /// copy is disabled (dangerous and useless)
            BoBase& operator=(const BoBase& other) = delete;

            /// return true if the statitics are enabled (they can be disabled to avoid dumping data, e.g. for unit tests)
            bool stats_enabled() const { return Params::bayes_opt_bobase::stats_enabled(); }

            /// return the name of the directory in which results (statistics) are written
            const std::string& res_dir() const { return _res_dir; }

            /// return the vector of points of observations (observations can be multi-dimensional, hence the VectorXd) -- f(x)
            const std::vector<Eigen::VectorXd>& observations() const { return _observations; }

            /// return the list of the points that have been evaluated so far (x)
            const std::vector<Eigen::VectorXd>& samples() const { return _samples; }

            /// return the current iteration number
            int current_iteration() const { return _current_iteration; }

            int total_iterations() const { return _total_iterations; }

            /// Add a new sample / observation pair
            /// - does not update the model!
            /// - we don't add NaN and inf observations
            void add_new_sample(const Eigen::VectorXd& s, const Eigen::VectorXd& v)
            {
                if (tools::is_nan_or_inf(v))
                    throw EvaluationError();
                _samples.push_back(s);
                _observations.push_back(v);
            }

            /// Evaluate a sample and add the result to the 'database' (sample / observations vectors) -- it does not update the model
            template <typename StateFunction>
            void eval_and_add(const StateFunction& seval, const Eigen::VectorXd& sample)
            {
                this->add_new_sample(sample, seval(sample));
            }

        protected:
            template <typename StateFunction, typename AggregatorFunction>
            void _init(const StateFunction& seval, const AggregatorFunction& afun, bool reset = true)
            {
                this->_current_iteration = 0;
                if (reset) {
                    this->_total_iterations = 0;
                    this->_samples.clear();
                    this->_observations.clear();
                }

                if (this->_total_iterations == 0)
                    init_function_t()(seval, afun, *this);
            }

            template <typename BO, typename AggregatorFunction>
            bool _stop(const BO& bo, const AggregatorFunction& afun) const
            {
                stop::ChainCriteria<BO, AggregatorFunction> chain(bo, afun);
                return boost::fusion::accumulate(_stopping_criteria, false, chain);
            }

            template <typename BO, typename AggregatorFunction>
            void _update_stats(BO& bo, const AggregatorFunction& afun)
            { // not const, because some stat class
                // modify the optimizer....
                boost::fusion::for_each(_stat, RefreshStat_f<BO, AggregatorFunction>(bo, afun));
            }

            void _make_res_dir()
            {
                if (!Params::bayes_opt_bobase::stats_enabled())
                    return;
                _res_dir = tools::hostname() + "_" + tools::date() + "_" + tools::getpid();
                boost::filesystem::path my_path(_res_dir);
                boost::filesystem::create_directory(my_path);
            }

            std::string _res_dir;
            int _current_iteration;
            int _total_iterations;
            stopping_criteria_t _stopping_criteria;
            stat_t _stat;

            std::vector<Eigen::VectorXd> _observations;
            std::vector<Eigen::VectorXd> _samples;
        };
    }
}

#endif


// This file is part of DM-HEOM (https://github.com/noma/dm-heom)
//
// Copyright (c) 2015-2019 Matthias Noack, Zuse Institute Berlin
//
// Licensed under the 3-clause BSD License, see accompanying LICENSE,
// CONTRIBUTORS.md, and README.md for further information.

#include "heom/config.hpp"

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/hermitian.hpp>

#include "heom/constants.hpp"

namespace heom {

namespace bpo = ::boost::program_options;
namespace blas = ::boost::numeric::ublas;

config::config(const std::string& config_file_name, bool parse_config)
	: config_base(config_file_name, false)
{
	std::stringstream observation_type_help;
	observation_type_help << "List of observations, pairs {(observation_type, filename), ...}. Valid observation_types are: ";
	parser::write_map_value_list(observation_type_names, observation_type_help);

	// generate help text for filtering strategy
	std::stringstream filtering_strategy_help;
	filtering_strategy_help << "Strategy by which the hierarchy graph is filtered, one of: ";
	parser::write_map_value_list(filtering_strategy_names, filtering_strategy_help);

	std::stringstream solver_stepper_type_help;
	solver_stepper_type_help << "Stepper type for numerical integration, one of: ";
	parser::write_map_value_list(num::stepper_type_names, solver_stepper_type_help);

	// initialise program options description
	desc_.add_options()
		// program
		("program.observations", bpo::value(&observations_)->required(), observation_type_help.str().c_str())
		("program.observe_steps", bpo::value(&program_observe_steps_)->required(), "Observe (i.e. generate ouput) every n steps.")

		// filter
		("filtering.strategy", bpo::value(&filtering_strategy_)->required(), filtering_strategy_help.str().c_str())
		("filtering.first_layer", bpo::value(&filtering_first_layer_)->required(), "Hierarchy layer where filtering begins.")

		// solver
		("solver.stepper_type", bpo::value(&solver_stepper_type_)->required(), solver_stepper_type_help.str().c_str())
		("solver.step_size", bpo::value(&solver_step_size_)->required(), "Simulation time step in [s]")
		("solver.steps", bpo::value(&solver_steps_)->required(), "Simulated time [steps].")
		("solver.track_flows", bpo::value(&solver_track_flows_)->required(), "Enable flow tracking [true or false].")
		("solver.flow_filename", bpo::value(&solver_flow_filename_)->required(), "Output file for flow tracking.")

		// system
		("system.sites", bpo::value(&system_sites_)->required(), "Number of sites in the input system.")
		("system.ado_depth", bpo::value(&system_ado_depth_)->required(), "Depth of the hierarchy in the input system.")
		("system.hamiltonian", bpo::value(&system_hamiltonian_)->required(), "Hamiltonian, complex matrix (num-sites x num-sites).")

		// baths
		("baths.number", bpo::value(&baths_number_)->required(), "Total number of independent baths.")
		("baths.max_per_site", bpo::value(&baths_max_per_site_)->required(), "Maximumexception number of baths coupled to a single site.")
		("baths.coupling", bpo::value(&baths_coupling_)->required(), "Baths connections to sites (baths_max_persite x system_sites), -1 for n/a.")
		("baths.lambda", bpo::value(&baths_lambda_)->required(), "Bath reorganization energies [lambda] for each bath [invcm].")
		("baths.invnu", bpo::value(&baths_invnu_)->required(), "Bath correlation times [nu] for each bath [inv fs].")
		("baths.Omega", bpo::value(&baths_uppercase_omega_)->required(), "Bath peak shift [Omega] for each bath [invcm].")
		("baths.matsubaras", bpo::value(&baths_matsubaras_)->required(), "Number of Matsubara frequencies per bath.")
		("baths.temperature", bpo::value(&baths_temperature_)->required(), "Temperature [K].")
		;

	if (parse_config)
		parse(config_file_name);
}

void config::check()
{
	config_base::check(); // call direct super-class' check()

	// observations
	if (observations().get().empty())
		throw config_error("observations must at least contain one entry.");

	DEBUG_ONLY( std::cout << "heom::config_base::parse(): observations=" << observations_ << std::endl; )

	// program_observe_steps
	if (!(program_observe_steps_ > 0))
		throw config_error("program.observe_steps must be > 0");

	// filtering_strategy
	if (filtering_strategy_ == filtering_strategy_t::NONE && filtering_first_layer_ != -1){
		throw config_error("filtering.first_layer must be -1, because it has no effect for filtering_strategy = none");
	}

	// filtering_first_layer
	if (filtering_strategy_ == filtering_strategy_t::SINGLE_EXCITATION && !(filtering_first_layer_ >= 0))
		throw config_error("filtering.first_layer must be greater or equal to zero for filtering.strategy = single excitation filter");

	// compare ADO cut off depth with filtering first layer
	if(system_ado_depth_ < filtering_first_layer_){
		throw config_error("filtering.first_layer cannot be larger than system_ado_depth");
	}

	// solver_step_size
	if (!(solver_step_size_ > 0.0)) // TODO: upper bound ?
		throw config_error("solver.step_size must be > 0");

	// solver_steps
	if (!(solver_steps_ > 0))
		throw config_error("solver.steps must be > 0");

	auto remainder_steps = solver_steps_ % program_observe_steps_;
	if (remainder_steps != 0)
		throw config_error("there's a remainder when dividing solver.steps by program.observe_steps");

	// solver_flow_filename
	// if (solver_track_flows_)
	// TODO: what happens if the path does not exist, what happens if the file exists already

	// system_sites
	if (!(system_sites_ > 0)) // TODO: upper bound
		throw config_error("system.sites must be > 0");

	// system_ado_depth
	if (!(system_ado_depth_ > 0)) // TODO: upper bound
		throw config_error("system.ado_depth must be > 0");

	// system_hamiltonian
	if (!(static_cast<int_t>(system_hamiltonian_.rows()) == system_sites_ &&
	      static_cast<int_t>(system_hamiltonian_.cols()) == system_sites_))
		throw config_error("system.hamiltonien must be a system.sites by system.sites matrix");

	// create boost ublas matrix from hamiltonian
	using matrix_t = blas::matrix<complex_t>;
	matrix_t hamiltonian_m(system_sites_, system_sites_);
	for (int_t i = 0; i < system_sites_; ++i)
		for (int_t j = 0; j < system_sites_; ++j)
			hamiltonian_m(i,j) = system_hamiltonian_.at(i, j);

	if(!blas::is_hermitian(hamiltonian_m))
		throw config_error("system.hamiltonien must be hermitian");

	// baths_number
	if (!(baths_number_ > 0)) // TODO: upper bound
		throw config_error("baths.number must be > 0");

	// baths_max_per_site
	if (!(baths_max_per_site_ > 0)) // TODO: upper bound
		throw config_error("baths.max_per_site must be > 0");
	if (!(baths_max_per_site_ <= baths_number_)) // TODO: upper bound
		throw config_error("baths.max_per_site must be <= baths.number");

	// baths_coupling
	if (!(static_cast<int_t>(baths_coupling_.rows()) == system_sites_ &&
	      static_cast<int_t>(baths_coupling_.cols()) == baths_max_per_site_))
		throw config_error("baths.coupling must be a system.sites by baths_max_per_site_ matrix");

	for (size_t i = 0; i < baths_coupling_.rows(); ++i)
		for (size_t j = 0; j < baths_coupling_.cols(); ++j)
			if (!(baths_coupling_.at(i,j) < baths_number_))
				throw config_error("baths.coupling values must between 0 and baths.number, or negative for non-coupling");

	// baths_lambda
	if (!(static_cast<int_t>(baths_lambda_.size()) == baths_number_))
		throw config_error("baths.lambda must have a length of baths.number");

	// baths_invnu
	if (!(static_cast<int_t>(baths_invnu_.size()) == baths_number_))
		throw config_error("baths.invnu must have a length of baths.number");
	for (size_t i = 0; i < baths_invnu_.size(); ++i)
		if (!(baths_invnu_.at(i) > 0)) // TODO: upper bound
			throw config_error("baths.invnu must have values > 0");

	// baths_uppercase_omage
	if (!(static_cast<int_t>(baths_uppercase_omega_.size()) == baths_number_))
		throw config_error("baths.Omega must have a length of baths.number");
	// TODO: if one Omega != 0, there must be a counterpart with same invnu, lambda, -Omega
}

void config::check_final()
{

}

void config::post_process()
{
	config_base::post_process(); // call direct super-class' post_process()

	// scale to SI units
	system_hamiltonian_.scale(constants::invcm_to_joule);
	baths_lambda_.scale(constants::invcm_to_joule);
	baths_uppercase_omega_.scale(constants::invcm_to_joule/constants::h_bar);
	baths_invnu_.scale(1.0E-15); // TODO: maybe change input format to SI seconds
}

} // namespace heom