/*
fitting.cpp

Copyright (c) 2014,2015 Terumasa Tadano

This file is distributed under the terms of the MIT license.
Please see the file 'LICENCE.txt' in the root directory
or http://opensource.org/licenses/mit-license.php for information.
*/

#include <iostream>
#include <fstream>
#include <cmath>
#include <string>
#include <vector>
#include <set>
#include <boost/lexical_cast.hpp>
#include "fitting.h"
#include "files.h"
#include "error.h"
#include "memory.h"
#include "symmetry.h"
#include "system.h"
#include "fcs.h"
#include "interaction.h"
#include "timer.h"
#include "combination.h"
#include "constants.h"
#include "constraint.h"
#include "mathfunctions.h"

#ifdef _USE_EIGEN
#include <Eigen/Dense>
#endif

#include <time.h>

#ifdef _VSL
#include "mkl_vsl.h"

#else
#include <cstdlib>
#endif

using namespace ALM_NS;

Fitting::Fitting(ALM *alm): Pointers(alm){
    seed = (unsigned int) time(NULL);
#ifdef _VSL
    brng = VSL_BRNG_MT19937;
    vslNewStream(&stream, brng, seed);
#else
    std::srand(seed);
#endif
}

Fitting::~Fitting() {
    if (alm->mode == "fitting") {
        memory->deallocate(params);
    }
}

void Fitting::fitmain()
{
    int i;
    int nat = system->nat;
    int natmin = symmetry->natmin;
    int ntran = symmetry->ntran;

    int ndata = system->ndata;
    int nstart = system->nstart;
    int nend = system->nend;
    int nskip = system->nskip;

    int N, M, N_new;
    int maxorder = interaction->maxorder;
    int P = constraint->P;

    int nmulti;
    int ndata_used = nend - nstart + 1;

    double **u, **f;
    double **amat, *fsum;
    double *fsum_orig;
    double *param_tmp;

    std::cout << " FITTING" << std::endl;
    std::cout << " =======" << std::endl << std::endl;

    std::cout << "  Reference files" << std::endl;
    std::cout << "   Displacement: " << files->file_disp << std::endl;
    std::cout << "   Force       : " << files->file_force << std::endl;
    std::cout << std::endl;

    std::cout << "  NSTART = " << nstart << "; NEND = " << nend << std::endl;
    std::cout << "  " << ndata_used << " entries will be used for fitting." << std::endl << std::endl;

    // Read displacement-force training data set from files

    data_multiplier(nat, ndata, nstart, nend, ndata_used, nmulti,
                    symmetry->multiply_data, u, f,
                    files->file_disp, files->file_force);

    N = 0;
    for (i = 0; i < maxorder; ++i) {
        N += fcs->ndup[i].size();
    }
    std::cout << "  Total Number of Parameters : " << N << std::endl << std::endl;

    // Calculate matrix elements for fitting

    M = 3 * natmin * ndata_used * nmulti;

    if (constraint->constraint_algebraic) {

        N_new = 0;
        for (i = 0; i < maxorder; ++i) {
            N_new += constraint->index_bimap[i].size();
        }
        std::cout << "  Total Number of Free Parameters : " << N_new << std::endl << std::endl;

        memory->allocate(amat, M, N_new);
        memory->allocate(fsum, M);
        memory->allocate(fsum_orig, M);

        calc_matrix_elements_algebraic_constraint(M, N, N_new, nat, natmin, ndata_used,
            nmulti, maxorder, u, f, amat, fsum, fsum_orig);

    } else {

        memory->allocate(amat, M, N);
        memory->allocate(fsum, M);

        calc_matrix_elements(M, N, nat, natmin, ndata_used, nmulti, maxorder, u, f, amat, fsum);
    }

    memory->deallocate(u);
    memory->deallocate(f);

    // Execute fitting

    memory->allocate(param_tmp, N);

    if (nskip == 0) {

        // Fitting with singular value decomposition or QR-Decomposition

        if (constraint->constraint_algebraic) {
            fit_algebraic_constraints(N_new, M, amat, fsum, param_tmp,
                                      fsum_orig, maxorder);

        } else if (constraint->exist_constraint) {
            fit_with_constraints(N, M, P, amat, fsum, param_tmp,
                                 constraint->const_mat, constraint->const_rhs);
        } else {
            fit_without_constraints(N, M, amat, fsum, param_tmp);
        }

    } else if (nskip > 0) {

        // Execute fittings consecutively with different input data.

        if (constraint->exist_constraint) {
            fit_consecutively(N, P, natmin, ndata_used, nmulti, nskip, amat, fsum,
                              constraint->const_mat, constraint->const_rhs);
        } else {
            error->exit("fitmain", "nskip has to be 0 when constraint_mode = 0");
        }
    } else {

        // Execute bootstrap simulation for estimating deviations of parameters.

        if (constraint->exist_constraint) {
            fit_bootstrap(N, P, natmin, ndata_used, nmulti, amat, fsum,
                          constraint->const_mat, constraint->const_rhs);
            fit_with_constraints(N, M, P, amat, fsum, param_tmp,
                                 constraint->const_mat, constraint->const_rhs);
        } else {
            error->exit("fitmain", "bootstrap analysis for LSE without constraint is not supported yet");
        }
    }

    // Copy force constants to public variable "params"

    memory->allocate(params, N);

    if (constraint->constraint_algebraic) {

        for (i = 0; i < N; ++i) {
            params[i] = param_tmp[i];
        }
        memory->deallocate(fsum_orig);

    } else {

        for (i = 0; i < N; ++i) params[i] = param_tmp[i];

    }

    memory->deallocate(amat);
    memory->deallocate(fsum);
    memory->deallocate(param_tmp);

    std::cout << std::endl;
    timer->print_elapsed();
    std::cout << " --------------------------------------------------------------" << std::endl;
    std::cout << std::endl;

}

void Fitting::data_multiplier(const int nat, const int ndata, const int nstart, const int nend,
                              const int ndata_used, int &nmulti, const int multiply_data, double **&u, double **&f,
                              const std::string file_disp, const std::string file_force)
{
    int i, j, k;
    int idata, itran, isym;
    int n_mapped;
    double u_rot[3], f_rot[3];
    double u_in, f_in;
    double *u_tmp, *f_tmp;
    std::vector<int> vec_data;
    unsigned int nline_f, nline_u;
    unsigned int nreq;

    std::ifstream ifs_disp, ifs_force;

    ifs_disp.open(file_disp.c_str(), std::ios::in);
    if (!ifs_disp) error->exit("openfiles", "cannot open disp file");
    ifs_force.open(file_force.c_str(), std::ios::in);
    if (!ifs_force) error->exit("openfiles", "cannot open force file");

    nreq = 3 * nat * ndata;

    memory->allocate(u_tmp, nreq);
    memory->allocate(f_tmp, nreq);

    // Read displacements from DFILE

    nline_u = 0;
    while (ifs_disp >> u_in) {
        u_tmp[nline_u++] = u_in;
        if (nline_u == nreq) break;
    }
    if (nline_u < nreq) error->exit("data_multiplier",
        "The number of lines in DFILE is too small for the given NDATA = ", ndata);

    // Read forces from FFILE

    nline_f = 0;
    while (ifs_force >> f_in) {
        f_tmp[nline_f++] = f_in;
        if (nline_f == nreq) break;
    }
    if (nline_f < nreq) error->exit("data_multiplier",
        "The number of lines in FFILE is too small for the given NDATA = ", ndata);

    // Multiply data

    if (multiply_data == 0) {

        std::cout << " MULTDAT = 0: Given displacement-force data sets will be used as is." << std::endl << std::endl;

        nmulti = 1;

        memory->allocate(u, ndata_used * nmulti, 3 * nat);
        memory->allocate(f, ndata_used * nmulti, 3 * nat);

        idata = 0;

        for (i = 0; i < ndata; ++i) {
            if (i < nstart - 1) continue;
            if (i > nend - 1) break;

            for (j = 0; j < nat; ++j) {
                for (k = 0; k < 3; ++k) {
                    u[idata][3 * j + k] = u_tmp[3*nat*i + 3*j + k];
                    f[idata][3 * j + k] = f_tmp[3*nat*i + 3*j + k];
                }
            }
            ++idata;
        }

    } else if (multiply_data == 1) {

        std::cout << "  MULTDAT = 1: Generate symmetrically equivalent displacement-force data sets " << std::endl;
        std::cout << "               by using pure translational operations only." << std::endl << std::endl;

        nmulti = symmetry->ntran;

        memory->allocate(u, ndata_used * nmulti, 3 * nat);
        memory->allocate(f, ndata_used * nmulti, 3 * nat);

        idata = 0;

        for (i = 0; i < ndata; ++i) {
            if (i < nstart - 1) continue;
            if (i > nend - 1) break;

            for (itran = 0; itran < symmetry->ntran; ++itran) {
                for (j = 0; j < nat; ++j) {
                    n_mapped = symmetry->map_sym[j][symmetry->symnum_tran[itran]];

                    for (k = 0; k < 3; ++k) {
                        u[idata][3 * n_mapped + k] = u_tmp[3*nat*i + 3*j + k];
                        f[idata][3 * n_mapped + k] = f_tmp[3*nat*i + 3*j + k];
                    }
                }
                ++idata;
            }
        }

    } else if (multiply_data == 2) {

        std::cout << "  MULTDAT = 2: Generate symmetrically equivalent displacement-force data sets." << std::endl;
        std::cout << "               (including rotational part) " << std::endl << std::endl;

        nmulti = symmetry->nsym;

        memory->allocate(u, ndata_used * nmulti, 3 * nat);
        memory->allocate(f, ndata_used * nmulti, 3 * nat);

        idata = 0;

        for (i = 0; i < ndata; ++i) {
            if (i < nstart - 1) continue;
            if (i > nend - 1) break;

#pragma omp parallel for private(j, n_mapped, k, u_rot, f_rot)
            for (isym = 0; isym < symmetry->nsym; ++isym) {
                for (j = 0; j < nat; ++j) {
                    n_mapped = symmetry->map_sym[j][isym];

                    for (k = 0; k < 3; ++k) {
                        u_rot[k] = u_tmp[3*nat*i + 3*j + k];
                        f_rot[k] = f_tmp[3*nat*i + 3*j + k];
                    }

                    rotvec(u_rot, u_rot, symmetry->symrel[isym]);
                    rotvec(f_rot, f_rot, symmetry->symrel[isym]);

                    for (k = 0; k < 3; ++k) {
                        u[nmulti * idata + isym][3 * n_mapped + k] = u_rot[k];
                        f[nmulti * idata + isym][3 * n_mapped + k] = f_rot[k];
                    }
                }
            }
            ++idata;
        }

    } else {
        error->exit("data_multiplier", "Unsupported MULTDAT");
    }

    memory->deallocate(u_tmp);
    memory->deallocate(f_tmp);

    ifs_disp.close();
    ifs_force.close();
}

void Fitting::fit_without_constraints(int N, int M, double **amat, double *bvec, double *param_out)
{
    int i, j;
    unsigned long k;
    int nrhs = 1, nrank, INFO, LWORK;
    int LMIN, LMAX;
    double rcond = -1.0;
    double f_square = 0.0;
    double *WORK, *S, *amat_mod, *fsum2;

    std::cout << "  Entering fitting routine: SVD without constraints" << std::endl;

    LMIN = std::min<int>(M, N);
    LMAX = std::max<int>(M, N);

    LWORK = 3*LMIN + std::max<int>(2*LMIN, LMAX);
    LWORK = 2 * LWORK;

    memory->allocate(WORK, LWORK);
    memory->allocate(S, LMIN);

    // transpose matrix A
    memory->allocate(amat_mod, M * N);
    memory->allocate(fsum2, LMAX);

    k = 0;
    for (j = 0; j < N; ++j) {
        for (i = 0; i < M; ++i) {
            amat_mod[k++] = amat[i][j];
        }
    }
    for (i = 0; i < M; ++i) {
        fsum2[i] = bvec[i];
        f_square += std::pow(bvec[i], 2);
    }
    for (i = M; i < LMAX; ++i) fsum2[i] = 0.0;

    std::cout << "  SVD has started ... ";

    // Fitting with singular value decomposition
    dgelss_(&M, &N, &nrhs, amat_mod, &M, fsum2, &LMAX, S, &rcond, &nrank, WORK, &LWORK, &INFO);

    std::cout << "finished !" << std::endl << std::endl;

    std::cout << "  RANK of the matrix = " << nrank << std::endl;
    if (nrank < N) error->warn("fit_without_constraints",
        "Matrix is rank-deficient. Force constants could not be determined uniquely :(");

    if (nrank == N) {
        double f_residual = 0.0;
        for (i = N; i < M; ++i) {
            f_residual += std::pow(fsum2[i], 2);
        }
        std::cout << std::endl << "  Residual sum of squares for the solution: " << sqrt(f_residual) << std::endl;
        std::cout << "  Fitting error (%) : "<< sqrt(f_residual/f_square) * 100.0 << std::endl;
    }

    for (i = 0; i < N; ++i) {
        param_out[i] = fsum2[i];
    }

    memory->deallocate(WORK);
    memory->deallocate(S);
    memory->deallocate(fsum2);
    memory->deallocate(amat_mod);
}

void Fitting::fit_with_constraints(int N, int M, int P,
                                   double **amat, double *bvec, double *param_out,
                                   double **cmat, double *dvec)
{
    int i, j;
    unsigned long k;
    int nrank;
    double f_square, f_residual;
    double *fsum2;

    std::cout << "  Entering fitting routine: QRD with constraints" << std::endl;

    memory->allocate(fsum2, M);

#ifdef _USE_EIGEN

    double **mat_tmp2;
    memory->allocate(mat_tmp2, M + P, N);
    for (i = 0; i < M; ++i) {
        for (j = 0; j < N; ++j) {
            mat_tmp2[i][j] = amat[i][j];
        }
    }
    for (i = 0; i < P; ++i) {
        for (j = 0; j < N; ++j) {
            mat_tmp2[M + i][j] = cmat[i][j];
        }
    }

    nrank = getRankEigen(M+P, N, mat_tmp2);
    memory->deallocate(mat_tmp2);

#else

    double *mat_tmp;

    memory->allocate(mat_tmp, (M + P) * N);

    k = 0;

    for (j = 0; j < N; ++j) {
        for (i = 0; i < M; ++i) {
            mat_tmp[k++] = amat[i][j];
        }
        for (i = 0; i < P; ++i) {
            mat_tmp[k++] = cmat[i][j];
        }
    }

    nrank = rankQRD((M+P), N, mat_tmp, eps12);
    memory->deallocate(mat_tmp);

#endif

    if (nrank != N) {
        std::cout << std::endl;
        std::cout << " **************************************************************************" << std::endl;
        std::cout << "  WARNING : rank deficient.                                                " << std::endl;
        std::cout << "  rank ( (A) ) ! = N            A: Fitting matrix     B: Constraint matrix " << std::endl;
        std::cout << "       ( (B) )                  N: The number of parameters                " << std::endl;
        std::cout << "  rank = " << nrank << " N = " << N << std::endl << std::endl;
        std::cout << "  This can cause a difficulty in solving the fitting problem properly      " << std::endl;
        std::cout << "  with DGGLSE, especially when the difference is large. Please check if    " << std::endl;
        std::cout << "  you obtain reliable force constants in the .fcs file.                    " << std::endl << std::endl;
        std::cout << "  This issue may be resolved by setting MULTDAT = 2 in the &fitting field. " << std::endl;
        std::cout << "  If not, you may need to reduce the cutoff radii and/or increase NDATA    " << std::endl;
        std::cout << "  by giving linearly-independent displacement patterns.                    " << std::endl;
        std::cout << " **************************************************************************" << std::endl;
        std::cout << std::endl;
    }

    f_square = 0.0;
    for (i = 0; i < M; ++i) {
        fsum2[i] = bvec[i];
        f_square += std::pow(bvec[i], 2);
    }
    std::cout << "  QR-Decomposition has started ...";

    double *amat_mod, *cmat_mod;
    memory->allocate(amat_mod, M * N);
    memory->allocate(cmat_mod, P * N);

    // transpose matrix A and C
    k = 0;
    for (j = 0; j < N; ++j) {
        for (i = 0; i < M; ++i) {
            amat_mod[k++] = amat[i][j];
        }
    }
    k = 0;
    for (j = 0; j < N; ++j) {
        for (i = 0; i < P; ++i) {
            cmat_mod[k++] = cmat[i][j];
        }
    }

    // Fitting

    int LWORK = P + std::min<int>(M, N) + 10 * std::max<int>(M, N);
    int INFO;
    double *WORK, *x;
    memory->allocate(WORK, LWORK);
    memory->allocate(x, N);

    dgglse_(&M, &N, &P, amat_mod, &M, cmat_mod, &P, fsum2, dvec, x, WORK, &LWORK, &INFO);

    std::cout << " finished. " << std::endl;

    f_residual = 0.0;
    for (i = N - P; i < M; ++i) {
        f_residual += std::pow(fsum2[i], 2);
    }
    std::cout << std::endl << "  Residual sum of squares for the solution: " << sqrt(f_residual) << std::endl;
    std::cout << "  Fitting error (%) : "<< std::sqrt(f_residual/f_square) * 100.0 << std::endl;

    // copy fcs to bvec

    for (i = 0; i < N; ++i) {
        param_out[i] = x[i];
    }

    memory->deallocate(amat_mod);
    memory->deallocate(cmat_mod);
    memory->deallocate(WORK);
    memory->deallocate(x);
    memory->deallocate(fsum2);
}

void Fitting::fit_algebraic_constraints(int N, int M, double **amat, double *bvec,
                                        double *param_out, double *bvec_orig,
                                        const int maxorder)
{
    int i, j;
    unsigned long k;
    int nrhs = 1, nrank, INFO, LWORK;
    int LMIN, LMAX;
    double rcond = -1.0;
    double f_square = 0.0;
    double *WORK, *S, *amat_mod, *fsum2;

    std::cout << "  Entering fitting routine: SVD with constraints considered algebraically." << std::endl;

    LMIN = std::min<int>(M, N);
    LMAX = std::max<int>(M, N);

    LWORK = 3*LMIN + std::max<int>(2*LMIN, LMAX);
    LWORK = 2 * LWORK;

    memory->allocate(WORK, LWORK);
    memory->allocate(S, LMIN);

    // transpose matrix A
    memory->allocate(amat_mod, M * N);
    memory->allocate(fsum2, LMAX);

    k = 0;
    for (j = 0; j < N; ++j) {
        for (i = 0; i < M; ++i) {
            amat_mod[k++] = amat[i][j];
        }
    }
    for (i = 0; i < M; ++i) {
        fsum2[i] = bvec[i];
        f_square += std::pow(bvec_orig[i], 2);
    }
    for (i = M; i < LMAX; ++i) fsum2[i] = 0.0;

    std::cout << "  SVD has started ... ";

    // Fitting with singular value decomposition
    dgelss_(&M, &N, &nrhs, amat_mod, &M, fsum2, &LMAX, S, &rcond, &nrank, WORK, &LWORK, &INFO);

    std::cout << "finished !" << std::endl << std::endl;

    std::cout << "  RANK of the matrix = " << nrank << std::endl;
    if (nrank < N) error->warn("fit_without_constraints",
        "Matrix is rank-deficient. Force constants could not be determined uniquely :(");

    if (nrank == N) {
        double f_residual = 0.0;
        for (i = N; i < M; ++i) {
            f_residual += std::pow(fsum2[i], 2);
        }
        std::cout << std::endl << "  Residual sum of squares for the solution: " << sqrt(f_residual) << std::endl;
        std::cout << "  Fitting error (%) : "<< sqrt(f_residual/f_square) * 100.0 << std::endl;
    }

    int ishift = 0;
    int iparam = 0;
    double tmp;
    int inew, iold;

    for (i = 0; i < maxorder; ++i) {
        for (j = 0; j < constraint->const_fix[i].size(); ++j) {
            param_out[constraint->const_fix[i][j].p_index_target + ishift] = constraint->const_fix[i][j].val_to_fix;
        }

        for (boost::bimap<int, int>::const_iterator it = constraint->index_bimap[i].begin();
            it != constraint->index_bimap[i].end(); ++it) {
                inew = (*it).left + iparam;
                iold = (*it).right + ishift;

                param_out[iold] = fsum2[inew];
        }

        for (j = 0; j < constraint->const_relate[i].size(); ++j) {
            tmp = 0.0;

            for (k = 0; k < constraint->const_relate[i][j].alpha.size(); ++k) {
                tmp += constraint->const_relate[i][j].alpha[k] * param_out[constraint->const_relate[i][j].p_index_orig[k] + ishift];
            }
            param_out[constraint->const_relate[i][j].p_index_target + ishift] = -tmp;
        }

        ishift += fcs->ndup[i].size();
        iparam += constraint->index_bimap[i].size();
    }

    memory->deallocate(WORK);
    memory->deallocate(S);
    memory->deallocate(fsum2);
    memory->deallocate(amat_mod);
}

void Fitting::fit_bootstrap(int N, int P, int natmin, int ndata_used, int nmulti,
                            double **amat, double *bvec, double **cmat, double *dvec)
{
    int i, j;
    unsigned long k, l;
    int M_Start, M_End;
    int mset;
    unsigned int iboot;
    int M;

    mset = 3 * natmin * nmulti;

    M_Start = 0;
    M_End = mset * ndata_used;

    M = M_End - M_Start;

    std::string file_fcs_bootstrap;
    file_fcs_bootstrap = files->job_title + ".fcs_bootstrap";

    std::ofstream ofs_fcs_boot;
    ofs_fcs_boot.open(file_fcs_bootstrap.c_str(), std::ios::out);
    if(!ofs_fcs_boot) error->exit("fit_bootstrap", "cannot open file_fcs_bootstrap");

    ofs_fcs_boot.setf(std::ios::scientific);

    double f_residual, f_square;
    double *fsum2;
    int INFO;
    double *WORK, *x;
    double *amat_mod, *cmat_mod;
    double *const_tmp;

    int *rnd_index;
    int iloc;

    memory->allocate(x, N);
    memory->allocate(cmat_mod, P * N);
    memory->allocate(const_tmp, P);

    std::cout << "  NSKIP < 0: Bootstrap analysis for error estimation." << std::endl;
    std::cout << "             The number of trials is NBOOT (=" << nboot << ")" << std::endl;
    std::cout << std::endl;
    std::cout << "  Relative errors and FCs are stored in file: " << file_fcs_bootstrap << std::endl;

    ofs_fcs_boot << "# Relative Error(%), FCs ..." ;

    for (i = 0; i < interaction->maxorder; ++i) {
        ofs_fcs_boot << std::setw(10) << fcs->ndup[i].size();
    }
    ofs_fcs_boot << std::endl;

    memory->allocate(fsum2, M);
    memory->allocate(amat_mod, N * M);
    memory->allocate(rnd_index, ndata_used);

    int LWORK = P + std::min<int>(M, N) + 100 * std::max<int>(M, N);
    memory->allocate(WORK, LWORK);

    for (iboot = 0; iboot < nboot; ++iboot) {
#ifdef _VSL
        // Use Intel MKL VSL if available
        viRngUniform(VSL_METHOD_IUNIFORM_STD, stream, ndata_used, rnd_index, 0, ndata_used);
#else
        for (i = 0; i < ndata_used; ++i) {
            rnd_index[i] = std::rand() % ndata_used; // random number uniformly distributed in [0, ndata_used)
        }
#endif

        f_square = 0.0;
        k = 0;
        for (i = 0; i < ndata_used; ++i) {
            iloc = rnd_index[i];
            for (j = iloc * mset; j < (iloc + 1) * mset; ++j) {
                fsum2[k++] = bvec[j];
                f_square += std::pow(bvec[j], 2);
            }
        }
        l = 0;
        for (j = 0; j < N; ++j) {
            for (i = 0; i < ndata_used; ++i) {
                iloc = rnd_index[i];
                for (k = iloc * mset; k < (iloc + 1) * mset; ++k) {
                    amat_mod[l++] = amat[k][j];
                }
            }
        }

        k = 0;
        for (j = 0; j < N; ++j) {
            for (i = 0; i < P; ++i) {
                cmat_mod[k++] = cmat[i][j];
            }
        }

        for (i = 0; i < P; ++i) {
            const_tmp[i] = dvec[i];
        }

        dgglse_(&M, &N, &P, amat_mod, &M, cmat_mod, &P, fsum2, const_tmp, x, WORK, &LWORK, &INFO);

        f_residual = 0.0;
        for (i = N - P; i < M; ++i) {
            f_residual += std::pow(fsum2[i], 2);
        }

        ofs_fcs_boot << 100.0 * std::sqrt(f_residual/f_square);

        for (i = 0; i < N; ++i) {
            ofs_fcs_boot << std::setw(15) << x[i];
        }
        ofs_fcs_boot << std::endl;
    }
    ofs_fcs_boot.close();

    memory->deallocate(x);
    memory->deallocate(fsum2);
    memory->deallocate(cmat_mod);
    memory->deallocate(const_tmp);
    memory->deallocate(amat_mod);
    memory->deallocate(rnd_index);
    memory->deallocate(WORK);

    std::cout << "  Bootstrap analysis finished." << std::endl;
    std::cout << "  Normal fitting will be performed" << std::endl;
}

void Fitting::fit_consecutively(int N, int P, const int natmin, const int ndata_used, const int nmulti, const int nskip,
                                double **amat, double *bvec, double **cmat, double *dvec)
{
    int i, j;
    unsigned long k;
    int iend;
    int M_Start, M_End;
    int mset;
    int M;

    mset = 3 * natmin * nmulti;

    M_Start = 0;

    std::string file_fcs_sequence;
    file_fcs_sequence = files->job_title + ".fcs_sequence";

    std::ofstream ofs_fcs_seq;
    ofs_fcs_seq.open(file_fcs_sequence.c_str(), std::ios::out);
    if(!ofs_fcs_seq) error->exit("fit_consecutively", "cannot open file_fcs_sequence");

    ofs_fcs_seq.setf(std::ios::scientific);

    double f_residual, f_square;
    double *fsum2;
    int INFO;
    double *WORK, *x;
    double *amat_mod, *cmat_mod;
    double *const_tmp;

    memory->allocate(x, N);
    memory->allocate(cmat_mod, P * N);
    memory->allocate(const_tmp, P);

    std::cout << "  NSKIP > 0: Fitting will be performed consecutively" << std::endl;
    std::cout << "             with variously changing NEND as NEND = NSTART + i*NSKIP" << std::endl;
    std::cout << std::endl;
    std::cout << "  Relative errors and FCs will be stored in the file " << file_fcs_sequence << std::endl;

    ofs_fcs_seq << "# Relative Error(%), FCS..." ;

    for (i = 0; i < interaction->maxorder; ++i) {
        ofs_fcs_seq << std::setw(10) << fcs->ndup[i].size();
    }
    ofs_fcs_seq << std::endl;

    for (iend = 1; iend <= ndata_used; iend += nskip) {

        M_End = mset * iend;
        M = M_End - M_Start;

        memory->allocate(fsum2, M);

        f_square = 0.0;
        j = 0;
        for (i = M_Start; i < M_End; ++i) {
            fsum2[j++] = bvec[i];
            f_square += std::pow(bvec[i], 2);
        }

        memory->allocate(amat_mod, M * N);

        // Transpose matrix A
        k = 0;
        for (j = 0; j < N; ++j) {
            for (i = M_Start; i < M_End; ++i) {
                amat_mod[k++] = amat[i][j];
            }
        }

        k = 0;
        for (j = 0; j < N; ++j) {
            for (i = 0; i < P; ++i) {
                cmat_mod[k++] = cmat[i][j];
            }
        }

        for (i = 0; i < P; ++i) {
            const_tmp[i] = dvec[i];
        }

        // Fitting

        int LWORK = P + std::min<int>(M, N) + 100 * std::max<int>(M, N);
        memory->allocate(WORK, LWORK);

        dgglse_(&M, &N, &P, amat_mod, &M, cmat_mod, &P, fsum2, const_tmp, x, WORK, &LWORK, &INFO);

        memory->deallocate(amat_mod);
        memory->deallocate(WORK);

        f_residual = 0.0;
        for (i = N - P; i < M; ++i) {
            f_residual += std::pow(fsum2[i], 2);
        }

        ofs_fcs_seq << 100.0 * std::sqrt(f_residual/f_square);

        for (i = 0; i < N; ++i) {
            ofs_fcs_seq << std::setw(15) << x[i];
        }
        ofs_fcs_seq << std::endl;
        memory->deallocate(fsum2);
    }

    for (i = 0; i < N; ++i) {
        bvec[i] = x[i];
    }

    memory->deallocate(cmat_mod);
    memory->deallocate(const_tmp);
    memory->deallocate(x);

    ofs_fcs_seq.close();

    std::cout << "  Consecutive fitting finished." << std::endl;
}

void Fitting::calc_matrix_elements(const int M, const int N, const int nat, const int natmin,
                                   const int ndata_fit, const int nmulti, const int maxorder,
                                   double **u, double **f, double **amat, double *bvec)
{
    int i, j;
    int irow;
    int ncycle;

    std::cout << "  Calculation of matrix elements for direct fitting started ... ";
    for (i = 0; i < M; ++i) {
        for (j = 0; j < N; ++j) {
            amat[i][j] = 0.0;
        }
        bvec[i] = 0.0;
    }

    ncycle = ndata_fit * nmulti;

#ifdef _OPENMP
#pragma omp parallel private(irow, i, j)
#endif
    {
        int *ind;
        int mm, order, iat, k;
        int im, idata, iparam;
        double amat_tmp;

        memory->allocate(ind, maxorder + 1);

#ifdef _OPENMP
#pragma omp for schedule(guided)
#endif
        for (irow = 0; irow < ncycle; ++irow) {

            // generate r.h.s vector B
            for (i = 0; i < natmin; ++i) {
                iat = symmetry->map_p2s[i][0];
                for (j = 0; j < 3; ++j) {
                    im = 3 * i + j + 3 * natmin * irow;
                    bvec[im] = f[irow][3 * iat + j];
                }
            }

            // generate l.h.s. matrix A

            idata = 3 * natmin * irow;
            iparam = 0;

            for (order = 0; order < maxorder; ++order) {

                mm = 0;

                for (std::vector<int>::iterator iter = fcs->ndup[order].begin(); iter != fcs->ndup[order].end(); ++iter) {
                    for (i = 0; i < *iter; ++i) {
                        ind[0] = fcs->fc_set[order][mm].elems[0];
                        k = idata + inprim_index(fcs->fc_set[order][mm].elems[0]);
                        amat_tmp = 1.0;
                        for (j = 1; j < order + 2; ++j) {
                            ind[j] = fcs->fc_set[order][mm].elems[j];
                            amat_tmp *= u[irow][fcs->fc_set[order][mm].elems[j]];
                        }
                        amat[k][iparam] -= gamma(order + 2, ind) * fcs->fc_set[order][mm].coef * amat_tmp;
                        ++mm;
                    }
                    ++iparam;
                }
            }
        }

        memory->deallocate(ind);

    }

    std::cout << "done!" << std::endl << std::endl;
}

void Fitting::calc_matrix_elements_algebraic_constraint(const int M, const int N, const int N_new, const int nat,
                                                        const int natmin, const int ndata_fit, const int nmulti,
                                                        const int maxorder, double **u, double **f, double **amat,
                                                        double *bvec, double *bvec_orig)
{
    int i, j;
    int irow;
    int ncycle;

    std::cout << "  Calculation of matrix elements for direct fitting started ... ";

    ncycle = ndata_fit * nmulti;

#ifdef _OPENMP
#pragma omp parallel for private(j)
#endif
    for (i = 0; i < M; ++i) {
        for (j = 0; j < N_new; ++j) {
            amat[i][j] = 0.0;
        }
        bvec[i] = 0.0;
        bvec_orig[i] = 0.0;
    }

#ifdef _OPENMP
#pragma omp parallel private(irow, i, j)
#endif
    {
        int *ind;
        int mm, order, iat, k;
        int im, idata, iparam;
        int ishift;
        int iold, inew;
        double amat_tmp;
        double **amat_orig;
        double **amat_mod;

        memory->allocate(ind, maxorder + 1);
        memory->allocate(amat_orig, 3 * natmin, N);
        memory->allocate(amat_mod, 3 * natmin, N_new);

#ifdef _OPENMP
#pragma omp for schedule(guided)
#endif
        for (irow = 0; irow < ncycle; ++irow) {

            // generate r.h.s vector B
            for (i = 0; i < natmin; ++i) {
                iat = symmetry->map_p2s[i][0];
                for (j = 0; j < 3; ++j) {
                    im = 3 * i + j + 3 * natmin * irow;
                    bvec[im] = f[irow][3 * iat + j];
                    bvec_orig[im] = f[irow][3 * iat + j];
                }
            }

            for (i = 0; i < 3 * natmin; ++i) {
                for (j = 0; j < N; ++j) {
                    amat_orig[i][j] = 0.0;
                }
                for (j = 0; j < N_new; ++j) {
                    amat_mod[i][j] = 0.0;
                }
            }

            // generate l.h.s. matrix A

            idata = 3 * natmin * irow;
            iparam = 0;

            for (order = 0; order < maxorder; ++order) {

                mm = 0;

                for (std::vector<int>::iterator iter = fcs->ndup[order].begin(); iter != fcs->ndup[order].end(); ++iter) {
                    for (i = 0; i < *iter; ++i) {
                        ind[0] = fcs->fc_set[order][mm].elems[0];
                  //      k = idata + inprim_index(fcs->fc_set[order][mm].elems[0]);
                        k = inprim_index(ind[0]);

                        amat_tmp = 1.0;
                        for (j = 1; j < order + 2; ++j) {
                            ind[j] = fcs->fc_set[order][mm].elems[j];
                            amat_tmp *= u[irow][fcs->fc_set[order][mm].elems[j]];
                        }
                        amat_orig[k][iparam] -= gamma(order + 2, ind) * fcs->fc_set[order][mm].coef * amat_tmp;
                        ++mm;
                    }
                    ++iparam;
                }
            }

            ishift = 0;
            iparam = 0;

            for (order = 0; order < maxorder; ++order) {

                for (i = 0; i < constraint->const_fix[order].size(); ++i) {

                    for (j = 0; j < 3 * natmin; ++j) {
                        bvec[j + idata] -=  constraint->const_fix[order][i].val_to_fix
                                         * amat_orig[j][ishift + constraint->const_fix[order][i].p_index_target];
                    }
                }

                for (boost::bimap<int, int>::const_iterator it = constraint->index_bimap[order].begin();
                    it != constraint->index_bimap[order].end(); ++it) {
                        inew = (*it).left + iparam;
                        iold = (*it).right + ishift;

                    for (j = 0; j < 3 * natmin; ++j) {
                            amat_mod[j][inew] = amat_orig[j][iold];
                   }
                }

                for (i = 0; i < constraint->const_relate[order].size(); ++i) {

                    iold = constraint->const_relate[order][i].p_index_target + ishift;

                    for (j = 0; j < constraint->const_relate[order][i].alpha.size(); ++j) {

                        inew = constraint->index_bimap[order].right.at(constraint->const_relate[order][i].p_index_orig[j]) + iparam;
                        for (k = 0; k < 3 * natmin; ++k) {
                            amat_mod[k][inew] -= amat_orig[k][iold] * constraint->const_relate[order][i].alpha[j];
                        }
                    }
                }

                ishift += fcs->ndup[order].size();
                iparam += constraint->index_bimap[order].size();
            }

            for (i = 0; i < 3 * natmin; ++i) {
                for (j = 0; j < N_new; ++j) {
                    amat[i + idata][j] = amat_mod[i][j];
                }
            }

        }

        memory->deallocate(ind);
        memory->deallocate(amat_orig);
        memory->deallocate(amat_mod);
    }

    std::cout << "done!" << std::endl << std::endl;
}

int Fitting::inprim_index(const int n)
{
    int in;
    int atmn = n / 3;
    int crdn = n % 3;

    for (int i = 0; i < symmetry->natmin; ++i) {
        if (symmetry->map_p2s[i][0] == atmn) {
            in = 3 * i + crdn;
            break;
        }
    }
    return in;
}

double Fitting::gamma(const int n, const int *arr)
{
    int *arr_tmp, *nsame;
    int i;
    int ind_front, nsame_to_front;

    memory->allocate(arr_tmp, n);
    memory->allocate(nsame, n);

    for (i = 0; i < n; ++i) {
        arr_tmp[i] = arr[i];
        nsame[i] = 0;
    }

    ind_front = arr[0];
    nsame_to_front = 1;

    interaction->insort(n, arr_tmp);

    int nuniq = 1;
    int iuniq = 0;

    nsame[0] = 1;

    for (i = 1; i < n; ++i) {
        if (arr_tmp[i] == arr_tmp[i-1]) {
            ++nsame[iuniq];
        } else {
            ++nsame[++iuniq];
            ++nuniq;
        }

        if (arr[i] == ind_front) ++nsame_to_front;
    }

    int denom = 1;

    for (i = 0; i < nuniq; ++i) {
        denom *= factorial(nsame[i]);
    }

    memory->deallocate(arr_tmp);
    memory->deallocate(nsame);

    return static_cast<double>(nsame_to_front) / static_cast<double>(denom);
}

int Fitting::factorial(const int n)
{
    if (n == 1 || n == 0) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}

#ifdef _USE_EIGEN
int Fitting::getRankEigen(const int m, const int n, double **mat)
{
    using namespace Eigen;

    MatrixXd mat_tmp(m, n);

    int i, j;

    for (i = 0; i < m; ++i) {
        for (j = 0; j < n; ++j) {
            mat_tmp(i,j) = mat[i][j];
        }
    }
    ColPivHouseholderQR<MatrixXd> qr(mat_tmp);
    return qr.rank();
}
#endif

int Fitting::rankQRD(const int m, const int n, double *mat, const double tolerance)
{
    // Return the rank of matrix mat revealed by the column pivoting QR decomposition
    // The matrix mat is destroyed.

    int m_ = m;
    int n_ = n;

    int LDA = m_;

    int LWORK = 10 * n_;
    int INFO;
    int *JPVT;
    double *WORK, *TAU;

    int nmin = std::min<int>(m_, n_);

    memory->allocate(JPVT, n_);
    memory->allocate(WORK, LWORK);
    memory->allocate(TAU, nmin);

    for (int i = 0; i < n_; ++i) JPVT[i] = 0;

    dgeqp3_(&m_, &n_, mat, &LDA, JPVT, TAU, WORK, &LWORK, &INFO);

    memory->deallocate(JPVT);
    memory->deallocate(WORK);
    memory->deallocate(TAU);

    if (std::abs(mat[0]) < eps) return 0;

    double **mat_tmp;
    memory->allocate(mat_tmp, m_, n_);

    unsigned long k = 0;

    for (int j = 0; j < n_; ++j) {
        for (int i = 0; i < m_; ++i) {
            mat_tmp[i][j] = mat[k++];
        }
    }

    int nrank = 0;
    for (int i = 0; i < nmin; ++i) {
        if (std::abs(mat_tmp[i][i]) > tolerance * std::abs(mat[0])) ++nrank;
    }

    memory->deallocate(mat_tmp);

    return nrank;
}

int Fitting::rankSVD(const int m, const int n, double *mat, const double tolerance)
{
    int i;
    int m_ = m;
    int n_ = n;

    int LWORK = 10 * m;
    int INFO;
    int *IWORK;
    int ldu = 1, ldvt = 1;
    double *s, *WORK;
    double u[1], vt[1];

    int nmin = std::min<int>(m, n);

    memory->allocate(IWORK, 8 * nmin);
    memory->allocate(WORK, LWORK);
    memory->allocate(s, nmin);

    char mode[]  = "N";

    dgesdd_(mode, &m_, &n_, mat, &m_, s, u, &ldu, vt, &ldvt, WORK, &LWORK, IWORK, &INFO);

    int rank = 0;
    for (i = 0; i < nmin; ++i) {
        if (s[i] > s[0] * tolerance) ++rank;
    }

    memory->deallocate(WORK);
    memory->deallocate(IWORK);
    memory->deallocate(s);

    return rank;
}

int Fitting::rankSVD2(const int m_in, const int n_in, double **mat, const double tolerance)
{
    // Reveal the rank of matrix mat without destroying the matrix elements

    int i, j, k;
    double *arr;

    int m = m_in;
    int n = n_in;

    memory->allocate(arr, m*n);

    k = 0;

    for (j = 0; j < n; ++j ) {
        for (i = 0; i < m; ++i) {
            arr[k++] = mat[i][j];
        }
    }

    int LWORK = 10 * m;
    int INFO;
    int *IWORK;
    int ldu = 1, ldvt = 1;
    double *s, *WORK;
    double u[1], vt[1];

    int nmin = std::min<int>(m, n);

    memory->allocate(IWORK, 8 * nmin);
    memory->allocate(WORK, LWORK);
    memory->allocate(s, nmin);

    char mode[]  = "N";

    dgesdd_(mode, &m, &n, arr, &m, s, u, &ldu, vt, &ldvt, WORK, &LWORK, IWORK, &INFO);

    int rank = 0;
    for (i = 0; i < nmin; ++i){
        if (s[i] > s[0] * tolerance) ++rank;
    }

    memory->deallocate(IWORK);
    memory->deallocate(WORK);
    memory->deallocate(s);
    memory->deallocate(arr);

    return rank;
}

/*
void Fitting::calc_covariance(int m, int n)
{
Eigen::MatrixXd Atmp(m, n), Hess(n, n);
int i, j;

for (i = 0; i < m; ++i){
for (j = 0; j < n; ++j){
Atmp(i,j) = amat[i][j];
}
}

Hess = (Atmp.transpose()*Atmp).inverse();

for (i = 0; i < n; ++i){
for (j = 0; j < n; ++j){
varcovar[i][j] = Hess(i, j);
}
}

}
*/


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/complex.h>
#include <pybind11/numpy.h>

#include <vector>
#include <string>
#include <cstdarg>
#include <cstring>
#include <cstddef>
#include <iterator>

#include <boost/lexical_cast.hpp>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Delaunay_mesh_face_base_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>

namespace py = pybind11;

using K = CGAL::Exact_predicates_inexact_constructions_kernel;
using Vb = CGAL::Triangulation_vertex_base_with_info_2<unsigned int, K>;
using Tds = CGAL::Triangulation_data_structure_2<Vb>;
using DT = CGAL::Delaunay_triangulation_2<K, Tds>;

using Point = K::Point_2;
using Vertex_handle = DT::Vertex_handle;
using Vi = DT::Finite_vertices_iterator;

template <typename T>
class TypedInputIterator
{
public:
    using iterator_category = std::input_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = T;
    using pointer = T*;
    using reference = T&;

    explicit TypedInputIterator(py::iterator& py_iter) :
        py_iter_(py_iter)
    {
    }

    explicit TypedInputIterator(py::iterator&& py_iter) :
        py_iter_(py_iter)
    {
    }

    value_type operator*()
    {
        return (*py_iter_).template cast<value_type>();
    }

    TypedInputIterator operator++(int)
    {
        auto copy = *this;
        ++py_iter_;
        return copy;
    }

    TypedInputIterator& operator++()
    {
        ++py_iter_;
        return *this;
    }

    bool operator!=(TypedInputIterator &rhs)
    {
        return py_iter_ != rhs.py_iter_;
    }

    bool operator==(TypedInputIterator &rhs)
    {
        return py_iter_ == rhs.py_iter_;
    }

private:
    py::iterator py_iter_;
};

PYBIND11_MODULE(delaunay_class, m)
{
    py::class_<Point>(m, "Point")
            .def(py::init<int, int>(),  py::arg("x"), py::arg("y"))
            .def(py::init<double, double>(), py::arg("x"), py::arg("y"))
            .def_property_readonly("x", &Point::x)
            .def_property_readonly("y", &Point::y)
            .def("__repr__",
            [](const Point &p) {
                std::string r("Point(");
                r += boost::lexical_cast<std::string>(p.x());
                r += ", ";
                r += boost::lexical_cast<std::string>(p.y());
                r += ")";
                return r;
            })
            ;

    py::class_<Vertex_handle>(m, "VertexHandle")
        .def_property_readonly(
        "point",
        [](const Vertex_handle& vertex_handle)
        {
            return vertex_handle->point();
        })
        ;

        py::class_<DT>(m, "DelaunayTriangulation")

            .def(py::init())

            .def("insert", [](DT & dt, const std::vector<double> & p) {
                  std::vector< std::pair<Point,unsigned> > points;
                  int num_points = p.size()/2;
                  // start adding at the end of the current table
                  int start = dt.number_of_vertices();
                  for(std::size_t i = 0; i < num_points; ++i)
                  {
                    // add index information to form face table later
                     points.push_back( std::make_pair( Point(p[i*2+0],p[i*2+1]), start) );
                     start += 1;
                  }
                  return dt.insert(points.begin(),points.end());
                })

            .def("remove", [](DT & dt, const std::vector<unsigned int> & to_remove) {
                    int num_to_remove= to_remove.size();
                    std::vector<Vertex_handle> handles;
                    for (Vi vi = dt.finite_vertices_begin(); vi != dt.finite_vertices_end(); vi++){
                        handles.push_back(vi);
                    }
                    for(std::size_t i=0; i < num_to_remove; ++i){
                        dt.remove(handles[to_remove[i]]);
                    }
                    return dt;
                })

            .def("move", [](DT & dt, const std::vector<unsigned int> & to_move, const std::vector<double> & new_positions){
                    std::vector<Vertex_handle> handles;
                    std::vector<Point> new_pos;
                    int num_to_move= to_move.size();
                    // store all vertex handles
                    for (Vi vi = dt.finite_vertices_begin(); vi != dt.finite_vertices_end(); vi++){
                        handles.push_back(vi);
                    }
                    // store new positions as a vector of Point
                    for(std::size_t i = 0; i < num_to_move; ++i)
                    {
                       new_pos.push_back(Point(new_positions[2*i], new_positions[2*i+1]));
                    }
                    //
                    for(std::size_t i = 0; i < num_to_move; ++i)
                    {
                       dt.move( handles[to_move[i]], new_pos[i] );
                    }
                    return dt;
                    })

            .def("number_of_vertices", &DT::number_of_vertices)

            .def("number_of_faces", [](DT & dt){
                int count=0;
                for(DT::Finite_faces_iterator fit = dt.finite_faces_begin();
                fit != dt.finite_faces_end(); ++fit) {
                    count += 1;
                }
                    return count;
                })

            .def("finite_vertices", [](DT & dt) -> py::iterator
             {
                 return py::make_iterator(dt.finite_vertices_begin(), dt.finite_vertices_end());
             })

            .def("get_finite_cells", [](DT & dt)
            {
              // ouput the face table
              // YOU MUST CALL get_finite_vertices before if any incremental operations
              // were performed
              std::vector<int> faces;
              faces.resize(dt.number_of_faces()*3);

              int i=0;
              for(DT::Finite_faces_iterator fit = dt.finite_faces_begin();
                fit != dt.finite_faces_end(); ++fit) {

                DT::Face_handle face = fit;
                faces[i*3]=face->vertex(0)->info();
                faces[i*3+1]=face->vertex(1)->info();
                faces[i*3+2]=face->vertex(2)->info();
                i+=1;
              }
              ssize_t              soint      = sizeof(int);
              ssize_t              num_faces = faces.size()/3;
              ssize_t              ndim      = 2;
              std::vector<ssize_t> shape     = {num_faces, 3};
              std::vector<ssize_t> strides   = {soint*3, soint};

              // return 2-D NumPy array
              return py::array(py::buffer_info(
                faces.data(),                           /* data as contiguous array  */
                sizeof(int),                          /* size of one scalar        */
                py::format_descriptor<int>::format(), /* data type                 */
                2,                                    /* number of dimensions      */
                shape,                                   /* shape of the matrix       */
                strides                                  /* strides for each axis     */
              ));
            })

            .def("get_finite_vertices", [](DT & dt)
             {
               // ouput the vertices
               std::vector<double> vertices;
               vertices.resize(dt.number_of_vertices()*2);

               int i=0;
               for(DT::Finite_vertices_iterator fit = dt.finite_vertices_begin();
                 fit != dt.finite_vertices_end(); ++fit) {

                 Vertex_handle vertex = fit;
                 // critical! update the point index table so faces comes out correctly
                 vertex->info() = i;
                 vertices[i*2]=vertex->point().x();
                 vertices[i*2+1]=vertex->point().y();
                 i+=1;
               }
               ssize_t              sdble   = sizeof(double);
               ssize_t              num_vertices = vertices.size()/2;
               ssize_t              ndim      = 2;
               std::vector<ssize_t> shape     = {num_vertices, 2};
               std::vector<ssize_t> strides   = {sdble*2, sdble};

               // return 2-D NumPy array
               return py::array(py::buffer_info(
                 vertices.data(),                           /* data as contiguous array  */
                 sizeof(double),                          /* size of one scalar        */
                 py::format_descriptor<double>::format(), /* data type                 */
                 2,                                    /* number of dimensions      */
                 shape,                                   /* shape of the matrix       */
                 strides                                  /* strides for each axis     */
               ));
             })
            ;

    py::class_<DT::Finite_vertices_iterator::value_type>(m, "Vertex")
            .def_property_readonly(
                "point", [](DT::Finite_vertices_iterator::value_type& vertex)
                {
                    return vertex.point();
                }
            )
            ;

    py::class_<DT::Finite_faces_iterator::value_type>(m, "Face")
            .def("vertex_handle",
                [](DT::Finite_faces_iterator::value_type& face, int index)
                {
                    return face.vertex(index);
                },
                py::arg("index")
            )
            ;

}


#include <iostream>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/boyer_myrvold_planar_test.hpp>

using std::vector;
using namespace boost;

//a class to hold the coordinates of the straight line embedding
struct coord_t
{
    std::size_t x;
    std::size_t y;
};

vector<int> check_k33{ 4060, 13242, 15481, 15751, 21878, 23221, 23371, 26323, 26413, 26862}; // all masks of k33 graph with 6 vertexes
vector<int> check_k5{ 5871, 11127, 19899, 29149, 32286, 32736};                             // all masks of k5 graph with 6 vertexes

int mask_to_int(std::string &mask) {
    int ans = 0;
    for (auto c : mask) {
        ans <<= 1;
        if (c == '1')
            ans += 1;
    }
    return ans;
}
bool hard_check_k5(std::string mask) {
    vector<int> deg(6, 0);
    int cur = 0;
    for (int i = 1; i < 6; i++) {
        for (int j = 0; j < i; j++) {
            if (mask[cur] == '1') {
                deg[i]++;
                deg[j]++;
            }
            cur++;
        }
    }

    int counter = 0;
    for(auto v : deg) {
        if (v == 4)
            counter++;
    }
    if (counter == 5) {
        return true;
    }  else
        return false;
}

bool chek_k5_or_k33(int mask) {
    for (int check_mask : check_k5) {
        if ((check_mask & mask) == check_mask)
            return true;
    }
    for (int check_mask : check_k33) {
        if ((check_mask & mask) == check_mask)
            return true;
    }
    return false;
}

void boost_foo(std::string mask, int size, bool &flag);

void solve() {
    int t;
    bool flag, flag1;
    std::string mask, tmp;
    std::getline(std::cin, tmp);
    t = atoi(tmp.c_str());
    for (int i = 0; i < t; ++i) {
        std::getline(std::cin, mask);
        size_t size = mask.size();
        boost_foo(mask, 6, flag);
        if (size < 10) { // less then 5 vertex
            //std::cout << "YES\n";
        } else if (size == 10) {  // when 5 vertex
            if (mask_to_int(mask) == 1023) { // 1111111111 -- K_{5}
                //std::cout << "NO\n";
            } else {
                //std::cout << "YES\n";
            }
        } else if (size == 15) { // when 6 vertex
            if (chek_k5_or_k33(mask_to_int(mask))) {
                flag1 = false;//std::cout << "NO\n";
            } else {
                flag1 = true;//std::cout << "YES\n";
            }
        }
        if (flag != flag1) {
            std::cout << "Error in mask: " << mask << "\n";
            std::cout << "Expected: ";
            if (flag)
                std::cout << "YES";
            else
                std::cout << "NO";
            std::cout << "\n";
        }
    }
}

void boost_foo(std::string mask, int size, bool &flag) {
    typedef adjacency_list<vecS,
            vecS,
            undirectedS,
            property<vertex_index_t, int>
    > graph;

    graph g(size);
    int k = 0;
    for (int i = 1; i < size; i++) {
        for (int j = 0; j < i; j++) {
            if (mask[k] == '1') {
                add_edge(i, j, g);
            }
            k++;
        }
    }

    if (boyer_myrvold_planarity_test(g))
        flag = true;//std::cout << "YES --- ";
    else
        flag = false;//std::cout << "NO --- ";
}
int main() {
    freopen("planaritycheck.in", "r", stdin);
    freopen("planaritycheck.out", "w", stdout);
    solve();

    return 0;
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_FAST_RSQRT_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_FAST_RSQRT_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/fast_rsqrt.hpp>
#include <boost/simd/include/functions/scalar/bitwise_cast.hpp>
#include <boost/simd/include/functions/scalar/rsqrt.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , tag::cpu_
                                    , (A0)
                                    , (scalar_< single_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 a0) const
    {
      typedef typename dispatch::meta::as_integer<A0>::type i_t;

      // Quake III Arena RSQRT approximation
      i_t x = bitwise_cast<i_t>(a0);
      i_t y = 0x5f3759df - (x >> 1);

      // make negative values be NaN
      y |= x >> (sizeof(i_t)*CHAR_BIT-1);

      A0 x2 = a0 * 0.5f;
      A0 y2 = bitwise_cast<A0>(y);

      // Newton-Rhapson refinement steps: 2 NR steps for precision purpose
      y2    = y2 * ( 1.5f - ( x2 * y2 * y2 ) );
      return  y2 * ( 1.5f - ( x2 * y2 * y2 ) );
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , tag::cpu_
                                    , (A0)
                                    , (scalar_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(A0 a0) const
    {
      return simd::rsqrt(a0);
    }
  };
} } }

#endif


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 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: Fabian Castelli, Karlsruhe Institute of Technology (KIT)
 */

// 首先我们包括本教程所需的deal.II库的典型头文件。

#include <deal.II/base/function.h>
#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/timer.h>
#include <deal.II/base/vectorization.h>

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

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

#include <deal.II/grid/grid_generator.h>
#include <deal.II/grid/grid_out.h>
#include <deal.II/grid/manifold_lib.h>
#include <deal.II/grid/tria.h>
#include <deal.II/grid/tria_accessor.h>
#include <deal.II/grid/tria_iterator.h>

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

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

// 特别是，我们需要包括无矩阵框架的头文件。

#include <deal.II/matrix_free/fe_evaluation.h>
#include <deal.II/matrix_free/matrix_free.h>
#include <deal.II/matrix_free/operators.h>
#include <deal.II/matrix_free/tools.h>

// 由于我们要使用几何多网格预处理程序，所以我们还需要多级头文件。

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

// 最后是一些常用的C++头文件，用于输入和输出。

#include <fstream>
#include <iostream>

namespace Step66
{
  using namespace dealii;

//  @sect3{Matrix-free JacobianOperator}

// 在开始时，我们定义了雅各布系数的无矩阵算子。作为指导，我们遵循教程  step-37  和  step-48  ，其中广泛记录了  MatrixFreeOperators::Base  类的精确接口。

// 由于我们希望将雅各布（Jacobian）作为系统矩阵使用，并将其传递给线性求解器以及多级预处理类，我们从 MatrixFreeOperators::Base 类派生出 JacobianOperator 类，这样我们就有了正确的接口。我们需要从基类中覆盖的两个函数是 MatrixFreeOperators::Base::apply_add() 和 MatrixFreeOperators::Base::compute_diagonal() 函数。为了允许用浮动精度进行预处理，我们将数字类型定义为模板参数。

// 正如在介绍中提到的，我们需要在最后一个牛顿步骤 $u_h^n$ 中评估雅各布 $F'$ ，以便计算牛顿更新 $s_h^n$ 。为了获得最后一个牛顿步骤 $u_h^n$ 的信息，我们的做法与 step-37 基本相同，在使用无矩阵算子之前，我们将一个系数函数的值存储在一个表中 nonlinear_values 。我们在这里实现的不是一个函数  evaluate_coefficient()  ，而是一个函数  evaluate_newton_step()  。

// 作为 JacobianOperator 的额外私有成员函数，我们实现了 local_apply() 和 local_compute_diagonal() 函数。第一个是矩阵-向量应用的实际工作函数，我们在 apply_add() 函数中将其传递给 MatrixFree::cell_loop() 。后面一个是计算对角线的工作函数，我们把它传递给 MatrixFreeTools::compute_diagonal() 函数。

// 为了提高源代码的可读性，我们进一步为FEEvaluation对象定义了一个别名。

  template <int dim, int fe_degree, typename number>
  class JacobianOperator
    : public MatrixFreeOperators::
        Base<dim, LinearAlgebra::distributed::Vector<number>>
  {
  public:
    using value_type = number;

    using FECellIntegrator =
      FEEvaluation<dim, fe_degree, fe_degree + 1, 1, number>;

    JacobianOperator();

    virtual void clear() override;

    void evaluate_newton_step(
      const LinearAlgebra::distributed::Vector<number> &newton_step);

    virtual void compute_diagonal() override;

  private:
    virtual void apply_add(
      LinearAlgebra::distributed::Vector<number> &      dst,
      const LinearAlgebra::distributed::Vector<number> &src) const override;

    void
    local_apply(const MatrixFree<dim, number> &                   data,
                LinearAlgebra::distributed::Vector<number> &      dst,
                const LinearAlgebra::distributed::Vector<number> &src,
                const std::pair<unsigned int, unsigned int> &cell_range) const;

    void local_compute_diagonal(FECellIntegrator &integrator) const;

    Table<2, VectorizedArray<number>> nonlinear_values;
  };

//  JacobianOperator 的构造函数只是调用基类 MatrixFreeOperators::Base, 的构造函数，而基类本身就是派生于Subscriptor类。

  template <int dim, int fe_degree, typename number>
  JacobianOperator<dim, fe_degree, number>::JacobianOperator()
    : MatrixFreeOperators::Base<dim,
                                LinearAlgebra::distributed::Vector<number>>()
  {}

//  clear() 函数重置了保存非线性值的表格，并调用基类的 clear() 函数。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::clear()
  {
    nonlinear_values.reinit(0, 0);
    MatrixFreeOperators::Base<dim, LinearAlgebra::distributed::Vector<number>>::
      clear();
  }

//  @sect4{Evaluation of the old Newton step}

// 下面的  evaluate_newton_step()  函数是基于  step-37  的  evaluate_coefficient()  函数。然而，它并不评估一个函数对象，而是评估一个代表有限元函数的向量，即雅各布系数所需的最后一个牛顿步骤。因此，我们设置了一个FEEvaluation对象，用 FEEvaluation::read_dof_values_plain() 和 FEEvaluation::evaluate() 函数评估正交点的有限元函数。我们将有限元函数的评估值直接存储在 nonlinear_values 表中。

//这样做会很好，在 local_apply() 函数中我们可以使用存储在表中的值来应用矩阵-向量乘积。然而，我们也可以在这个阶段优化雅各布系数的实现。我们可以直接评估非线性函数 std::exp(newton_step[q]) 并将这些值存储在表中。这就跳过了在每次调用 vmult() 函数时对非线性的所有评估。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::evaluate_newton_step(
    const LinearAlgebra::distributed::Vector<number> &newton_step)
  {
    const unsigned int n_cells = this->data->n_cell_batches();
    FECellIntegrator   phi(*this->data);

    nonlinear_values.reinit(n_cells, phi.n_q_points);

    for (unsigned int cell = 0; cell < n_cells; ++cell)
      {
        phi.reinit(cell);
        phi.read_dof_values_plain(newton_step);
        phi.evaluate(EvaluationFlags::values);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            nonlinear_values(cell, q) = std::exp(phi.get_value(q));
          }
      }
  }

//  @sect4{Nonlinear matrix-free operator application}

// 现在在  local_apply()  函数中，实际上实现了系统矩阵的单元格动作，我们可以使用存储在表  nonlinear_values  中的最后一个牛顿步骤的信息。这个函数的其余部分与  step-37  中的基本相同。我们设置 FEEvaluation 对象，收集并评估输入向量的值和梯度  src  ，根据雅各布的形式提交值和梯度，最后调用  FEEvaluation::integrate_scatter()  进行单元积分，将局部贡献分配到全局向量   dst  。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::local_apply(
    const MatrixFree<dim, number> &                   data,
    LinearAlgebra::distributed::Vector<number> &      dst,
    const LinearAlgebra::distributed::Vector<number> &src,
    const std::pair<unsigned int, unsigned int> &     cell_range) const
  {
    FECellIntegrator phi(data);

    for (unsigned int cell = cell_range.first; cell < cell_range.second; ++cell)
      {
        AssertDimension(nonlinear_values.size(0),
                        phi.get_matrix_free().n_cell_batches());
        AssertDimension(nonlinear_values.size(1), phi.n_q_points);

        phi.reinit(cell);

        phi.gather_evaluate(src,
                            EvaluationFlags::values |
                              EvaluationFlags::gradients);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            phi.submit_value(-nonlinear_values(cell, q) * phi.get_value(q), q);
            phi.submit_gradient(phi.get_gradient(q), q);
          }

        phi.integrate_scatter(EvaluationFlags::values |
                                EvaluationFlags::gradients,
                              dst);
      }
  }

// 接下来我们使用 MatrixFree::cell_loop() 对所有单元进行实际循环，计算单元对矩阵-向量积的贡献。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::apply_add(
    LinearAlgebra::distributed::Vector<number> &      dst,
    const LinearAlgebra::distributed::Vector<number> &src) const
  {
    this->data->cell_loop(&JacobianOperator::local_apply, this, dst, src);
  }

//  @sect4{Diagonal of the JacobianOperator}

// 用于计算对角线的内部工作函数  local_compute_diagonal()  与上述工作函数  local_apply()  类似。然而，作为主要区别，我们不从输入向量中读取数值，也不将任何局部结果分配给输出向量。相反，唯一的输入参数是使用的FEEvaluation对象。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::local_compute_diagonal(
    FECellIntegrator &phi) const
  {
    AssertDimension(nonlinear_values.size(0),
                    phi.get_matrix_free().n_cell_batches());
    AssertDimension(nonlinear_values.size(1), phi.n_q_points);

    const unsigned int cell = phi.get_current_cell_index();

    phi.evaluate(EvaluationFlags::values | EvaluationFlags::gradients);

    for (unsigned int q = 0; q < phi.n_q_points; ++q)
      {
        phi.submit_value(-nonlinear_values(cell, q) * phi.get_value(q), q);
        phi.submit_gradient(phi.get_gradient(q), q);
      }

    phi.integrate(EvaluationFlags::values | EvaluationFlags::gradients);
  }

// 最后我们覆盖  MatrixFreeOperators::Base::compute_diagonal()  的基类的  JacobianOperator  的函数。虽然这个函数的名字表明只是计算对角线，但这个函数的作用更大。因为我们实际上只需要矩阵对角线元素的逆值，用于多网格预处理器的切比雪夫平滑器，我们计算对角线并存储逆值元素。因此我们首先初始化 inverse_diagonal_entries  。然后我们通过将工作函数 local_compute_diagonal() 传递给 MatrixFreeTools::compute_diagonal() 函数来计算对角线。最后，我们在对角线上循环，用手反转这些元素。注意，在这个循环过程中，我们捕捉受限的DOF，并手动将其设置为1。

  template <int dim, int fe_degree, typename number>
  void JacobianOperator<dim, fe_degree, number>::compute_diagonal()
  {
    this->inverse_diagonal_entries.reset(
      new DiagonalMatrix<LinearAlgebra::distributed::Vector<number>>());
    LinearAlgebra::distributed::Vector<number> &inverse_diagonal =
      this->inverse_diagonal_entries->get_vector();
    this->data->initialize_dof_vector(inverse_diagonal);

    MatrixFreeTools::compute_diagonal(*this->data,
                                      inverse_diagonal,
                                      &JacobianOperator::local_compute_diagonal,
                                      this);

    for (auto &diagonal_element : inverse_diagonal)
      {
        diagonal_element = (std::abs(diagonal_element) > 1.0e-10) ?
                             (1.0 / diagonal_element) :
                             1.0;
      }
  }

//  @sect3{GelfandProblem class}

// 在实现了无矩阵运算符之后，我们现在可以为Gelfand problem定义求解器类。这个类是基于之前所有教程程序的共同结构，特别是它是基于 step-15 ，解决的也是一个非线性问题。由于我们使用的是无矩阵框架，所以我们不再需要assemble_system函数，相反，在每次调用 vmult() 函数时都会重建矩阵的信息。然而，对于牛顿方案的应用，我们需要组装线性化问题的右手边并计算残差。因此，我们实现了一个额外的函数 evaluate_residual() ，后来我们在 assemble_rhs() and the compute_residual() 函数中调用了它。最后，这里典型的 solve() 函数实现了牛顿方法，而线性化系统的解是在 compute_update() 函数中计算的。由于MatrixFree框架将拉格朗日有限元方法的多项式程度作为一个模板参数来处理，我们也将其作为问题求解器类的模板参数来声明。

  template <int dim, int fe_degree>
  class GelfandProblem
  {
  public:
    GelfandProblem();

    void run();

  private:
    void make_grid();

    void setup_system();

    void evaluate_residual(
      LinearAlgebra::distributed::Vector<double> &      dst,
      const LinearAlgebra::distributed::Vector<double> &src) const;

    void local_evaluate_residual(
      const MatrixFree<dim, double> &                   data,
      LinearAlgebra::distributed::Vector<double> &      dst,
      const LinearAlgebra::distributed::Vector<double> &src,
      const std::pair<unsigned int, unsigned int> &     cell_range) const;

    void assemble_rhs();

    double compute_residual(const double alpha);

    void compute_update();

    void solve();

    double compute_solution_norm() const;

    void output_results(const unsigned int cycle) const;

// 对于并行计算，我们定义了一个  parallel::distributed::Triangulation.  由于计算域在二维是一个圆，在三维是一个球，我们除了为边界单元分配SphericalManifold外，还为内部单元的映射分配了一个TransfiniteInterpolationManifold对象，它负责处理内部单元的映射。在这个例子中，我们使用了一个等参数的有限元方法，因此使用了MappingQGeneric类。注意，我们也可以创建一个MappingQ类的实例，并在构造函数调用中设置 use_mapping_q_on_all_cells 标志为 true  。关于MappingQ和MappingQGeneric连接的进一步细节，你可以阅读这些类的详细描述。

    parallel::distributed::Triangulation<dim> triangulation;
    const MappingQGeneric<dim>                mapping;

// 像往常一样，我们接着定义拉格朗日有限元FE_Q和一个DoFHandler。

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

// 对于线性化的离散系统，我们定义一个AffineConstraints对象和 system_matrix  ，在本例中它被表示为一个无矩阵算子。

    AffineConstraints<double> constraints;
    using SystemMatrixType = JacobianOperator<dim, fe_degree, double>;
    SystemMatrixType system_matrix;

// 多级对象也是基于雅各布系数的无矩阵算子。由于我们需要用最后一个牛顿步骤来评估雅各布，所以我们也需要用最后一个牛顿步骤来评估预处理器的水平算子。因此，除了 mg_matrices 之外，我们还需要一个MGLevelObject来存储每一级的插值解向量。与 step-37 一样，我们对预处理程序使用浮点精度。此外，我们将MGTransferMatrixFree对象定义为一个类变量，因为我们只需要在三角形变化时设置一次，然后可以在每个牛顿步骤中再次使用它。

    MGConstrainedDoFs mg_constrained_dofs;
    using LevelMatrixType = JacobianOperator<dim, fe_degree, float>;
    MGLevelObject<LevelMatrixType>                           mg_matrices;
    MGLevelObject<LinearAlgebra::distributed::Vector<float>> mg_solution;
    MGTransferMatrixFree<dim, float>                         mg_transfer;

// 当然，我们还需要持有  solution  ,  newton_update and the system_rhs  的向量。这样，我们就可以一直将上一个牛顿步存储在解的向量中，只需添加更新就可以得到下一个牛顿步。

    LinearAlgebra::distributed::Vector<double> solution;
    LinearAlgebra::distributed::Vector<double> newton_update;
    LinearAlgebra::distributed::Vector<double> system_rhs;

// 最后我们有一个变量，用来表示线性求解器的迭代次数。

    unsigned int linear_iterations;

// 对于与MPI并行运行的程序中的输出，我们使用ConditionalOStream类来避免不同MPI等级对同一数据的多次输出。

    ConditionalOStream pcout;

// 最后，对于时间测量，我们使用一个TimerOutput对象，它在程序结束后将每个函数的耗时CPU和墙体时间打印在一个格式良好的表格中。

    TimerOutput computing_timer;
  };

//  GelfandProblem 的构造函数初始化了类的变量。特别是，我们为 parallel::distributed::Triangulation, 设置了多级支持，将映射度设为有限元度，初始化ConditionalOStream，并告诉TimerOutput，我们只想在需求时看到墙体时间。

  template <int dim, int fe_degree>
  GelfandProblem<dim, fe_degree>::GelfandProblem()
    : triangulation(MPI_COMM_WORLD,
                    Triangulation<dim>::limit_level_difference_at_vertices,
                    parallel::distributed::Triangulation<
                      dim>::construct_multigrid_hierarchy)
    , mapping(fe_degree)
    , fe(fe_degree)
    , dof_handler(triangulation)
    , pcout(std::cout, Utilities::MPI::this_mpi_process(MPI_COMM_WORLD) == 0)
    , computing_timer(MPI_COMM_WORLD,
                      pcout,
                      TimerOutput::never,
                      TimerOutput::wall_times)
  {}

//  @sect4{GelfandProblem::make_grid}

// 作为计算域，我们使用 dim  -维的单位球。我们按照TransfiniteInterpolationManifold类的说明，也为边界指定了一个SphericalManifold。最后，我们将初始网格细化为3

// -  dim 次全局。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::make_grid()
  {
    TimerOutput::Scope t(computing_timer, "make grid");

    SphericalManifold<dim>                boundary_manifold;
    TransfiniteInterpolationManifold<dim> inner_manifold;

    GridGenerator::hyper_ball(triangulation);

    triangulation.set_all_manifold_ids_on_boundary(0);

    triangulation.set_manifold(0, boundary_manifold);

    inner_manifold.initialize(triangulation);
    triangulation.set_manifold(1, inner_manifold);

    triangulation.refine_global(3 - dim);
  }

//  @sect4{GelfandProblem::setup_system}

//  setup_system() 函数与  step-37  中的函数基本相同。唯一的区别显然是时间测量只有一个 TimerOutput::Scope ，而不是单独测量每个部分，更重要的是对前一个牛顿步骤的内插解向量的MGLevelObject的初始化。另一个重要的变化是MGTransferMatrixFree对象的设置，我们可以在每个牛顿步骤中重复使用它，因为 triangulation 不会被改变。

// 注意我们如何在 JacobianOperator 和多网格预处理程序中两次使用同一个MatrixFree对象。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::setup_system()
  {
    TimerOutput::Scope t(computing_timer, "setup system");

    system_matrix.clear();
    mg_matrices.clear_elements();

    dof_handler.distribute_dofs(fe);
    dof_handler.distribute_mg_dofs();

    IndexSet locally_relevant_dofs;
    DoFTools::extract_locally_relevant_dofs(dof_handler, locally_relevant_dofs);

    constraints.clear();
    constraints.reinit(locally_relevant_dofs);
    DoFTools::make_hanging_node_constraints(dof_handler, constraints);
    VectorTools::interpolate_boundary_values(dof_handler,
                                             0,
                                             Functions::ZeroFunction<dim>(),
                                             constraints);
    constraints.close();

    {
      typename MatrixFree<dim, double>::AdditionalData additional_data;
      additional_data.tasks_parallel_scheme =
        MatrixFree<dim, double>::AdditionalData::partition_color;
      additional_data.mapping_update_flags =
        (update_values | update_gradients | update_JxW_values |
         update_quadrature_points);
      auto system_mf_storage = std::make_shared<MatrixFree<dim, double>>();
      system_mf_storage->reinit(mapping,
                                dof_handler,
                                constraints,
                                QGauss<1>(fe.degree + 1),
                                additional_data);

      system_matrix.initialize(system_mf_storage);
    }

    system_matrix.initialize_dof_vector(solution);
    system_matrix.initialize_dof_vector(newton_update);
    system_matrix.initialize_dof_vector(system_rhs);

    const unsigned int nlevels = triangulation.n_global_levels();
    mg_matrices.resize(0, nlevels - 1);
    mg_solution.resize(0, nlevels - 1);

    std::set<types::boundary_id> dirichlet_boundary;
    dirichlet_boundary.insert(0);
    mg_constrained_dofs.initialize(dof_handler);
    mg_constrained_dofs.make_zero_boundary_constraints(dof_handler,
                                                       dirichlet_boundary);

    mg_transfer.initialize_constraints(mg_constrained_dofs);
    mg_transfer.build(dof_handler);

    for (unsigned int level = 0; level < nlevels; ++level)
      {
        IndexSet relevant_dofs;
        DoFTools::extract_locally_relevant_level_dofs(dof_handler,
                                                      level,
                                                      relevant_dofs);

        AffineConstraints<double> level_constraints;
        level_constraints.reinit(relevant_dofs);
        level_constraints.add_lines(
          mg_constrained_dofs.get_boundary_indices(level));
        level_constraints.close();

        typename MatrixFree<dim, float>::AdditionalData additional_data;
        additional_data.tasks_parallel_scheme =
          MatrixFree<dim, float>::AdditionalData::partition_color;
        additional_data.mapping_update_flags =
          (update_values | update_gradients | update_JxW_values |
           update_quadrature_points);
        additional_data.mg_level = level;
        auto mg_mf_storage_level = std::make_shared<MatrixFree<dim, float>>();
        mg_mf_storage_level->reinit(mapping,
                                    dof_handler,
                                    level_constraints,
                                    QGauss<1>(fe.degree + 1),
                                    additional_data);

        mg_matrices[level].initialize(mg_mf_storage_level,
                                      mg_constrained_dofs,
                                      level);
        mg_matrices[level].initialize_dof_vector(mg_solution[level]);
      }
  }

//  @sect4{GelfandProblem::evaluate_residual}

// 接下来我们实现一个函数，该函数对给定的输入向量评估非线性离散残差（  $\texttt{dst} = F(\texttt{src})$  ）。这个函数随后被用于组装线性化系统的右手边，随后用于计算下一个牛顿步骤的残差，以检查我们是否已经达到了误差容忍度。由于这个函数不应该影响任何类别的变量，我们把它定义为一个常数函数。在内部，我们通过FEEvaluation类和类似于 MatrixFree::cell_loop(), 的 apply_add() function of the JacobianOperator 来利用快速有限元评估。

// 首先我们创建一个指向MatrixFree对象的指针，它被存储在  system_matrix  中。然后，我们将用于残差的单元评估的工作函数  local_evaluate_residual()  以及输入和输出向量传递给  MatrixFree::cell_loop().  此外，我们在循环中启用输出向量的清零，这比之前单独调用dst = 0.0更有效率。

// 注意，使用这种方法，我们不必关心MPI相关的数据交换，因为所有的记账工作都是由  MatrixFree::cell_loop().  完成的。
  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::evaluate_residual(
    LinearAlgebra::distributed::Vector<double> &      dst,
    const LinearAlgebra::distributed::Vector<double> &src) const
  {
    auto matrix_free = system_matrix.get_matrix_free();

    matrix_free->cell_loop(
      &GelfandProblem::local_evaluate_residual, this, dst, src, true);
  }

//  @sect4{GelfandProblem::local_evaluate_residual}

// 这是用于评估残差的内部工作函数。本质上它与  JacobianOperator  的  local_apply()  函数具有相同的结构，在给定的单元格集合  cell_range  上对输入向量  src  进行残差评估。与上述 local_apply() 函数不同的是，我们将 FEEvaluation::gather_evaluate() 函数分成 FEEvaluation::read_dof_values_plain() 和 FEEvaluation::evaluate(), ，因为输入向量可能有受限的DOF。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::local_evaluate_residual(
    const MatrixFree<dim, double> &                   data,
    LinearAlgebra::distributed::Vector<double> &      dst,
    const LinearAlgebra::distributed::Vector<double> &src,
    const std::pair<unsigned int, unsigned int> &     cell_range) const
  {
    FEEvaluation<dim, fe_degree, fe_degree + 1, 1, double> phi(data);

    for (unsigned int cell = cell_range.first; cell < cell_range.second; ++cell)
      {
        phi.reinit(cell);

        phi.read_dof_values_plain(src);
        phi.evaluate(EvaluationFlags::values | EvaluationFlags::gradients);

        for (unsigned int q = 0; q < phi.n_q_points; ++q)
          {
            phi.submit_value(-std::exp(phi.get_value(q)), q);
            phi.submit_gradient(phi.get_gradient(q), q);
          }

        phi.integrate_scatter(EvaluationFlags::values |
                                EvaluationFlags::gradients,
                              dst);
      }
  }

//  @sect4{GelfandProblem::assemble_rhs}

// 使用上述函数 evaluate_residual() 来评估非线性残差，组装线性化系统的右手边现在变得非常容易。我们只需调用 evaluate_residual() 函数并将结果乘以减一。

// 经验表明，使用FEEvaluation类要比使用FEValues和co的经典实现快得多。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::assemble_rhs()
  {
    TimerOutput::Scope t(computing_timer, "assemble right hand side");

    evaluate_residual(system_rhs, solution);

    system_rhs *= -1.0;
  }

//  @sect4{GelfandProblem::compute_residual}

// 根据 step-15 ，下面的函数在 $u_h^n + \alpha s_h^n$ 函数的帮助下计算出解的非线性残差的规范。如果我们使用牛顿方法的自适应版本，牛顿步长 $\alpha$ 就变得很重要。例如，我们将计算不同步长的残差并比较残差。然而，对于我们的问题，使用 $\alpha=1$ 的完整牛顿步长是我们能做的最好的。如果我们没有好的初始值，牛顿方法的自适应版本就变得有趣了。请注意，在理论上，牛顿方法是以二次方顺序收敛的，但只有当我们有一个合适的初始值时才会收敛。对于不合适的初始值，牛顿方法甚至在二次方程下也会发散。一个常见的方法是使用阻尼版本 $\alpha<1$ ，直到牛顿步骤足够好，可以进行完整的牛顿步骤。这在  step-15  中也有讨论。

  template <int dim, int fe_degree>
  double GelfandProblem<dim, fe_degree>::compute_residual(const double alpha)
  {
    TimerOutput::Scope t(computing_timer, "compute residual");

    LinearAlgebra::distributed::Vector<double> residual;
    LinearAlgebra::distributed::Vector<double> evaluation_point;

    system_matrix.initialize_dof_vector(residual);
    system_matrix.initialize_dof_vector(evaluation_point);

    evaluation_point = solution;
    if (alpha > 1e-12)
      {
        evaluation_point.add(alpha, newton_update);
      }

    evaluate_residual(residual, evaluation_point);

    return residual.l2_norm();
  }

//  @sect4{GelfandProblem::compute_update}

// 为了计算每个牛顿步骤中的牛顿更新，我们用CG算法和一个几何多网格预处理程序来解决线性系统。为此，我们首先像在  step-37  中那样，用切比雪夫平滑器设置PreconditionMG对象。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::compute_update()
  {
    TimerOutput::Scope t(computing_timer, "compute update");

// 我们记得，雅各布系数取决于存储在解决方案向量中的最后一个牛顿步骤。所以我们更新牛顿步骤的鬼魂值，并将其传递给 JacobianOperator 来存储信息。

    solution.update_ghost_values();

    system_matrix.evaluate_newton_step(solution);

// 接下来我们还要将最后一个牛顿步骤传递给多级运算符。因此，我们需要将牛顿步骤插值到三角形的所有层面。这是用 MGTransferMatrixFree::interpolate_to_mg(). 来完成的。
    mg_transfer.interpolate_to_mg(dof_handler, mg_solution, solution);

// 现在我们可以设置预处理程序了。我们定义平滑器并将牛顿步的内插向量传递给多级运算器。

    using SmootherType =
      PreconditionChebyshev<LevelMatrixType,
                            LinearAlgebra::distributed::Vector<float>>;
    mg::SmootherRelaxation<SmootherType,
                           LinearAlgebra::distributed::Vector<float>>
                                                         mg_smoother;
    MGLevelObject<typename SmootherType::AdditionalData> smoother_data;
    smoother_data.resize(0, triangulation.n_global_levels() - 1);
    for (unsigned int level = 0; level < triangulation.n_global_levels();
         ++level)
      {
        if (level > 0)
          {
            smoother_data[level].smoothing_range     = 15.;
            smoother_data[level].degree              = 4;
            smoother_data[level].eig_cg_n_iterations = 10;
          }
        else
          {
            smoother_data[0].smoothing_range = 1e-3;
            smoother_data[0].degree          = numbers::invalid_unsigned_int;
            smoother_data[0].eig_cg_n_iterations = mg_matrices[0].m();
          }

        mg_matrices[level].evaluate_newton_step(mg_solution[level]);
        mg_matrices[level].compute_diagonal();

        smoother_data[level].preconditioner =
          mg_matrices[level].get_matrix_diagonal_inverse();
      }
    mg_smoother.initialize(mg_matrices, smoother_data);

    MGCoarseGridApplySmoother<LinearAlgebra::distributed::Vector<float>>
      mg_coarse;
    mg_coarse.initialize(mg_smoother);

    mg::Matrix<LinearAlgebra::distributed::Vector<float>> mg_matrix(
      mg_matrices);

    MGLevelObject<MatrixFreeOperators::MGInterfaceOperator<LevelMatrixType>>
      mg_interface_matrices;
    mg_interface_matrices.resize(0, triangulation.n_global_levels() - 1);
    for (unsigned int level = 0; level < triangulation.n_global_levels();
         ++level)
      {
        mg_interface_matrices[level].initialize(mg_matrices[level]);
      }
    mg::Matrix<LinearAlgebra::distributed::Vector<float>> mg_interface(
      mg_interface_matrices);

    Multigrid<LinearAlgebra::distributed::Vector<float>> mg(
      mg_matrix, mg_coarse, mg_transfer, mg_smoother, mg_smoother);
    mg.set_edge_matrices(mg_interface, mg_interface);

    PreconditionMG<dim,
                   LinearAlgebra::distributed::Vector<float>,
                   MGTransferMatrixFree<dim, float>>
      preconditioner(dof_handler, mg, mg_transfer);

// 最后我们设置了SolverControl和SolverCG来解决当前牛顿更新的线性化问题。实现SolverCG或SolverGMRES的一个重要事实是，持有线性系统解决方案的向量（这里是 newton_update  ）可以用来传递一个起始值。为了使迭代求解器总是以零向量开始，我们在调用 SolverCG::solve(). 之前明确地重置了 newton_update ，然后我们分配了存储在 constraints 中的Dirichlet边界条件，并为以后的输出存储了迭代的步数。

    SolverControl solver_control(100, 1.e-12);
    SolverCG<LinearAlgebra::distributed::Vector<double>> cg(solver_control);

    newton_update = 0.0;

    cg.solve(system_matrix, newton_update, system_rhs, preconditioner);

    constraints.distribute(newton_update);

    linear_iterations = solver_control.last_step();

// 然后，为了记账，我们将幽灵值清零。

    solution.zero_out_ghost_values();
  }

//  @sect4{GelfandProblem::solve}

// 现在我们实现非线性问题的实际牛顿求解器。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::solve()
  {
    TimerOutput::Scope t(computing_timer, "solve");

// 我们定义了牛顿步骤的最大数量和收敛标准的公差。通常情况下，如果有好的起始值，牛顿方法在三到六步内就能收敛，所以最大的十步应该是完全足够的。作为公差，我们使用 $\|F(u^n_h)\|<\text{TOL}_f = 10^{-12}$ 作为残差的规范， $\|s_h^n\| < \text{TOL}_x = 10^{-10}$ 作为牛顿更新的规范。这似乎有点过头了，但我们将看到，对于我们的例子，我们将在几步之后达到这些公差。

    const unsigned int itmax = 10;
    const double       TOLf  = 1e-12;
    const double       TOLx  = 1e-10;

    Timer solver_timer;
    solver_timer.start();

// 现在我们开始实际的牛顿迭代。

    for (unsigned int newton_step = 1; newton_step <= itmax; ++newton_step)
      {

// 我们将线性化问题的右侧集合起来，计算牛顿更新。

        assemble_rhs();
        compute_update();

// 然后，我们计算误差，即牛顿更新的规范和残差。注意，在这一点上，我们可以通过改变compute_residual函数的输入参数 $\alpha$ 来加入牛顿方法的步长控制。然而，在这里我们只是使用 $\alpha$ 等于1来进行普通的牛顿迭代。

        const double ERRx = newton_update.l2_norm();
        const double ERRf = compute_residual(1.0);

// 接下来我们通过将牛顿更新添加到当前的牛顿步骤中来推进牛顿步骤。

        solution.add(1.0, newton_update);

// 一个简短的输出将告知我们当前的牛顿步数。

        pcout << "   Nstep " << newton_step << ", errf = " << ERRf
              << ", errx = " << ERRx << ", it = " << linear_iterations
              << std::endl;

// 在每个牛顿步骤之后，我们检查收敛标准。如果其中至少有一个得到满足，我们就完成了，并结束循环。如果我们在牛顿迭代的最大数量之后还没有找到一个满意的解决方案，我们就会通知用户这个缺点。

        if (ERRf < TOLf || ERRx < TOLx)
          {
            solver_timer.stop();

            pcout << "Convergence step " << newton_step << " value " << ERRf
                  << " (used wall time: " << solver_timer.wall_time() << " s)"
                  << std::endl;

            break;
          }
        else if (newton_step == itmax)
          {
            solver_timer.stop();
            pcout << "WARNING: No convergence of Newton's method after "
                  << newton_step << " steps." << std::endl;

            break;
          }
      }
  }

//  @sect4{GelfandProblem::compute_solution_norm}

// 解的H1-seminorm的计算可以用与 step-59 相同的方法进行。我们更新幽灵值并使用函数  VectorTools::integrate_difference().  最后我们收集所有MPI行列的所有计算，并返回规范。

  template <int dim, int fe_degree>
  double GelfandProblem<dim, fe_degree>::compute_solution_norm() const
  {
    solution.update_ghost_values();

    Vector<float> norm_per_cell(triangulation.n_active_cells());

    VectorTools::integrate_difference(mapping,
                                      dof_handler,
                                      solution,
                                      Functions::ZeroFunction<dim>(),
                                      norm_per_cell,
                                      QGauss<dim>(fe.degree + 2),
                                      VectorTools::H1_seminorm);

    solution.zero_out_ghost_values();

    return VectorTools::compute_global_error(triangulation,
                                             norm_per_cell,
                                             VectorTools::H1_seminorm);
  }

//  @sect4{GelfandProblem::output_results}

// 我们通过调用  DataOut::write_vtu_with_pvtu_record()  函数，以与  step-37  中相同的方式，一次性生成 vtu 格式的图形输出文件和 pvtu 主文件。此外，与  step-40  一样，我们查询每个单元的  types::subdomain_id  并将三角形在MPI行列中的分布写进输出文件。最后，我们通过调用 DataOut::build_patches(). 生成解决方案的补丁。然而，由于我们的计算域有一个弯曲的边界，我们另外传递 mapping 和有限元度作为细分的数量。但这仍然不足以正确表示解决方案，例如在ParaView中，因为我们将TransfiniteInterpolationManifold附在内部单元上，这导致内部的单元是弯曲的。因此，我们将 DataOut::curved_inner_cells 选项作为第三个参数，这样，内部单元也会使用相应的流形描述来构建补丁。

// 注意，我们可以用标志 DataOutBase::VtkFlags::write_higher_order_cells. 来处理高阶元素，但是由于对ParaView以前版本的兼容性有限，而且VisIt也不支持，所以我们把这个选项留给未来的版本。

  template <int dim, int fe_degree>
  void
  GelfandProblem<dim, fe_degree>::output_results(const unsigned int cycle) const
  {
    if (triangulation.n_global_active_cells() > 1e6)
      return;

    solution.update_ghost_values();

    DataOut<dim> data_out;
    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(solution, "solution");

    Vector<float> subdomain(triangulation.n_active_cells());
    for (unsigned int i = 0; i < subdomain.size(); ++i)
      {
        subdomain(i) = triangulation.locally_owned_subdomain();
      }
    data_out.add_data_vector(subdomain, "subdomain");

    data_out.build_patches(mapping,
                           fe.degree,
                           DataOut<dim>::curved_inner_cells);

    DataOutBase::VtkFlags flags;
    flags.compression_level = DataOutBase::VtkFlags::best_speed;
    data_out.set_flags(flags);
    data_out.write_vtu_with_pvtu_record(
      "./", "solution_" + std::to_string(dim) + "d", cycle, MPI_COMM_WORLD, 3);

    solution.zero_out_ghost_values();
  }

//  @sect4{GelfandProblem::run}

// Gelfand
 problem的求解器类的最后一个缺失的函数是运行函数。在开始的时候，我们打印关于系统规格和我们使用的有限元空间的信息。该问题在一个连续细化的网格上被多次求解。

  template <int dim, int fe_degree>
  void GelfandProblem<dim, fe_degree>::run()
  {
    {
      const unsigned int n_ranks =
        Utilities::MPI::n_mpi_processes(MPI_COMM_WORLD);
      const unsigned int n_vect_doubles = VectorizedArray<double>::size();
      const unsigned int n_vect_bits    = 8 * sizeof(double) * n_vect_doubles;

      std::string DAT_header = "START DATE: " + Utilities::System::get_date() +
                               ", TIME: " + Utilities::System::get_time();
      std::string MPI_header = "Running with " + std::to_string(n_ranks) +
                               " MPI process" + (n_ranks > 1 ? "es" : "");
      std::string VEC_header =
        "Vectorization over " + std::to_string(n_vect_doubles) +
        " doubles = " + std::to_string(n_vect_bits) + " bits (" +
        Utilities::System::get_current_vectorization_level() +
        "), VECTORIZATION_LEVEL=" +
        std::to_string(DEAL_II_COMPILER_VECTORIZATION_LEVEL);
      std::string SOL_header = "Finite element space: " + fe.get_name();

      pcout << std::string(80, '=') << std::endl;
      pcout << DAT_header << std::endl;
      pcout << std::string(80, '-') << std::endl;

      pcout << MPI_header << std::endl;
      pcout << VEC_header << std::endl;
      pcout << SOL_header << std::endl;

      pcout << std::string(80, '=') << std::endl;
    }

    for (unsigned int cycle = 0; cycle < 9 - dim; ++cycle)
      {
        pcout << std::string(80, '-') << std::endl;
        pcout << "Cycle " << cycle << std::endl;
        pcout << std::string(80, '-') << std::endl;

// 实际解决问题的第一项任务是生成或完善三角图。

        if (cycle == 0)
          {
            make_grid();
          }
        else
          {
            triangulation.refine_global(1);
          }

// 现在我们建立了系统并解决这个问题。这些步骤都伴随着时间测量和文本输出。

        Timer timer;

        pcout << "Set up system..." << std::endl;
        setup_system();

        pcout << "   Triangulation: " << triangulation.n_global_active_cells()
              << " cells" << std::endl;
        pcout << "   DoFHandler:    " << dof_handler.n_dofs() << " DoFs"
              << std::endl;
        pcout << std::endl;

        pcout << "Solve using Newton's method..." << std::endl;
        solve();
        pcout << std::endl;

        timer.stop();
        pcout << "Time for setup+solve (CPU/Wall) " << timer.cpu_time() << "/"
              << timer.wall_time() << " s" << std::endl;
        pcout << std::endl;

// 在问题被解决后，我们计算出解决方案的法线，并生成图形输出文件。

        pcout << "Output results..." << std::endl;
        const double norm = compute_solution_norm();
        output_results(cycle);

        pcout << "  H1 seminorm: " << norm << std::endl;
        pcout << std::endl;

// 最后在每个周期后，我们打印计时信息。

        computing_timer.print_summary();
        computing_timer.reset();
      }
  }
} // namespace Step66

//  @sect3{The main function}

// 作为使用MPI并行运行的典型程序，我们设置了MPI框架，并通过限制线程数为1来禁用共享内存并行化。最后，为了运行Gelfand problem的求解器，我们创建一个 GelfandProblem 类的对象并调用运行函数。例如，我们用四阶拉格朗日有限元在二维和三维中各解决一次问题。

int main(int argc, char *argv[])
{
  try
    {
      using namespace Step66;

      Utilities::MPI::MPI_InitFinalize mpi_init(argc, argv, 1);

      {
        GelfandProblem<2, 4> gelfand_problem;
        gelfand_problem.run();
      }

      {
        GelfandProblem<3, 4> gelfand_problem;
        gelfand_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;
}