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

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

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_SIDE_BY_CROSS_TRACK_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_SIDE_BY_CROSS_TRACK_HPP

#include <boost/mpl/if.hpp>
#include <boost/type_traits.hpp>
#include <boost/core/ignore_unused.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/radian_access.hpp>

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

#include <boost/geometry/strategies/side.hpp>
//#include <boost/geometry/strategies/concepts/side_concept.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace side
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

/// Calculate course (bearing) between two points. Might be moved to a "course formula" ...
template <typename Point>
static inline double course(Point const& p1, Point const& p2)
{
    // http://williams.best.vwh.net/avform.htm#Crs
    double dlon = get_as_radian<0>(p2) - get_as_radian<0>(p1);
    double cos_p2lat = cos(get_as_radian<1>(p2));

    // "An alternative formula, not requiring the pre-computation of d"
    return atan2(sin(dlon) * cos_p2lat,
        cos(get_as_radian<1>(p1)) * sin(get_as_radian<1>(p2))
        - sin(get_as_radian<1>(p1)) * cos_p2lat * cos(dlon));
}

}
#endif // DOXYGEN_NO_DETAIL

/*!
\brief Check at which side of a Great Circle segment a point lies
         left of segment (> 0), right of segment (< 0), on segment (0)
\ingroup strategies
\tparam CalculationType \tparam_calculation
 */
template <typename CalculationType = void>
class side_by_cross_track
{

public :
    template <typename P1, typename P2, typename P>
    static inline int apply(P1 const& p1, P2 const& p2, P const& p)
    {
        typedef typename boost::mpl::if_c
            <
                boost::is_void<CalculationType>::type::value,
                typename select_most_precise
                    <
                        typename select_most_precise
                            <
                                typename coordinate_type<P1>::type,
                                typename coordinate_type<P2>::type
                            >::type,
                        typename coordinate_type<P>::type
                    >::type,
                CalculationType
            >::type coordinate_type;

        boost::ignore_unused<coordinate_type>();

        double d1 = 0.001; // m_strategy.apply(sp1, p);
        double crs_AD = detail::course(p1, p);
        double crs_AB = detail::course(p1, p2);
        double XTD = asin(sin(d1) * sin(crs_AD - crs_AB));

        return math::equals(XTD, 0) ? 0 : XTD < 0 ? 1 : -1;
    }
};

}} // namespace strategy::side

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_SIDE_BY_CROSS_TRACK_HPP


#ifndef QST_OBSERVERWAVEFUNCTION_HPP
#define QST_OBSERVERWAVEFUNCTION_HPP

#include <iostream>
#include <Eigen/Dense>
#include <iomanip>
#include <fstream>

namespace qst{

template<class Wavefunction> class ObserverPSI{

    Wavefunction &PSI_;

    int N_;
    int npar_;
    Eigen::VectorXcd target_psi_;
    std::vector<Eigen::VectorXcd> rotated_wf_;
    Eigen::MatrixXd basis_states_;      // Hilbert space basis
    std::map<std::string,Eigen::MatrixXcd> U_;
    std::vector<std::vector<std::string> > basisSet_;
    std::string basis_;
public:

    double KL_;
    double overlap_;
    double Z_;
    double NLL_;

    ObserverPSI(Wavefunction &PSI,std::string &basis):PSI_(PSI){

        std::cout<<"- Initializing observer module"<<std::endl;
        N_ = PSI_.N();
        npar_ = PSI_.Npar();
        basis_ = basis;
        basis_states_.resize(1<<N_,N_);
        std::bitset<10> bit;
        // Create the basis of the Hilbert space
        for(int i=0;i<1<<N_;i++){
            bit = i;
            for(int j=0;j<N_;j++){
                basis_states_(i,j) = bit[N_-j-1];
            }
        }
    }

    //Compute different estimators for the training performance
    void Scan(int i){//,Eigen::MatrixXd &nll_test,std::ofstream &obs_out){
        ExactPartitionFunction();
        ExactKL();
        Overlap();
        PrintStats(i);
    }

    //Compute the partition function by exact enumeration
    void ExactPartitionFunction() {
        Z_ = 0.0;
        for(int i=0;i<basis_states_.rows();i++){
            Z_ += norm(PSI_.psi(basis_states_.row(i)));
        }
    }

    // Compute the overlap with the target wavefunction
    void Overlap(){
        overlap_ = 0.0;
        std::complex<double> tmp;
        for(int i=0;i<basis_states_.rows();i++){
            tmp += conj(target_psi_(i))*PSI_.psi(basis_states_.row(i))/std::sqrt(Z_);
        }
        overlap_ = abs(tmp);
    }
    // Compute the fidelity with the target wavefunction
    void Fidelity(){
        Overlap();
        return overlap_*overlap_;
    }

    void NLL(Eigen::MatrixXd &data){
        //TODO NOTE THIS IS ONLY FOR REFERENCE BASIS
        NLL_ = 0.0;
        for (int i=0;i<data.rows();i++){
            NLL_ -= log(norm(PSI_.psi(data.row(i))));
            NLL_ += log(Z_);
        }
        NLL_ /= float(data.rows());
    }

    //Compute KL divergence exactly
    void ExactKL(){
        Eigen::VectorXcd rotated_psi(1<<N_);
        //KL in the standard basis
        KL_ = 0.0;
        for(int i=0;i<1<<N_;i++){
            if (norm(target_psi_(i))>0.0){
                KL_ += norm(target_psi_(i))*log(norm(target_psi_(i)));
            }
            KL_ -= norm(target_psi_(i))*log(norm(PSI_.psi(basis_states_.row(i))));
            KL_ += norm(target_psi_(i))*log(Z_);
        }
        if (basis_.compare("std")!=0){
            //KL in the rotated bases
            for (int b=1;b<basisSet_.size();b++){
                rotateRbmWF(basisSet_[b],rotated_psi);
                for(int i=0;i<1<<N_;i++){
                    if (norm(rotated_wf_[b-1](i))>0.0){
                        KL_ += norm(rotated_wf_[b-1](i))*log(norm(rotated_wf_[b-1](i)));
                    }
                    KL_ -= norm(rotated_wf_[b-1](i))*log(norm(rotated_psi(i)));
                    KL_ += norm(rotated_wf_[b-1](i))*log(Z_);
                }
            }
        }
    }

    //Print observer
    void PrintStats(int i){
        std::cout << "Epoch: " << i << "\t";
        std::cout << "KL = " << std::setprecision(10) << KL_ << "\t";
        std::cout << "Overlap = " << std::setprecision(10) << overlap_<< "\t";//<< Fcheck_;
        std::cout << std::endl;
    }

    //Set the value of the target wavefunction
    void setWavefunction(Eigen::VectorXcd & psi){
        target_psi_.resize(1<<N_);
        for(int i=0;i<1<<N_;i++){
            target_psi_(i) = psi(i);
        }
    }

    void setRotatedWavefunctions(std::vector<Eigen::VectorXcd> & psi){
        for(int b=0;b<psi.size();b++){
            rotated_wf_.push_back(psi[b]);
        }
    }
    //Set the value of the target wavefunction
    void setBasisRotations(std::map<std::string,Eigen::MatrixXcd> & U){
        U_ = U;
    }
    void setBasis(std::vector<std::vector<std::string> > basis) {
        basisSet_ = basis;
    }

    void rotateRbmWF(const std::vector<std::string> & basis, Eigen::VectorXcd &psiR){//VectorRbmT & psiR){
        int t,counter;
        std::complex<double> U,Upsi;
        std::bitset<16> bit;
        std::bitset<16> st;
        std::bitset<16> tmp;
        std::vector<int> basisIndex;
        Eigen::VectorXd state(N_);
        Eigen::VectorXd v(N_);

        for(int x=0;x<1<<N_;x++){
            U = 1.0;
            Upsi=0.0;
            basisIndex.clear();
            t = 0;
            st = x;
            for (int j=0;j<N_;j++){
                state(j) = st[N_-1-j];
            }
            for(int j=0;j<N_;j++){
                if (basis[j]!="Z"){
                    t++;
                    basisIndex.push_back(j);
                }
            }
            for(int i=0;i<1<<t;i++){
                counter  =0;
                bit = i;
                v=state;
                for(int j=0;j<N_;j++){
                    if (basis[j] != "Z"){
                        v(j) = bit[counter];
                        counter++;
                    }
                }
                U=1.0;
                for(int ii=0;ii<t;ii++){
                    U = U * U_[basis[basisIndex[ii]]](int(state(basisIndex[ii])),int(v(basisIndex[ii])));
                }
                for(int j=0;j<N_;j++){
                    tmp[j]=v(N_-1-j);
                }
                Upsi += U*PSI_.psi(v);
            }
            psiR(x) = Upsi;
        }
    }
};
}

#endif


#include <gnuplot-iostream/gnuplot-iostream.h>
#include <boost/tuple/tuple.hpp>
#include <iostream>
#include <functional>
#include <iomanip>
#include <string>
#include <cmath>
#include "Tools.hpp"
using namespace std;

/*****************************************************************************

This program uses the classes and a few functions from Tools.hpp to model a
block-spring system in simple harmonic motion (SHM). The program implements
two methods: Euler's method and the Euler-Cromer method. The variables of the
differential equation are stored in a classes named accordingly, along with
accessor methods and a mutator to apply the defined euler step. Since
EulerCromer1D is a subclass of Euler1D, they can be used polymorphically by
allocating memory for them and referencing them by a pointer to Euler1D.

In the applyMethod function, the line with obj->update(dt) applies the update
function of whichever class is supplied, not the one for Euler1D. It returns a
struct of type Path1D (from Tools.hpp) to avoid nasty data types like 2D
vectors or having to return multiple values.

The plotpath function simply takes the Path1D, along with some information
about what's being plotted and how big it is, and sends it to gnuplot.

Finally, the main method creates the objects, applies the method, and plots
them both. The getMaxValue function returns the amplitude of the Euler Method
path (which should be larger) and plots them both methods at the same scale.
Then, the created objects are deleted and the program ends.

*****************************************************************************/

const double x_0 = inputDouble(0, "Initial Position");		// Initial position
const double v_0 = inputDouble(10, "Initial Velocity");		// Initial velocity
const double dt = inputDouble(0.005, "Time Step");			// Time step
const double k = inputDouble(25, "Spring Constant");		// Spring Constant
const double m = inputDouble(1, "Mass");					// Mass
const double om = sqrt(k/m);								// Omega
const double SIM_TIME = inputDouble(50, "Simulation Time");	// Time to simulate

function<double(double,double)> springStep = [](double x, double v){ return -om*om*x; };	// The lambda function used as the differential equation

Path1D applyMethod(Euler1D* obj){	// Function to apply euler method to a pointer of Euler1D, allowing for polymorphism
	Path1D out;						// Define an empty path to put the results in and output

	for(double t = 0; t < SIM_TIME; t += dt){	// Iterate from 0 to SIM_TIME with the defined time step
		out.x.push_back(obj->getX());	// Add the current position to the path
		out.v.push_back(obj->getV());	// Add the current velocity to the path (for phase space)
		out.t.push_back(t);				// Add the current time to the path
		obj->update(dt);				// Apply the update method of obj (either Euler's or Euler-Cromer)
	}

	return out;	// Return the path variable
}

void plotPath(Path1D p, double amp, string title, string fname){	// Used to plot a path, specialized for this program...
	// Accepts input for the path, maximum amplitude (so graphs have the same scale and aren't terrible), the title of the method, and the file name to output
	Gnuplot gp;	// Instance of gnuplot terminal stream

	gp << setprecision(3);
	gp << "set xrange [0:" << SIM_TIME << "]\n";
	gp << "set yrange [" << -amp*1.1 << ":" << amp*1.1 << "]\n";
	gp << "set format y \"%.1f\"\n";
	gp << "set term png size 720,480 font \"FreeSerif,12\"\n";
	gp << "set xlabel \"t (s)\"\n";
	gp << "set ylabel \"x (m)\"\n";
	gp << "set title \"Position Vs. Time in Simple Harmonic Motion\\nUsing " << title << "\"\n";
	gp << "set output \"" << fname << "\"\n";
	gp << "plot '-' with dots lc rgb \"black\" notitle\n";
	gp.send1d(boost::make_tuple(p.t,p.x));	// Separate the path into components and plot it!
}

void plotPhaseSpace(Path1D p, string title, string fname){
	Gnuplot gp;	// Instance of gnuplot terminal stream
	vector<double> v = getMomentum(p.v, m);

	gp << setprecision(3);
	gp << "set xrange [" << -1.1*getMaxVal(p.x) << ":" << 1.1*getMaxVal(p.x) << "]\n";
	gp << "set yrange [" << -1.1*getMaxVal(v) << ":" << 1.1*getMaxVal(v) << "]\n";
	gp << "set format y \"%.1f\"\n";
	gp << "set term png size 720,480 font \"FreeSerif,12\"\n";
	gp << "set xlabel \"x (m)\"\n";
	gp << "set ylabel \"v (m/s)\"\n";
	gp << "set title \"Phase Space Diagram of SHM using " << title << "\"\n";
	gp << "set output \"" << fname << "\"\n";
	gp << "plot '-' with dots lc rgb \"black\" notitle\n";
	gp.send1d(boost::make_tuple(p.x,v));	// Separate the path into components and plot it!
}

int main(){
	Euler1D* badSpring = new Euler1D(x_0, v_0, springStep);			// Make a pointer to a Euler1D object
	Euler1D* goodSpring = new EulerCromer1D(x_0, v_0, springStep);	// And one to a EulerCromer1D object

	Path1D badPath = applyMethod(badSpring);	// Apply the method for both objects
	Path1D goodPath = applyMethod(goodSpring);

	double bigAmp = getMaxVal(badPath.x);	// Amplitude of the oscillation (max value of pos)
	plotPath(badPath, bigAmp, "Euler's Method", "BadMethod.png");			// Plot them accordingly!
	plotPath(goodPath, bigAmp, "The Euler-Cromer Method", "GoodMethod.png");

	plotPhaseSpace(badPath, "Euler's Method", "BadPSD.png");
	plotPhaseSpace(goodPath, "the Euler-Cromer Method", "GoodPSD.png");

	delete badSpring;	// Garbage collection to free up allocated memory
	delete goodSpring;
	return 0;			// All done!
}


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

#include <cmath>
#include <iostream>
#include <vector>
#include <boost/numeric/mtl/mtl.hpp>

using namespace std;

template <typename Vector>
void test(const char* name)
{
    cout << "Testing multi_vector with " << name << endl;
    mtl::multi_vector<Vector> A(4, 6), B(4, 6);
    A= 3.0;
    cout << "A is\n" << A << endl;

    B= A;
    cout << "B= A yields\n" << B << endl;
    cout << "A is\n" << B << endl;

    B= A + A;
    cout << "A + A is\n" << B << endl;
    MTL_THROW_IF(B[1][1] != 6.0, mtl::runtime_error("Wrong value on diagonal\n"));
    MTL_THROW_IF(B[1][0] != 0.0, mtl::runtime_error("Wrong value off diagonal\n"));

    B= 2 * A;
    cout << "2 * A is\n" << B << endl;
    MTL_THROW_IF(B[1][1] != 6.0, mtl::runtime_error("Wrong value on diagonal\n"));
    MTL_THROW_IF(B[1][0] != 0.0, mtl::runtime_error("Wrong value off diagonal\n"));

    B= 2 * A + A;
    cout << "2 * A is\n" << B << endl;
    MTL_THROW_IF(B[1][1] != 9.0, mtl::runtime_error("Wrong value on diagonal\n"));
    MTL_THROW_IF(B[1][0] != 0.0, mtl::runtime_error("Wrong value off diagonal\n"));

    B= 2.0 * A + 3 * A;
    cout << "2 * A + 3 * A is\n" << B << endl;
    MTL_THROW_IF(B[1][1] != 15.0, mtl::runtime_error("Wrong value on diagonal\n"));
    MTL_THROW_IF(B[1][0] != 0.0, mtl::runtime_error("Wrong value off diagonal\n"));

    B= 2.0 * A + 3 * A - 2.6 * A;
    cout << "2 * A + 3 * A - 2.6 * A is\n" << B << endl;
    MTL_THROW_IF(std::abs(B[1][1] - 7.2) > 0.001, mtl::runtime_error("Wrong value on diagonal\n"));
    MTL_THROW_IF(B[1][0] != 0.0, mtl::runtime_error("Wrong value off diagonal\n"));

    mtl::multi_vector<Vector> C;
    C.change_dim(3, 10);
    C= 7;
    cout << "C is\n" << C;
}

int main(int, char**)
{
    test<mtl::dense_vector<double> >("dense_vector<double>");

    return 0;
}


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2003 - 2021 by the deal.II authors
 *
 * This file is part of the deal.II library.
 *
 * The deal.II library is free software; you can use it, redistribute
 * it, and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * The full text of the license can be found in the file LICENSE.md at
 * the top level directory of deal.II.
 *
 * ---------------------------------------------------------------------
 *
 * Authors: Guido Kanschat, University of Heidelberg, 2003
 *          Baerbel Janssen, University of Heidelberg, 2010
 *          Wolfgang Bangerth, Texas A&M University, 2010
 */

// @sect3{Include files}

// 同样，前几个include文件已经知道了，所以我们不会对它们进行评论。

#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/function.h>
#include <deal.II/base/logstream.h>
#include <deal.II/base/utilities.h>

#include <deal.II/lac/affine_constraints.h>
#include <deal.II/lac/vector.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>

#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_refinement.h>

#include <deal.II/dofs/dof_tools.h>

#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>

#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/data_out.h>
#include <deal.II/numerics/error_estimator.h>

// 这些，现在，是多级方法所必需的包括。第一个声明了如何处理多网格方法每个层次上的Dirichlet边界条件。对于自由度的实际描述，我们不需要任何新的包含文件，因为DoFHandler已经实现了所有必要的方法。我们只需要将自由度分配给更多的层次。

// 其余的包含文件涉及到作为线性算子（求解器或预处理器）的多重网格的力学问题。

#include <deal.II/multigrid/mg_constrained_dofs.h>
#include <deal.II/multigrid/multigrid.h>
#include <deal.II/multigrid/mg_transfer.h>
#include <deal.II/multigrid/mg_tools.h>
#include <deal.II/multigrid/mg_coarse.h>
#include <deal.II/multigrid/mg_smoother.h>
#include <deal.II/multigrid/mg_matrix.h>

// 最后我们包括MeshWorker框架。这个框架通过其函数loop()和integration_loop()，自动在单元格上进行循环，并将数据组装成向量、矩阵等。它自动服从约束。由于我们必须建立几个矩阵，并且必须注意几组约束，这将使我们省去很多麻烦。

#include <deal.II/meshworker/dof_info.h>
#include <deal.II/meshworker/integration_info.h>
#include <deal.II/meshworker/simple.h>
#include <deal.II/meshworker/output.h>
#include <deal.II/meshworker/loop.h>

// 为了节省精力，我们使用了在以下文件中找到的预先实现的拉普拉斯。

#include <deal.II/integrators/laplace.h>
#include <deal.II/integrators/l2.h>

// 这就是C++。

#include <iostream>
#include <fstream>

using namespace dealii;

namespace Step16
{
// @sect3{The integrator on each cell}

//  MeshWorker::integration_loop() 希望有一个类能够提供在单元格和边界及内部面的积分功能。这是由下面的类来完成的。在构造函数中，我们告诉循环应该计算单元格积分（"真"），但不应该计算边界和内部面的积分（两个 "假"）。因此，我们只需要一个单元格函数，而不需要面的函数。

  template <int dim>
  class LaplaceIntegrator : public MeshWorker::LocalIntegrator<dim>
  {
  public:
    LaplaceIntegrator();
    virtual void cell(MeshWorker::DoFInfo<dim> &        dinfo,
                      MeshWorker::IntegrationInfo<dim> &info) const override;
  };

  template <int dim>
  LaplaceIntegrator<dim>::LaplaceIntegrator()
    : MeshWorker::LocalIntegrator<dim>(true, false, false)
  {}

// 接下来是每个单元上的实际积分器。我们解决一个泊松问题，在右半平面上的系数为1，在左半平面上的系数为十分之一。

//  MeshWorker::LocalResults 的基类 MeshWorker::DoFInfo 包含可以在这个局部积分器中填充的对象。在MeshWorker框架内，有多少对象被创建是由装配器类决定的。在这里，我们举例测试一下，需要一个矩阵 (MeshWorker::LocalResults::n_matrices()).  矩阵是通过 MeshWorker::LocalResults::matrix(), 来访问的，它的第一个参数是矩阵的编号。第二个参数只用于面的积分，当每个测试函数使用两个矩阵时。那么，第二个指标为 "true "的矩阵将以相同的索引存在。

//  MeshWorker::IntegrationInfo 提供了一个或几个FEValues对象，下面这些对象被 LocalIntegrators::Laplace::cell_matrix() 或 LocalIntegrators::L2::L2(). 使用，因为我们只组装一个PDE，所以也只有一个索引为0的对象。

// 此外，我们注意到这个积分器的作用是计算多级预处理的矩阵，以及全局系统的矩阵和右手边。由于系统的汇编器需要一个额外的向量， MeshWorker::LocalResults::n_vectors() 要返回一个非零值。相应地，我们在这个函数的末尾填充了一个右边的向量。由于LocalResults可以处理多个BlockVector对象，但我们这里又是最简单的情况，所以我们将信息输入到零号向量的零号块中。

  template <int dim>
  void
  LaplaceIntegrator<dim>::cell(MeshWorker::DoFInfo<dim> &        dinfo,
                               MeshWorker::IntegrationInfo<dim> &info) const
  {
    AssertDimension(dinfo.n_matrices(), 1);
    const double coefficient = (dinfo.cell->center()(0) > 0.) ? .1 : 1.;

    LocalIntegrators::Laplace::cell_matrix(dinfo.matrix(0, false).matrix,
                                           info.fe_values(0),
                                           coefficient);

    if (dinfo.n_vectors() > 0)
      {
        std::vector<double> rhs(info.fe_values(0).n_quadrature_points, 1.);
        LocalIntegrators::L2::L2(dinfo.vector(0).block(0),
                                 info.fe_values(0),
                                 rhs);
      }
  }
// @sect3{The LaplaceProblem class template}

// 这个主类与  step-6  中的类基本相同。就成员函数而言，唯一增加的是 assemble_multigrid 函数，它组装了对应于中间层离散运算符的矩阵。

  template <int dim>
  class LaplaceProblem
  {
  public:
    LaplaceProblem(const unsigned int degree);
    void run();

  private:
    void setup_system();
    void assemble_system();
    void assemble_multigrid();
    void solve();
    void refine_grid();
    void output_results(const unsigned int cycle) const;

    Triangulation<dim> triangulation;
    FE_Q<dim>          fe;
    DoFHandler<dim>    dof_handler;

    SparsityPattern      sparsity_pattern;
    SparseMatrix<double> system_matrix;

    AffineConstraints<double> constraints;

    Vector<double> solution;
    Vector<double> system_rhs;

    const unsigned int degree;

// 以下成员是多网格方法的基本数据结构。前两个表示稀疏模式和多级层次结构中各个层次的矩阵，非常类似于上面的全局网格的对象。

// 然后，我们有两个新的矩阵，只需要在自适应网格上进行局部平滑的多网格方法。它们在细化区域的内部和细化边缘之间传递数据，在 @ref mg_paper "多网格论文 "中详细介绍过。

// 最后一个对象存储了每个层次上的边界指数信息和位于两个不同细化层次之间的细化边缘上的指数信息。因此，它的作用与AffineConstraints类似，但在每个层次上。

    MGLevelObject<SparsityPattern>      mg_sparsity_patterns;
    MGLevelObject<SparseMatrix<double>> mg_matrices;
    MGLevelObject<SparseMatrix<double>> mg_interface_in;
    MGLevelObject<SparseMatrix<double>> mg_interface_out;
    MGConstrainedDoFs                   mg_constrained_dofs;
  };
// @sect3{The LaplaceProblem class implementation}

// 关于三角形的构造函数只有一个简短的评论：按照惯例，deal.II中所有自适应精化的三角形在单元格之间的面的变化不会超过一个级别。然而，对于我们的多网格算法，我们需要一个更严格的保证，即网格在连接两个单元的顶点上的变化也不超过细化级别。换句话说，我们必须防止出现以下情况。

//  @image html limit_level_difference_at_vertices.png ""

// 这可以通过向三角化类的构造函数传递 Triangulation::limit_level_difference_at_vertices 标志来实现。

  template <int dim>
  LaplaceProblem<dim>::LaplaceProblem(const unsigned int degree)
    : triangulation(Triangulation<dim>::limit_level_difference_at_vertices)
    , fe(degree)
    , dof_handler(triangulation)
    , degree(degree)
  {}

//  @sect4{LaplaceProblem::setup_system}

// 除了只是在DoFHandler中分配自由度之外，我们在每一层都做同样的事情。然后，我们按照之前的程序，在叶子网格上设置系统。

  template <int dim>
  void LaplaceProblem<dim>::setup_system()
  {
    dof_handler.distribute_dofs(fe);
    dof_handler.distribute_mg_dofs();

    deallog << "   Number of degrees of freedom: " << dof_handler.n_dofs()
            << " (by level: ";
    for (unsigned int level = 0; level < triangulation.n_levels(); ++level)
      deallog << dof_handler.n_dofs(level)
              << (level == triangulation.n_levels() - 1 ? ")" : ", ");
    deallog << std::endl;

    DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
    DoFTools::make_sparsity_pattern(dof_handler, dsp);

    solution.reinit(dof_handler.n_dofs());
    system_rhs.reinit(dof_handler.n_dofs());

    constraints.clear();
    DoFTools::make_hanging_node_constraints(dof_handler, constraints);

    std::set<types::boundary_id> dirichlet_boundary_ids = {0};
    Functions::ZeroFunction<dim> homogeneous_dirichlet_bc;
    const std::map<types::boundary_id, const Function<dim> *>
      dirichlet_boundary_functions = {
        {types::boundary_id(0), &homogeneous_dirichlet_bc}};
    VectorTools::interpolate_boundary_values(dof_handler,
                                             dirichlet_boundary_functions,
                                             constraints);
    constraints.close();
    constraints.condense(dsp);
    sparsity_pattern.copy_from(dsp);
    system_matrix.reinit(sparsity_pattern);

// 多网格约束必须被初始化。他们也需要知道边界值，所以我们也在这里传递 dirichlet_boundary 。

    mg_constrained_dofs.clear();
    mg_constrained_dofs.initialize(dof_handler);
    mg_constrained_dofs.make_zero_boundary_constraints(dof_handler,
                                                       dirichlet_boundary_ids);

// 现在是关于多网格数据结构的事情。首先，我们调整多级对象的大小，以容纳每一级的矩阵和稀疏模式。粗略的级别是零（现在是强制性的，但在未来的修订中可能会改变）。注意，这些函数在这里采取的是一个完整的、包容的范围（而不是一个起始索引和大小），所以最细的级别是 n_levels-1  。我们首先要调整容纳SparseMatrix类的容器的大小，因为它们必须在调整大小时释放它们的SparsityPattern才能被销毁。

    const unsigned int n_levels = triangulation.n_levels();

    mg_interface_in.resize(0, n_levels - 1);
    mg_interface_in.clear_elements();
    mg_interface_out.resize(0, n_levels - 1);
    mg_interface_out.clear_elements();
    mg_matrices.resize(0, n_levels - 1);
    mg_matrices.clear_elements();
    mg_sparsity_patterns.resize(0, n_levels - 1);

// 现在，我们必须在每个层面上提供一个矩阵。为此，我们首先使用 MGTools::make_sparsity_pattern 函数在每个层次上生成一个初步的压缩稀疏模式（关于这个主题的更多信息，请参见 @ref Sparsity 模块），然后把它复制到我们真正想要的那个层次上。下一步是用这些稀疏模式初始化两种层次矩阵。

// 值得指出的是，界面矩阵只有位于较粗的网格和较细的网格之间的界面上的自由度条目。因此，它们甚至比我们多网格层次结构中的各个层次的矩阵还要稀少。如果我们更关心内存的使用（可能还有我们使用这些矩阵的速度），我们应该对这两种矩阵使用不同的稀疏性模式。

    for (unsigned int level = 0; level < n_levels; ++level)
      {
        DynamicSparsityPattern dsp(dof_handler.n_dofs(level),
                                   dof_handler.n_dofs(level));
        MGTools::make_sparsity_pattern(dof_handler, dsp, level);

        mg_sparsity_patterns[level].copy_from(dsp);

        mg_matrices[level].reinit(mg_sparsity_patterns[level]);
        mg_interface_in[level].reinit(mg_sparsity_patterns[level]);
        mg_interface_out[level].reinit(mg_sparsity_patterns[level]);
      }
  }
// @sect4{LaplaceProblem::assemble_system}

// 下面的函数将线性系统装配在网格的最细层上。由于我们想在下面的层次装配中重用这里的代码，我们使用本地积分器类LaplaceIntegrator，而将循环留给MeshWorker框架。因此，这个函数首先设置了这个框架所需的对象，即

// - 一个 MeshWorker::IntegrationInfoBox 对象，它将提供单元格上正交点的所有需要的数据。这个对象可以看作是FEValues的扩展，提供更多的有用信息。

// - 一个 MeshWorker::DoFInfo 对象，它一方面扩展了单元格迭代器的功能，另一方面也为其基类LocalResults的返回值提供了空间。

// - 一个汇编器，在这里是指整个系统。这里的 "简单 "指的是全局系统没有一个块状结构。

// - 本地集成器，它实现了实际的形式。

// 在循环将所有这些组合成一个矩阵和一个右手边之后，还有一件事要做：集合器对受限自由度的矩阵行和列不做任何处理。因此，我们在对角线上放一个一，使整个系统摆好。一的值或任何固定的值都有一个好处，即它对矩阵的频谱的影响很容易理解。由于相应的特征向量形成了一个不变的子空间，所选择的值不会影响Krylov空间求解器的收敛性。

  template <int dim>
  void LaplaceProblem<dim>::assemble_system()
  {
    MappingQ1<dim>                      mapping;
    MeshWorker::IntegrationInfoBox<dim> info_box;
    UpdateFlags                         update_flags =
      update_values | update_gradients | update_hessians;
    info_box.add_update_flags_all(update_flags);
    info_box.initialize(fe, mapping);

    MeshWorker::DoFInfo<dim> dof_info(dof_handler);

    MeshWorker::Assembler::SystemSimple<SparseMatrix<double>, Vector<double>>
      assembler;
    assembler.initialize(constraints);
    assembler.initialize(system_matrix, system_rhs);

    LaplaceIntegrator<dim> matrix_integrator;
    MeshWorker::integration_loop<dim, dim>(dof_handler.begin_active(),
                                           dof_handler.end(),
                                           dof_info,
                                           info_box,
                                           matrix_integrator,
                                           assembler);

    for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
      if (constraints.is_constrained(i))
        system_matrix.set(i, i, 1.);
  }
// @sect4{LaplaceProblem::assemble_multigrid}

// 下一个函数是建立线性算子（矩阵），定义每一级网格上的多栅方法。积分的核心和上面的一样，但是下面的循环会遍历所有已有的单元，而不仅仅是活动的单元，而且结果必须输入正确的层次矩阵。幸运的是，MeshWorker对我们隐藏了大部分的内容，因此这个函数和之前的函数的区别只在于汇编器的设置和循环中不同的迭代器。另外，最后修复矩阵的过程也比较复杂。

  template <int dim>
  void LaplaceProblem<dim>::assemble_multigrid()
  {
    MappingQ1<dim>                      mapping;
    MeshWorker::IntegrationInfoBox<dim> info_box;
    UpdateFlags                         update_flags =
      update_values | update_gradients | update_hessians;
    info_box.add_update_flags_all(update_flags);
    info_box.initialize(fe, mapping);

    MeshWorker::DoFInfo<dim> dof_info(dof_handler);

    MeshWorker::Assembler::MGMatrixSimple<SparseMatrix<double>> assembler;
    assembler.initialize(mg_constrained_dofs);
    assembler.initialize(mg_matrices);
    assembler.initialize_interfaces(mg_interface_in, mg_interface_out);

    LaplaceIntegrator<dim> matrix_integrator;
    MeshWorker::integration_loop<dim, dim>(dof_handler.begin_mg(),
                                           dof_handler.end_mg(),
                                           dof_info,
                                           info_box,
                                           matrix_integrator,
                                           assembler);

    const unsigned int nlevels = triangulation.n_levels();
    for (unsigned int level = 0; level < nlevels; ++level)
      {
        for (unsigned int i = 0; i < dof_handler.n_dofs(level); ++i)
          if (mg_constrained_dofs.is_boundary_index(level, i) ||
              mg_constrained_dofs.at_refinement_edge(level, i))
            mg_matrices[level].set(i, i, 1.);
      }
  }

//  @sect4{LaplaceProblem::solve}

// 这是另外一个在支持多栅求解器（或者说，事实上，我们使用多栅方法的前提条件）方面有明显不同的函数。

// 让我们从建立多层次方法的两个组成部分开始：层次间的转移运算器和最粗层次上的求解器。在有限元方法中，转移算子来自所涉及的有限元函数空间，通常可以用独立于所考虑问题的通用方式计算。在这种情况下，我们可以使用MGTransferPrebuilt类，给定最终线性系统的约束和MGConstrainedDoFs对象，该对象知道每个层次的边界条件和不同细化层次之间接口的自由度，可以从具有层次自由度的DoFHandler对象中建立这些转移操作的矩阵。

// 下面几行的第二部分是关于粗略网格求解器的。由于我们的粗网格确实非常粗，我们决定采用直接求解器（最粗层次矩阵的Householder分解），即使其实现不是特别复杂。如果我们的粗网格比这里的5个单元多得多，那么这里显然需要更合适的东西。

  template <int dim>
  void LaplaceProblem<dim>::solve()
  {
    MGTransferPrebuilt<Vector<double>> mg_transfer(mg_constrained_dofs);
    mg_transfer.build(dof_handler);

    FullMatrix<double> coarse_matrix;
    coarse_matrix.copy_from(mg_matrices[0]);
    MGCoarseGridHouseholder<double, Vector<double>> coarse_grid_solver;
    coarse_grid_solver.initialize(coarse_matrix);

// 多级求解器或预处理器的下一个组成部分是，我们需要在每一级上有一个平滑器。这方面常见的选择是使用松弛方法的应用（如SOR、Jacobi或Richardson方法）或求解器方法的少量迭代（如CG或GMRES）。 mg::SmootherRelaxation 和MGSmootherPrecondition类为这两种平滑器提供支持。这里，我们选择应用单一的SOR迭代。为此，我们定义一个适当的别名，然后设置一个平滑器对象。

// 最后一步是用我们的水平矩阵初始化平滑器对象，并设置一些平滑参数。 initialize() 函数可以有选择地接受额外的参数，这些参数将被传递给每一级的平滑器对象。在当前SOR平滑器的情况下，这可能包括一个松弛参数。然而，我们在这里将这些参数保留为默认值。对 set_steps() 的调用表明我们将在每个级别上使用两个前平滑步骤和两个后平滑步骤；为了在不同级别上使用可变数量的平滑器步骤，可以在对 mg_smoother 对象的构造函数调用中设置更多选项。

// 最后一步的结果是我们使用SOR方法作为平滑器的事实

// --这不是对称的

// 但我们在下面使用共轭梯度迭代（需要对称的预处理），我们需要让多级预处理确保我们得到一个对称的算子，即使是非对称的平滑器。

    using Smoother = PreconditionSOR<SparseMatrix<double>>;
    mg::SmootherRelaxation<Smoother, Vector<double>> mg_smoother;
    mg_smoother.initialize(mg_matrices);
    mg_smoother.set_steps(2);
    mg_smoother.set_symmetric(true);

// 下一个准备步骤是，我们必须将我们的水平矩阵和接口矩阵包裹在一个具有所需乘法函数的对象中。我们将为从粗到细的接口对象创建两个对象，反之亦然；多网格算法将在以后的操作中使用转置运算器，允许我们用已经建立的矩阵初始化该运算器的上下版本。

    mg::Matrix<Vector<double>> mg_matrix(mg_matrices);
    mg::Matrix<Vector<double>> mg_interface_up(mg_interface_in);
    mg::Matrix<Vector<double>> mg_interface_down(mg_interface_out);

// 现在，我们准备设置V型循环算子和多级预处理程序。

    Multigrid<Vector<double>> mg(
      mg_matrix, coarse_grid_solver, mg_transfer, mg_smoother, mg_smoother);
    mg.set_edge_matrices(mg_interface_down, mg_interface_up);

    PreconditionMG<dim, Vector<double>, MGTransferPrebuilt<Vector<double>>>
      preconditioner(dof_handler, mg, mg_transfer);

// 有了这一切，我们终于可以用通常的方法来解决这个线性系统了。

    SolverControl            solver_control(1000, 1e-12);
    SolverCG<Vector<double>> solver(solver_control);

    solution = 0;

    solver.solve(system_matrix, solution, system_rhs, preconditioner);
    constraints.distribute(solution);
  }

//  @sect4{Postprocessing}

// 下面两个函数在计算出解决方案后对其进行后处理。特别是，第一个函数在每个周期开始时细化网格，第二个函数在每个周期结束时输出结果。这些函数与 step-6 中的函数几乎没有变化，只有一个小的区别：我们以VTK格式生成输出，以使用当今更现代的可视化程序，而不是 step-6 编写时的那些。

  template <int dim>
  void LaplaceProblem<dim>::refine_grid()
  {
    Vector<float> estimated_error_per_cell(triangulation.n_active_cells());

    KellyErrorEstimator<dim>::estimate(
      dof_handler,
      QGauss<dim - 1>(fe.degree + 1),
      std::map<types::boundary_id, const Function<dim> *>(),
      solution,
      estimated_error_per_cell);
    GridRefinement::refine_and_coarsen_fixed_number(triangulation,
                                                    estimated_error_per_cell,
                                                    0.3,
                                                    0.03);
    triangulation.execute_coarsening_and_refinement();
  }

  template <int dim>
  void LaplaceProblem<dim>::output_results(const unsigned int cycle) const
  {
    DataOut<dim> data_out;

    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(solution, "solution");
    data_out.build_patches();

    std::ofstream output("solution-" + std::to_string(cycle) + ".vtk");
    data_out.write_vtk(output);
  }
// @sect4{LaplaceProblem::run}

// 和上面的几个函数一样，这几乎是对  step-6  中相应函数的复制。唯一的区别是对 assemble_multigrid 的调用，它负责形成我们在多网格方法中需要的每一层的矩阵。

  template <int dim>
  void LaplaceProblem<dim>::run()
  {
    for (unsigned int cycle = 0; cycle < 8; ++cycle)
      {
        deallog << "Cycle " << cycle << std::endl;

        if (cycle == 0)
          {
            GridGenerator::hyper_ball(triangulation);
            triangulation.refine_global(1);
          }
        else
          refine_grid();

        deallog << "   Number of active cells:       "
                << triangulation.n_active_cells() << std::endl;

        setup_system();

        assemble_system();
        assemble_multigrid();

        solve();
        output_results(cycle);
      }
  }
} // namespace Step16
// @sect3{The main() function}

// 这又是与 step-6 中相同的函数。

int main()
{
  try
    {
      using namespace Step16;

      deallog.depth_console(2);

      LaplaceProblem<2> laplace_problem(1);
      laplace_problem.run();
    }
  catch (std::exception &exc)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Exception on processing: " << std::endl
                << exc.what() << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;

      return 1;
    }
  catch (...)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Unknown exception!" << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }

  return 0;
}


/**
 * @file mmchecker.hpp:  Cube_Product_Checker template class
 * and its implementation.
 *
 * Class is for finding Strassen-like algorithms for cube product.
 *
 * @author Eugene Petkevich
 * @version pre-alpha
 */

#ifndef MMCHECKER_HPP_INCLUDED
#define MMCHECKER_HPP_INCLUDED

#include <algorithm>
#include <iostream>
#include <fstream>
#include <set>
#include <vector>
#include <map>
#include <string>
#include <numeric>
#include <omp.h>

#include <boost/dynamic_bitset.hpp>
#include <boost/algorithm/string/join.hpp>

#include "slae.hpp"
#include "utils.hpp"

using namespace std;

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

class Solution_Properties;

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

/**
 * Main class, calculates all nesessary information
 * for given template parameters:
 *
 * @param N: size of the cube;
 * @param D: dimension of the cube;
 * @param NM: number of bits in multiplication vectors ((N^D)^D);
 * @param NMH: number of elements in the array (N^D).
 */
template <int N, int D, size_t NM, size_t NMH>
class Cube_Product_Checker {
public:
    typedef mm_bitset<NM> Multiplication_Vector;
    typedef mm_bitset<NMH> Multiplication_Part_Vector;
    typedef set<int> Candidate;
    int length; /// size of cube (N)
    int dimension; /// dimension of cube (D)
    int element_count; /// number of elements in cube (N^D)
    mm_vector_with_properties_options vector_options; /// options for vector properties
    mm_vector_with_properties<NM>* r_vectors; /// result vectors
    mm_vector_with_properties<NM>* m_vectors; /// multiplication vectors
    bool owns_arrays; /// If arrays are created by the object.
    int m_count; /// number of non-zero multiplication vectors = (2^(N^D)-1)^D
    int m_length; /// number non-zero element sums = 2^(N^D)-1
    int f_count; /// number of vectors to choose for a basis (for minimal improvement it is N^(D+1)-1)
    set<int> good_vectors_indexes; /// set of indexes for vectors that are in the current span
    set<int> n_vectors_indexes; /// set of indexes of current chosen vectors
    set<Candidate> neighbours; /// set of neighbours;
    set<set<int>> solutions; /// set of found unique solutions
    map<set<int>, int> solution_distribution; /// set of all found solutions
    int iteration_count; /// number of finished iterations
    int local_max_iterations; /// number of iterations in probable local maximum
    int restarts; /// number of restarts
    int local_iterations; /// number of iterations
    int checked_sets_count; /// number of sets checked
    int raw_solution_count; /// number of found solutions, including duplicates
    int best_result; /// maximum number of vectors in the span found
    int lin_dependent_sets;
    Timewatch tw; /// timer that is used for getting calculation time
    Random rnd; /// random number generator
    int thread_number; /// thread number
    map<Candidate, int> candidate_cache; /// cache of already checked candidates
    int cache_limit; /// limit of the cache size
    int cache_hits; /// cache hits
    bool* stop_signal; /// if execution should be stopped
    int bit_check_hits; /// number of bit check hits
    int gaussian_eliminations; /// number of gaussian eliminations
    set<set<int>> top_best_solutions; /// best solutions found so far
    int top_best_result; /// best result so far
    set<set<int>> candidate_space; /// candidate space

    //=============--- constructors and destructors
    Cube_Product_Checker();
    ~Cube_Product_Checker();

    //=============--- index operations
    /// return linear index in a bit vector by its indexes in matrices
    int get_vector_index(int ai, int aj, int bi, int bj) const; /// for 2-dimensional case
    int get_vector_index(int ai, int aj, int ak, int bi, int bj, int bk, int ci, int cj, int ck) const; /// for 3-d case
    /// return linear index in a bit vector by combined indexes in matrices
    int get_vector_index(int a, int b) const; /// for 2-d case
    int get_vector_index(int a, int b, int c) const; /// for 3-d case
    /// return indices from bit index
    void decode_indices_from_index(int index, int& ai, int& aj, int& bi, int& bj) const; /// for 2-d case
    void decode_indices_from_index(int index, int& ai, int& aj, int& ak, int& bi, int& bj, int& bk, int& ci, int& cj, int& ck) const; /// for 3-d case
    /// return linear index of an element in a matrix
    int get_element_index(int i, int j) const; /// for 2-d case
    int get_element_index(int i, int j, int k) const; /// for 3-d case
    /// return index of multiplication vector in the set
    int get_m_index(int i, int j) const; /// for 2-d case
    int get_m_index(int i, int j, int k) const; /// for 3-d case
    vector<int> decode_m_index(int index) const; /// return the coefficients from m-vector index

    //=============--- Initial calculations
    void init(int mult_count, const string& filename, const string& space_filename); /// calculate all properties
    void init(const Cube_Product_Checker& cpc); /// Link to all properties in other object.
    void calculate_r_vectors(); /// write result vectors to array
    void calculate_m_vectors(); /// write multiplication vectors to array

    //=============--- checking routines
    void clear_sets(); /// clear current sets of vectors
    void add_vector_to_set(int index); /// add vector to current sets
    bool check_vectors_for_goodness(); /// check current set of vectors
    void clear_statistics(); /// clear statistics of solutions
    void make_random_candidate(); /// make random candidate solution
    void make_candidate_from_space(); /// make random candidate from restricted space
    bool check_cache(); /// check if candidate result is in cache
    void update_cache(); /// update the cache with new result

    //=============--- searching for solution
    bool check_for_good_vectors(); /// check all solution space
    bool check_for_good_vectors_randomized(); /// do random search
    bool solve_hill_climbing(int local_max_limit, bool use_space); /// do local search
    void start_neighbourhood(); /// make list of neighbours

    //=============--- utilities
    void output_vector(Multiplication_Vector v) const; /// output vector to screen
    void output_vector_text(Multiplication_Vector v) const; /// output vector to screen in letters
    void save_random_samples(int size, const char* filename) const; /// save random sets to a file (for testing later)
    void read_samples_and_check(const char* filename, const char* filenameout) const; /// check sets from a file
    void output_current_state() const; // output current state of the checker
    void read_m_vectors(const string& filename); /// read m_vectors from file
    void read_candidate_space(const string& filename); /// read candidate space from file
    void write_m_vectors(const string& filename); /// write m_vectors into file

    //=============--- Statistics and results
    void save_results(const char* filename); /// save results to a file
    bool check_solution(set<int> s, Solution_Properties& sp); /// check if a solution is valid
    void save_solution_properties(const Solution_Properties& sp, const char* filename); /// output solution properties to a file
    vector<int> sum_operations_cube(int index); /// number of summation operations inside cubes
};

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

class Solution_Properties {
public:
    set<int> multiplication_vectors; /// multiplication vector indices
    vector<boost::dynamic_bitset<>> coefficients; /// result vector coefficients
    int operation_count; /// number of addition operations used for calculating result matrix overall
};

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

/**
 * Class constructor.
 *
 * Create an object for work with D-dimensional cubes of size N.
 */
template <int N, int D, size_t NM, size_t NMH>
Cube_Product_Checker<N, D, NM, NMH>::
Cube_Product_Checker() :
    owns_arrays(false)
{
    length = N;
    dimension = D;
    element_count = power(length, dimension);
    f_count = power(length,dimension+1)-1;
    m_length = power(2,element_count)-1;
    m_count = power(m_length,dimension);
    cache_limit = 3000000;
    cache_hits = 0;
    bit_check_hits = 0;
    gaussian_eliminations = 0;
    rnd.init(0, m_count-1);
#ifdef VERBOSE_OUTPUT
    cout << "Cube Product Checker has been created." << endl;
#endif // VERBOSE_OUTPUT
}

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

/**
 * Destructor.
 */
template <int N, int D, size_t NM, size_t NMH>
Cube_Product_Checker<N, D, NM, NMH>::
~Cube_Product_Checker()
{
    if (owns_arrays) {
        delete [] r_vectors;
        delete [] m_vectors;
        delete stop_signal;
    }
}

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

/**
 * Initialize the object with all necessary properties.
 *
 * @param mult_count:  number of multiplications in resulting algorithm.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
init(int mult_count, const string& filename, const string& space_filename)
{
    f_count = mult_count;
    stop_signal = new bool(false);
    r_vectors = new mm_vector_with_properties<NM>[element_count];
    m_vectors = new mm_vector_with_properties<NM>[m_count];
    mm_vector_with_properties<NM>::make_options(vector_options);
    owns_arrays = true;
#ifdef VERBOSE_OUTPUT
    tw.watch();
#endif // VERBOSE_OUTPUT
    calculate_r_vectors();
    for (int i = 0; i < element_count; ++i) {
        r_vectors[i].calculate_properties(vector_options);
    }
#ifdef VERBOSE_OUTPUT
    cout << "[" << tw.watch() << " s] Result vectors calculated." << endl;
#endif // VERBOSE_OUTPUT
    if (filename.length() > 0) {
        read_m_vectors(filename);
    } else {
        calculate_m_vectors();
        for (int i = 0; i < m_count; ++i) {
            m_vectors[i].calculate_properties(vector_options);
        }
    }
    if (space_filename.length() > 0) {
        read_candidate_space(space_filename);
    }
#ifdef VERBOSE_OUTPUT
    cout << "[" << tw.watch() << " s] Multiplication vectors calculated." << endl;
#endif // VERBOSE_OUTPUT
}

/**
 * Initialize the object with all necessary properties from other object.
 *
 * Link to all big data fields in the other object.
 *
 * @param cpc: the object to get main arrays from.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
init(const Cube_Product_Checker& cpc)
{
    f_count = cpc.f_count;
    r_vectors = cpc.r_vectors;
    m_vectors = cpc.m_vectors;
    vector_options = cpc.vector_options;
    stop_signal = cpc.stop_signal;
    candidate_space = cpc.candidate_space;
}

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

/**
 * Get bit index in a multiplication vector (2-dimensional case).
 *
 * @param ai: element's first index in the first matrix;
 * @param aj: element's second index in the first matrix;
 * @param bi: element's first index in the second matrix;
 * @param bj: element's second index in the second matrix;
 *
 * @return bit index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_vector_index(int ai, int aj,
                 int bi, int bj) const
{
    int result = ai;
    result *= length;
    result += aj;
    result *= length;

    result += bi;
    result *= length;
    result += bj;
    return result;
}

/**
 * Get bit index in a multiplication vector (3-dimensional case).
 *
 * @param ai: element's first index in the first cube;
 * @param aj: element's second index in the first cube;
 * @param ak: element's third index in the first cube;
 * @param bi: element's first index in the second cube;
 * @param bj: element's second index in the second cube;
 * @param bk: element's third index in the second cube;
 * @param ci: element's first index in the third cube;
 * @param cj: element's second index in the third cube;
 * @param ck: element's third index in the third cube.
 *
 * @return bit index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_vector_index(int ai, int aj, int ak,
                 int bi, int bj, int bk,
                 int ci, int cj, int ck) const
{
    int result = ai;
    result *= length;
    result += aj;
    result *= length;
    result += ak;
    result *= length;

    result += bi;
    result *= length;
    result += bj;
    result *= length;
    result += bk;
    result *= length;

    result += ci;
    result *= length;
    result += cj;
    result *= length;
    result += ck;
    return result;
}

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

/**
 * Get bit index in a multiplication vector (2-dimensional case).
 *
 * @param a: element'S index in the first matrix;
 * @param b: element'S index in the second matrix.
 *
 * @return bit index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_vector_index(int a, int b) const
{
    int result = a;
    result *= element_count;
    result += b;
    return result;
}

/**
 * Get bit index in a multiplication vector (3-dimensional case).
 *
 * @param a: element's index in the first cube;
 * @param b: element's index in the second cube;
 * @param c: element's index in the third cube.
 *
 * @return bit index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_vector_index(int a, int b, int c) const
{
    int result = a;
    result *= element_count;
    result += b;
    result *= element_count;
    result += c;
    return result;
}

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

/**
 * Get element indices in matrices from bit index in the multiplication vector
 * (2-dimensional case).
 *
 * @param index: bit index in a multiplication vector;
 *
 * @param ai: element's first index in the first matrix;
 * @param aj: element's second index in the first matrix;
 * @param bi: element's first index in the second matrix;
 * @param bj: element's second index in the second matrix.
 */
template <int N, int D, size_t NM, size_t NMH>
inline void
Cube_Product_Checker<N, D, NM, NMH>::
decode_indices_from_index(int index,
                          int& ai, int& aj,
                          int& bi, int& bj) const
{
    bj = index % length;
    index /= length;
    bi = index % length;;
    index /= length;
    aj = index % length;
    index /= length;
    ai = index;
    return;
}

/**
 * Get element indices in cubes from bit index in a multiplication vector
 * (2-dimensional case).
 *
 * @param index: bit index in the multiplication vector;
 *
 * @param ai: element's first index in the first cube;
 * @param aj: element's second index in the first cube;
 * @param ak: element's third index in the first cube;
 * @param bi: element's first index in the second cube;
 * @param bj: element's second index in the second cube;
 * @param bk: element's third index in the second cube;
 * @param ci: element's first index in the third cube;
 * @param cj: element's second index in the third cube;
 * @param ck: element's third index in the third cube.
 */
template <int N, int D, size_t NM, size_t NMH>
inline void
Cube_Product_Checker<N, D, NM, NMH>::
decode_indices_from_index(int index,
                          int& ai, int& aj, int& ak,
                          int& bi, int& bj, int& bk,
                          int& ci, int& cj, int& ck) const
{
    ck = index % length;
    index /= length;
    cj = index % length;
    index /= length;
    ci = index % length;
    index /= length;

    bk = index % length;
    index /= length;
    bj = index % length;
    index /= length;
    bi = index % length;
    index /= length;

    ak = index % length;
    index /= length;
    aj = index % length;
    index /= length;
    ai = index;
    return;
}

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

/**
 * Get element's index in a matrix (2-dimensional case).
 *
 * @param i: element's first index in the cube;
 * @param j: element's second index in the cube;
 *
 * @return element's index in the cube.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_element_index(int i, int j) const
{
    return ((i*length) + j);
}

/**
 * Get element's index in a cube (3-dimensional case).
 *
 * @param i: element's first index in the cube;
 * @param j: element's second index in the cube;
 * @param k: element's third index in the cube;
 *
 * @return element's index in the cube.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_element_index(int i, int j, int k) const
{
    return (((i*length) + j)*length + k);
}

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

/**
 * Get index of a multiplication vector by its sum indices.
 *
 * This index is a unique number of the vector
 * in the set of all non-zero multiplication vectors.
 *
 * @param i: vector's first index;
 * @param j: vector's second index;
 *
 * @return multiplication vector index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_m_index(int i, int j) const
{
    return ((i*m_length) + j);
}

/**
 * Get index of a multiplication vector by its sum indices.
 *
 * This index is a unique number of the vector
 * in the set of all non-zero multiplication vectors.
 *
 * @param i: vector's first index;
 * @param j: vector's second index;
 * @param k: vector's third index;
 *
 * @return multiplication vector index.
 */
template <int N, int D, size_t NM, size_t NMH>
inline int
Cube_Product_Checker<N, D, NM, NMH>::
get_m_index(int i, int j, int k) const
{
    return (((i*m_length) + j)*m_length + k);
}

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

/**
 * Get coefficients of cube elements by number of multiplication vector.
 *
 * @param multiplication vector index.
 *
 * @return
 */
template <int N, int D, size_t NM, size_t NMH>
inline vector<int>
Cube_Product_Checker<N, D, NM, NMH>::
decode_m_index(int index) const
{
    int last, previous;
    last = index % m_length;
    index /= m_length;
    previous = index % m_length;
    vector<int> result;
    if (dimension == 3) {
        int first = index / m_length;
        result.push_back(first);
    }
    result.push_back(previous);
    result.push_back(last);
    return result;
}

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

/**
 * Calculate product result vectors (2x2 case).
 */
template <>
void
Cube_Product_Checker<2, 2, 16, 4>::
calculate_r_vectors()
{
    for (int i = 0; i < length; ++i) {
        for (int j = 0; j < length; ++j) {
            int index = get_element_index(i, j);
            r_vectors[index].v.reset();
            for (int l = 0; l < length; ++l) {
                r_vectors[index].v[get_vector_index(i, l, l, j)] = 1;
            }
        }
    }
}

/**
 * Calculate product result vectors (3x3 case).
 */
template <>
void
Cube_Product_Checker<3, 2, 81, 9>::
calculate_r_vectors()
{
    for (int i = 0; i < length; ++i) {
        for (int j = 0; j < length; ++j) {
            int index = get_element_index(i, j);
            r_vectors[index].v.reset();
            for (int l = 0; l < length; ++l) {
                r_vectors[index].v[get_vector_index(i, l, l, j)] = 1;
            }
        }
    }
}

/**
 * Calculate product result vectors (2x2x2 case).
 */
template <>
void
Cube_Product_Checker<2, 3, 512, 8>::
calculate_r_vectors()
{
    for (int i = 0; i < length; ++i) {
        for (int j = 0; j < length; ++j) {
            for (int k = 0; k < length; ++k) {
                int index = get_element_index(i, j, k);
                r_vectors[index].v.reset();
                for (int l = 0; l < length; ++l) {
                    r_vectors[index].v[get_vector_index(i, j, l, i, l, k, l, j, k)] = 1;
                }
            }
        }
    }
}

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

/**
 * Calculate non-zero multiplication vectors (2x2 case).
 */
template <>
void
Cube_Product_Checker<2, 2, 16, 4>::
calculate_m_vectors()
{
    for (int i = 1; i < power(2,element_count); ++i) {
        for (int j = 1; j < power(2,element_count); ++j) {
            Multiplication_Part_Vector av(i);
            Multiplication_Part_Vector bv(j);
            int index = get_m_index(i-1, j-1);
            m_vectors[index].v.reset();
            for (int k = 0; k < element_count; ++k) {
                for (int l = 0; l < element_count; ++l) {
                    if (av[k] && bv[l]) {
                        m_vectors[index].v.set(get_vector_index(k, l));
                    }
                }
            }
        }
    }
}

/**
 * Calculate non-zero multiplication vectors (3x3 case).
 */
template <>
void
Cube_Product_Checker<3, 2, 81, 9>::
calculate_m_vectors()
{
    for (int i = 1; i < power(2,element_count); ++i) {
        for (int j = 1; j < power(2,element_count); ++j) {
            Multiplication_Part_Vector av(i);
            Multiplication_Part_Vector bv(j);
            int index = get_m_index(i-1, j-1);
            m_vectors[index].v.reset();
            for (int k = 0; k < element_count; ++k) {
                for (int l = 0; l < element_count; ++l) {
                    if (av[k] && bv[l]) {
                        m_vectors[index].v.set(get_vector_index(k, l));
                    }
                }
            }
        }
    }
}

/**
 * Calculate non-zero multiplication vectors (3-d case).
 */
template <>
void
Cube_Product_Checker<2, 3, 512, 8>::
calculate_m_vectors()
{
    for (int i = 1; i < power(2,element_count); ++i) {
        for (int j = 1; j < power(2,element_count); ++j) {
            for (int k = 1; k < power(2,element_count); ++k) {
                Multiplication_Part_Vector av(i);
                Multiplication_Part_Vector bv(j);
                Multiplication_Part_Vector cv(k);
                int index = get_m_index(i-1, j-1, k-1);
                m_vectors[index].v.reset();
                for (int l = 0; l < element_count; ++l) {
                    for (int o = 0; o < element_count; ++o) {
                        for (int p = 0; p < element_count; ++p) {
                            if (av[l] & bv[o] & cv[p]) {
                                m_vectors[index].v.set(get_vector_index(l, o, p));
                            }
                        }
                    }
                }
            }
        }
    }
}

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

/**
 * Clear the current sets.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
clear_sets()
{
    n_vectors_indexes.clear();
    good_vectors_indexes.clear();
}

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

/**
 * Add a multiplication vector to the current set.
 *
 * @param index: index of the multiplication vector.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
add_vector_to_set(int index)
{
    n_vectors_indexes.insert(index);
}

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

/**
 * Clear statistics.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
clear_statistics()
{
    checked_sets_count = 0;
    iteration_count = 0;
    best_result = 0;
    lin_dependent_sets = 0;
#ifdef OUTPUT_STATISTICS
    raw_solution_count = 0;
    solutions.clear();
    solution_distribution.clear();
#endif // OUTPUT_STATISTICS
}

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

/**
 * Make a random candidate.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
make_random_candidate()
{
    clear_sets();
    for (int i = 0; i < (f_count-element_count); ++i) {
        int cc = rnd.next();
        while (n_vectors_indexes.count(cc) > 0) {
            cc = rnd.next();
        }
        add_vector_to_set(cc);
    }
}

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

/**
 * Make a random candidate.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
make_candidate_from_space()
{
    clear_sets();
    n_vectors_indexes = (*candidate_space.begin());
}

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

/**
 * If current candidate is in cache, than get info from cache.
 *
 * @return true if the current candidate is in cache.
 */
template <int N, int D, size_t NM, size_t NMH>
bool
Cube_Product_Checker<N, D, NM, NMH>::
check_cache()
{
    map<Candidate, int>::const_iterator it;
    if ((it = candidate_cache.find(n_vectors_indexes)) != candidate_cache.end()) {
        best_result = it->second;
        ++cache_hits;
        return true;
    } else {
        return false;
    }
}

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

/**
 * Update the cache with new result.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
update_cache()
{
    if (candidate_cache.size() >= cache_limit) {
        candidate_cache.clear();
        //cout << "------------->> Cache cleared" << endl;
    }
    candidate_cache[n_vectors_indexes] = best_result;
}

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

/**
 * Check the current set for being a solution.
 *
 * @return true if the current set is a solution.
 */
template <int N, int D, size_t NM, size_t NMH>
bool
Cube_Product_Checker<N, D, NM, NMH>::
check_vectors_for_goodness()
{
#ifdef VERY_DETAILED_OUTPUT
    cout << "<" << thread_number << "> ";
    cout << "Checking of vectors { ";
    for (int i: n_vectors_indexes) {
        cout << i << " ";
    }
    cout << "} has started..." << endl;
    tw.watch();
#endif // VERY_DETAILED_OUTPUT
    ++checked_sets_count;
#ifdef USE_CACHE
    if (check_cache()) {
        if (best_result == f_count) {
            return true;
        } else {
            return false;
        }
    }
#endif // USE_CACHE
    vector<mm_vector_with_properties<NM>> nvwp; /// set of span vectors for SLAE
    vector<mm_vector_with_properties<NM>> gvwp; /// set of good vectors for SLAE
    Vectors_Presolve_Data<NM> v; /// vector presolve data for span vectors
    Gauss_WP_Presolve_Data<NM> pwp(0); /// presolve data for SLAE for span vectors
    Gauss_WP_Presolve_Data<NM> pwpg(0); /// presolve data for SLAE for good vectors

    for (set<int>::iterator i = n_vectors_indexes.begin(); i != n_vectors_indexes.end(); ++i) {
        nvwp.push_back(m_vectors[*i]);
        v.add_vector(m_vectors[*i].v);
    }
    for (int i = 0; i < element_count; ++i) {
        nvwp.push_back(r_vectors[i]);
        v.add_vector(r_vectors[i].v);
    }
    if (!gauss_wp_presolve(nvwp, pwp)) { // vectors are linearly dependent
        ++lin_dependent_sets;
#ifdef USE_CACHE
        update_cache();
#endif // USE_CACHE
        return false;
    }
    gauss_wp_presolve(gvwp, pwpg);
    v.presolve();
    for (int i = 0; i < m_count; ++i) {
        //if (!v.check(m_vectors[i].v)) { // discard vector by checking bits
        //    ++bit_check_hits;
        //    continue;
        //}
#ifdef OUTPUT_STATISTICS
        ++gaussian_eliminations;
#endif // OUTPUT_STATISTICS
        if (gauss_wp_solve(pwp, m_vectors[i])) { // if vector is in the current span
#ifdef OUTPUT_STATISTICS
            ++gaussian_eliminations;
#endif // OUTPUT_STATISTICS
            if (!gauss_wp_solve(pwpg, m_vectors[i])) { // if not linearly dependent
                good_vectors_indexes.insert(i);
                gvwp.push_back(m_vectors[i]);
                gauss_wp_presolve(gvwp, pwpg);
            }
            if (good_vectors_indexes.size() >= f_count) // we have found solution
                break;
        }
    }
#ifdef VERY_DETAILED_OUTPUT
    cout << "  [" << tw.watch() << " s] Done." << endl;
#endif // VERY_DETAILED_OUTPUT
    best_result = good_vectors_indexes.size();
    if (best_result > (f_count-element_count)) {
        if (best_result >= top_best_result) {
            if (best_result > top_best_result) {
                top_best_result = best_result;
                top_best_solutions.clear();
#ifdef SAVE_BEST_RESULTS_TO_FILE
                ofstream fout(to_string(thread_number)+string("bestsofar.txt"), ios_base::app);
                fout << top_best_result << " vectors" << endl;
                fout.close();
#endif // SAVE_BEST_RESULTS_TO_FILE
            }
            top_best_solutions.insert(n_vectors_indexes);
#ifdef SAVE_BEST_RESULTS_TO_FILE
            ofstream fout(to_string(thread_number)+string("bestsofar.txt"), ios_base::app);
            for (set<int>::iterator cc = n_vectors_indexes.begin(); cc != n_vectors_indexes.end(); ++cc) {
                fout << *cc << " ";
            }
            fout << endl;
            fout.close();
#endif // SAVE_BEST_RESULTS_TO_FILE
        }
    }
#ifdef USE_CACHE
        update_cache();
#endif // USE_CACHE
    if (good_vectors_indexes.size() >= f_count) { // there is a solution
#ifdef VERBOSE_OUTPUT
        cout << "  Good vectors have been found: { ";
        for (set<int>::iterator cc = good_vectors_indexes.begin(); cc != good_vectors_indexes.end(); ++cc) {
            cout << *cc << " ";
        }
        cout << "}" << endl;
#endif // VERBOSE_OUTPUT
#ifdef OUTPUT_SOLUTIONS_TO_FILE
        ofstream mvfile("good.txt");
        mvfile << "Found good vectors!!!\n";
        for (set<int>::iterator cc = good_vectors_indexes.begin(); cc != good_vectors_indexes.end(); ++cc) {
            mvfile << *cc << " ";
        }
        mvfile << "\n";
#endif // OUTPUT_SOLUTIONS_TO_FILE
        return true;
    }
    return false;
}

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

/**
 * Check all solution space for solutions (2x2 case).
 *
 * @return true if at least one solution was found.
 */
template <>
bool
Cube_Product_Checker<2, 2, 16, 4>::
check_for_good_vectors()
{
    clear_statistics();
    for (int c1 = 0; c1 < m_count-2; ++c1) {
        //cout << c1 << endl;
        for (int c2 = c1+1; c2 < m_count-1; ++c2) {
            for (int c3 = c2+1; c3 < m_count; ++c3) {
                if (*stop_signal)
                    return false;
                clear_sets();
                add_vector_to_set(c1);
                add_vector_to_set(c2);
                add_vector_to_set(c3);
                if (check_vectors_for_goodness()) {
#ifdef OUTPUT_STATISTICS
                    solutions.insert(good_vectors_indexes);
                    ++solution_distribution[good_vectors_indexes];
                    ++raw_solution_count;
#endif // OUTPUT_STATISTICS
                }
            }
        }
    }
    return true;
}

/**
 * Check all solution space for solutions (2x2x2 case).
 *
 * @return true if a solution was found.
 */
template <>
bool
Cube_Product_Checker<2, 3, 512, 8>::
check_for_good_vectors()
{
    clear_statistics();
    for (int c1 = 0; c1 < m_count-6; ++c1)
        for (int c2 = c1+1; c2 < m_count-5; ++c2)
            for (int c3 = c2+1; c3 < m_count-4; ++c3)
                for (int c4 = c3+1; c4 < m_count-3; ++c4)
                    for (int c5 = c4+1; c5 < m_count-2; ++c5)
                        for (int c6 = c5+1; c6 < m_count-1; ++c6)
                            for (int c7 = c6+1; c7 < m_count; ++c7) {
                                if (*stop_signal)
                                    return false;
                                clear_sets();
                                add_vector_to_set(c1);
                                add_vector_to_set(c2);
                                add_vector_to_set(c3);
                                add_vector_to_set(c4);
                                add_vector_to_set(c5);
                                add_vector_to_set(c6);
                                add_vector_to_set(c7);
                                if (check_vectors_for_goodness())
                                    return true;
                            }
    return false;
}

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

/**
 * Check solutions by randomly picking vector sets.
 *
 * @return true if a solution was found.
 */
template <int N, int D, size_t NM, size_t NMH>
bool
Cube_Product_Checker<N, D, NM, NMH>::
check_for_good_vectors_randomized()
{
    clear_statistics();
    while (true) {
        if (*stop_signal)
            return false;
        make_random_candidate();
        if (check_vectors_for_goodness())
            return true;
    }
    return false;
}

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

/**
 * Make neighbourhood of the current candidate.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
start_neighbourhood()
{
    //cout << "Start making neighbours" << endl;
    neighbours.clear();
    // all possible one-coefficient changes in one of the vectors
    for (int i: n_vectors_indexes) {
        //cout << "  start with vector " << i << endl;
        Candidate c_set = Candidate(n_vectors_indexes);
        c_set.erase(i);
        vector<int> coef = decode_m_index(i);
        vector<Multiplication_Part_Vector> cur_coef;
        for (int j = 0; j < dimension; ++j) {
            cur_coef.push_back(Multiplication_Part_Vector(coef[j]+1));
        }
#ifdef OUTPUT_STATISTICS
        int value = cur_coef[0].to_ulong()-1;
        for (int o = 1; o < dimension; ++o) {
            value = value*m_length + cur_coef[o].to_ulong()-1;
        }
        if (value != i) {
            cout << "Hardcore error here! " << value << endl;
        }
#endif // OUTPUT_STATISTICS
        for (int j = 0; j < dimension; ++j) {
            //cout << "    start with cube " << j << " with total bits " << cur_coef[j] << endl;
            for (int k = 0; k < cur_coef[j].size(); ++k) {
                //cout << "      start with bit " << k << endl;
                cur_coef[j].flip(k);
                if (cur_coef[j].count() == 0) {
                    cur_coef[j].flip(k);
                    continue;
                }
                Candidate new_set = Candidate(c_set);
                int value = cur_coef[0].to_ulong()-1;
                for (int o = 1; o < dimension; ++o) {
                    value = value*m_length + cur_coef[o].to_ulong()-1;
                }
#ifdef OUTPUT_STATISTICS
                if ((value < 0) || (value >= m_count)) {
                    cout << "--->  got value " << value << endl
                              << "--->  after " << i << endl
                              << "--->  on j = " << j << " and k = " << k << endl
                              << "--->  with cur_coef[j] = " << cur_coef[j] << " and it's long as " << cur_coef[j].to_ulong() << endl;
                }
#endif // OUTPUT_STATISTICS
                new_set.insert(value);
                if (new_set.size() == n_vectors_indexes.size()) {
                    neighbours.insert(new_set);
                }
                cur_coef[j].flip(k);
            }
        }
    }
    //cout << "Made " << neighbours.size() << " neigbours." << endl;
}

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

/**
 * Check solutions by doing local search.
 *
 * @return true if a solution was found.
 */
template <int N, int D, size_t NM, size_t NMH>
bool
Cube_Product_Checker<N, D, NM, NMH>::
solve_hill_climbing(int local_max_limit, bool use_space)
{
    Random r;
    clear_statistics();
    clear_sets();
    if (use_space) {
        make_candidate_from_space();
    } else {
        make_random_candidate();
    }
    check_vectors_for_goodness();
    local_max_iterations = 0;
    local_iterations = 0;
    restarts = 0;
    while (true) {
        if (local_max_iterations > local_max_limit) {
#ifdef VERBOSE_OUTPUT
            cout << omp_get_thread_num() << "] Doing restart! local iterations = " << local_iterations << " / " << iteration_count << endl;
#endif // VERBOSE_OUTPUT
            clear_sets();
            if (use_space) {
                make_candidate_from_space();
            } else {
                make_random_candidate();
            }
            check_vectors_for_goodness();
            local_max_iterations = 0;
            local_iterations = 0;
            ++restarts;
        }
#ifdef VERBOSE_OUTPUT
        cout << omp_get_thread_num() << "] Iteration " << local_iterations << ": best is " << best_result << endl;
#endif // VERBOSE_OUTPUT
        if (best_result >= f_count)
            break;
        int next_best_result = 0;
        int old_best_result = best_result;
        vector<Candidate> best_candidates;
        start_neighbourhood();
        for (const Candidate& c: neighbours) {
            if (*stop_signal)
                return false;
            n_vectors_indexes = c;
            good_vectors_indexes.clear();
            check_vectors_for_goodness();
            if (best_result > next_best_result) {
                next_best_result = best_result;
                best_candidates.clear();
                best_candidates.push_back(n_vectors_indexes);
            } else if (best_result == next_best_result) {
                best_candidates.push_back(n_vectors_indexes);
            }
        }
        r.init(0, best_candidates.size()-1);
        n_vectors_indexes = best_candidates[r.next()];
        best_result = next_best_result;
        if (next_best_result > old_best_result) {
            local_max_iterations = 0;
        } else {
            ++local_max_iterations;
        }
        ++iteration_count;
        ++local_iterations;
#ifdef VERBOSE_OUTPUT
        output_current_state();
#endif // VERBOSE_OUTPUT
        //cout << "Iteration " << iteration_count << ": best is " << best_result << endl;
    }
    good_vectors_indexes.clear();
    check_vectors_for_goodness();
    if (best_result >= f_count)
        return true;
    else {
        cout << "Error!" << endl;
        return false;
    }
}

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

/**
 * Output current state variables of the checker.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
output_current_state() const
{
    cout << endl;
    cout << "===================================================" << endl;
    cout << "Current state of thread <" << thread_number << "> is:" << endl;
    cout << checked_sets_count << " checked sets" << endl;
    cout << restarts << " restarts" << endl;
    cout << iteration_count << " iterations" << endl;
    cout << lin_dependent_sets << " linearly dependent sets hits" << endl;
    cout << bit_check_hits << " bit check hits" << endl;
    cout << gaussian_eliminations << " gaussian eliminations" << endl;
    cout << cache_hits << " cache hits" << endl;
    cout << "===================================================" << endl;
}

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

/**
 * Write m_vectors into file.
 *
 * @param filename: filename.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
write_m_vectors(const string& filename)
{
    ofstream fout(filename);
    for (int i = 0; i < m_count; ++i) {
        fout << m_vectors[i].v.to_string() << m_vectors[i].r.to_string();
    }
    fout.close();
}

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

/**
 * Read m_vectors from file.
 *
 * @param filename: filename.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
read_m_vectors(const string& filename)
{
    ifstream fin(filename);
    for (int i = 0; i < m_count; ++i) {
        fin >> m_vectors[i].v >> m_vectors[i].r;
    }
    fin.close();
}

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

/**
 * Read candidate space from file.
 *
 * @param filename: filename.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
read_candidate_space(const string& filename)
{
    ifstream fin(filename);
    set<int> candidate;
    for (int i = 0; i < f_count-element_count; ++i) {
        int v;
        fin >> v;
        candidate.insert(v);
    }
    candidate_space.insert(candidate);
    fin.close();
}

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

/**
 * Print binary value of a vector.
 *
 * @param v: the vector.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
output_vector(Multiplication_Vector v) const
{
    cout << v;
}

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

/**
 * Print value of a vector in letters (2-d case).
 *
 * @param v: the vector.
 */
template <>
void
Cube_Product_Checker<2, 2, 16, 4>::
output_vector_text(Multiplication_Vector v) const
{
    for (size_t i = 0; i < v.size(); ++i) {
        if (v[i]) {
            int ai, aj, bi, bj;
            decode_indices_from_index(i, ai, aj, bi, bj);
            cout << "A" << ai+1 << aj+1 << "B" << bi+1 << bj+1 << " ";
        }
    }
}

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

/**
 * Generate and save to a file a sequence of random sets.
 *
 * @param size: number of sets;
 * @param filename: filename.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
save_random_samples(int size, const char* filename) const
{
    ofstream fout(filename);
    Random rnd(0, m_count-1);
    fout << size << "\n";
    for (int i = 0; i < size; ++i) {
        clear_sets();
        for (int i = 0; i < (f_count-element_count); ++i) {
            int cc = rnd.next();
            while (n_vectors_indexes.count(cc) > 0) {
                cc = rnd.next();
            }
            add_vector_to_set(cc);
            fout << cc << " ";
        }
        fout << "\n";
    }
    fout.close();
}

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

/**
 * Reda a set sequence from the file and check it.
 *
 * @param filenamein: filename to read from;
 * @param filenameout: filename for output.
 */
template <int N, int D, size_t NM, size_t NMH>
void Cube_Product_Checker<N, D, NM, NMH>::
read_samples_and_check(const char* filenamein, const char* filenameout) const
{
    ifstream fin(filenamein);
    ofstream fout(filenameout, ios_base::app);
    fout << "\n ################################### \n";
    int size;
    fin >> size;
    Timewatch timer;
    double time = 0.0;
    int gv = 0;
    for (int i = 0; i < size; ++i) {
        if (N*D > 5) {
            cout << "working on case " << i << "\n";
        }
        clear_sets();
        for (int i = 0; i < (f_count-element_count); ++i) {
            int cc;
            fin >> cc;
            add_vector_to_set(cc);
        }
        timer.watch();
        if (check_vectors_for_goodness()) {
            ++gv;
        }
        double curtime = timer.watch();
        time += curtime;
        if (N*D > 5) {
            fout << "\t" << i << ": " << curtime << " s\n";
        }
    }
    cout << "Found " << gv << " solutions\n";
    fout << "Total time: " << time << " s (" << (time/size) << " s avg) (found " << gv << " good vectors)\n";
    fout.close();
}

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

/**
 * Calculate statistics and save to a file.
 *
 * @param filename: filename to save statistics.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
save_results(const char* filename) {
    ofstream fout(filename);
    fout << "Results: " << solutions.size() << " different solutions were found." << endl;
    fout << raw_solution_count << " vector sets were successful." << endl;
    map<int, int> vector_distribution = map<int, int>();
    vector<map<int, int>> bit_distributions;
    for (set<set<int>>::iterator i = solutions.begin(); i != solutions.end(); ++i) {
        fout << "[ ";
        map<int, int> bd;
        for (set<int>::iterator j = (*i).begin(); j != (*i).end(); ++j) {
            fout << (*j) << " ";
            ++vector_distribution[*j];
            for (size_t k = 0; k < m_vectors[*j].v.size(); ++k) {
                if (m_vectors[*j].v.test(k)) {
                    ++bd[k];
                }
            }
        }
        bit_distributions.push_back(bd);
        fout << "] : " << solution_distribution[(*i)] << endl;
    }
    fout << endl << "Used vectors are:" << endl;
    for (const auto& e: vector_distribution) {
        fout << e.first << "\t" << m_vectors[e.first].v << " : " << e.second << endl;
    }
    fout << endl << "Bit distributions per solution are:" << endl;
    for (auto& e: bit_distributions) {
        for (int i = 0; i < NM; ++i) {
            fout << e[i];
        }
        fout << endl;
    }
    fout.close();
}

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

/**
 * Check if a given set of vectors is a valid solution.
 *
 * @param s:  set of result vectors;
 *
 * @param sp: solution properties;
 *
 * @return if it is a solution.
 */
template <int N, int D, size_t NM, size_t NMH>
bool
Cube_Product_Checker<N, D, NM, NMH>::
check_solution(set<int> s, Solution_Properties& sp) {
    sp.coefficients.clear();
    sp.multiplication_vectors.clear();
    sp.multiplication_vectors = s;
    sp.operation_count = 0;
    vector<mm_bitset<NM>> vectors;
    for (auto i: s) {
        vectors.push_back(m_vectors[i].v);
        vector<int> ops = sum_operations_cube(i);
        sp.operation_count += accumulate(ops.begin(), ops.end(), -ops.size());
    }
    for (int i = 0; i < element_count; ++i) {
        if (!gauss_solve(vectors, r_vectors[i].v)) {
            return false;
        }
        boost::dynamic_bitset<> x = binary_solve_result(vectors, r_vectors[i].v);
        sp.coefficients.push_back(x);
        sp.operation_count += x.count() - 1;
    }
    return true;
}

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

/**
 * Return number of summation operations inside cubes for a given index of multiplication vector.
 *
 * @param index: multiplication vector index;
 *
 * @return list of operation count for each cube.
 */
template <int N, int D, size_t NM, size_t NMH>
vector<int>
Cube_Product_Checker<N, D, NM, NMH>::
sum_operations_cube(int index)
{
    vector<int> result;
    if (dimension == 2) {
        int i, j;
        j = index % m_length + 1;
        i = index / m_length + 1;
        result.push_back(popcount(i));
        result.push_back(popcount(j));
    } else if (dimension == 3) {
        int i, j, k;
        k = index % m_length + 1;
        index /= m_length;
        j = index % m_length + 1;
        i = index / m_length + 1;
        result.push_back(popcount(i));
        result.push_back(popcount(j));
        result.push_back(popcount(k));
    }
    return result;
}

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

/**
 * Output solution properties to a file in readable text form.
 *
 * @param sp: solution properties object;
 * @param filename: filename.
 */
template <int N, int D, size_t NM, size_t NMH>
void
Cube_Product_Checker<N, D, NM, NMH>::
save_solution_properties(const Solution_Properties& sp, const char* filename)
{
    ofstream out(filename);
    //----- header
    out << "Strassen solution for ";
    for (int i = 0; i < dimension-1; ++i) {
        out << length << "×";
    }
    out << length << " cube product with " << f_count << " multiplications." << endl << endl;
    //----- multiplication vectors numbers
    out << "Multiplication numbers: [ ";
    for (auto i: sp.multiplication_vectors) {
        out << i << " ";
    }
    out << "]" << endl;
    //----- multiplications
    int c = 1;
    int m_summations = 0;
    for (auto i = sp.multiplication_vectors.begin(); i != sp.multiplication_vectors.end(); ++i, ++c) {
        out << "M" << c << " = ";
        vector<int> ops = sum_operations_cube(*i);
        m_summations += accumulate(ops.begin(), ops.end(), -ops.size());
        vector<string> sm, smc;
        set<string> sa, sb, sc;
        Multiplication_Vector& mv = m_vectors[*i].v;
        if (dimension == 2) {
            for (size_t j = 0; j < mv.size(); ++j) {
                if (mv[j]) {
                    int ai, aj, bi, bj;
                    decode_indices_from_index(j, ai, aj, bi, bj);
                    sm.push_back("A" + to_string(ai+1) + to_string(aj+1) +
                                 "B" + to_string(bi+1) + to_string(bj+1));
                    sa.insert("A" + to_string(ai+1) + to_string(aj+1));
                    sb.insert("B" + to_string(bi+1) + to_string(bj+1));
                }
            }
            out << "(" << boost::algorithm::join(sa, " + ") + ")×";
            out << "(" << boost::algorithm::join(sb, " + ") + ")";
        } else if (dimension == 3) {
            // TODO
        }
        out << " = ";
        out << boost::algorithm::join(sm, " + ") << endl;
    }
    //----- result elements
    int r_summations = 0;
    for (int i = 0; i < element_count; ++i) {
        vector<string> sr;
        boost::dynamic_bitset<> x = sp.coefficients[i];
        r_summations += x.count() - 1;
        if (dimension == 2) {
            out << "C" << i/length+1 << i%length+1 << " = ";
        } else if (dimension == 3) {
            // TODO
        }
        for (boost::dynamic_bitset<>::size_type j = 0; j < x.size(); ++j) {
            if (x.test(j)) {
                sr.push_back("M" + to_string(j+1));
            }
        }
        out << boost::algorithm::join(sr, " + ") << endl;
    }
    //----- summation count
    out << "Summation count for multiplications = " << m_summations << endl;
    out << "Summation count for result elements = " << r_summations << endl;
    out << "Total number of summations used = " << sp.operation_count << endl;
    //----- end
    out.close();
}

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

#endif // MMCHECKER_HPP_INCLUDED