#include "problems/mga_transx.h"

#include <boost/array.hpp>
#include <boost/math/constants/constants.hpp>

#include <keplerian_toolbox/core_functions/fb_vel.h>
#include <keplerian_toolbox/core_functions/array3D_operations.h>
#include <keplerian_toolbox/lambert_problem.h>

#include <cmath>
#include <numeric>
#include <string>
#include <vector>

namespace pagmo { namespace problem {

mga_transx::mga_transx(const std::vector<kep_toolbox::planet::planet_ptr> seq,
    const double dep_altitude, const double arr_altitude, const bool circularize,
    const kep_toolbox::epoch t0_l, const kep_toolbox::epoch t0_u,
    const double tof_l, const double tof_u,
    const double vinf_l, const double vinf_u,
    const bool add_vinf_dep, const bool add_vinf_arr,
    const bool multi_obj) : transx_problem(seq, dep_altitude, arr_altitude, circularize, seq.size() + 1, 1 + (int)multi_obj), m_add_vinf_dep(add_vinf_dep), m_add_vinf_arr(add_vinf_arr), m_multi_obj(multi_obj) {

        size_t dim(get_dimension());
        decision_vector lb(dim), ub(dim);
        lb[0] = t0_l.mjd2000(); ub[0] = t0_u.mjd2000();
        lb[1] = tof_l * 365.25; ub[1] = tof_u * 365.25;

        for (int i = 0; i < dim - 2; ++i) {
          lb[2 + i] = 1e-5; ub[2 + i] = 1 - 1e-5;
        }

        set_bounds(lb, ub);
}

mga_transx::mga_transx(const mga_transx &p) : transx_problem(p.get_seq(), p.get_dep_altitude(), p.get_arr_altitude(), p.get_circularize(), p.get_dimension(), p.get_f_dimension()), m_add_vinf_dep(p.m_add_vinf_dep), m_add_vinf_arr(p.m_add_vinf_arr), m_multi_obj(p.m_multi_obj) {
  set_bounds(p.get_lb(), p.get_ub());

}

base_ptr mga_transx::clone() const {
  return base_ptr(new mga_transx(*this));
}

void mga_transx::calc_objective(fitness_vector &f, const decision_vector &x, bool should_print, TransXSolution * solution) const {

  int n = get_n_legs();

  std::vector<double> T(n, 0.0);

  double alpha_sum = 0.0;
  for (int i = 0; i < get_n_legs(); ++i) {
    double tmp = -log(x[2 + i]);
    alpha_sum += tmp;
    T[i] = x[1] * tmp;
  }

  for (int i = 0; i < T.size(); ++i) {
    T[i] /= alpha_sum;
  }

  std::vector<kep_toolbox::epoch>   t_P(get_seq().size());
  std::vector<kep_toolbox::array3D> r_P(get_seq().size());
  std::vector<kep_toolbox::array3D> v_P(get_seq().size());
  std::vector<double> DV(get_seq().size());
  for (int i = 0; i < get_seq().size(); ++i) {
    kep_toolbox::planet::planet_ptr planet = get_seq()[i];
    t_P[i] = kep_toolbox::epoch(x[0] + std::accumulate(T.begin(), T.begin()+i, 0.0));
    planet->eph(t_P[i], r_P[i], v_P[i]);
  }

  kep_toolbox::array3D r(r_P[0]), v(v_P[0]);
  kep_toolbox::array3D v_end_l;
  kep_toolbox::array3D v_beg_l;

  if (should_print) {
    transx_time_info(solution->mutable_times(), get_seq(), t_P);
  }

  kep_toolbox::array3D vout, vin;
  for (int i = 0; i < get_n_legs(); ++i) {
    r = r_P[i]; v = v_P[i];

    double dt = (t_P[i + 1].mjd() - t_P[i].mjd()) * ASTRO_DAY2SEC;
    kep_toolbox::lambert_problem l(r, r_P[i + 1], dt, get_common_mu(), false, false);
    v_beg_l = l.get_v1()[0];
    v_end_l = l.get_v2()[0];

    vout = v_beg_l;

    if (i == 0) {
      kep_toolbox::array3D vout_rel(vout);
      kep_toolbox::diff(vout_rel, vout, v_P[i]);
      if (get_add_vinf_dep()) {
        DV[0] = burn_cost(get_seq()[0], vout_rel, false, true);
      }
      if (should_print) {
        transx_escape(solution->mutable_escape(), get_seq()[0], v_P[0], r_P[0], vout_rel, t_P[0].mjd());
      }
    } else {
      kep_toolbox::array3D v_rel_in(vin), v_rel_out(vout);
      kep_toolbox::diff(v_rel_in, vin, v_P[i]);
      kep_toolbox::diff(v_rel_out, vout, v_P[i]);
      kep_toolbox::planet::planet_ptr planet = get_seq()[i];
      kep_toolbox::fb_vel(DV[i], v_rel_in, v_rel_out, *planet);

      double ta  = acos(kep_toolbox::dot(v_rel_in, v_rel_out)/sqrt(kep_toolbox::dot(v_rel_in,v_rel_in))/sqrt(kep_toolbox::dot(v_rel_out,v_rel_out)));
      double alt = (planet->get_mu_self() / kep_toolbox::dot(v_rel_in,v_rel_in)*(1/sin(ta/2)-1) - planet->get_radius())/1000;
      if (alt > planet->get_safe_radius()) {
        f[0] = DBL_MAX;
        return;
      }

      if (should_print) {
        transx_flyby(solution->add_flybyes(), planet, v_P[i], r_P[i], v_rel_in, v_rel_out, t_P[i].mjd());
      }
    }

    vin = v_end_l;
  }

  kep_toolbox::array3D Vexc_arr;
  kep_toolbox::diff(Vexc_arr, v_end_l, v_P[v_P.size() - 1]);
  if (get_add_vinf_arr()) {
    DV[DV.size() - 1] = burn_cost(get_seq()[get_seq().size() - 1], Vexc_arr, true, get_circularize());
  }

  if (should_print) {
    transx_arrival(solution->mutable_arrival(), get_seq()[get_seq().size() - 1], Vexc_arr, t_P[t_P.size() - 1].mjd());
  }

  double fuelCost = std::accumulate(DV.begin(), DV.end(), 0.0);
  double totalTime = std::accumulate(T.begin(), T.end(), 0.0);

  if (should_print) {
    solution->set_fuel_cost(fuelCost);
  }

  f[0] = fuelCost;
  if (get_f_dimension() == 2) {
    f[1] = totalTime;
  }

}

std::string mga_transx::get_name() const {
  return "MGA";
}

}} // namespaces

BOOST_CLASS_EXPORT_IMPLEMENT(pagmo::problem::mga_transx)


#include <iostream>
#include <cmath>
#include <concepts>

#include <boost/numeric/linear_algebra/operators.hpp>
#include <boost/numeric/linear_algebra/inverse.hpp>
#include <boost/numeric/linear_algebra/is_invertible.hpp>
#include <boost/numeric/linear_algebra/new_concepts.hpp>
#include <boost/numeric/linear_algebra/concept_maps.hpp>
#include <boost/numeric/linear_algebra/power.hpp>

template <std::HasPlus T>
    requires std::Convertible<std::HasPlus<T, T>::result_type, T>
          && std::Semiregular<T>
T f(const T& x, const T& y)
{
    std::cout << "\nAdditive magma.\n";
    return x + y;
}

template <math::AdditiveSemiGroup T>
    requires std::Convertible<std::HasPlus<T, T>::result_type, T>
          && std::Semiregular<T>
T f(const T& x, const T& y)
{
    std::cout << "\nAdditive semi-group.\n";
    return x + y;
}

template <math::AdditiveMonoid T>
    requires std::Convertible<std::HasPlus<T, T>::result_type, T>
          && std::Semiregular<T>
T f(const T& x, const T& y)
{
    std::cout << "\nAdditive monoid.\n";
    return x + y;
}

#if 0
template <math::AdditivePIMonoid T>
    requires std::Convertible<std::HasPlus<T, T>::result_type, T>
          && std::Semiregular<T>
T f(const T& x, const T& y)
{
    std::cout << "\nAdditive partially invertible monoid.\n";
    return x + y;
}

template <math::AdditiveGroup T>
    requires std::Convertible<std::HasPlus<T, T>::result_type, T>
          && std::Semiregular<T>
T f(const T& x, const T& y)
{
    std::cout << "\nAdditive group.\n";
    return x + y;
}
#endif

int main(int, char* [])
{
    std::cout << "f(3, 4) " << f(3, 4) << '\n';
    std::cout << "f(3l, 4l) " << f(3l, 4l) << '\n';
    std::cout << "f(3u, 4u) " << f(3u, 4u) << '\n';
    std::cout << "f(3.0f, 4.0f) " << f(3.0f, 4.0f) << '\n';
    std::cout << "f(3.0, 4.0) " << f(3.0, 4.0) << '\n';

    return 0;
}


/*
   Copyright 2018-2021 Tatsuya Yamasaki.

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

#ifndef INCLUDED_MEEVAX_VISUAL_GEOMETRY_HPP
#define INCLUDED_MEEVAX_VISUAL_GEOMETRY_HPP

#include <utility>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/geometry/geometries/register/point.hpp>

#include <Eigen/Core>

BOOST_GEOMETRY_REGISTER_POINT_2D(Eigen::Vector2d, double, cs::cartesian, x(), y())

namespace meevax::visual
{
  using vector = Eigen::Vector2d;
  using point = vector;

  using polygon = boost::geometry::model::polygon<point>;

  class geometry
  {
    using pointer = point*;

    pointer const position_;

  public:
    polygon extents;

    geometry(pointer p)
      : position_ {p}
    {}

    auto& position()
    {
      return *position_;
    }
  };
} // namespace meevax::visual

#endif // INCLUDED_MEEVAX_VISUAL_GEOMETRY_HPP


#define BOOST_TEST_MODULE Gpufit

#define PI 3.1415926535897f

#include "Gpufit/gpufit.h"

#include <boost/test/included/unit_test.hpp>

#include <array>
#include <cmath>

template<std::size_t SIZE>
void generate_gauss_2d_rotated(std::array< REAL, SIZE>& values, std::array< REAL, 7 > const & parameters)
{
    int const size_x = int(std::sqrt(SIZE));
    int const size_y = size_x;

    REAL const a = parameters[0];
    REAL const x0 = parameters[1];
    REAL const y0 = parameters[2];
    REAL const sx = parameters[3];
    REAL const sy = parameters[4];
    REAL const b = parameters[5];
    REAL const r = parameters[6];

    for (int point_index_y = 0; point_index_y < size_y; point_index_y++)
    {
        for (int point_index_x = 0; point_index_x < size_x; point_index_x++)
        {
            int const point_index = point_index_y * size_x + point_index_x;
            REAL const arga = ((point_index_x - x0) * cos(r)) - ((point_index_y - y0) * sin(r));
            REAL const argb = ((point_index_x - x0) * sin(r)) + ((point_index_y - y0) * cos(r));
            REAL const ex = exp((-.5f) * (((arga / sx) * (arga / sx)) + ((argb / sy) * (argb / sy))));
            values[point_index] = a * ex + b;
        }
    }
}

BOOST_AUTO_TEST_CASE( Gauss_Fit_2D_Rotated )
{
    std::size_t const n_fits{ 1 } ;
    std::size_t const n_points{ 64 } ;

    std::array< REAL, 7 > true_parameters{ { 10, 3.5f, 3.5f, .4f, .5f, 1, PI / 16 } };

    std::array< REAL, n_points > data{};
    generate_gauss_2d_rotated(data, true_parameters);

    std::array< REAL, n_points > weights{};
    std::fill(weights.begin(), weights.end(), 1.f);

    std::array< REAL, 7 > initial_parameters{ { 8, 3.4f, 3.6f, .4f, .5f, 2, 0 } };

    REAL tolerance{ .0000001f };

    int max_n_iterations{ 10 };

    std::array< int, 7 > parameters_to_fit{ { 1, 1, 1, 1, 1, 1, 1 } };

    std::array< REAL, 7 > output_parameters;
    int output_states;
    REAL output_chi_square;
    int output_n_iterations;

    int const status
            = gpufit
            (
                n_fits,
                n_points,
                data.data(),
                weights.data(),
                GAUSS_2D_ROTATED,
                initial_parameters.data(),
                tolerance,
                max_n_iterations,
                parameters_to_fit.data(),
                LSE,
                0,
                0,
                output_parameters.data(),
                &output_states,
                &output_chi_square,
                &output_n_iterations
            ) ;

    BOOST_CHECK(status == 0);
    BOOST_CHECK(output_states == 0);
    BOOST_CHECK(output_n_iterations <= max_n_iterations);
    BOOST_CHECK(output_chi_square < 1e-6f);

    BOOST_CHECK(std::abs(output_parameters[0] - true_parameters[0]) < 2e-5f);
    BOOST_CHECK(std::abs(output_parameters[1] - true_parameters[1]) < 1e-6f);
    BOOST_CHECK(std::abs(output_parameters[2] - true_parameters[2]) < 1e-6f);
    BOOST_CHECK(std::abs(output_parameters[3] - true_parameters[3]) < 1e-6f);
    BOOST_CHECK(std::abs(output_parameters[4] - true_parameters[4]) < 1e-6f);
    BOOST_CHECK(std::abs(output_parameters[5] - true_parameters[5]) < 1e-6f);
    BOOST_CHECK(std::abs(output_parameters[6] - true_parameters[6]) < 1e-6f);
}


/*
 *  Copyright 2011-2015 Maxim Milakov
 *
 *  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.
 */

#include "convert_to_polar_data_transformer.h"

#include "neural_network_exception.h"

#include <opencv2/imgproc/imgproc.hpp>
#include <boost/format.hpp>

namespace nnforge
{
	const float convert_to_polar_data_transformer::pi = atan(1.0F) * 4.0F;

	convert_to_polar_data_transformer::convert_to_polar_data_transformer(
		const std::vector<unsigned int>& input_window_sizes,
		const std::vector<unsigned int>& output_window_sizes,
		float start_angle,
		float step_angle,
		float input_radius,
		unsigned char border_value)
		: input_window_sizes(input_window_sizes)
		, output_window_sizes(output_window_sizes)
		, start_angle(start_angle)
		, step_angle(step_angle)
		, input_radius(input_radius)
		, border_value(border_value)
	{
		if (input_window_sizes.size() != output_window_sizes.size())
			throw neural_network_exception((boost::format("convert_to_polar_data_transformer is created with different dimensions: %1% and %2%") % input_window_sizes.size() % output_window_sizes.size()).str());

		if (input_window_sizes.size() != 2)
			throw neural_network_exception((boost::format("convert_to_polar_data_transformer is created with wrong dimension count: %1%") % input_window_sizes.size()).str());

		map_x.create(output_window_sizes[1], output_window_sizes[0]);
		map_y.create(output_window_sizes[1], output_window_sizes[0]);
		float step_theta = step_angle * (pi * (2.0F / 360.0F));
		float start_theta = start_angle * (pi * (2.0F / 360.0F));
		float step_r = input_radius / static_cast<float>(output_window_sizes[1]);
		float start_r = step_r;
		float mid_input_x = static_cast<float>(input_window_sizes[0] - 1) * 0.5F;
		float mid_input_y = static_cast<float>(input_window_sizes[1] - 1) * 0.5F;
		for(int outut_y = 0; outut_y < map_x.rows; ++outut_y)
		{
			float r = start_r + static_cast<float>(map_x.rows - 1 - outut_y) * step_r;
			for(int ouput_x = 0; ouput_x < map_x.cols; ++ouput_x)
			{
				float theta = start_theta + static_cast<float>(ouput_x) * step_theta;
				float input_x = cosf(theta) * r + mid_input_x;
				float input_y = sinf(theta) * r + mid_input_y;
				map_x.at<float>(outut_y, ouput_x) = input_x;
				map_y.at<float>(outut_y, ouput_x) = input_y;
			}
		}
	}

	convert_to_polar_data_transformer::~convert_to_polar_data_transformer()
	{
	}

	void convert_to_polar_data_transformer::transform(
		const void * data,
		void * data_transformed,
		neuron_data_type::input_type type,
		const layer_configuration_specific& original_config,
		unsigned int sample_id)
	{
		if (type != neuron_data_type::type_byte)
			throw neural_network_exception("convert_to_polar_data_transformer is implemented for data stored as bytes only");

		if (original_config.dimension_sizes.size() != 2)
			throw neural_network_exception((boost::format("convert_to_polar_data_transformer is processing 2D data only, data is passed with number of dimensions %1%") % original_config.dimension_sizes.size()).str());

		if (original_config.dimension_sizes != input_window_sizes)
			throw neural_network_exception("convert_to_polar_data_transformer: input window size mismatch between creation and actual transform");

		unsigned int original_neuron_count_per_feature_map = original_config.get_neuron_count_per_feature_map();
		unsigned int transformed_neuron_count_per_feature_map = get_transformed_configuration(original_config).get_neuron_count_per_feature_map();
		for(unsigned int feature_map_id = 0; feature_map_id < original_config.feature_map_count; ++feature_map_id)
		{
			cv::Mat1b original_image(static_cast<int>(original_config.dimension_sizes[1]), static_cast<int>(original_config.dimension_sizes[0]), const_cast<unsigned char *>(static_cast<const unsigned char *>(data)) + (original_neuron_count_per_feature_map * feature_map_id));
			cv::Mat1b dest_image(static_cast<int>(output_window_sizes[1]), static_cast<int>(output_window_sizes[0]), static_cast<unsigned char *>(data_transformed) + (transformed_neuron_count_per_feature_map * feature_map_id));

			// Should try INTER_CUBIC and INTER_LANCZOS4 as well
			cv::remap(original_image, dest_image, map_x, map_y, cv::INTER_LINEAR, cv::BORDER_CONSTANT, border_value);
		}
	}

	layer_configuration_specific convert_to_polar_data_transformer::get_transformed_configuration(const layer_configuration_specific& original_config) const
	{
		layer_configuration_specific res(original_config.feature_map_count);
		res.dimension_sizes = output_window_sizes;

		return res;
	}

	bool convert_to_polar_data_transformer::is_in_place() const
	{
		return false;
	}

 	bool convert_to_polar_data_transformer::is_deterministic() const
	{
		return true;
	}
}


//
// Created by dchansen on 9/4/18.
//

#include "bounded_field_map.h"
#include <boost/math/constants/constants.hpp>
#include <boost/math/tools/minima.hpp>
#include <numeric>
#include <GadgetronTimer.h>

constexpr float PI = boost::math::constants::pi<float>();

namespace Gadgetron {
    namespace FatWater {
        namespace {
            template<unsigned int N>
            struct FieldMapModel {

                FieldMapModel(const Parameters &parameters,
                              const std::array<complext<float>, N> &data) : TEs_(parameters.echo_times_s) {

                    std::transform(data.begin(), data.end(), angles.begin(), [](auto c) { return arg(c); });

                    auto data_norm = std::accumulate(data.begin(), data.end(), 0.0,
                                                     [](auto acc, auto c) { return acc + norm(c); });
                    for (int i = 0; i < data.size(); i++) {
                        for (int j = 0; j < data.size(); j++) {
                            weights[j + i * N] = norm(data[i] * data[j]) / data_norm;
                        }
                    }
                }

                float operator()(float field_value) const {

                    float result = 0;
                    for (int i = 0; i < N; i++) {
                        for (int j = 0; j < N; j++) {
                            result += magnitude_internal(field_value, TEs_[i], TEs_[j], angles[i], angles[j],
                                                         weights[j + i * N]);
                        }
                    }
                    return result;
                }

                float magnitude_internal(float field_value, float time1, float time2, float angle1, float angle2,
                                         float weight) const {
                    assert(weight >= 0);
                    return weight * (1.0f - std::cos(field_value * (time1 - time2) + angle1 - angle2));

                }

                const std::vector<float> TEs_;
                std::array<float, N * N> weights;
                std::array<float, N> angles;

            };

            template<unsigned int N>
            void bounded_field_map_N(Gadgetron::hoNDArray<float> &field_map,
                                     const Gadgetron::hoNDArray<std::complex<float>> &input_data,
                                     const Gadgetron::FatWater::Parameters &parameters,
                                     float delta_field) {

                const size_t X = input_data.get_size(0);
                const size_t Y = input_data.get_size(1);
                const size_t Z = input_data.get_size(2);
                const size_t S = input_data.get_size(5);

#ifdef WIN32
    #pragma omp parallel for
#else
    #pragma omp parallel for collapse(2)
#endif
                for (int ky = 0; ky < Y; ky++) {
                    for (size_t kx = 0; kx < X; kx++) {

                        std::array<complext<float>, N> signal;

                        for (int k3 = 0; k3 < S; k3++) {
                            signal[k3] = input_data(kx, ky, 0, 0, 0, k3, 0);
                        }

                        auto model = FieldMapModel<N>(parameters, signal);

                        auto result_pair = boost::math::tools::brent_find_minima(model, field_map(kx,ky)-delta_field,field_map(kx,ky)+delta_field, 24);
                        field_map(kx, ky) = result_pair.first;
                    }
                }

            }
        }

        void bounded_field_map(Gadgetron::hoNDArray<float> &field_map,
                                 const Gadgetron::hoNDArray<std::complex<float>> &input_data,
                                 const Gadgetron::FatWater::Parameters &parameters,
                                 float delta_field
        ) {

            if (input_data.get_size(4) > 1) throw std::runtime_error("Only single repetition supported");

            switch (input_data.get_size(5)) {
                case 2:
                    bounded_field_map_N<2>(field_map, input_data, parameters, delta_field);
                    break;
                case 3:
                    bounded_field_map_N<3>(field_map, input_data, parameters, delta_field);
                    break;
                case 4:
                    bounded_field_map_N<4>(field_map, input_data, parameters, delta_field);
                    break;
                case 5:
                    bounded_field_map_N<5>(field_map, input_data, parameters, delta_field);
                    break;
                case 6:
                    bounded_field_map_N<6>(field_map, input_data, parameters, delta_field);
                    break;
                case 7:
                    bounded_field_map_N<7>(field_map, input_data, parameters, delta_field);
                    break;
                case 8:
                    bounded_field_map_N<8>(field_map, input_data, parameters, delta_field);
                    break;
                default:
                    throw std::runtime_error("Unsupported number of echoes");

            }
        }

    }
}


#include "linear_relations.h"
#include <vector>
#include <hamming/knowledge_of_valid_opening/knowledge_of_valid_opening.h>
#include <NTL/GF2X.h>
#include <utils/utils.h>

void hamming_metric::linear_relations::initialize_commitments_and_responses(
        commitments_t *commitments,
        responses_t *responses) {

    for (int i = 0; i < I; i++) {
        responses->t0.append(NTL::vec_GF2());
        responses->t1.append(NTL::vec_GF2());
        responses->t2.append(NTL::vec_GF2());
        commitments->r0.append(NTL::vec_GF2());
        commitments->r1.append(NTL::vec_GF2());
        commitments->r2.append(NTL::vec_GF2());
        commitments->c0.append(NTL::vec_GF2());
        commitments->c1.append(NTL::vec_GF2());
        commitments->c2.append(NTL::vec_GF2());
    }
}

void hamming_metric::linear_relations::generate_private_key(
        private_key_t *private_key,
        const linear_relation_matrices_t *matrices) {

    {
        private_key->m.kill();
        private_key->m.SetLength(I);

        private_key->m[0] = NTL::random_vec_GF2(JAIN_V);
        private_key->m[1] = NTL::random_vec_GF2(JAIN_V);
        private_key->m[2] = (matrices->x_0 * private_key->m[0]) + (matrices->x_1 * private_key->m[1]);
    }

    {
        private_key->e.kill();
        private_key->e.SetLength(I);

        for (int i = 0; i < I; i++) {
            utils::generate_vector_of_weight_w(
                    private_key->e[i],
                    JAIN_K,
                    W);
        }
    }
}

void hamming_metric::linear_relations::generate_random_values(
        random_values_t *random_values,
        const linear_relation_matrices_t *matrices) {

    {
        random_values->u.kill();
        random_values->u.SetLength(I);
        random_values->v.kill();
        random_values->v.SetLength(I);
        random_values->u_v.kill();
        random_values->u_v.SetLength(I);
        random_values->f.kill();
        random_values->f.SetLength(I);
    }
    for (int i = 0; i < I; i++) {
        utils::generate_random_binary_vector(
                random_values->u[i],
                JAIN_L);
        utils::generate_random_binary_vector(
                random_values->f[i],
                JAIN_K);
    }

    for (int i = 0; i < I - 1; i++) {
        utils::generate_random_binary_vector(
                random_values->v[i],
                JAIN_V);
    }

    random_values->v[2] = (matrices->x_0 * random_values->v[0]) + (matrices->x_1 * random_values->v[1]);

    random_values->u_v = random_values->u;
    for (int i = 0; i < I; i++) {
        NTL::append(random_values->u_v[i], random_values->v[i]);
    }
}

void hamming_metric::linear_relations::generate_public_key(
        public_key_t *public_key,
        const private_key_t *private_key) {

    public_key->commitments.kill();
    public_key->commitments.SetLength(I);

    utils::generate_random_binary_matrix(
            public_key->A,
            JAIN_K,
            JAIN_L + JAIN_V);

    for (int i = 0; i < I; i++) {
        NTL::vec_GF2 r;
        utils::generate_random_binary_vector(
                r,
                JAIN_L);

        hamming_metric::commitment::generate_commitment(
                public_key->commitments[i],
                public_key->A,
                r,
                private_key->m[i],
                private_key->e[i]);
    }
}

void hamming_metric::linear_relations::generate_revealed_values(
        revealed_values_t *revealed_values) {

    ::utils::generate_random_binary_matrix(
            revealed_values->matrices.x_0,
            JAIN_V,
            JAIN_V);
    ::utils::generate_random_binary_matrix(
            revealed_values->matrices.x_1,
            JAIN_V,
            JAIN_V);

    for(int i = 0; i < I; i++) {

        revealed_values->P.append(NTL::mat_GF2());

        utils::create_permutation_matrix(
                revealed_values->P[i],
                JAIN_K);
    }

}

void hamming_metric::linear_relations::generate_commitments_and_responses(
        responses_t *responses,
        commitments_t *commitments,
        const random_values_t *random_values,
        const revealed_values_t *revealed_values,
        const public_key_t *public_key,
        const private_key_t *private_key) {

    for (int i = 0; i < I; i++) {
        hamming_metric::knowledge_of_valid_opening::generate_commitment_and_response_0(
                commitments->c0[i],
                commitments->r0[i],
                responses->t0[i],
                random_values->u_v[i],
                public_key->A,
                random_values->f[i]);

        hamming_metric::knowledge_of_valid_opening::generate_commitment_and_response_1(
                commitments->c1[i],
                commitments->r1[i],
                responses->t1[i],
                public_key->A,
                revealed_values->P[i],
                random_values->f[i]);

        hamming_metric::knowledge_of_valid_opening::generate_commitment_and_response_2(
                commitments->c2[i],
                commitments->r2[i],
                responses->t2[i],
                public_key->A,
                revealed_values->P[i],
                random_values->f[i],
                private_key->e[i]);
    }
}

int hamming_metric::linear_relations::verify_0(
        const NTL::Vec<NTL::vec_GF2> &c0,
        const NTL::Vec<NTL::vec_GF2> &r0,
        const NTL::Vec<NTL::vec_GF2> &t0,
        const NTL::Vec<NTL::vec_GF2> &c1,
        const NTL::Vec<NTL::vec_GF2> &r1,
        const NTL::Vec<NTL::vec_GF2> &t1,
        const NTL::Vec<NTL::mat_GF2> &P,
        const NTL::mat_GF2 &x_0,
        const NTL::mat_GF2 &x_1,
        const public_key_t *public_key) {

    for(int i = 0; i < I; i++) {
        auto _t0 = utils::encode_binary_vector(t0[i], JAIN_V);
        auto _t1 = utils::encode_binary_vector(t1[i], JAIN_V);

        if (hamming_metric::commitment::verify(
                c0[i],
                public_key->A,
                r0[i],
                _t0) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 0 and c0" << std::endl;
            return 1;
        }

        if (hamming_metric::commitment::verify(
                c1[i],
                public_key->A,
                r1[i],
                _t1) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 0 and c1" << std::endl;
            return 1;
        }
    }

    NTL::Vec<NTL::vec_GF2> results;
    for (int i = 0; i < I; i++) {
        NTL::vec_GF2 result;

        if (utils::solve_equation(
                result,
                public_key->A,
                t0[i] + (NTL::inv(P[i]) * t1[i])) != 0) {
            std::cout << "No Solutions" << std::endl;
            return 1;
        }
        results.append(result);
    }

    NTL::Vec<NTL::vec_GF2> b;
    for (int i = 0; i < I; i++) {
        {
            b.append(NTL::vec_GF2());
            b[i].SetLength(JAIN_V);
        }

        for (int j = 0; j < JAIN_V; j++) {
            b[i][j] = results[i].at(j + JAIN_L);
        }
    }

    if (NTL::IsZero(b[2] + ((x_0 * b[0]) + (x_1 * b[1])))) {
        return 0;
    } else {
        std::cout << b[2] << std::endl;
        std::cout << ((b[0] * x_0) + (b[1] * x_1)) << std::endl;
        return 1;
    }

}

int hamming_metric::linear_relations::verify_1(
        const NTL::Vec<NTL::vec_GF2> &c0,
        const NTL::Vec<NTL::vec_GF2> &r0,
        const NTL::Vec<NTL::vec_GF2> &t0,
        const NTL::Vec<NTL::vec_GF2> &c2,
        const NTL::Vec<NTL::vec_GF2> &r2,
        const NTL::Vec<NTL::vec_GF2> &t2,
        const NTL::Vec<NTL::mat_GF2> &P,
        const NTL::mat_GF2 &x_0,
        const NTL::mat_GF2 &x_1,
        const public_key_t *public_key) {

    for (int i = 0; i < I; i++) {
        auto _t0 = utils::encode_binary_vector(t0[i], JAIN_V);
        auto _t2 = utils::encode_binary_vector(t2[i], JAIN_V);

        if (hamming_metric::commitment::verify(
                c0[i],
                public_key->A,
                r0[i],
                _t0) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 1 and c0" << std::endl;
            return 1;
        }

        if (hamming_metric::commitment::verify(
                c2[i],
                public_key->A,
                r2[i],
                _t2) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 1 and c2" << std::endl;
            return 1;
        }
    }

    NTL::Vec<NTL::vec_GF2> results;
    for (int i = 0; i < 3; i++) {
        NTL::vec_GF2 result;

        if (utils::solve_equation(
                result,
                public_key->A,
                t0[i] + (NTL::inv(P[i]) * t2[i]) + public_key->commitments[i]) != 0) {
            std::cout << "No Solutions" << std::endl;
            return 1;
        }
        results.append(result);
    }

    NTL::Vec<NTL::vec_GF2> d;
    for (int i = 0; i < 3; i++) {
        {
            d.append(NTL::vec_GF2());
            d[i].SetLength(JAIN_V);
        }

        for (int j = 0; j < JAIN_V; j++) {
            d[i][j] = results[i].at(j + JAIN_L);
        }
    }

    if (NTL::IsZero(d[2] + ((x_0 * d[0]) + (x_1 * d[1])))) {
        return 0;
    } else {
        std::cout << d[2] << std::endl;
        std::cout << ((d[2] + ((x_0 * d[0]) + (x_1 * d[1])))) << std::endl;
        return 1;
    }
}

int hamming_metric::linear_relations::verify_2(
        const NTL::Vec<NTL::vec_GF2> &c1,
        const NTL::Vec<NTL::vec_GF2> &r1,
        const NTL::Vec<NTL::vec_GF2> &t1,
        const NTL::Vec<NTL::vec_GF2> &c2,
        const NTL::Vec<NTL::vec_GF2> &r2,
        const NTL::Vec<NTL::vec_GF2> &t2,
        const public_key_t *public_key) {

    for (int i = 0; i < I; i++) {
        auto _t1 = utils::encode_binary_vector(t1[i], JAIN_V);
        auto _t2 = utils::encode_binary_vector(t2[i], JAIN_V);

        if (hamming_metric::commitment::verify(
                c1[i],
                public_key->A,
                r1[i],
                _t1) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 2 and c1" << std::endl;
            return 1;
        }

        if (hamming_metric::commitment::verify(
                c2[i],
                public_key->A,
                r2[i],
                _t2) != 0) {
            std::cout << "Linear Relations. Verification failed on ch = 2 and c2" << std::endl;
            return 1;
        }

        if(NTL::weight(t1[i] + t2[i]) != W) {
            std::cout << "Invalid weight" << std::endl;
            return 1;
        }

    }

    return 0;
}


/*

CandyPoker
https://github.com/sweeterthancandy/CandyPoker

MIT License

Copyright (c) 2019 Gerry Candy

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

*/
#include "ps/heads_up_solver.h"
#include "ps/heads_up.h"

#include <numeric>
#include <boost/timer/timer.hpp>
#include <thread>
#include <future>

namespace ps{

/*

                                        <>
                                 ______/  \______
                                /                \
                             <SB Push>         <SB Fold>
                         ____/     \_____
                        /                \
                    <BB Call>          <BB Fold>

                               +-----------+
                               |Total Value|
                               +-----------+

                        <SB fold>           = -sb
                        <SB push | BB fold> = bb
                        <SB push | BB call> = 2 S Eq - S
                                            = S( 2 Eq - 1 )

 */

// given hands
//    {sb_id, bb_id}
double calc_detail(calc_context& ctx)
{
        /*
                                         <root>
                                    ______/  \______
                                   /                \
                              <sb_push>          <sb_fold>
                         ____/        \_____
                        /                   \
               <sb_push_bb_call>      <sb_push_bb_fold>

         */
        struct sb_push{
                struct sb_push__bb_call{
                        double operator()(calc_context& ctx)const{
                                auto equity = ctx.cec->visit_boards(std::vector<ps::holdem_class_id>{ ctx.sb_id,ctx.bb_id }).equity();
                                return ctx.eff_stack * ( 2 * equity - 1 );
                        }
                };
                struct sb_push__bb_fold{
                        double operator()(calc_context& ctx)const{
                                return +ctx.bb;
                        }
                };
                double operator()(calc_context& ctx)const{
                        if( ctx.bb_call_strat[ctx.bb_id] == 0.0 )
                                return bb_fold_(ctx);
                        return
                                (  ctx.bb_call_strat[ctx.bb_id]) * bb_call_(ctx) +
                                (1-ctx.bb_call_strat[ctx.bb_id]) * bb_fold_(ctx);
                }
                sb_push__bb_call bb_call_;
                sb_push__bb_fold bb_fold_;
        };
        struct sb_fold{
                double operator()(calc_context& ctx)const{
                        return -ctx.sb;
                }
        };
        struct root{
                double operator()(calc_context& ctx)const{

                        // short-circuit
                        if( ctx.sb_push_strat[ctx.sb_id] == 0.0 )
                                return sb_fold_(ctx);

                        return
                                (  ctx.sb_push_strat[ctx.sb_id]) * sb_push_(ctx) +
                                (1-ctx.sb_push_strat[ctx.sb_id]) * sb_fold_(ctx);
                }
                sb_push sb_push_;
                sb_fold sb_fold_;
        };
        static root root_;
        return root_(ctx);
}
double calc( class_equity_cacher& cec,
                   hu_strategy const& sb_push_strat,
                   hu_strategy const& bb_call_strat,
                   double eff_stack, double sb, double bb)
{
        calc_context ctx = {
                &cec,
                sb_push_strat,
                bb_call_strat,
                eff_stack,
                sb,
                bb,
                0,
                0
        };
        return calc(ctx);
}
double calc(calc_context& ctx){
        double sigma{0.0};
        for(ctx.sb_id = 0; ctx.sb_id != 169;++ctx.sb_id){
                for(ctx.bb_id = 0; ctx.bb_id != 169;++ctx.bb_id){
                        auto p =  holdem_class_decl::prob(ctx.sb_id, ctx.bb_id) ;
                        sigma += calc_detail(ctx) * p;
                }
                        //PRINT(sigma);
        }
        return sigma;
}

hu_strategy solve_hu_push_fold_bb_maximal_exploitable(ps::class_equity_cacher& cec,
                                               hu_strategy const& sb_push_strat,
                                               double eff_stack, double sb, double bb)
{
        struct context{
                class_equity_cacher* cec;
                hu_strategy sb_push_strat;
                hu_strategy bb_call_strat;
                double eff_stack;
                double sb;
                double bb;
                holdem_class_id bb_id;

                hu_strategy debug;
        };
        /*
                                         <solver>
                                    ______/  \______
                                   /                \
                              <call>              <fold>

         */
        struct fold{
                double operator()(context& ctx)const{
                        return 0;
                        //return -ctx.bb;
                }
        };
        struct call{
                double operator()(context& ctx)const{
                        hu_fresult_t res;
                        for(holdem_class_id sb_id{0}; sb_id != 169;++sb_id){
                                hu_fresult_t tmp{ctx.cec->visit_boards(std::vector<ps::holdem_class_id>{ ctx.bb_id, sb_id })};
                                auto weight = ctx.sb_push_strat[sb_id];

                                tmp.times(weight);
                                res.append(tmp);

                        }

                        // edge case, can return anything here probably
                        if( res.sigma() == 0 )
                                return 0.0;

                        //PRINT_SEQ((ctx.eff_stack)(ctx.sb)(ctx.bb));

                        double ev{ ctx.eff_stack * 2 * res.equity() - ( ctx.eff_stack - ctx.bb ) };
                        //         \----equity of pot to win -----/   \------cost of bet-------/

                        ctx.debug[ctx.bb_id] = ev;
                        return ev;
                }
        };
        struct solver{
                void operator()(context& ctx)const{
                        //ctx.debug[ctx.bb_id] = ev_call;
                        if( call_(ctx) > fold_(ctx) ){
                                ctx.bb_call_strat[ctx.bb_id] = 1.0;
                        }
                }
        private:
                call call_;
                fold fold_;
        };

        context ctx = {
                &cec,
                sb_push_strat,
                hu_strategy{0.0},
                eff_stack,
                sb,
                bb,
                0
        };
        solver solver_;

        for(holdem_class_id bb_id{0}; bb_id != 169;++bb_id){
                ctx.bb_id = bb_id;
                solver_(ctx);
        }
        //std::cout << "HERE is BB Call diff\n";
        //ctx.debug.display();
        #if 0
        std::cout << "HERE is BB Strat\n";
        ctx.bb_call_strat.display();
        #endif
        return std::move(ctx.bb_call_strat);
}
hu_strategy solve_hu_push_fold_sb_maximal_exploitable(ps::class_equity_cacher& cec,
                                               hu_strategy const& bb_call_strat,
                                               double eff_stack, double sb, double bb)
{
        struct context{
                class_equity_cacher* cec;
                hu_strategy sb_push_strat;
                hu_strategy bb_call_strat;
                double eff_stack;
                double sb;
                double bb;
                holdem_class_id bb_id;
                holdem_class_id sb_id;
                hu_strategy debug;
        };
        struct sb_push__bb_call{
                double operator()(context& ctx)const{
                        auto equity = ctx.cec->visit_boards(std::vector<ps::holdem_class_id>{ ctx.sb_id, ctx.bb_id }).equity();
                        return ctx.eff_stack * 2 *  equity - ( ctx.eff_stack -  ctx.sb );
                        //     \- reuity of pot to win  -/   \--- cost of bet  --------/
                }
        };
        struct sb_push__bb_fold{
                double operator()(context& ctx)const{
                        return ctx.sb + ctx.bb;
                }
        };
        #if 0
        struct sb_push{
                double operator()(context& ctx)const{

                        hu_fresult_t agg;
                        for(ctx.bb_id = 0; ctx.bb_id != 169;++ctx.bb_id){
                                agg.append(ctx.cec->visit_boards(std::vector<ps::holdem_class_id>{ ctx.sb_id, ctx.bb_id }));
                        }
                        auto ret =  ctx.eff_stack * 2 *  agg.equity() - ( ctx.eff_stack -  ctx.sb ) ;
                        PRINT_SEQ((ctx.eff_stack)(agg.equity())(ctx.sb)( ctx.eff_stack * 2 *  agg.equity() - ( ctx.eff_stack -  ctx.sb ) ));
                        ctx.debug[ctx.sb_id] = ret;
                        return ret;
                }
        private:
                sb_push__bb_call bb_call_;
                sb_push__bb_fold bb_fold_;
        };
        #else
        struct sb_push{
                double operator()(context& ctx)const{
                        double sigma{0.0};
                        double factor{0.0};

                        for(ctx.bb_id = 0; ctx.bb_id != 169;++ctx.bb_id){
                                auto ev_bb_call =  bb_call_(ctx) ;
                                auto ev_bb_fold =  bb_fold_(ctx) ;
                                //PRINT_SEQ((ev_bb_call)(ev_bb_fold));
                                double ev_bb{
                                           ctx.bb_call_strat[ctx.bb_id]  * ev_bb_call +
                                        (1-ctx.bb_call_strat[ctx.bb_id]) * ev_bb_fold};
                                auto weight = holdem_class_decl::weight(ctx.sb_id, ctx.bb_id);
                                sigma  += weight * ev_bb;
                                factor += weight;
                        }
                        sigma /= factor;

                        return sigma;
                }
        private:
                sb_push__bb_call bb_call_;
                sb_push__bb_fold bb_fold_;
        };
        #endif
        struct sb_fold{
               double operator()(context& ctx)const{
                        //return ctx.sb;
                        return 0;
                }
        };
        struct solver{
                void operator()(context& ctx)const{
                        auto ev_push =  sb_push_(ctx) ;
                        auto ev_fold =  sb_fold_(ctx) ;
                        //PRINT_SEQ((ev_push)(ev_fold));
                        if( ev_push > ev_fold )
                                ctx.sb_push_strat[ctx.sb_id] = 1.0;
                }
        private:
                sb_push sb_push_;
                sb_fold sb_fold_;
        };
        context ctx = {
                &cec,
                hu_strategy{0.0},
                bb_call_strat,
                eff_stack,
                sb,
                bb,
                0,
                0
        };
        solver solver_;

        for(ctx.sb_id = 0; ctx.sb_id != 169;++ctx.sb_id){
                solver_(ctx);
        }

        //std::cout << "HERE is SB Push diff\n";
        ctx.debug.display();
        return std::move(ctx.sb_push_strat);
}

hu_strategy solve_hu_push_fold_sb(ps::class_equity_cacher& cec,
                           double eff_stack, double sb, double bb){
        double alpha{0.3};
        hu_strategy sb_strat{1.0};

        std::set< hu_strategy > circular_set;

        for(size_t i=0;;++i){
                boost::timer::auto_cpu_timer at;

                auto bb_me = solve_hu_push_fold_bb_maximal_exploitable(cec,
                                                                     sb_strat,
                                                                     eff_stack,
                                                                     sb,
                                                                     bb);

                double ev = calc(cec, sb_strat, bb_me, eff_stack, sb, bb);

                auto sb_me = solve_hu_push_fold_sb_maximal_exploitable(cec,
                                                                       bb_me,
                                                                       eff_stack,
                                                                       sb,
                                                                       bb);
                #if 0
                auto sb_me{solve_hu_push_fold_sb_maximal_exploitable(cec,
                                                                     bb_me,
                                                                     eff_stack,
                                                                     sb,
                                                                     bb)};
                auto sb_me_alt{solve_hu_push_fold_sb_maximal_exploitable__ev(cec,
                                                                     bb_me,
                                                                     eff_stack,
                                                                     sb,
                                                                     bb)};
                PRINT( (sb_me - sb_me_alt).norm() );
                (sb_me - sb_me_alt).display();
                #endif

                double ev_{calc(cec, sb_me, bb_me, eff_stack, sb, bb)};

                std::cout << "BB COUNTER\n";
                bb_me.display();
                std::cout << "SB COUNTER COUNTER\n";
                sb_me.display();

                auto sb_next =  sb_strat * ( 1 - alpha) + sb_me * alpha ;
                auto sb_norm =  (sb_next - sb_strat).norm() ;
                sb_strat = std::move(sb_next);
                double ev_d{ std::fabs(ev - ev_) };

                PRINT_SEQ((sb_norm)(ev)(ev_)(ev_d));

                if( circular_set.count(sb_strat) ){
                        std::cerr << "ok circular!\n";
                        break;
                }
                circular_set.insert(sb_strat);

                sb_strat.display();

                #if 0
                if( ( ev_d ) < 1e-3 ){
                        std::cout << "_ev_d_break_\n";
                        break;
                }
                #endif
                if( ( sb_norm ) < 1e-5 ){
                        std::cout << "_break_\n";
                        break;
                }
                if( i == 50){
                        std::cout << "_i_break_\n";
                        break;
                }
        }
        return std::move(sb_strat);
}

void make_heads_up_table(){
        using namespace ps;

        equity_cacher ec;
        ec.load("cache.bin");
        class_equity_cacher cec(ec);
        cec.load("hc_cache.bin");

        double bb{1.0};
        double sb{0.5};

        hu_strategy sb_table{0.0};
        hu_strategy bb_table{0.0};

        std::vector<
                std::tuple<
                        double, // stack
                        std::future<hu_strategy>
                >
        > results;

        for( double eff_stack{4.0};eff_stack < 10;eff_stack += 1){

                auto work = [&cec, eff_stack,sb,bb]()->hu_strategy{
                        auto sb_strat = solve_hu_push_fold_sb(cec, eff_stack, sb, bb);
                        return std::move(sb_strat);
                };
                std::packaged_task<hu_strategy()> pt(work);
                results.emplace_back( eff_stack, pt.get_future());
                std::thread{std::move(pt)}.detach();

        }
        for(auto& r : results ){
                std::get<1>(r).wait();
                auto eff_stack =  std::get<0>(r) ;
                auto sb_strat =  std::get<1>(r).get() ;
                auto bb_strat = solve_hu_push_fold_bb_maximal_exploitable(cec,
                                                                          sb_strat,
                                                                          eff_stack,
                                                                          sb,
                                                                          bb);
                for(size_t i{0};i!=169;++i){
                        if( sb_strat[i] < 1e-3 )
                                continue;
                        if( std::fabs(1.0 - sb_strat[i]) < 1e-3 )
                                sb_table[i] = eff_stack;
                }
                for(size_t i{0};i!=169;++i){
                        if( bb_strat[i] < 1e-3 )
                                continue;
                        if( std::fabs(1.0 - bb_strat[i]) < 1e-3 )
                                bb_table[i] = eff_stack;
                }
        }
        std::cout << "------------ sb push ----------------\n";
        sb_table.display();
        std::cout << "------------ bb call ----------------\n";
        bb_table.display();

}

} // ps


//
// Copyright 2020 Olzhas Zhumabek <anonymous.from.applecity@gmail.com>
//
// Use, modification and distribution are subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#include "../test_fixture.hpp"
#include "boost/gil/algorithm.hpp"
#include <boost/gil/color_base_algorithm.hpp>
#include <boost/gil/image.hpp>
#include <boost/gil/image_processing/diffusion.hpp>
#include <boost/gil/image_view.hpp>
#include <boost/gil/pixel.hpp>
#include <boost/gil/typedefs.hpp>

#include <boost/core/lightweight_test.hpp>

#include <cmath>
#include <limits>
#include <random>

namespace gil = boost::gil;

template <typename PixelType, typename DiffusionFunction>
void diffusion_function_check(DiffusionFunction diffusion)
{
    using limits = std::numeric_limits<gil::uint8_t>;
    using channel_type = typename gil::channel_type<PixelType>::type;
    for (channel_type value = limits::min(); value <= limits::max(); ++value)
    {
        PixelType pixel;
        gil::static_fill(pixel, value);
        auto diffusivity_value = diffusion(pixel);
        gil::static_for_each(diffusivity_value, [](channel_type channel_value) {
            BOOST_TEST(0 <= channel_value && channel_value <= 1.0);
        });
    }
}

void brightness_function_test()
{
    std::vector<gil::rgb32f_pixel_t> rgb_pixels{
        gil::rgb32f_pixel_t(0, 11, 14),     gil::rgb32f_pixel_t(2, 117, 200),
        gil::rgb32f_pixel_t(223, 2, 180),   gil::rgb32f_pixel_t(250, 254, 100),
        gil::rgb32f_pixel_t(255, 255, 255),
    };
    for (const auto& pixel : rgb_pixels)
    {
        boost::gil::laplace_function::stencil_type<gil::rgb32f_pixel_t> stencil;
        std::fill(stencil.begin(), stencil.end(), pixel);
        auto brightness_stencil = boost::gil::brightness_function::identity{}(stencil);
        for (const auto& brightness_pixel : brightness_stencil)
        {
            BOOST_TEST(pixel == brightness_pixel);
        }
    }

    std::vector<float> corresponding_luminance_values{8.878f, 98.5436f, 61.8362f, 242.0308f, 255};
    std::size_t index = 0;
    for (const auto& pixel : rgb_pixels)
    {
        boost::gil::laplace_function::stencil_type<gil::rgb32f_pixel_t> stencil;
        std::fill(stencil.begin(), stencil.end(), pixel);
        auto brightness_stencil = boost::gil::brightness_function::rgb_luminance{}(stencil);
        for (const auto& brightness_pixel : brightness_stencil)
        {
            gil::static_for_each(brightness_pixel, [&corresponding_luminance_values,
                                                    index](boost::gil::float32_t value) {
                BOOST_TEST(std::abs(value - corresponding_luminance_values[index]) < 1.0f);
            });
        }
        ++index;
    }

    std::vector<gil::gray32f_pixel_t> gray_pixels{
        gil::gray32f_pixel_t(11),  gil::gray32f_pixel_t(0),  gil::gray32f_pixel_t(255),
        gil::gray32f_pixel_t(123), gil::gray32f_pixel_t(17),
    };
    for (const auto& pixel : gray_pixels)
    {
        boost::gil::laplace_function::stencil_type<gil::gray32f_pixel_t> stencil;
        std::fill(stencil.begin(), stencil.end(), pixel);
        auto brightness_stencil = boost::gil::brightness_function::identity{}(stencil);
        for (const auto& brightness_pixel : brightness_stencil)
        {
            BOOST_TEST(pixel == brightness_pixel);
        }
    }
}

template <typename ImageType, typename OutputImageType>
void heat_conservation_test(std::uint32_t seed)
{
    gil::test::fixture::random_value<std::uint32_t> dist(seed, 0,
                                                         std::numeric_limits<gil::uint8_t>::max());

    ImageType image(32, 32);
    auto view = gil::view(image);
    constexpr std::ptrdiff_t num_channels = gil::num_channels<decltype(view)>::value;
    double before_diffusion[num_channels] = {0};
    for (auto& pixel : view)
    {
        for (std::size_t channel_index = 0; channel_index < num_channels; ++channel_index)
        {
            pixel[channel_index] = static_cast<gil::uint8_t>(dist());
            before_diffusion[channel_index] += pixel[channel_index];
        }
    }

    OutputImageType output(32, 32);
    auto output_view = gil::view(output);
    gil::default_anisotropic_diffusion(view, output_view, 10, 5);
    double after_diffusion[num_channels] = {0};
    for (const auto& pixel : output_view)
    {
        for (std::size_t channel_index = 0; channel_index < num_channels; ++channel_index)
        {
            after_diffusion[channel_index] += pixel[channel_index];
        }
    }

    for (std::ptrdiff_t channel_index = 0; channel_index < num_channels; ++channel_index)
    {
        const auto percentage =
            before_diffusion[channel_index] != 0
                ? std::abs(after_diffusion[channel_index] - before_diffusion[channel_index]) /
                      after_diffusion[channel_index] * 100.0
                : std::abs(after_diffusion[channel_index] - before_diffusion[channel_index]) /
                      after_diffusion[channel_index] * 100.0;
#ifdef BOOST_GIL_TEST_DEBUG
        std::cout << percentage << ' ';
#endif
        BOOST_TEST(percentage < 1);
    }
#ifdef BOOST_GIL_TEST_DEBUG
    std::cout << '\n';
#endif
}

template <typename PixelType>
void test_stencil_pixels(const gil::laplace_function::stencil_type<PixelType>& stencil,
                         const PixelType& reference)
{
    for (const auto& stencil_entry : stencil)
    {
        BOOST_TEST(stencil_entry == reference);
    }
}

template <typename PixelType>
void test_stencil(const gil::laplace_function::stencil_type<PixelType>& stencil,
                  const std::array<float, 8>& expected_values)
{
    for (std::size_t i = 0; i < 8; ++i)
    {
        PixelType expected_pixel;
        gil::static_fill(expected_pixel, expected_values[i]);
        BOOST_TEST(stencil[i] == expected_pixel);
    }
}

void laplace_functions_test()
{
    // sanity checks
    auto zero_rgb_pixel = []() {
        gil::rgb32f_pixel_t zero_pixel;
        gil::static_fill(zero_pixel, 0);
        return zero_pixel;
    }();
    auto zero_gray_pixel = []() {
        gil::gray32f_pixel_t zero_pixel;
        gil::static_fill(zero_pixel, 0);
        return zero_pixel;
    }();

    auto image_size = gil::point_t(16, 16);
    gil::rgb32f_image_t rgb_image(image_size, zero_rgb_pixel);
    gil::gray32f_image_t gray_image(image_size, zero_gray_pixel);
    auto rgb = gil::view(rgb_image);
    auto gray = gil::view(gray_image);

    auto s4way = gil::laplace_function::stencil_5points{};
    auto s8way = gil::laplace_function::stencil_9points_standard{};
    for (std::ptrdiff_t y = 1; y < image_size.y - 1; ++y)
    {
        for (std::ptrdiff_t x = 1; x < image_size.x - 1; ++x)
        {
            auto rgb_4way_stencil = s4way.compute_laplace(rgb, {x, y});
            auto gray_4way_stencil = s4way.compute_laplace(gray, {x, y});

            auto rgb_8way_stencil = s8way.compute_laplace(rgb, {x, y});
            auto gray_8way_stencil = s8way.compute_laplace(gray, {x, y});

            test_stencil_pixels(rgb_4way_stencil, zero_rgb_pixel);
            test_stencil_pixels(rgb_8way_stencil, zero_rgb_pixel);
            test_stencil_pixels(gray_4way_stencil, zero_gray_pixel);
            test_stencil_pixels(gray_8way_stencil, zero_gray_pixel);
        }
    }

    // a predefined case
    // 5  11 2
    // 17 25 58
    // 90 31 40

    using rgb_pixel = gil::rgb32f_pixel_t;
    rgb(1, 1) = rgb_pixel(5, 5, 5);
    rgb(2, 1) = rgb_pixel(11, 11, 11);
    rgb(3, 1) = rgb_pixel(2, 2, 2);
    rgb(1, 2) = rgb_pixel(17, 17, 17);
    rgb(2, 2) = rgb_pixel(25, 25, 25);
    rgb(3, 2) = rgb_pixel(58, 58, 58);
    rgb(1, 3) = rgb_pixel(90, 90, 90);
    rgb(2, 3) = rgb_pixel(31, 31, 31);
    rgb(3, 3) = rgb_pixel(40, 40, 40);

    using gray_pixel = gil::gray32f_pixel_t;
    gray(1, 1) = gray_pixel(5);
    gray(2, 1) = gray_pixel(11);
    gray(3, 1) = gray_pixel(2);
    gray(1, 2) = gray_pixel(17);
    gray(2, 2) = gray_pixel(25);
    gray(3, 2) = gray_pixel(58);
    gray(1, 3) = gray_pixel(90);
    gray(2, 3) = gray_pixel(31);
    gray(3, 3) = gray_pixel(40);

    // 4 way stencil should be
    // 0 -14 0
    // -8  <not computed for center> 33
    // 0 6 0

    auto test_point = gil::point_t(2, 2);
    std::array<float, 8> _4way_expected{0, -14, 0, 33, 0, 6, 0, -8};
    auto rgb_4way = s4way.compute_laplace(rgb, test_point);
    test_stencil(rgb_4way, _4way_expected);
    auto gray_4way = s4way.compute_laplace(gray, test_point);
    test_stencil(gray_4way, _4way_expected);

    // 8 way stencil should be
    // -20 -14 -23
    // -8 <not computed for center> 33
    // 65 6 15

    std::array<float, 8> _8way_expected{-20, -14, -23, 33, 15, 6, 65, -8};
    auto rgb_8way = s8way.compute_laplace(rgb, test_point);
    test_stencil(rgb_8way, _8way_expected);
    auto gray_8way = s8way.compute_laplace(gray, test_point);
    test_stencil(gray_8way, _8way_expected);

    // reduce result for 4 way should be (-14 - 8 + 6 + 33) * 0.25 = 4.25
    auto rgb_reduced_4way = s4way.reduce(rgb_4way);
    gil::static_for_each(rgb_reduced_4way,
                         [](gil::float32_t value) { BOOST_TEST(value == 4.25f); });
    auto gray_reduced_4way = s4way.reduce(gray_4way);
    gil::static_for_each(gray_reduced_4way,
                         [](gil::float32_t value) { BOOST_TEST(value == 4.25f); });

    // reduce result for 8 way should be ((-20-23+15+65)*0.5+(-14+33+6-8)) * 0.125 = (18.5+17) *
    // 0.125 = 4.4375
    auto rgb_reduced_8way = s8way.reduce(rgb_8way);
    gil::static_for_each(rgb_reduced_8way,
                         [](gil::float32_t value) { BOOST_TEST(value == 4.4375); });
    auto gray_reduced_8way = s8way.reduce(gray_8way);
    gil::static_for_each(gray_reduced_8way,
                         [](gil::float32_t value) { BOOST_TEST(value == 4.4375); });
}

int main()
{
    for (std::uint32_t seed = 0; seed < 100; ++seed)
    {
        heat_conservation_test<gil::gray8_image_t, gil::gray32f_image_t>(seed);
        heat_conservation_test<gil::rgb8_image_t, gil::rgb32f_image_t>(seed);
    }

    for (double kappa = 5; kappa <= 70; ++kappa)
    {
        diffusion_function_check<gil::rgb32f_pixel_t>(
            gil::conductivity::perona_malik_conductivity{kappa});
        diffusion_function_check<gil::rgb32f_pixel_t>(
            gil::conductivity::gaussian_conductivity{kappa});
        diffusion_function_check<gil::rgb32f_pixel_t>(
            gil::conductivity::wide_regions_conductivity{kappa});
        diffusion_function_check<gil::rgb32f_pixel_t>(
            gil::conductivity::more_wide_regions_conductivity{kappa});

        diffusion_function_check<gil::gray32f_pixel_t>(
            gil::conductivity::perona_malik_conductivity{kappa});
        diffusion_function_check<gil::gray32f_pixel_t>(
            gil::conductivity::gaussian_conductivity{kappa});
        diffusion_function_check<gil::gray32f_pixel_t>(
            gil::conductivity::wide_regions_conductivity{kappa});
        diffusion_function_check<gil::gray32f_pixel_t>(
            gil::conductivity::more_wide_regions_conductivity{kappa});
    }

    brightness_function_test();

    laplace_functions_test();

    return boost::report_errors();
}


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

// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

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

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

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

#ifndef BOOST_GEOMETRY_ARITHMETIC_ARITHMETIC_HPP
#define BOOST_GEOMETRY_ARITHMETIC_ARITHMETIC_HPP

#include <functional>

#include <boost/call_traits.hpp>
#include <boost/concept/requires.hpp>

#include <boost/geometry/core/coordinate_type.hpp>
#include <boost/geometry/geometries/concepts/point_concept.hpp>
#include <boost/geometry/util/algorithm.hpp>
#include <boost/geometry/util/select_coordinate_type.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template <typename Point>
struct param
{
    typedef typename boost::call_traits
        <
            typename coordinate_type<Point>::type
        >::param_type type;
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

/*!
    \brief Adds the same value to each coordinate of a point
    \ingroup arithmetic
    \details
    \tparam Point \tparam_point
    \param p point
    \param value value to add
 */
template <typename Point>
inline void add_value(Point& p, typename detail::param<Point>::type value)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point>) );

    detail::for_each_dimension<Point>([&](auto index)
    {
        set<index>(p, get<index>(p) + value);
    });
}

/*!
    \brief Adds a point to another
    \ingroup arithmetic
    \details The coordinates of the second point will be added to those of the first point.
             The second point is not modified.
    \tparam Point1 \tparam_point
    \tparam Point2 \tparam_point
    \param p1 first point
    \param p2 second point
 */
template <typename Point1, typename Point2>
inline void add_point(Point1& p1, Point2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point1>) );
    BOOST_CONCEPT_ASSERT( (concepts::ConstPoint<Point2>) );

    detail::for_each_dimension<Point1>([&](auto index)
    {
        using calc_t = typename select_coordinate_type<Point1, Point2>::type;
        set<index>(p1, calc_t(get<index>(p1)) + calc_t(get<index>(p2)));
    });
}

/*!
    \brief Subtracts the same value to each coordinate of a point
    \ingroup arithmetic
    \details
    \tparam Point \tparam_point
    \param p point
    \param value value to subtract
 */
template <typename Point>
inline void subtract_value(Point& p, typename detail::param<Point>::type value)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point>) );

    detail::for_each_dimension<Point>([&](auto index)
    {
        set<index>(p, get<index>(p) - value);
    });
}

/*!
    \brief Subtracts a point to another
    \ingroup arithmetic
    \details The coordinates of the second point will be subtracted to those of the first point.
             The second point is not modified.
    \tparam Point1 \tparam_point
    \tparam Point2 \tparam_point
    \param p1 first point
    \param p2 second point
 */
template <typename Point1, typename Point2>
inline void subtract_point(Point1& p1, Point2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point1>) );
    BOOST_CONCEPT_ASSERT( (concepts::ConstPoint<Point2>) );

    detail::for_each_dimension<Point1>([&](auto index)
    {
        using calc_t = typename select_coordinate_type<Point1, Point2>::type;
        set<index>(p1, calc_t(get<index>(p1)) - calc_t(get<index>(p2)));
    });
}

/*!
    \brief Multiplies each coordinate of a point by the same value
    \ingroup arithmetic
    \details
    \tparam Point \tparam_point
    \param p point
    \param value value to multiply by
 */
template <typename Point>
inline void multiply_value(Point& p, typename detail::param<Point>::type value)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point>) );

    detail::for_each_dimension<Point>([&](auto index)
    {
        set<index>(p, get<index>(p) * value);
    });
}

/*!
    \brief Multiplies a point by another
    \ingroup arithmetic
    \details The coordinates of the first point will be multiplied by those of the second point.
             The second point is not modified.
    \tparam Point1 \tparam_point
    \tparam Point2 \tparam_point
    \param p1 first point
    \param p2 second point
    \note This is *not* a dot, cross or wedge product. It is a mere field-by-field multiplication.
 */
template <typename Point1, typename Point2>
inline void multiply_point(Point1& p1, Point2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point1>) );
    BOOST_CONCEPT_ASSERT( (concepts::ConstPoint<Point2>) );

    detail::for_each_dimension<Point1>([&](auto index)
    {
        using calc_t = typename select_coordinate_type<Point1, Point2>::type;
        set<index>(p1, calc_t(get<index>(p1)) * calc_t(get<index>(p2)));
    });
}

/*!
    \brief Divides each coordinate of the same point by a value
    \ingroup arithmetic
    \details
    \tparam Point \tparam_point
    \param p point
    \param value value to divide by
 */
template <typename Point>
inline void divide_value(Point& p, typename detail::param<Point>::type value)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point>) );

    detail::for_each_dimension<Point>([&](auto index)
    {
        set<index>(p, get<index>(p) / value);
    });
}

/*!
    \brief Divides a point by another
    \ingroup arithmetic
    \details The coordinates of the first point will be divided by those of the second point.
             The second point is not modified.
    \tparam Point1 \tparam_point
    \tparam Point2 \tparam_point
    \param p1 first point
    \param p2 second point
 */
template <typename Point1, typename Point2>
inline void divide_point(Point1& p1, Point2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point1>) );
    BOOST_CONCEPT_ASSERT( (concepts::ConstPoint<Point2>) );

    detail::for_each_dimension<Point1>([&](auto index)
    {
        using calc_t = typename select_coordinate_type<Point1, Point2>::type;
        set<index>(p1, calc_t(get<index>(p1)) / calc_t(get<index>(p2)));
    });
}

/*!
    \brief Assign each coordinate of a point the same value
    \ingroup arithmetic
    \details
    \tparam Point \tparam_point
    \param p point
    \param value value to assign
 */
template <typename Point>
inline void assign_value(Point& p, typename detail::param<Point>::type value)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point>) );

    detail::for_each_dimension<Point>([&](auto index)
    {
        set<index>(p, value);
    });
}

/*!
    \brief Assign a point with another
    \ingroup arithmetic
    \details The coordinates of the first point will be assigned those of the second point.
             The second point is not modified.
    \tparam Point1 \tparam_point
    \tparam Point2 \tparam_point
    \param p1 first point
    \param p2 second point
 */
template <typename Point1, typename Point2>
inline void assign_point(Point1& p1, Point2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concepts::Point<Point1>) );
    BOOST_CONCEPT_ASSERT( (concepts::ConstPoint<Point2>) );

    detail::for_each_dimension<Point1>([&](auto index)
    {
        set<index>(p1, get<index>(p2));
    });
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ARITHMETIC_ARITHMETIC_HPP


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2011 - 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.
 *
 * ---------------------------------------------------------------------
 *
 * Author: Katharina Kormann, Martin Kronbichler, Uppsala University, 2011-2012
 */

// deal.II库中的必要文件。

#include <deal.II/base/logstream.h>
#include <deal.II/base/utilities.h>
#include <deal.II/base/function.h>
#include <deal.II/base/conditional_ostream.h>
#include <deal.II/base/timer.h>
#include <deal.II/lac/vector.h>
#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>
#include <deal.II/dofs/dof_tools.h>
#include <deal.II/dofs/dof_handler.h>
#include <deal.II/lac/affine_constraints.h>
#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/data_out.h>
#include <deal.II/distributed/tria.h>

// 这包括用于有效实现无矩阵方法的数据结构。

#include <deal.II/lac/la_parallel_vector.h>
#include <deal.II/matrix_free/matrix_free.h>
#include <deal.II/matrix_free/fe_evaluation.h>

#include <fstream>
#include <iostream>
#include <iomanip>

namespace Step48
{
  using namespace dealii;

// 我们首先定义了两个全局变量，以便在一个地方收集所有需要改变的参数。一个是尺寸，一个是有限元度。维度在主函数中是作为实际类的模板参数使用的（就像所有其他deal.II程序一样），而有限元的度数则更为关键，因为它是作为模板参数传递给Sine-Gordon算子的实现。因此，它需要成为一个编译时常数。

  const unsigned int dimension = 2;
  const unsigned int fe_degree = 4;
// @sect3{SineGordonOperation}

//  SineGordonOperation 类实现了每个时间步骤中需要的基于单元的操作。这个非线性操作可以在 MatrixFree 类的基础上直接实现，与线性操作在这个实现的有限元算子应用中的处理方式相同。我们对该类应用了两个模板参数，一个是尺寸，一个是有限元的程度。这与deal.II中的其他函数不同，其中只有维度是模板参数。这对于为 @p FEEvaluation 中的内循环提供关于循环长度等的信息是必要的，这对于效率是至关重要的。另一方面，这使得将度数作为一个运行时参数来实现更具挑战性。

  template <int dim, int fe_degree>
  class SineGordonOperation
  {
  public:
    SineGordonOperation(const MatrixFree<dim, double> &data_in,
                        const double                   time_step);

    void apply(LinearAlgebra::distributed::Vector<double> &dst,
               const std::vector<LinearAlgebra::distributed::Vector<double> *>
                 &src) const;

  private:
    const MatrixFree<dim, double> &            data;
    const VectorizedArray<double>              delta_t_sqr;
    LinearAlgebra::distributed::Vector<double> inv_mass_matrix;

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

//  @sect4{SineGordonOperation::SineGordonOperation}

// 这是SineGordonOperation类的构造函数。它接收一个对MatrixFree的引用，该引用持有问题信息和时间步长作为输入参数。初始化程序设置了质量矩阵。由于我们使用Gauss-Lobatto元素，质量矩阵是一个对角矩阵，可以存储为一个矢量。利用FEEvaluation提供的数据结构，质量矩阵对角线的计算很容易实现。只要在所有的单元格批次上循环，即由于SIMD矢量化的单元格集合，并通过使用 integrate 函数与 @p true 参数在数值的槽上对所有正交点上常一的函数进行积分。最后，我们将对角线条目进行反转，以便在每个时间步长中直接获得反质量矩阵。

  template <int dim, int fe_degree>
  SineGordonOperation<dim, fe_degree>::SineGordonOperation(
    const MatrixFree<dim, double> &data_in,
    const double                   time_step)
    : data(data_in)
    , delta_t_sqr(make_vectorized_array(time_step * time_step))
  {
    data.initialize_dof_vector(inv_mass_matrix);

    FEEvaluation<dim, fe_degree> fe_eval(data);
    const unsigned int           n_q_points = fe_eval.n_q_points;

    for (unsigned int cell = 0; cell < data.n_cell_batches(); ++cell)
      {
        fe_eval.reinit(cell);
        for (unsigned int q = 0; q < n_q_points; ++q)
          fe_eval.submit_value(make_vectorized_array(1.), q);
        fe_eval.integrate(EvaluationFlags::values);
        fe_eval.distribute_local_to_global(inv_mass_matrix);
      }

    inv_mass_matrix.compress(VectorOperation::add);
    for (unsigned int k = 0; k < inv_mass_matrix.locally_owned_size(); ++k)
      if (inv_mass_matrix.local_element(k) > 1e-15)
        inv_mass_matrix.local_element(k) =
          1. / inv_mass_matrix.local_element(k);
      else
        inv_mass_matrix.local_element(k) = 1;
  }

//  @sect4{SineGordonOperation::local_apply}

// 这个算子实现了程序的核心操作，即对正弦-戈登问题的非线性算子进行单元范围的积分。其实现是基于  step-37  中的FEEvaluation类。由于Gauss-Lobatto元素的特殊结构，某些操作变得更加简单，特别是正交点上的形状函数值的评估，这只是单元自由度值的注入。MatrixFree类在初始化时检测了正交点上有限元的可能结构，然后由FEEvaluation自动用于选择最合适的数值核。

// 我们要为时间步进例程评估的非线性函数包括当前时间的函数值 @p current 以及前一个时间步进的值 @p old. 这两个值都在源向量集合 @p src, 中传递给运算器，该集合只是一个指向实际解向量的 <tt>std::vector</tt> 指针。这种将多个源向量收集到一起的结构是必要的，因为 @p MatrixFree 中的单元格循环正好需要一个源向量和一个目的向量，即使我们碰巧使用了很多向量，比如本例中的两个。请注意，单元格循环接受任何有效的输入和输出类，这不仅包括向量，还包括一般的数据类型。 然而，只有在遇到收集这些向量的 LinearAlgebra::distributed::Vector<Number> 或 <tt>std::vector</tt> 时，它才会在循环的开始和结束时调用由于MPI而交换幽灵数据的函数。在单元格的循环中，我们首先要读入与本地值相关的向量中的值。 然后，我们评估当前求解向量的值和梯度以及正交点的旧向量的值。接下来，我们在正交点的循环中结合方案中的条款。最后，我们将结果与测试函数进行积分，并将结果累积到全局解向量 @p  dst。

  template <int dim, int fe_degree>
  void SineGordonOperation<dim, fe_degree>::local_apply(
    const MatrixFree<dim> &                                          data,
    LinearAlgebra::distributed::Vector<double> &                     dst,
    const std::vector<LinearAlgebra::distributed::Vector<double> *> &src,
    const std::pair<unsigned int, unsigned int> &cell_range) const
  {
    AssertDimension(src.size(), 2);
    FEEvaluation<dim, fe_degree> current(data), old(data);
    for (unsigned int cell = cell_range.first; cell < cell_range.second; ++cell)
      {
        current.reinit(cell);
        old.reinit(cell);

        current.read_dof_values(*src[0]);
        old.read_dof_values(*src[1]);

        current.evaluate(EvaluationFlags::values | EvaluationFlags::gradients);
        old.evaluate(EvaluationFlags::values);

        for (unsigned int q = 0; q < current.n_q_points; ++q)
          {
            const VectorizedArray<double> current_value = current.get_value(q);
            const VectorizedArray<double> old_value     = old.get_value(q);

            current.submit_value(2. * current_value - old_value -
                                   delta_t_sqr * std::sin(current_value),
                                 q);
            current.submit_gradient(-delta_t_sqr * current.get_gradient(q), q);
          }

        current.integrate(EvaluationFlags::values | EvaluationFlags::gradients);
        current.distribute_local_to_global(dst);
      }
  }

//  @sect4{SineGordonOperation::apply}

// 该函数根据单元本地策略执行时间步进例程。请注意，在添加当前时间步长的积分贡献之前，我们需要将目标向量设置为零（通过 FEEvaluation::distribute_local_to_global() 调用）。在本教程中，我们通过传递给 MatrixFree::cell_loop. 的第五个`true`参数让单元格循环进行归零操作。 循环可以将归零操作安排在更接近对支持的向量项的操作，从而可能提高数据的定位性（首先被归零的向量项后来在`distribute_local_to_global()`调用中重新使用）。单元循环的结构是在单元有限元运算器类中实现的。在每个单元上，它应用定义为类 local_apply() 方法的例程  SineGordonOperation, i.e., this  。我们也可以提供一个具有相同签名的、不属于类的函数。最后，积分的结果要乘以质量矩阵的逆值。

  template <int dim, int fe_degree>
  void SineGordonOperation<dim, fe_degree>::apply(
    LinearAlgebra::distributed::Vector<double> &                     dst,
    const std::vector<LinearAlgebra::distributed::Vector<double> *> &src) const
  {
    data.cell_loop(
      &SineGordonOperation<dim, fe_degree>::local_apply, this, dst, src, true);
    dst.scale(inv_mass_matrix);
  }

//  @sect3{Equation data}

// 我们定义了一个随时间变化的函数，作为初始值使用。通过改变起始时间，可以得到不同的解决方案。这个函数取自 step-25 ，将代表一维中所有时间的分析解，但在这里只是用来设置一些感兴趣的起始解。在  step-25  中给出了可以测试该程序收敛性的更详细的选择。

  template <int dim>
  class InitialCondition : public Function<dim>
  {
  public:
    InitialCondition(const unsigned int n_components = 1,
                     const double       time         = 0.)
      : Function<dim>(n_components, time)
    {}
    virtual double value(const Point<dim> &p,
                         const unsigned int /*component*/) const override
    {
      double t = this->get_time();

      const double m  = 0.5;
      const double c1 = 0.;
      const double c2 = 0.;
      const double factor =
        (m / std::sqrt(1. - m * m) * std::sin(std::sqrt(1. - m * m) * t + c2));
      double result = 1.;
      for (unsigned int d = 0; d < dim; ++d)
        result *= -4. * std::atan(factor / std::cosh(m * p[d] + c1));
      return result;
    }
  };

//  @sect3{SineGordonProblem class}

// 这是在  step-25  中的类基础上的主类。 然而，我们用MatrixFree类代替了SparseMatrix<double>类来存储几何数据。另外，我们在这个例子中使用了一个分布式三角形。

  template <int dim>
  class SineGordonProblem
  {
  public:
    SineGordonProblem();
    void run();

  private:
    ConditionalOStream pcout;

    void make_grid_and_dofs();
    void output_results(const unsigned int timestep_number);

#ifdef DEAL_II_WITH_P4EST
    parallel::distributed::Triangulation<dim> triangulation;
#else
    Triangulation<dim> triangulation;
#endif
    FE_Q<dim>       fe;
    DoFHandler<dim> dof_handler;

    MappingQ1<dim> mapping;

    AffineConstraints<double> constraints;
    IndexSet                  locally_relevant_dofs;

    MatrixFree<dim, double> matrix_free_data;

    LinearAlgebra::distributed::Vector<double> solution, old_solution,
      old_old_solution;

    const unsigned int n_global_refinements;
    double             time, time_step;
    const double       final_time;
    const double       cfl_number;
    const unsigned int output_timestep_skip;
  };
// @sect4{SineGordonProblem::SineGordonProblem}

// 这是SineGordonProblem类的构造函数。时间间隔和时间步长在此定义。此外，我们使用在程序顶部定义的有限元的程度来初始化一个基于Gauss-Lobatto支持点的FE_Q有限元。这些点很方便，因为与同阶的QGauss-Lobatto正交规则相结合，它们可以得到一个对角线质量矩阵，而不会太影响精度（注意，虽然积分是不精确的），也可以参见介绍中的讨论。请注意，FE_Q默认选择Gauss-Lobatto结点，因为它们相对于等距结点有更好的条件。为了使事情更加明确，我们还是要说明节点的选择。

  template <int dim>
  SineGordonProblem<dim>::SineGordonProblem()
    : pcout(std::cout, Utilities::MPI::this_mpi_process(MPI_COMM_WORLD) == 0)
    ,
#ifdef DEAL_II_WITH_P4EST
    triangulation(MPI_COMM_WORLD)
    ,
#endif
    fe(QGaussLobatto<1>(fe_degree + 1))
    , dof_handler(triangulation)
    , n_global_refinements(10 - 2 * dim)
    , time(-10)
    , time_step(10.)
    , final_time(10.)
    , cfl_number(.1 / fe_degree)
    , output_timestep_skip(200)
  {}
// @sect4{SineGordonProblem::make_grid_and_dofs}

// 和 step-25 一样，这个函数在 dim 维度上设置了一个范围为 $[-15,15]$ 的立方体网格。我们在域的中心更多的细化网格，因为解决方案都集中在那里。我们首先细化所有中心在半径为11的单元，然后再细化一次半径为6的单元。 这种简单的临时细化可以通过在时间步进过程中使用误差估计器来适应网格，并使用 parallel::distributed::SolutionTransfer 将解决方案转移到新的网格中来完成。

  template <int dim>
  void SineGordonProblem<dim>::make_grid_and_dofs()
  {
    GridGenerator::hyper_cube(triangulation, -15, 15);
    triangulation.refine_global(n_global_refinements);
    {
      typename Triangulation<dim>::active_cell_iterator
        cell     = triangulation.begin_active(),
        end_cell = triangulation.end();
      for (; cell != end_cell; ++cell)
        if (cell->is_locally_owned())
          if (cell->center().norm() < 11)
            cell->set_refine_flag();
      triangulation.execute_coarsening_and_refinement();

      cell     = triangulation.begin_active();
      end_cell = triangulation.end();
      for (; cell != end_cell; ++cell)
        if (cell->is_locally_owned())
          if (cell->center().norm() < 6)
            cell->set_refine_flag();
      triangulation.execute_coarsening_and_refinement();
    }

    pcout << "   Number of global active cells: "
#ifdef DEAL_II_WITH_P4EST
          << triangulation.n_global_active_cells()
#else
          << triangulation.n_active_cells()
#endif
          << std::endl;

    dof_handler.distribute_dofs(fe);

    pcout << "   Number of degrees of freedom: " << dof_handler.n_dofs()
          << std::endl;

// 我们生成悬挂节点约束，以确保解决方案的连续性。如同在 step-40 中，我们需要为约束矩阵配备本地相关自由度的IndexSet，以避免它在大问题中消耗过多的内存。接下来，问题的MatrixFree对象被设置。请注意，我们为共享内存并行化指定了一个特定的方案（因此，人们会使用多线程来实现节点内的并行化，而不是MPI；我们在这里选择了标准选项&mdash；如果我们想在程序中有一个以上的TBB线程的情况下禁用共享内存并行化，我们会选择 MatrixFree::AdditionalData::TasksParallelScheme::none).  另外请注意，我们没有使用默认的QGauss正交参数，而是提供一个QGaussLobatto正交公式来实现期望的行为。最后，三个求解向量被初始化。MatrixFree期望有一个特定的鬼魂索引布局（因为它在MPI本地数字中处理索引访问，需要在向量和MatrixFree之间匹配），所以我们只是要求它初始化向量，以确保鬼魂交换得到正确处理。

    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);
    constraints.close();

    typename MatrixFree<dim>::AdditionalData additional_data;
    additional_data.tasks_parallel_scheme =
      MatrixFree<dim>::AdditionalData::TasksParallelScheme::partition_partition;

    matrix_free_data.reinit(mapping,
                            dof_handler,
                            constraints,
                            QGaussLobatto<1>(fe_degree + 1),
                            additional_data);

    matrix_free_data.initialize_dof_vector(solution);
    old_solution.reinit(solution);
    old_old_solution.reinit(solution);
  }

//  @sect4{SineGordonProblem::output_results}

// 这个函数打印出解的规范，并将解的向量写到一个文件中。法线是标准的（除了我们需要累积所有处理器上的法线，用于并行网格，我们通过  VectorTools::compute_global_error()  函数来做），第二项类似于我们在  step-40  或  step-37  . 请注意，我们可以使用与计算过程中使用的相同的向量进行输出。无矩阵框架中的向量总是提供所有本地拥有的单元的全部信息（这也是本地评估中需要的），包括这些单元上的鬼向量条目。这是 VectorTools::integrate_difference() 函数以及DataOut中唯一需要的数据。这时唯一要做的就是确保在我们从矢量中读取数据之前更新其鬼魂值，并在完成后重置鬼魂值。这是一个只存在于 LinearAlgebra::distributed::Vector 类中的特性。另一方面，带有PETSc和Trilinos的分布式向量需要被复制到包括ghost值的特殊向量（见 step-40 中的相关章节 ）。如果我们还想访问幽灵单元上的所有自由度（例如，当计算使用单元边界上的解的跳跃的误差估计时），我们将需要更多的信息，并创建一个初始化了本地相关自由度的向量，就像在  step-40  中一样。还请注意，我们需要为输出分配约束条件

// --它们在计算过程中不被填充（相反，它们在无矩阵的方法中被实时插值  FEEvaluation::read_dof_values()).
  template <int dim>
  void
  SineGordonProblem<dim>::output_results(const unsigned int timestep_number)
  {
    constraints.distribute(solution);

    Vector<float> norm_per_cell(triangulation.n_active_cells());
    solution.update_ghost_values();
    VectorTools::integrate_difference(mapping,
                                      dof_handler,
                                      solution,
                                      Functions::ZeroFunction<dim>(),
                                      norm_per_cell,
                                      QGauss<dim>(fe_degree + 1),
                                      VectorTools::L2_norm);
    const double solution_norm =
      VectorTools::compute_global_error(triangulation,
                                        norm_per_cell,
                                        VectorTools::L2_norm);

    pcout << "   Time:" << std::setw(8) << std::setprecision(3) << time
          << ", solution norm: " << std::setprecision(5) << std::setw(7)
          << solution_norm << std::endl;

    DataOut<dim> data_out;

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

    data_out.write_vtu_with_pvtu_record(
      "./", "solution", timestep_number, MPI_COMM_WORLD, 3);

    solution.zero_out_ghost_values();
  }
// @sect4{SineGordonProblem::run}

// 这个函数被主函数调用，并步入类的子程序中。

// 在打印了一些关于并行设置的信息后，第一个动作是设置网格和单元运算器。然后，根据构造函数中给出的CFL编号和最细的网格尺寸计算出时间步长。最细的网格尺寸计算为三角形中最后一个单元的直径，也就是网格中最细层次上的最后一个单元。这只适用于一个层次上的所有元素都具有相同尺寸的网格，否则就需要对所有单元进行循环。请注意，我们需要查询所有处理器的最细单元，因为不是所有的处理器都可能持有网格处于最细级别的区域。然后，我们重新调整一下时间步长，以准确地达到最后的时间。

  template <int dim>
  void SineGordonProblem<dim>::run()
  {
    {
      pcout << "Number of MPI ranks:            "
            << Utilities::MPI::n_mpi_processes(MPI_COMM_WORLD) << std::endl;
      pcout << "Number of threads on each rank: "
            << MultithreadInfo::n_threads() << std::endl;
      const unsigned int n_vect_doubles = VectorizedArray<double>::size();
      const unsigned int n_vect_bits    = 8 * sizeof(double) * n_vect_doubles;
      pcout << "Vectorization over " << n_vect_doubles
            << " doubles = " << n_vect_bits << " bits ("
            << Utilities::System::get_current_vectorization_level() << ")"
            << std::endl
            << std::endl;
    }
    make_grid_and_dofs();

    const double local_min_cell_diameter =
      triangulation.last()->diameter() / std::sqrt(dim);
    const double global_min_cell_diameter =
      -Utilities::MPI::max(-local_min_cell_diameter, MPI_COMM_WORLD);
    time_step = cfl_number * global_min_cell_diameter;
    time_step = (final_time - time) / (int((final_time - time) / time_step));
    pcout << "   Time step size: " << time_step
          << ", finest cell: " << global_min_cell_diameter << std::endl
          << std::endl;

// 接下来是初始值的设置。由于我们有一个两步的时间步进方法，我们还需要一个在时间步进时的解的值。为了得到准确的结果，需要根据初始时间的解的时间导数来计算，但是在这里我们忽略了这个困难，只是将其设置为该人工时间的初始值函数。

// 然后，我们继续将初始状态写入文件，并将两个初始解收集到 <tt>std::vector</tt> 的指针中，这些指针随后被 SineGordonOperation::apply() 函数消耗。接下来，根据文件顶部指定的有限元程度，建立一个  SineGordonOperation class  的实例。

    VectorTools::interpolate(mapping,
                             dof_handler,
                             InitialCondition<dim>(1, time),
                             solution);
    VectorTools::interpolate(mapping,
                             dof_handler,
                             InitialCondition<dim>(1, time - time_step),
                             old_solution);
    output_results(0);

    std::vector<LinearAlgebra::distributed::Vector<double> *>
      previous_solutions({&old_solution, &old_old_solution});

    SineGordonOperation<dim, fe_degree> sine_gordon_op(matrix_free_data,
                                                       time_step);

// 现在在时间步骤上循环。在每个迭代中，我们将解的向量移动一个，并调用`正弦戈登运算器'类的`应用'函数。然后，我们将解决方案写到一个文件中。我们对所需的计算时间和创建输出所需的时间进行计时，并在时间步长结束后报告这些数字。

// 注意这个交换是如何实现的。我们只是在两个向量上调用了交换方法，只交换了一些指针，而不需要复制数据，这在显式时间步进方法中是比较昂贵的操作。让我们来看看发生了什么。首先，我们交换 old_solution with old_old_solution ，这意味着 old_old_solution 得到 old_solution ，这就是我们所期望的。同样，在下一步中， old_solution gets the content from solution 也是如此。在这之后， solution 持有 old_old_solution ，但这将在这一步被覆盖。

    unsigned int timestep_number = 1;

    Timer  timer;
    double wtime       = 0;
    double output_time = 0;
    for (time += time_step; time <= final_time;
         time += time_step, ++timestep_number)
      {
        timer.restart();
        old_old_solution.swap(old_solution);
        old_solution.swap(solution);
        sine_gordon_op.apply(solution, previous_solutions);
        wtime += timer.wall_time();

        timer.restart();
        if (timestep_number % output_timestep_skip == 0)
          output_results(timestep_number / output_timestep_skip);

        output_time += timer.wall_time();
      }
    timer.restart();
    output_results(timestep_number / output_timestep_skip + 1);
    output_time += timer.wall_time();

    pcout << std::endl
          << "   Performed " << timestep_number << " time steps." << std::endl;

    pcout << "   Average wallclock time per time step: "
          << wtime / timestep_number << "s" << std::endl;

    pcout << "   Spent " << output_time << "s on output and " << wtime
          << "s on computations." << std::endl;
  }
} // namespace Step48

//  @sect3{The main function}

// 与 step-40 中一样，我们在程序开始时初始化MPI。由于我们一般会将MPI并行化与线程混合在一起，所以我们也将MPI_InitFinalize中控制线程数量的第三个参数设置为无效数字，这意味着TBB库会自动选择线程的数量，通常为系统中可用的内核数量。作为一种选择，如果你想设置一个特定的线程数（例如，当需要只使用MPI时），你也可以手动设置这个数字。

int main(int argc, char **argv)
{
  using namespace Step48;
  using namespace dealii;

  Utilities::MPI::MPI_InitFinalize mpi_initialization(
    argc, argv, numbers::invalid_unsigned_int);

  try
    {
      SineGordonProblem<dimension> sg_problem;
      sg_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;
}