//
// Created by huangkun on 2020/8/11.
//

#include <Eigen/Eigen>
#include <opencv2/core/eigen.hpp>
#include <opencv2/opencv.hpp>
#include <nanoflann.hpp>
#include <KDTreeVectorOfVectorsAdaptor.h>

#include <opengv2/sensor/PinholeCamera.hpp>
#include <opengv2/utility/utility.hpp>

opengv2::PinholeCamera::PinholeCamera(const Eigen::Ref<const Eigen::Vector2d> &size,
                                      const Eigen::Ref<const Eigen::Matrix3d> &K,
                                      const Eigen::Ref<const Eigen::VectorXd> &distCoeffs,
                                      cv::Mat mask) :
        CameraBase(size, mask), K_(K), invK_(K.inverse()), distCoeffs_(distCoeffs),
        inverseRadialPoly_(Eigen::VectorXd()) {}

inline Eigen::Vector2d opengv2::PinholeCamera::project(const Eigen::Ref<const Eigen::Vector3d> &Xc) const {
    if (distCoeffs_.size() == 0) {
        Eigen::Vector3d p = K_ * Xc;
        p /= p(2);
        return p.block<2, 1>(0, 0);
    } else {
        // TODO: implement using Eigen
        std::vector<cv::Point3f> objectPoints(1, cv::Point3f(Xc[0], Xc[1], Xc[2]));
        cv::Mat tvec = cv::Mat::zeros(3, 1, CV_32F), rvec, distCoeffs, cameraMatrix;
        cv::Rodrigues(cv::Mat::eye(3, 3, CV_32F), rvec);
        cv::eigen2cv(distCoeffs_, distCoeffs);
        cv::eigen2cv(K_, cameraMatrix);
        std::vector<cv::Point2f> imagePoints;
        cv::projectPoints(objectPoints, rvec, tvec, cameraMatrix, distCoeffs, imagePoints);
        return Eigen::Vector2d(imagePoints[0].x, imagePoints[0].y);
    }
}

inline Eigen::Vector3d opengv2::PinholeCamera::invProject(const Eigen::Ref<const Eigen::Vector2d> &p) const {
    Eigen::Vector3d Xc;
    if (inverseRadialPoly_.size() != 0) {
        Xc = invK_ * Eigen::Vector3d(p(0), p(1), 1);
        Xc /= Xc[2];

        Eigen::VectorXd r_coeff(inverseRadialPoly_.size());
        r_coeff[0] = Xc[0] * Xc[0] + Xc[1] * Xc[1];
        for (int i = 1; i < inverseRadialPoly_.size(); ++i) {
            r_coeff[i] = r_coeff[i - 1] * r_coeff[0];
        }
        Xc[0] *= 1 + r_coeff.transpose() * inverseRadialPoly_;
        Xc[1] *= 1 + r_coeff.transpose() * inverseRadialPoly_;
    } else if (distCoeffs_.size() != 0) {
        // TODO: implement using Eigen
        std::vector<cv::Point2f> src, dst;
        src.emplace_back(p[0], p[1]);
        cv::Mat cameraMatrix, distCoeffs;
        cv::eigen2cv(K_, cameraMatrix);
        cv::eigen2cv(distCoeffs_, distCoeffs);
        cv::undistortPoints(src, dst, cameraMatrix, distCoeffs);
        Xc = Eigen::Vector3d(dst[0].x, dst[0].y, 1);
    } else {
        Xc = invK_ * Eigen::Vector3d(p(0), p(1), 1);
    }

    Xc.normalize();
    return Xc;
}

Eigen::VectorXd
opengv2::PinholeCamera::inverseRadialDistortion(const Eigen::Ref<const Eigen::Vector4d> &radialDistortion) {
    const Eigen::Ref<const Eigen::VectorXd> &k = radialDistortion;
    Eigen::VectorXd b(5);

    double k00 = k[0] * k[0];
    double k000 = k[0] * k00;
    double k0000 = k[0] * k000;
    double k00000 = k[0] * k0000;
    double k01 = k[0] * k[1];
    double k001 = k[0] * k01;
    double k0001 = k[0] * k001;
    double k11 = k[1] * k[1];
    double k011 = k[0] * k11;
    double k02 = k[0] * k[2];
    double k002 = k[0] * k02;
    double k12 = k[1] * k[2];
    double k03 = k[0] * k[3];

    b[0] = -k[0];
    b[1] = 3 * k00 - k[1];
    b[2] = -12 * k000 + 8 * k01 - k[2];
    b[3] = 55 * k0000 - 55 * k001 + 5 * k11 + 10 * k02 - k[3];
    b[4] = -273 * k00000 + 364 * k0001 - 78 * k011 - 78 * k002 + 12 * k12 + 12 * k03;

    return b;
}

Eigen::Vector2d opengv2::PinholeCamera::undistortPoint(const Eigen::Ref<const Eigen::Vector2d> &p) const {
    Eigen::Vector3d Xc;
    if (inverseRadialPoly_.size() != 0) {
        Xc = invK_ * Eigen::Vector3d(p(0), p(1), 1);
        Xc /= Xc[2];

        Eigen::VectorXd r_coeff(inverseRadialPoly_.size());
        r_coeff[0] = Xc[0] * Xc[0] + Xc[1] * Xc[1];
        for (int i = 1; i < inverseRadialPoly_.size(); ++i) {
            r_coeff[i] = r_coeff[i - 1] * r_coeff[0];
        }
        Xc[0] *= 1 + r_coeff.transpose() * inverseRadialPoly_;
        Xc[1] *= 1 + r_coeff.transpose() * inverseRadialPoly_;
    } else if (distCoeffs_.size() != 0) {
        // TODO: implement using Eigen
        std::vector<cv::Point2f> src, dst;
        src.emplace_back(p[0], p[1]);
        cv::Mat cameraMatrix, distCoeffs;
        cv::eigen2cv(K_, cameraMatrix);
        cv::eigen2cv(distCoeffs_, distCoeffs);
        cv::undistortPoints(src, dst, cameraMatrix, distCoeffs);
        Xc = Eigen::Vector3d(dst[0].x, dst[0].y, 1);
    } else {
        return p;
    }

    Eigen::Vector3d p_c = K_ * Xc;
    p_c /= p_c[2];
    return p_c.block<2, 1>(0, 0);
}

cv::Mat opengv2::PinholeCamera::undistortImage(cv::Mat src) {
    if (inverseRadialPoly_.size() != 0) {
        if (undistortMap_.empty()) {
            // Initialization
            int dstRows = std::round(size_[1] * 1.2);
            int dstCols = std::round(size_[0] * 1.2);
            int emptyEdge_r = std::round(size_[1] * 0.1);
            int emptyEdge_c = std::round(size_[0] * 0.1);
            undistortMap_.assign(dstRows, std::vector(dstCols, std::pair(std::vector<std::pair<int, int>>(),
                                                                         std::vector<double>())));

            vectorofEigenMatrix<Eigen::Vector2d> correctedSet;
            correctedSet.reserve(size_[0] * size_[1]);
            const int step = size_[0]; // width
            for (int idx = 0; idx < size_[0] * size_[1]; ++idx) { // index of cv::Mat, row major
                int row = idx / step; // y
                int col = idx % step; // x
                correctedSet.push_back(
                        undistortPoint(Eigen::Vector2d(col, row)) + Eigen::Vector2d(emptyEdge_c, emptyEdge_r));
            }

            KDTreeVectorOfVectorsAdaptor<vectorofEigenMatrix<Eigen::Vector2d>, double, 2, nanoflann::metric_L2_Simple>
                    kdTree(2, correctedSet, 10);
            std::vector<std::pair<size_t, double>> indicesDists;
            for (int i = 0; i < dstRows; ++i) { // y, height
                for (int j = 0; j < dstCols; ++j) { // x, width
                    Eigen::Vector2d p(j, i);
                    kdTree.index->radiusSearch(p.data(), 2 * 2 /*square pixel unit*/,
                                               indicesDists, nanoflann::SearchParams(32, 0, false));
                    for (const auto &pair: indicesDists) {
                        int idx = pair.first;
                        double d = pair.second;
                        int row = idx / step;
                        int col = idx % step;
                        if (d < 100 * std::numeric_limits<double>::epsilon()) {
                            d = 100 * std::numeric_limits<double>::epsilon();
                        }
                        undistortMap_[i][j].first.emplace_back(row, col);
                        undistortMap_[i][j].second.push_back(1 / d);
                    }
                }
            }
        }

        if (src.type() != CV_8UC3) {
            throw std::logic_error("only support CV_8UC3 for now.");
        }
        cv::Mat dst(undistortMap_.size(), undistortMap_[0].size(), CV_32FC3, cv::Vec3f(0, 0, 0));
        for (int i = 0; i < dst.rows; ++i) {
            for (int j = 0; j < dst.cols; ++j) {
                int len = undistortMap_[i][j].first.size();
                double w_sum = 0;
                for (int k = 0; k < len; ++k) {
                    int row = undistortMap_[i][j].first[k].first;
                    int col = undistortMap_[i][j].first[k].second;
                    double w = undistortMap_[i][j].second[k];
                    // convert to float, since opencv didn't do that(uchar * double, return uchar).
                    cv::Vec3f temp = src.at<cv::Vec3b>(row, col);
                    dst.at<cv::Vec3f>(i, j) += w * temp;
                    w_sum += w;
                }
                if (len != 0) {
                    dst.at<cv::Vec3f>(i, j) /= w_sum;
                }
            }
        }

        cv::normalize(dst, dst, 0, 255, cv::NORM_MINMAX, CV_8UC3);
        return dst;
    } else if (distCoeffs_.size() != 0) {
        if (map1_.empty()) {
            cv::Size imageSize(size_[0], size_[1]);
            cv::Mat cameraMatrix, distCoeffs;
            cv::eigen2cv(K_, cameraMatrix);
            cv::eigen2cv(distCoeffs_, distCoeffs);
            cv::initUndistortRectifyMap(
                    cameraMatrix, distCoeffs, cv::Mat(),
                    getOptimalNewCameraMatrix(cameraMatrix, distCoeffs, imageSize, 1, imageSize, 0),
                    imageSize, CV_32FC1, map1_, map2_);
        }
        cv::Mat dst;
        remap(src, dst, map1_, map2_, cv::INTER_LINEAR);
        return dst;
    } else {
        return src;
    }
}

opengv2::PinholeCamera::PinholeCamera(const cv::FileNode &sensorNode) : CameraBase(sensorNode) {
    std::vector<double> v;
    cv::FileNode data;

    data = sensorNode["principal_point"];
    if (!data.isSeq())
        throw std::invalid_argument(sensorNode.name() + ": principal_point");
    for (cv::FileNodeIterator dataItr = data.begin(); dataItr != data.end(); dataItr++) {
        v.push_back(*dataItr);
    }
    if (v.size() != 2)
        throw std::invalid_argument(sensorNode.name() + ": principal_point");
    Eigen::Vector2d principal_point(v.data());
    std::cout << "principal_point: " << principal_point.transpose() << std::endl;

    v.clear();
    data = sensorNode["distortion_type"];
    if (!data.isString())
        throw std::invalid_argument(sensorNode.name() + ": distortion_type");
    std::string distortion_type = data.string();
    std::cout << "distortion_type: " << distortion_type << std::endl;

    v.clear();
    data = sensorNode["distortion"];
    if (!data.isSeq())
        throw std::invalid_argument(sensorNode.name() + ": distortion");
    for (cv::FileNodeIterator dataItr = data.begin(); dataItr != data.end(); dataItr++) {
        v.push_back(*dataItr);
    }
    Eigen::Map<Eigen::VectorXd, Eigen::Unaligned> distortion_tmp(v.data(), v.size());
    Eigen::VectorXd distortion(distortion_tmp);
    std::cout << "distortion: " << distortion.transpose() << std::endl;

    v.clear();
    data = sensorNode["focal_length"];
    if (!data.isSeq())
        throw std::invalid_argument(sensorNode.name() + ": focal_length");
    for (cv::FileNodeIterator dataItr = data.begin(); dataItr != data.end(); dataItr++) {
        v.push_back(*dataItr);
    }
    if (v.size() != 2)
        throw std::invalid_argument(sensorNode.name() + ": focal_length");
    Eigen::Vector2d focal_length(v.data());
    std::cout << "focal_length: " << focal_length.transpose() << std::endl;

    K_ << focal_length[0], 0, principal_point[0], 0, focal_length[1], principal_point[1], 0, 0, 1;
    invK_ = K_.inverse();
    if (distortion_type == "OpenCV") {
        distCoeffs_ = distortion;
        inverseRadialPoly_ = Eigen::VectorXd();
    } else {
        distCoeffs_ = Eigen::VectorXd();
        inverseRadialPoly_ = distortion;
    }
}


/* Copyright (C) 2012-2017 IBM Corp.
 * This program is Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */
#include <cassert>
#include <string>
#include <sstream>
#include <NTL/ZZ.h>
#include "NumbTh.h"
#include "FHEContext.h"
#include "debugging.h"

#include "gtest/gtest.h"
#include "test_common.h"

namespace {
struct Parameters {
    Parameters(long m, long p, long r, std::vector<long> gens, std::vector<long> ords) :
        m(m), // Cyclotomic index
              // e.g., m=1024, m=2047
        p(p), // plaintext base
              // use p=-1 for the complex field (CKKS)
        r(r), // lifting
        gens(gens), // use specified vector of generators
        ords(ords) // use specified vector of orders
    {};

    const long m;
    const long p;
    const long r;
    const std::vector<long> gens;
    const std::vector<long> ords;

    // Let googletest know how to print the Parameters
    friend std::ostream& operator<<(std::ostream& os, const Parameters& params) {
        return os << "{"
            << "m=" << params.m << ","
            << "p=" << params.p << ","
            << "r=" << params.r << ","
            << "gens=" << params.gens << ","
            << "ords=" << params.ords
            << "}";
    };
};

class GTest_PAlgebra : public ::testing::TestWithParam<Parameters> {
    protected:
        GTest_PAlgebra() :
            m(GetParam().m),
            p(GetParam().p),
            r(GetParam().r),
            gens(GetParam().gens),
            ords(GetParam().ords),
            context(m, p, r, gens, ords)
    {};

        const long m;
        const long p;
        const long r;
        const std::vector<long> gens;
        const std::vector<long> ords;
        FHEcontext context;

        static void printPrimeFactors(long m, const FHEcontext& context)
        {
            std::vector<long> f;
            factorize(f,m);
            std::cout << "factoring "<<m<<" gives [";
            for (const auto& factor : f)
                std::cout << factor << " ";
            std::cout << "]" << std::endl;
            context.zMStar.printout();
            std::cout << std::endl;
        }

        virtual void SetUp() override {
            buildModChain(context, 5, 2);
            if(!helib_test::noPrint) {
                printPrimeFactors(m, context);
            }
        };

        virtual void TearDown() override
        {
            cleanupGlobals();
        }
};

TEST_P(GTest_PAlgebra, reads_and_writes_contexts_as_strings)
{
  std::stringstream s1;
  writeContextBase(s1, context);
  s1 << context;

  std::string s2 = s1.str();

  if(!helib_test::noPrint) {
      std::cout << s2;
  }

  std::stringstream s3(s2);

  unsigned long m1, p1, r1;
  std::vector<long> gens, ords;
  readContextBase(s3, m1, p1, r1, gens, ords);

  FHEcontext c1(m1, p1, r1, gens, ords);
  s3 >> c1;

  EXPECT_EQ(context, c1);
};

INSTANTIATE_TEST_SUITE_P(small_parameters, GTest_PAlgebra, ::testing::Values(
            //FAST
            Parameters(91, 2, 1, std::vector<long>{}, std::vector<long>{})
            ));

} // namespace


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_ACOS_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_ACOS_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-trigonometric
    This function object returns the inverse cosine in radian.

    @par Header <boost/simd/function/acos.hpp>

    @par Call

    For every parameter of floating type

    @code
    auto r = acos(x);
    @endcode

    Returns the arc @c r in the interval
    \f$[0, \pi[\f$ such that <tt>cos(r) == x</tt>.
    If @c x is outside \f$[-1, 1[\f$ the result is @ref Nan.

    @par Decorators

    - std_           entries provides access to std::acos

    - pedantic_      is accurate on the full \f$[-1, 1[\f$ range

    - the regular version (no decorator) is less accurate around for x < 0.9
      (up to circa 256 ulp), but is faster by a factor 2 than the pedantic version.

    @see acosd, acospi, cos

    @par Example:

      @snippet acos.cpp acos

    @par Possible output:

      @snippet acos.txt acos

  **/
  IEEEValue acos(IEEEValue const & x);
} }
#endif

#include <boost/simd/function/scalar/acos.hpp>
#include <boost/simd/function/simd/acos.hpp>

#endif


/*
// Copyright 2018 University of Liege
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Authors:
// - Adrien Crovato
*/

//// Solver
// Build AIC matrices and solve iteratively the "body" and the "field"
// Body module: compute impermeability condition and solve linear system of equations (corresponding to the
// Linear Potential Equation)
// Field module: compute through AIC and FD the variables in the field, as well as the field sources (source term in
// the Full Potential Equation)
//
// I/O:
// - numC: list of numerical parameters (structure)
// - symY: defines symmetry about Y axis
// - sRef: reference surface of the full wing
// - alpha: freestream angle of attack
// - vInf: freestream velocity vector
// - Minf: freestream Mach number
// - bPan: (network of) body panels (structure)
// - wPan: (network of) wake panels (structure)
// - fPan: field panels (structure)
// - sp: sub-panels (structure)
// - cL: lift coefficient
// - cD: drag coefficient
//
// Output:
// Outputs:
// - 0, if function succeeded
// - 1, if function failed

#include <iostream>
#include <Eigen/Dense>
#include "solver.h"
#include "id_subpanel.h"
#include "build_AIC.h"
#include "solve_body.h"
#include "solve_field.h"
#include "compute_sVars.h"

#define ANSI_COLOR_RED     "\x1b[1;31m"
#define ANSI_COLOR_GREEN   "\x1b[1;32m"
#define ANSI_COLOR_YELLOW  "\x1b[1;33m"
#define ANSI_COLOR_BLUE    "\x1b[1;34m"
#define ANSI_COLOR_MAGENTA "\x1b[1;35m"
#define ANSI_COLOR_CYAN    "\x1b[1;36m"
#define ANSI_COLOR_WHITE   "\e[1;37m"
#define ANSI_COLOR_RESET   "\x1b[0m"

#define NDIM 3

using namespace std;
using namespace Eigen;

int solver(Numerical_CST &numC, bool symY, double sRef, double alpha, Vector3d &vInf, double Minf,
           Network &bPan, Network &wPan, Field &fPan, Subpanel &sp, double &cL, double &cD) {

    //// Begin solver
    cout << ANSI_COLOR_BLUE;
    cout << "*********************" << endl;
    cout << "*Beginning solver...*" << endl;
    cout << "*********************";
    cout << ANSI_COLOR_RESET << endl;

    //// Initialization
    // AIC matrices
    Body_AIC b2bAIC = {}, b2fAIC = {}; // body to body and body to field
    Field2field_AIC f2fAIC = {}; // field to field
    Field2body_AIC f2bAIC = {}; // field to field and field to body
    Subpanel_AIC spAIC = {}; // body to field (sub-panel)

    // Singularities
    bPan.tau.resize(bPan.nP);
    bPan.mu.resize(bPan.nP);
    fPan.sigma = VectorXd::Zero(fPan.nF);
    // Flow variables // TODO check if initialization is useful
    fPan.phi = VectorXd::Zero(fPan.nF);
    fPan.M = VectorXd::Zero(fPan.nF);
    fPan.U = MatrixX3d::Zero(fPan.nF, NDIM);
    fPan.rho = VectorXd::Zero(fPan.nF);
    fPan.dRho = MatrixX3d::Zero(fPan.nF, NDIM);
    fPan.a = VectorXd::Zero(fPan.nF);
    // Numerics
    fPan.epsilon = VectorXd::Zero(fPan.nF);

    // Temporary variables
    int itCnt = 0; // Global iteration counter
    double deltaSigma0 = 0; // Initial sigma change
    VectorXd sigmaTmp, deltaSigma; // To store sigma and delta sigma during iteration
    deltaSigma.resize(fPan.nF);
    sigmaTmp.resize(fPan.nF);
    VectorXd RHS; // Right hand side
    RHS.resize(bPan.nP);

    MatrixX3d vSigma; // Velocity induced by field sources on body
    vSigma = MatrixXd::Zero(bPan.nP, NDIM);

    //// Identify sub-panels
    id_subpanel(bPan, fPan, sp, spAIC);

    //// Build AIC matrices
    build_AIC(symY, bPan, wPan, fPan, b2bAIC, b2fAIC, f2fAIC, f2bAIC, sp, spAIC);

    //// Solver
    // Field Panel Method
    if (Minf != 0) {
        cout << "|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|" << endl;
        cout << "|Compressible computation.|" << endl;
        cout << " ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ " << endl << endl;
        do {
            // Store new field sources
            sigmaTmp = fPan.sigma;
            // Panel prediction and boundary condition
            solve_body(vInf, RHS, vSigma, bPan, b2bAIC);
            // Field correction
            solve_field(Minf, vInf, bPan, fPan, sp, b2fAIC, f2fAIC, spAIC);
            // Source induced velocity (to recompute B.C.)
            vSigma.col(0) = f2bAIC.Cu * fPan.sigma;
            vSigma.col(1) = f2bAIC.Cv * fPan.sigma;
            vSigma.col(2) = f2bAIC.Cw * fPan.sigma;
            // Stop criterion
            for (int i = 0; i < fPan.nF; ++i)
                deltaSigma(i) = abs(fPan.sigma(i) - sigmaTmp(i));
            if (!itCnt)
                deltaSigma0 = deltaSigma.norm();
            if (isnan(deltaSigma.norm())) {
                break;
            }
            cout << ANSI_COLOR_CYAN;
            cout << "Relative source change at iteration " << itCnt << ": " << log10(deltaSigma.norm()/deltaSigma0) << endl;
            cout << "FPE global residual at iteration " << itCnt << ": " << log10(fPan.epsilon.norm());
            cout << ANSI_COLOR_RESET << endl << endl;
            itCnt++;
        } while(log10(deltaSigma.norm()/deltaSigma0) > -numC.RRED);
        //TODO Consider using true residual (div(U) - sigma -> 0). Currently impossible since accuracy.
        if (isnan(deltaSigma.norm())) {
            cout << ANSI_COLOR_RED;
            cout << "∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨" << endl;
            cout << ">> Process diverged at iteration #" << itCnt + 1 << "!" << endl;
            cout << "∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧";
            cout << ANSI_COLOR_RESET << endl << endl;
        }
        else {
            cout << ANSI_COLOR_GREEN;
            cout << "∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨∨" << endl;
            cout << ">> Process converged in " << itCnt << " iteration(s)!" << endl;
            cout << "∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧";
            cout << ANSI_COLOR_RESET << endl << endl;
        }
    }
    // Panel Method
    else {
        cout << "|¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯|" << endl;
        cout << "|Incompressible computation.|" << endl;
        cout << " ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ " << endl << endl;
        solve_body(vInf, RHS, vSigma, bPan, b2bAIC);
    }

    // Surface velocity and pressure computation
    compute_sVars(symY, sRef, alpha, Minf, vInf, vSigma, bPan, cL, cD);

    //// End solver
    cout << ANSI_COLOR_BLUE;
    cout << "********************" << endl;
    cout << "*Solver successful!*" << endl;
    cout << "********************";
    cout << ANSI_COLOR_RESET << endl;
    return 0;
}


#pragma once

#include <initializer_list> // std::initializer_list
#include <map>              // std::map
#include <sstream>          // std::ostringstream

#include <boost/optional.hpp>

#include "monomial.hpp"

namespace math {

template <typename E, typename N>
class Polynomial final {
  private:
    // A class invariant is that this map cannot have a zero coefficient in it.
    // We do this by keeping this private and ensuring the public API maintains this invariant.
    std::map<Monomial<E, N>, N> terms;

  public:
    using value_type = typename decltype(terms)::value_type;

    // In a map, the elements are ordered from smallest to largest.
    // However, when iterating over a polynomial, we want to go from largest to smallest.
    using iterator = typename decltype(terms)::reverse_iterator;
    using const_iterator = typename decltype(terms)::const_reverse_iterator;

    using reverse_iterator = typename decltype(terms)::iterator;
    using const_reverse_iterator = typename decltype(terms)::const_iterator;

    explicit Polynomial(const std::initializer_list<value_type>& init)
        : terms{init} {}

    iterator begin() {
        return std::rbegin(terms);
    }

    iterator end() {
        return std::rend(terms);
    }

    const_iterator begin() const {
        return std::rbegin(terms);
    }

    const_iterator end() const {
        return std::rend(terms);
    }

    reverse_iterator rbegin() {
        return std::begin(terms);
    }

    reverse_iterator rend() {
        return std::end(terms);
    }

    const_reverse_iterator rbegin() const {
        return std::begin(terms);
    }

    const_reverse_iterator rend() const {
        return std::end(terms);
    }

    boost::optional<std::pair<Monomial<E, N>, N>> leading_term() const {
        const auto loc = begin();
        if (loc == end()) {
            return boost::none;
        } else {
            return std::make_pair(loc->first, loc->second);
        }
    }

    void add(const Polynomial<E, N>& other) {
        for (const auto& kv : other) {
            add(kv.second, kv.first);
        }
    }

    void sub(const Polynomial<E, N>& other) {
        for (const auto& kv : other) {
            sub(kv.second, kv.first);
        }
    }

    void add(const Monomial<E, N>& mono, const N coeff = 1) {

        const auto it = terms.find(mono);

        if (it == std::end(terms)) {
            // mono not in the map
            if (coeff != 0) {
                terms.emplace(mono, coeff);
            }
        } else {
            // mono in the map

            const auto sum = it->second + coeff;
            if (sum == 0) {
                terms.erase(it);
            } else {
                it->second = sum;
            }
        }
    }

    void sub(const Monomial<E, N>& mono, const N coeff = 1) {

        const auto it = terms.find(mono);

        if (it == std::end(terms)) {
            // mono not in the map
            if (coeff != 0) {
                terms.emplace(mono, -coeff);
            }
        } else {
            // mono in the map

            const auto diff = it->second - coeff;
            if (diff == 0) {
                terms.erase(it);
            } else {
                it->second = diff;
            }
        }
    }

    void scale(const N scale) {

        if (scale == 0) {
            terms.clear();
        } else {
            for (auto& kv : terms) {
                kv.second *= scale;
            }
        }
    }

    bool is_zero() const {
        return terms.empty();
    }

    size_t size() const {
        return terms.size();
    }

    N coeff(const Monomial<E, N>& mono) const {

        const auto it = terms.find(mono);

        if (it == std::end(terms)) {
            // default to 0 for things we can't find
            return 0;
        } else {
            return it->second;
        }
    }

    friend bool operator==(const Polynomial<E, N>& lhs, const Polynomial<E, N>& rhs) {
        return lhs.terms == rhs.terms;
    }

    friend bool operator<(const Polynomial<E, N>& lhs, const Polynomial<E, N>& rhs) {
        return lhs.terms < rhs.terms;
    }

    friend std::ostream& operator<<(std::ostream& os, const Polynomial<E, N>& poly) {

        // the sum of no elements is 0
        if (poly.is_zero()) {
            return os << '0';
        }

        // Set to false once we add the first coefficient
        bool front = true;

        for (const auto& kv : poly) {

            const auto& monom = kv.first;
            const auto coeff = kv.second;

            // add a positive sign if it is positive and not at the front
            if ((coeff > 0) && !front) {
                os << '+';
            }

            if (coeff == 1) {
                os << monom;
            } else if (coeff == -1) {
                os << '-' << monom;
            } else if (coeff != 0) {
                os << coeff;
                if (!monom.is_one()) {
                    os << '*' << monom;
                }
            } else {
                std::ostringstream err{};
                err << monom << " has zero coefficient in Polynomial";
                throw std::runtime_error(err.str());
            }

            front = false;
        }

        return os;
    }
};
}


/********************************************************/
/*                Laurent Hébert-Dufresne               */
/* Age of edges or nodes by OD of an undirected network */
/* 			     based on arXiv:1510.08542              */
/*                   Santa Fe Institute                 */
/********************************************************/
#include <iostream>
#include <cmath>
#include <fstream>
#include <string>
#include <cstdlib>
#include <vector>
#include <sstream>
#include <iomanip>
#include <algorithm>
#include <stdio.h>
#include <time.h>
#include <set>
//#include <boost/filesystem/operations.hpp>
//#include <boost/filesystem/fstream.hpp>
//#include <boost/filesystem/path.hpp>
using namespace std;

//COMPARISON (to order set of [node tag,degree] object by degree
struct classcomp {
  bool operator() (const pair<int,int>& lhs, const pair<int,int>& rhs) const
  {return lhs.second<rhs.second;}
};

//COMPARISON (to order set of [node tag,degree] object by degree
struct classcomp2 {
  bool operator() (const pair< pair<int,int> ,int>& lhs, const pair< pair<int,int> ,int>& rhs) const
  {return lhs.second<rhs.second;}
};

//GLOBAL VARIABLES DEFINITION
vector< vector<int> > adjmat; //[neighbours]
vector<int> degree; //[degree]
multiset< pair<int,int>, classcomp> tags; //[tag,degree]
multiset< pair<int,int> > edges; //[node1, node2]
multiset< pair< pair<int,int> ,int>, classcomp2> ranked_edges; //[tag,degree]

//MAIN
int main(int argc, const char *argv[])
{
  //NETWORK
  std::string name = argv[1]; //path to edgelist
  int nodes = 0;
  if(argc>2) nodes = atoi(argv[2]); //rank output for: 0==edges, 1==nodes

//INPUT AND CONSTRUCTION OF STRUCTURES
//PREPARES INPUT & OUTPUT
std::ifstream input(name.c_str());
string line;
ifstream input0(name.c_str());
int temp = 0;
int MAX = 0;
int node1;
int node2;
if (!input0.is_open()) {
	cerr<<"error in filenames of input directory!"<<endl;;
 	return EXIT_FAILURE;
} //end if
else {
	while ( input0 >> temp ) if(temp > MAX) MAX = temp;
}
MAX = MAX+1;
int link=0;
//cout << "Number of nodes: " << MAX << endl;
adjmat.resize(MAX);
degree.resize(MAX);
if (!input.is_open()) {
	cerr<<"error in filenames of input directory!"<<endl;;
 	return EXIT_FAILURE;
} //end if
else {
    string line_buffer;
    while (getline(input, line_buffer))
    {
      	stringstream ls(line_buffer);
      	ls >> node1;
      	ls >> node2;
	// while ( input >> node1 >> std::ws >> node2 >> std::ws >> tmp >> std::ws ) {
		//if(find(adjmat[node1].begin(),adjmat[node1].end(),node2)==adjmat[node1].end() && node1 != node2) {
		adjmat[node1].push_back(node2);
		adjmat[node2].push_back(node1);
		edges.insert(make_pair(node1,node2));
		++degree[node1];
		++degree[node2];
		link = link+2;
		//}
	} //end while
} //end else
input.close();
for(int el=0; el<MAX; ++el) tags.insert(make_pair(el,degree[el]));

vector<int> realindegree = degree;

//ALGORITHM
vector<int> core(MAX,-1);
vector<int> pass(MAX,-1);
vector<int> age(MAX,-1);
multiset< pair<int,int>, classcomp>::iterator v;
multiset< pair<int,int>, classcomp>::iterator it1;
multiset< pair<int,int>, classcomp>::iterator it2;
pair<multiset< pair<int,int>, classcomp>::iterator,multiset< pair<int,int>, classcomp>::iterator> ret_intern;
pair<multiset< pair<int,int>, classcomp>::iterator,multiset< pair<int,int>, classcomp>::iterator> ret_pass;
int source;
int newguy;
int neighbour;
int currentpass=1;
v = tags.begin();
int maxcore = 0; int t=0;
while(tags.size()>0) {
	newguy = (*v).first;
	if(degree[newguy]>maxcore) maxcore = degree[newguy];//keeping track of the number of cores
	ret_pass = tags.equal_range(make_pair(0,degree[newguy]));//all nodes in the next layer
	int number = tags.count(make_pair(0,degree[newguy]));//number of nodes in next layer
	it1=ret_pass.first;
	for(int tmp=0; tmp<number; ++tmp,++it1) {//loops around everyone in the same layer
		source = (*it1).first; //node in the layer
		core[source] = degree[source]; //core for that node
		pass[source] = currentpass; //layer for that node
		age[source] = t;
		for(int u=0; u<adjmat[source].size(); ++u) {
			neighbour = adjmat[source][u];
			if(degree[neighbour] > degree[source]) {
				ret_intern = tags.equal_range(make_pair(neighbour,degree[neighbour]));
				for(it2=ret_intern.first; it2!=ret_intern.second; ++it2) {
					if((*it2).first==neighbour) {
						tags.erase(it2);
						break;
					}
				}
				--degree[neighbour];
				tags.insert(make_pair(neighbour,degree[neighbour]));
			}
		}
	}
	t+=number;
	it1=ret_pass.first;
	for(int tmp=0; tmp<number; ++tmp,++it1) tags.erase(it1);
	v = tags.begin(); ++currentpass;
}

if(nodes==0) {//output edges

	for(multiset< pair<int,int> >::iterator e = edges.begin(); e!=edges.end(); ++e) {
		int age0=t-age[(*e).first];
		if(t-age[(*e).second]>age0) age0=t-age[(*e).second];
		ranked_edges.insert(make_pair((*e),age0));
	}

	int time = 0;
 	int	class_id = 0;
 	int class_base_time = 0;
	multiset< pair<int,int> > class_content;

	for(multiset< pair< pair<int,int>,int> >::iterator e = ranked_edges.begin(); e!=ranked_edges.end(); ++e) {
		if (e->second != class_id)
		{
			pair<int,int> last_out_edge = make_pair(class_content.begin()->first, class_content.begin()->second);
			int out_edge_tag = -1;
			for (multiset< pair<int,int> >::iterator e2 = class_content.begin(); e2 != class_content.end(); ++e2)
			{
				if (e2->first == last_out_edge.first && e2->second == last_out_edge.second)
				{
					++out_edge_tag;
				}
				else
				{
					out_edge_tag = 0;
					last_out_edge = make_pair(e2->first, e2->second);
				}
				cout << e2->first << " " << e2->second << " " << out_edge_tag << " " << class_base_time + float(class_content.size() + 1)/2 -1 << "\n";
			}
			class_content.clear();
			class_id = (*e).second;
			class_base_time = time;
		}
		class_content.insert(e->first);
		++time;
	}
	pair<int,int> last_out_edge = make_pair(class_content.begin()->first, class_content.begin()->second);
	int out_edge_tag = -1;
	for (multiset< pair<int,int> >::iterator e2 = class_content.begin(); e2 != class_content.end(); ++e2)
	{
		if (e2->first == last_out_edge.first && e2->second == last_out_edge.second)
		{
			++out_edge_tag;
		}
		else
		{
			out_edge_tag = 0;
			last_out_edge = make_pair(e2->first, e2->second);
		}
		cout << e2->first << " " << e2->second << " " << out_edge_tag << " " << class_base_time + float(class_content.size() + 1)/2 -1 << "\n";
	}
}
else {//output nodes

	for(int dummy=0; dummy<core.size(); ++dummy) cout << dummy << " " << pass[dummy] << "\n";

}

return 0;
} //end main


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include <eve/function/ellint_d.hpp>
#include <boost/math/special_functions/ellint_d.hpp>
#include <eve/function/next.hpp>
#include <eve/function/prev.hpp>
#include <eve/function/is_denormal.hpp>
#include <eve/constant/nan.hpp>
#include <eve/constant/inf.hpp>
#include <eve/constant/minf.hpp>
#include <eve/constant/minlog.hpp>
#include <eve/platform.hpp>

#include <cmath>

TTS_CASE_TPL("Check eve::ellint_d return type", EVE_TYPE)
{
  TTS_EXPR_IS(eve::ellint_d(T(0)), T);
}

TTS_CASE_TPL("Check eve::ellint_d behavior one parameter", EVE_TYPE)
{
  using v_t = eve::element_type_t<T>;

  if constexpr( eve::platform::supports_invalids )
  {
    TTS_IEEE_EQUAL(eve::ellint_d(eve::nan(eve::as<T>())) , eve::nan(eve::as<T>()) );
    TTS_IEEE_EQUAL(eve::ellint_d(T(1)) , eve::nan(eve::as<T>()) );
    TTS_IEEE_EQUAL(eve::ellint_d(T(-1)), eve::nan(eve::as<T>()) );
  }

  TTS_ULP_EQUAL( eve::ellint_d(T( 0.)), T(boost::math::ellint_d(v_t(0))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(T( 0.5)), T(boost::math::ellint_d(v_t(0.5))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(T( 0.9)), T(boost::math::ellint_d(v_t(0.9))), 1.0);

}

TTS_CASE_TPL("Check eve::ellint_d behavior two parameter", EVE_TYPE)
{
  using v_t = eve::element_type_t<T>;
  using eve::as;
  if constexpr( eve::platform::supports_invalids )
  {
    TTS_IEEE_EQUAL(eve::ellint_d(eve::pio_2(as<T>()), eve::nan(eve::as<T>())) , eve::nan(eve::as<T>()) );
  }

  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_2(as<T>()), T( 0.)),  T(boost::math::ellint_d(v_t(0)  , eve::pio_2(as<v_t>()))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_2(as<T>()), T( 0.5)), T(boost::math::ellint_d(v_t(0.5), eve::pio_2(as<v_t>()))), 1.5);
  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_2(as<T>()), T( 0.9)), T(boost::math::ellint_d(v_t(0.9), eve::pio_2(as<v_t>()))), 2.5);
  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_4(as<T>()), T( 0.)),  T(boost::math::ellint_d(v_t(0)  , eve::pio_4(as<v_t>()))), 1.5);
  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_4(as<T>()), T( 0.5)), T(boost::math::ellint_d(v_t(0.5), eve::pio_4(as<v_t>()))), 1.5);
  TTS_ULP_EQUAL( eve::ellint_d(eve::pio_4(as<T>()), T( 0.9)), T(boost::math::ellint_d(v_t(0.9), eve::pio_4(as<v_t>()))), 4.0);
  TTS_ULP_EQUAL( eve::ellint_d(3*eve::pio_4(as<T>()), T( 0.)),  T(boost::math::ellint_d(v_t(0)  , 3*eve::pio_4(as<v_t>()))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(3*eve::pio_4(as<T>()), T( 0.5)), T(boost::math::ellint_d(v_t(0.5), 3*eve::pio_4(as<v_t>()))), 1);
  TTS_ULP_EQUAL( eve::ellint_d(3*eve::pio_4(as<T>()), T( 0.9)), T(boost::math::ellint_d(v_t(0.9), 3*eve::pio_4(as<v_t>()))), 1.0);
  TTS_ULP_EQUAL( eve::ellint_d(T(100), T( 0.)),  T(boost::math::ellint_d(v_t(0)  , v_t(100))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(T(100), T( 0.5)), T(boost::math::ellint_d(v_t(0.5), v_t(100))), 0.5);
  TTS_ULP_EQUAL( eve::ellint_d(T(100), T( 0.9)), T(boost::math::ellint_d(v_t(0.9), v_t(100))), 2.0);
  TTS_ULP_EQUAL( eve::ellint_d(T(1.5), T(1)),    T(boost::math::ellint_d(v_t(1)  , v_t(1.5))), 7.0);
}


/*
 * test_STD.cpp
 *
 *  Created on: 2013-4-18
 *      Author: fasiondog
 */

#ifdef TEST_ALL_IN_ONE
    #include <boost/test/unit_test.hpp>
#else
    #define BOOST_TEST_MODULE test_hikyuu_indicator_suite
    #include <boost/test/unit_test.hpp>
#endif

#include <hikyuu/StockManager.h>
#include <hikyuu/indicator/crt/STDEV.h>
#include <hikyuu/indicator/crt/PRICELIST.h>

using namespace hku;

/**
 * @defgroup test_indicator_STDEV test_indicator_STDEV
 * @ingroup test_hikyuu_indicator_suite
 * @{
 */

/** @par 检测点 */
BOOST_AUTO_TEST_CASE( test_STDEV ) {
    /** @arg n > 1 的正常情况 */
    PriceList d;
    for (size_t i = 0; i < 15; ++i) {
        d.push_back(i+1);
    }
    d[5] = 4.0;
    d[7] = 4.0;
    d[11] = 6.0;

    Indicator ind = PRICELIST(d);
    Indicator dev = STDEV(ind, 10);
    BOOST_CHECK(dev.size() == 15);
    BOOST_CHECK(dev[8] == Null<price_t>());
    BOOST_CHECK(std::fabs(dev[9] - 2.923088) < 0.000001 );
    BOOST_CHECK(std::fabs(dev[10] - 3.142893) < 0.000001 );
    BOOST_CHECK(std::fabs(dev[11] - 2.830390) < 0.000001 );
    BOOST_CHECK(std::fabs(dev[12] - 3.267686) < 0.000001 );
    BOOST_CHECK(std::fabs(dev[13] - 3.653004) < 0.000001 );
    BOOST_CHECK(std::fabs(dev[14] - 4.001388) < 0.000001 );

    /** @arg n = 1时 */
    dev = STDEV(ind, 1);
    BOOST_CHECK(dev.size() == 15);
    for (size_t i = 0; i < dev.size(); ++i) {
        BOOST_CHECK(dev[i] == Null<price_t>());
    }

    /** @arg operator() */
    Indicator expect = STDEV(ind, 10);
    dev = STDEV(10);
    Indicator result = dev(ind);
    BOOST_CHECK(result.size() == expect.size());
    for (size_t i = 0; i < expect.size(); ++i) {
        BOOST_CHECK(result[i] == expect[i]);
    }
}

/** @} */


/* Boost interval/transc.hpp template implementation file
 *
 * Copyright 2000 Jens Maurer
 * Copyright 2002 Hervé Brönnimann, Guillaume Melquiond, Sylvain Pion
 *
 * Distributed under the Boost Software License, Version 1.0.
 * (See accompanying file LICENSE_1_0.txt or
 * copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_NUMERIC_INTERVAL_TRANSC_HPP
#define BOOST_NUMERIC_INTERVAL_TRANSC_HPP

#include <boost/config.hpp>
#include <boost/numeric/interval/detail/interval_prototype.hpp>
#include <boost/numeric/interval/detail/bugs.hpp>
#include <boost/numeric/interval/detail/test_input.hpp>
#include <boost/numeric/interval/rounding.hpp>
#include <boost/numeric/interval/constants.hpp>
#include <boost/numeric/interval/arith.hpp>
#include <boost/numeric/interval/arith2.hpp>
#include <algorithm>

namespace boost {
namespace numeric {

template<class T, class Policies> inline
interval<T, Policies> exp(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.exp_down(x.lower()), rnd.exp_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> log(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x) ||
      !interval_lib::user::is_pos(x.upper()))
    return I::empty();
  typename Policies::rounding rnd;
  typedef typename Policies::checking checking;
  T l = !interval_lib::user::is_pos(x.lower())
             ? checking::neg_inf() : rnd.log_down(x.lower());
  return I(l, rnd.log_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> cos(const interval<T, Policies>& x)
{
  if (interval_lib::detail::test_input(x))
    return interval<T, Policies>::empty();
  typename Policies::rounding rnd;
  typedef interval<T, Policies> I;
  typedef typename interval_lib::unprotect<I>::type R;

  // get lower bound within [0, pi]
  const R pi2 = interval_lib::pi_twice<R>();
  R tmp = fmod((const R&)x, pi2);
  if (width(tmp) >= pi2.lower())
    return I(static_cast<T>(-1), static_cast<T>(1), true); // we are covering a full period
  if (tmp.lower() >= interval_lib::constants::pi_upper<T>())
    return -cos(tmp - interval_lib::pi<R>());
  T l = tmp.lower();
  T u = tmp.upper();

  BOOST_USING_STD_MIN();
  // separate into monotone subintervals
  if (u <= interval_lib::constants::pi_lower<T>())
    return I(rnd.cos_down(u), rnd.cos_up(l), true);
  else if (u <= pi2.lower())
    return I(static_cast<T>(-1), rnd.cos_up(min BOOST_PREVENT_MACRO_SUBSTITUTION(rnd.sub_down(pi2.lower(), u), l)), true);
  else
    return I(static_cast<T>(-1), static_cast<T>(1), true);
}

template<class T, class Policies> inline
interval<T, Policies> sin(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  typedef typename interval_lib::unprotect<I>::type R;
  I r = cos((const R&)x - interval_lib::pi_half<R>());
  (void)&rnd;
  return r;
}

template<class T, class Policies> inline
interval<T, Policies> tan(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  typedef typename interval_lib::unprotect<I>::type R;

  // get lower bound within [-pi/2, pi/2]
  const R pi = interval_lib::pi<R>();
  R tmp = fmod((const R&)x, pi);
  const T pi_half_d = interval_lib::constants::pi_half_lower<T>();
  if (tmp.lower() >= pi_half_d)
    tmp -= pi;
  if (tmp.lower() <= -pi_half_d || tmp.upper() >= pi_half_d)
    return I::whole();
  return I(rnd.tan_down(tmp.lower()), rnd.tan_up(tmp.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> asin(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x)
     || x.upper() < static_cast<T>(-1) || x.lower() > static_cast<T>(1))
    return I::empty();
  typename Policies::rounding rnd;
  T l = (x.lower() <= static_cast<T>(-1))
             ? -interval_lib::constants::pi_half_upper<T>()
             : rnd.asin_down(x.lower());
  T u = (x.upper() >= static_cast<T>(1) )
             ?  interval_lib::constants::pi_half_upper<T>()
             : rnd.asin_up  (x.upper());
  return I(l, u, true);
}

template<class T, class Policies> inline
interval<T, Policies> acos(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x)
     || x.upper() < static_cast<T>(-1) || x.lower() > static_cast<T>(1))
    return I::empty();
  typename Policies::rounding rnd;
  T l = (x.upper() >= static_cast<T>(1) )
          ? static_cast<T>(0)
          : rnd.acos_down(x.upper());
  T u = (x.lower() <= static_cast<T>(-1))
          ? interval_lib::constants::pi_upper<T>()
          : rnd.acos_up  (x.lower());
  return I(l, u, true);
}

template<class T, class Policies> inline
interval<T, Policies> atan(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.atan_down(x.lower()), rnd.atan_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> sinh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.sinh_down(x.lower()), rnd.sinh_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> cosh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  if (interval_lib::user::is_neg(x.upper()))
    return I(rnd.cosh_down(x.upper()), rnd.cosh_up(x.lower()), true);
  else if (!interval_lib::user::is_neg(x.lower()))
    return I(rnd.cosh_down(x.lower()), rnd.cosh_up(x.upper()), true);
  else
    return I(static_cast<T>(1), rnd.cosh_up(-x.lower() > x.upper() ? x.lower() : x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> tanh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.tanh_down(x.lower()), rnd.tanh_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> asinh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x))
    return I::empty();
  typename Policies::rounding rnd;
  return I(rnd.asinh_down(x.lower()), rnd.asinh_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> acosh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x) || x.upper() < static_cast<T>(1))
    return I::empty();
  typename Policies::rounding rnd;
  T l = x.lower() <= static_cast<T>(1) ? static_cast<T>(0) : rnd.acosh_down(x.lower());
  return I(l, rnd.acosh_up(x.upper()), true);
}

template<class T, class Policies> inline
interval<T, Policies> atanh(const interval<T, Policies>& x)
{
  typedef interval<T, Policies> I;
  if (interval_lib::detail::test_input(x)
      || x.upper() < static_cast<T>(-1) || x.lower() > static_cast<T>(1))
    return I::empty();
  typename Policies::rounding rnd;
  typedef typename Policies::checking checking;
  T l = (x.lower() <= static_cast<T>(-1))
             ? checking::neg_inf() : rnd.atanh_down(x.lower());
  T u = (x.upper() >= static_cast<T>(1) )
             ? checking::pos_inf() : rnd.atanh_up  (x.upper());
  return I(l, u, true);
}

} // namespace numeric
} // namespace boost

#endif // BOOST_NUMERIC_INTERVAL_TRANSC_HPP


#include <fstream>
#include "multiindex.hpp"
#include "potgen.hpp"
#include "dynamic_grid.hpp"
#include "randomize.hpp"

#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_SUITE(potgen_internals_test)

BOOST_AUTO_TEST_CASE( randomize_error_test )
{
    DynamicGrid<complex_t> dgrid(2, 8, TransformationType::FFT_INDEX);
    auto rg = []() { return 0.5; };
    // uninitialized multi index
    BOOST_CHECK_THROW(randomize_generic( dgrid, rg, MultiIndex(1)), std::invalid_argument);
}

BOOST_AUTO_TEST_CASE( randomize_phases_area_check )
{
	DynamicGrid<complex_t> dgrid(2, 8, TransformationType::FFT_INDEX);
	for( auto& f : dgrid )
		f = 1;

	// multiply with -1, so twice (or never) will raise an error
	auto rg = []() { return pi; };
    randomize_generic( dgrid, rg, fft_indexing(dgrid) );
	for( auto& f : dgrid ) {
        BOOST_REQUIRE_EQUAL(std::real(f), -1);
    }
}

BOOST_AUTO_TEST_CASE( randomize_phases_symmetry_check )
{
	DynamicGrid<complex_t> dgrid(2, 8, TransformationType::FFT_INDEX);
	for( auto& f : dgrid )
		f = 1;

	randomizePhases( dgrid, 0 );

	MultiIndex index = fft_indexing(dgrid);

	/// \todo cannot use multi index here, because [] assignment not possible right now
	std::vector<int> inverted( 2 );

	for( ;index.valid(); ++index)
	{
		// set inverted index
		for(int i = 0; i < 2; ++i)
			inverted[i] = -index[i];

		BOOST_CHECK_EQUAL( std::conj(dgrid(index)), dgrid(inverted));
	}

}

/// \todo randomize precondition exceptions check

/// \todo generate potential in k space test
BOOST_AUTO_TEST_SUITE_END()


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2006 - 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: Wolfgang Bangerth, Texas A&M University, 2006, 2007;
 *          Denis Davydov, University of Erlangen-Nuremberg, 2016;
 *          Marc Fehling, Colorado State University, 2020.
 */

// @sect3{Include files}

// 前面几个文件已经在前面的例子中讲过了，因此不再做进一步的评论。

#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/dynamic_sparsity_pattern.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/lac/affine_constraints.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/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/data_out.h>
#include <deal.II/numerics/error_estimator.h>

// 这些是我们需要的新文件。第一个和第二个提供了FECollection和hp版本的FEValues类，如本程序介绍中所述。下一个文件提供了自动 $hp$ 适应的功能，为此我们将使用基于衰减系列扩展系数的估计算法，这是最后两个文件的一部分。

#include <deal.II/hp/fe_collection.h>
#include <deal.II/hp/fe_values.h>
#include <deal.II/hp/refinement.h>
#include <deal.II/fe/fe_series.h>
#include <deal.II/numerics/smoothness_estimator.h>

// 最后一组包含文件是标准的C++头文件。

#include <fstream>
#include <iostream>

// 最后，这和以前的程序一样。

namespace Step27
{
  using namespace dealii;
// @sect3{The main class}

// 这个程序的主类看起来非常像前几个教程程序中已经使用过的，例如  step-6  中的那个。主要的区别是我们将refine_grid和output_results函数合并为一个，因为我们还想输出一些用于决定如何细化网格的量（特别是估计的解决方案的平滑度）。

// 就成员变量而言，我们使用与 step-6 中相同的结构，但我们需要集合来代替单个的有限元、正交和面状正交对象。我们将在类的构造函数中填充这些集合。最后一个变量， max_degree ，表示所用形状函数的最大多项式程度。

  template <int dim>
  class LaplaceProblem
  {
  public:
    LaplaceProblem();
    ~LaplaceProblem();

    void run();

  private:
    void setup_system();
    void assemble_system();
    void solve();
    void create_coarse_grid();
    void postprocess(const unsigned int cycle);

    Triangulation<dim> triangulation;

    DoFHandler<dim>          dof_handler;
    hp::FECollection<dim>    fe_collection;
    hp::QCollection<dim>     quadrature_collection;
    hp::QCollection<dim - 1> face_quadrature_collection;

    AffineConstraints<double> constraints;

    SparsityPattern      sparsity_pattern;
    SparseMatrix<double> system_matrix;

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

    const unsigned int max_degree;
  };

//  @sect3{Equation data}

// 接下来，让我们为这个问题定义右手边的函数。它在1d中是 $x+1$ ，在2d中是 $(x+1)(y+1)$ ，以此类推。

  template <int dim>
  class RightHandSide : public Function<dim>
  {
  public:
    virtual double value(const Point<dim> & p,
                         const unsigned int component) const override;
  };

  template <int dim>
  double RightHandSide<dim>::value(const Point<dim> &p,
                                   const unsigned int /*component*/) const
  {
    double product = 1;
    for (unsigned int d = 0; d < dim; ++d)
      product *= (p[d] + 1);
    return product;
  }

//  @sect3{Implementation of the main class}
// @sect4{LaplaceProblem::LaplaceProblem constructor}

// 这个类的构造函数是相当直接的。它将DoFHandler对象与三角形相关联，然后将最大多项式度数设置为7（在1d和2d中）或5（在3d及以上）。我们这样做是因为使用高阶多项式度数会变得非常昂贵，尤其是在更高的空间维度上。

// 在这之后，我们填充有限元、单元和面的四分法对象集合。我们从二次元开始，每个正交公式的选择都是为了适合 hp::FECollection 对象中的匹配有限元。

  template <int dim>
  LaplaceProblem<dim>::LaplaceProblem()
    : dof_handler(triangulation)
    , max_degree(dim <= 2 ? 7 : 5)
  {
    for (unsigned int degree = 2; degree <= max_degree; ++degree)
      {
        fe_collection.push_back(FE_Q<dim>(degree));
        quadrature_collection.push_back(QGauss<dim>(degree + 1));
        face_quadrature_collection.push_back(QGauss<dim - 1>(degree + 1));
      }
  }
// @sect4{LaplaceProblem::~LaplaceProblem destructor}

// 解构器与我们在  step-6  中已经做过的没有变化。

  template <int dim>
  LaplaceProblem<dim>::~LaplaceProblem()
  {
    dof_handler.clear();
  }
// @sect4{LaplaceProblem::setup_system}

// 这个函数又是对我们在  step-6  中已经做过的事情的逐字复制。尽管函数调用的名称和参数完全相同，但内部使用的算法在某些方面是不同的，因为这里的dof_handler变量是在  $hp$  -mode。

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

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

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

    DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
    DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, false);
    sparsity_pattern.copy_from(dsp);

    system_matrix.reinit(sparsity_pattern);
  }

//  @sect4{LaplaceProblem::assemble_system}

// 这是一个从每个单元的局部贡献中集合全局矩阵和右侧向量的函数。它的主要工作与之前许多教程中描述的一样。重要的差异是hp有限元方法所需要的。特别是，我们需要使用FEValues对象的集合（通过 hp::FEValues 类实现），并且在将局部贡献复制到全局对象时，我们必须消除受限自由度。这两点在本程序的介绍中都有详细解释。

// 还有一个小问题是，由于我们在不同的单元格中使用了不同的多项式度数，持有局部贡献的矩阵和向量在所有单元格中的大小不尽相同。因此，在所有单元的循环开始时，我们每次都必须将它们的大小调整到正确的大小（由 dofs_per_cell 给出）。因为这些类的实现方式是减少矩阵或向量的大小不会释放当前分配的内存（除非新的大小为零），所以在循环开始时调整大小的过程只需要在最初几次迭代中重新分配内存。一旦我们在一个单元中找到了最大的有限元度，就不会再发生重新分配，因为所有后续的 reinit 调用只会将大小设置为适合当前分配的内存。这一点很重要，因为分配内存是很昂贵的，而且每次我们访问一个新的单元时都这样做会花费大量的计算时间。

  template <int dim>
  void LaplaceProblem<dim>::assemble_system()
  {
    hp::FEValues<dim> hp_fe_values(fe_collection,
                                   quadrature_collection,
                                   update_values | update_gradients |
                                     update_quadrature_points |
                                     update_JxW_values);

    RightHandSide<dim> rhs_function;

    FullMatrix<double> cell_matrix;
    Vector<double>     cell_rhs;

    std::vector<types::global_dof_index> local_dof_indices;

    for (const auto &cell : dof_handler.active_cell_iterators())
      {
        const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();

        cell_matrix.reinit(dofs_per_cell, dofs_per_cell);
        cell_matrix = 0;

        cell_rhs.reinit(dofs_per_cell);
        cell_rhs = 0;

        hp_fe_values.reinit(cell);

        const FEValues<dim> &fe_values = hp_fe_values.get_present_fe_values();

        std::vector<double> rhs_values(fe_values.n_quadrature_points);
        rhs_function.value_list(fe_values.get_quadrature_points(), rhs_values);

        for (unsigned int q_point = 0; q_point < fe_values.n_quadrature_points;
             ++q_point)
          for (unsigned int i = 0; i < dofs_per_cell; ++i)
            {
              for (unsigned int j = 0; j < dofs_per_cell; ++j)
                cell_matrix(i, j) +=
                  (fe_values.shape_grad(i, q_point) * // grad phi_i(x_q)
                   fe_values.shape_grad(j, q_point) * // grad phi_j(x_q)
                   fe_values.JxW(q_point));           // dx

              cell_rhs(i) += (fe_values.shape_value(i, q_point) * // phi_i(x_q)
                              rhs_values[q_point] *               // f(x_q)
                              fe_values.JxW(q_point));            // dx
            }

        local_dof_indices.resize(dofs_per_cell);
        cell->get_dof_indices(local_dof_indices);

        constraints.distribute_local_to_global(
          cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
      }
  }

//  @sect4{LaplaceProblem::solve}

// 解决线性系统的函数与之前的例子完全没有变化。我们只是试图将初始残差（相当于右手边的 $l_2$ 准则）减少一定的系数。

  template <int dim>
  void LaplaceProblem<dim>::solve()
  {
    SolverControl            solver_control(system_rhs.size(),
                                 1e-12 * system_rhs.l2_norm());
    SolverCG<Vector<double>> cg(solver_control);

    PreconditionSSOR<SparseMatrix<double>> preconditioner;
    preconditioner.initialize(system_matrix, 1.2);

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

    constraints.distribute(solution);
  }

//  @sect4{LaplaceProblem::postprocess}

// 解完线性系统后，我们要对解进行后处理。在这里，我们所做的就是估计误差，估计解的局部平滑度，如介绍中所述，然后写出图形输出，最后根据之前计算的指标细化 $h$ 和 $p$ 中的网格。我们在同一个函数中完成这一切，因为我们希望估计的误差和平滑度指标不仅用于细化，而且还包括在图形输出中。

  template <int dim>
  void LaplaceProblem<dim>::postprocess(const unsigned int cycle)
  {

// 让我们开始计算估计的误差和平滑度指标，这两个指标对于我们三角测量的每个活动单元来说都是一个数字。对于误差指标，我们一如既往地使用KellyErrorEstimator类。

    Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
    KellyErrorEstimator<dim>::estimate(
      dof_handler,
      face_quadrature_collection,
      std::map<types::boundary_id, const Function<dim> *>(),
      solution,
      estimated_error_per_cell);

// 估计平滑度是用介绍中所述的衰减膨胀系数的方法进行的。我们首先需要创建一个对象，能够将每一个单元上的有限元解转化为一串傅里叶级数系数。SmoothnessEstimator命名空间为这样一个 FESeries::Fourier 对象提供了一个工厂函数，它为估计平滑度的过程进行了优化。然后在最后一个函数中实际确定每个单独单元上的傅里叶系数的衰减情况。

    Vector<float> smoothness_indicators(triangulation.n_active_cells());
    FESeries::Fourier<dim> fourier =
      SmoothnessEstimator::Fourier::default_fe_series(fe_collection);
    SmoothnessEstimator::Fourier::coefficient_decay(fourier,
                                                    dof_handler,
                                                    solution,
                                                    smoothness_indicators);

// 接下来我们要生成图形输出。除了上面得出的两个估计量之外，我们还想输出网格上每个元素所使用的有限元的多项式程度。

// 要做到这一点，我们需要在所有单元上循环，用  cell-@>active_fe_index()  轮询它们的活动有限元索引。然后我们使用这个操作的结果，在有限元集合中查询具有该索引的有限元，最后确定该元素的多项式程度。我们将结果放入一个矢量，每个单元有一个元素。DataOut类要求这是一个 float or double 的向量，尽管我们的值都是整数，所以我们就用这个向量。

    {
      Vector<float> fe_degrees(triangulation.n_active_cells());
      for (const auto &cell : dof_handler.active_cell_iterators())
        fe_degrees(cell->active_cell_index()) =
          fe_collection[cell->active_fe_index()].degree;

// 现在有了所有的数据向量--解决方案、估计误差和平滑度指标以及有限元度--我们创建一个用于图形输出的DataOut对象并附加所有数据。

      DataOut<dim> data_out;

      data_out.attach_dof_handler(dof_handler);
      data_out.add_data_vector(solution, "solution");
      data_out.add_data_vector(estimated_error_per_cell, "error");
      data_out.add_data_vector(smoothness_indicators, "smoothness");
      data_out.add_data_vector(fe_degrees, "fe_degree");
      data_out.build_patches();

// 生成输出的最后一步是确定一个文件名，打开文件，并将数据写入其中（这里，我们使用VTK格式）。

      const std::string filename =
        "solution-" + Utilities::int_to_string(cycle, 2) + ".vtk";
      std::ofstream output(filename);
      data_out.write_vtk(output);
    }

// 在这之后，我们想在 $h$ 和 $p$ 两个地方实际细化网格。我们要做的是：首先，我们用估计的误差来标记那些误差最大的单元，以便进行细化。这就是我们一直以来的做法。

    {
      GridRefinement::refine_and_coarsen_fixed_number(triangulation,
                                                      estimated_error_per_cell,
                                                      0.3,
                                                      0.03);

// 接下来我们要弄清楚哪些被标记为细化的单元格实际上应该增加 $p$ 而不是减少 $h$ 。我们在这里选择的策略是，我们查看那些被标记为细化的单元格的平滑度指标，并为那些平滑度大于某个相对阈值的单元格增加 $p$ 。换句话说，对于每一个(i)细化标志被设置，(ii)平滑度指标大于阈值，以及(iii)我们在有限元集合中仍有一个多项式度数高于当前度数的有限元的单元，我们将分配一个未来的FE指数，对应于一个比当前度数高一的多项式。下面的函数正是能够做到这一点。在没有更好的策略的情况下，我们将通过在标记为细化的单元上的最小和最大平滑度指标之间进行插值来设置阈值。由于角部奇点具有很强的局部性，我们将支持 $p$ 。

// - 而不是 $h$  - 精细化的数量。我们通过设置0.2的小插值系数，以低门槛实现这一点。用同样的方法，我们处理那些要被粗化的单元，当它们的平滑度指标低于在要粗化的单元上确定的相应阈值时，减少它们的多项式程度。

      hp::Refinement::p_adaptivity_from_relative_threshold(
        dof_handler, smoothness_indicators, 0.2, 0.2);

// 上面的函数只决定了多项式程度是否会通过未来的FE指数发生变化，但并没有操作 $h$  -细化标志。因此，对于被标记为两个细化类别的单元格，我们更倾向于 $p$  。

// 而不是 $h$  -细化。下面的函数调用确保只有 $p$ 中的一个

// - 或  $h$  - 精炼中的一种，而不是同时实施两种。

      hp::Refinement::choose_p_over_h(dof_handler);

// 对于网格自适应细化，我们通过调用 Triangulation::prepare_coarsening_and_refinement(). 将相邻单元的细化水平差限制为1来确保2:1的网格平衡。 我们希望对相邻单元的p水平实现类似的效果：未来有限元的水平差不允许超过指定的差。通过其默认参数，调用 hp::Refinement::limit_p_level_difference() 可以确保它们的级差被限制在1以内。这不一定会减少域中的悬挂节点的数量，但可以确保高阶多项式不会被限制在面的低得多的多项式上，例如五阶多项式到二阶多项式。

      triangulation.prepare_coarsening_and_refinement();
      hp::Refinement::limit_p_level_difference(dof_handler);

// 在这个过程结束后，我们再细化网格。在这个过程中，正在进行分割的单元的子单元会继承其母单元的有限元索引。此外，未来的有限元指数将变成活动的，因此新的有限元将在下一次调用 DoFHandler::distribute_dofs(). 后被分配给单元。
      triangulation.execute_coarsening_and_refinement();
    }
  }
// @sect4{LaplaceProblem::create_coarse_grid}

// 在创建初始网格时，会用到下面这个函数。我们想要创建的网格实际上与 step-14 中的网格类似，即中间有方孔的方形域。它可以由完全相同的函数生成。然而，由于它的实现只是2d情况下的一种特殊化，我们将介绍一种不同的方法来创建这个域，它是独立于维度的。

// 我们首先创建一个有足够单元的超立方体三角形，这样它就已经包含了我们想要的域 $[-1,1]^d$ ，并细分为 $4^d$ 单元。然后，我们通过测试每个单元上顶点的坐标值来移除域中心的那些单元。最后，我们像往常一样对如此创建的网格进行全局细化。

  template <int dim>
  void LaplaceProblem<dim>::create_coarse_grid()
  {
    Triangulation<dim> cube;
    GridGenerator::subdivided_hyper_cube(cube, 4, -1., 1.);

    std::set<typename Triangulation<dim>::active_cell_iterator> cells_to_remove;
    for (const auto &cell : cube.active_cell_iterators())
      for (unsigned int v = 0; v < GeometryInfo<dim>::vertices_per_cell; ++v)
        if (cell->vertex(v).square() < .1)
          cells_to_remove.insert(cell);

    GridGenerator::create_triangulation_with_removed_cells(cube,
                                                           cells_to_remove,
                                                           triangulation);

    triangulation.refine_global(3);
  }

//  @sect4{LaplaceProblem::run}

// 这个函数实现了程序的逻辑，就像以前大多数程序中的相应函数一样，例如见  step-6  。

// 基本上，它包含了自适应循环：在第一次迭代中创建一个粗略的网格，然后建立线性系统，对其进行组合，求解，并对解进行后处理，包括网格细化。然后再重新开始。同时，也为那些盯着屏幕试图弄清楚程序是干什么的人输出一些信息。

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

        if (cycle == 0)
          create_coarse_grid();

        setup_system();

        std::cout << "   Number of active cells      : "
                  << triangulation.n_active_cells() << std::endl
                  << "   Number of degrees of freedom: " << dof_handler.n_dofs()
                  << std::endl
                  << "   Number of constraints       : "
                  << constraints.n_constraints() << std::endl;

        assemble_system();
        solve();
        postprocess(cycle);
      }
  }
} // namespace Step27
// @sect3{The main function}

// 主函数仍然是我们之前的版本：将创建和运行一个主类的对象包装成一个 try 块，并捕捉任何抛出的异常，从而在出现问题时产生有意义的输出。

int main()
{
  try
    {
      using namespace Step27;

      LaplaceProblem<2> laplace_problem;
      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;
}


#include <memory>
#include <vector>

#include <boost/multiprecision/cpp_int.hpp>

#include "block_system.hpp"
#include "bsgs.hpp"
#include "dbg.hpp"
#include "perm.hpp"
#include "perm_group.hpp"
#include "perm_set.hpp"

namespace mpsym
{

namespace internal
{

std::vector<PermGroup> PermGroup::wreath_decomposition() const
{
  DBG(DEBUG) << "Finding wreath product decomposition for";
  DBG(DEBUG) << *this;

  for (BlockSystem const &block_system : BlockSystem::non_trivial(*this)) {
    DBG(TRACE) << "Considering block system:";
    DBG(TRACE) << block_system;

    // determine block permuter subgroup
    PermGroup block_permuter(block_system.size(),
                             block_system.block_permuter(generators()));

    DBG(TRACE) << "Block permuter is:";
    DBG(TRACE) << block_permuter;

    // determine block stabilizer subgroups
    auto stabilizers(wreath_decomp_find_stabilizers(
      block_system, block_permuter));

    if (stabilizers.empty())
      continue;

    // check if a monomorphism can be found heuristically
    auto block_permuter_image(wreath_decomp_construct_block_permuter_image(
      block_system, block_permuter));

    bool found_monomorphism(wreath_decomp_reconstruct_block_permuter(
      block_system, block_permuter, block_permuter_image));

    if (!found_monomorphism)
      break;

    // construct the wreath decomposition
    std::vector<PermGroup> decomposition(block_system.size() + 1u);

    decomposition[0] = PermGroup(degree(), block_permuter_image);
    for (unsigned i = 0u; i < block_system.size(); ++i)
      decomposition[i + 1u] = stabilizers[i];

    DBG(DEBUG) << "=> Found wreath product decomposition:";
#ifndef NDEBUG
    for (PermGroup const &pg : decomposition)
      DBG(DEBUG) << pg;
#endif

    return decomposition;
  }

  DBG(DEBUG) << "=> No wreath product decomposition found";
  return {};
}

std::vector<PermGroup> PermGroup::wreath_decomp_find_stabilizers(
  BlockSystem const &block_system,
  PermGroup const &block_permuter) const
{
  using boost::multiprecision::pow;

  std::vector<PermGroup> stabilizers(block_system.size());

  auto create_stabilizer = [&](unsigned i) {
    auto block(block_system[i]);

    // find stabilizer subgroup generators
    auto stabilizer_generators(
      BlockSystem::block_stabilizers(generators(), block));

    // restrict stabilizer subgroup generators to block
    PermSet stabilizer_generators_restricted;
    for (Perm const &gen : stabilizer_generators) {
      auto gen_restricted(gen.restricted(block.begin(), block.end()));

      if (!gen_restricted.id())
        stabilizer_generators_restricted.insert(gen_restricted);
    }

    // construct stabilizer subgroup
    stabilizers[i] = PermGroup(degree(), stabilizer_generators_restricted);

    DBG(TRACE) << "Block stabilizer of " << block_system[i] << ":";
    DBG(TRACE) << stabilizers[i];
  };

  // determine stabilizer subgroup of first block
  create_stabilizer(0);

  // skip blocksystem if order equality not fullfilled
  BSGS::order_type expected_order = pow(
    stabilizers[0].order(), block_system.size()) * block_permuter.order();

  if (_order != expected_order) {
    DBG(TRACE) << "Group order equality not satisfied";
    return {};
  }

  // determine stabilizers subgroups of remaining blocks
  for (unsigned i = 1u; i < block_system.size(); ++i)
    create_stabilizer(i);

  return stabilizers;
}

PermSet PermGroup::wreath_decomp_construct_block_permuter_image(
  BlockSystem const &block_system,
  PermGroup const &block_permuter) const
{
  PermSet block_permuter_image;

  for (Perm const &gen : block_permuter.generators()) {
    std::vector<unsigned> perm(degree());

    for (unsigned i = 0u; i < block_system.size(); ++i) {
      auto block(block_system[i]);

      for (auto j = 0u; j < block.size(); ++j)
        perm[block[j] - 1u] = block_system[gen[i + 1u] - 1u][j];
    }

    block_permuter_image.insert(Perm(perm));
  }

  DBG(TRACE) << "Heuristic monomorphism image generators:";
  DBG(TRACE) << block_permuter_image;

  return block_permuter_image;
}

bool PermGroup::wreath_decomp_reconstruct_block_permuter(
  BlockSystem const &block_system,
  PermGroup const &block_permuter,
  PermSet const &block_permuter_image) const
{
  bool found_monomorphism = true;

  PermSet block_permuter_reconstruction;

  for (Perm const &gen : block_permuter_image) {
    std::vector<unsigned> perm(block_system.size());

    for (unsigned i = 0u; i < block_system.size(); ++i)
      perm[i] = block_system.block_index(gen[block_system[i][0]]) + 1u;

    Perm reconstructed_gen(perm);

    if (!block_permuter.contains_element(reconstructed_gen)) {
      found_monomorphism = false;
      break;
    }

    block_permuter_reconstruction.insert(reconstructed_gen);
  }

  DBG(TRACE) << "Block permuter reconstruction yields generators:";
  DBG(TRACE) << block_permuter_reconstruction;

  if (found_monomorphism) {
    if (PermGroup(block_system.size(), block_permuter_reconstruction).order()
        != block_permuter.order())
      found_monomorphism = false;
  }

  if (!found_monomorphism) {
    DBG(WARN) << "Wreath decomposition exists but was not found by heuristic";
  }

  return found_monomorphism;
}

} // namespace internal

} // namespace mpsym