/**
 * grove: PreemptiveRansac.cpp
 * Copyright (c) Torr Vision Group, University of Oxford, 2017. All rights reserved.
 */

#include "ransac/interface/PreemptiveRansac.h"
using namespace tvgutil;

#ifdef WITH_ALGLIB
#include <alglib/optimization.h>
#endif

#include <boost/lexical_cast.hpp>
#include <boost/timer/timer.hpp>

#include <Eigen/Dense>

#ifdef WITH_OPENMP
#include <omp.h>
#endif

#include <orx/base/MemoryBlockFactory.h>
#include <orx/geometry/GeometryUtil.h>
using namespace orx;

//#################### MACROS ####################

// Enable/disable the print-out of more detailed timings (very verbose, so disabled by default).
//#define ENABLE_TIMERS

namespace grove {

//#################### CONSTRUCTORS ####################

PreemptiveRansac::PreemptiveRansac(const SettingsContainer_CPtr& settings, const std::string& settingsNamespace)
: m_timerCandidateGeneration("Candidate Generation"),
  m_timerFirstComputeEnergy("First Energy Computation"),
  m_timerFirstTrim("First Trim"),
  m_timerTotal("P-RANSAC Total"),
  m_poseCandidatesAfterCull(0),
  m_settings(settings)
{
  // By default, we set all parameters as in SCoRe forests.
  m_checkMinDistanceBetweenSampledModes = m_settings->get_first_value<bool>(settingsNamespace + "checkMinDistanceBetweenSampledModes", true);                   // Whether or not to force sampled modes to have a minimum distance between them.
  m_checkRigidTransformationConstraint = m_settings->get_first_value<bool>(settingsNamespace + "checkRigidTransformationConstraint", true);                     // Setting this to false speeds things up a lot, at the expense of quality.
  m_maxCandidateGenerationIterations = m_settings->get_first_value<uint32_t>(settingsNamespace + "maxCandidateGenerationIterations", 6000);                     // The maximum number of times we sample three pixel-mode pairs in the attempt to generate a pose candidate.
  m_maxPoseCandidates = m_settings->get_first_value<uint32_t>(settingsNamespace + "maxPoseCandidates", 1024);                                                   // The number of initial pose candidates.
  m_maxPoseCandidatesAfterCull = m_settings->get_first_value<uint32_t>(settingsNamespace + "maxPoseCandidatesAfterCull", 64);                                   // Aggressively cull hypotheses to this number.
  m_maxTranslationErrorForCorrectPose = m_settings->get_first_value<float>(settingsNamespace + "maxTranslationErrorForCorrectPose", 0.05f);                     // In m.
  m_minSquaredDistanceBetweenSampledModes = m_settings->get_first_value<float>(settingsNamespace + "minSquaredDistanceBetweenSampledModes", 0.3f * 0.3f);       // In m.

  // Optimisation parameters defaulted as in Valentin's paper.
  m_poseOptimisationEnergyThreshold = m_settings->get_first_value<double>(settingsNamespace + "poseOptimisationEnergyThreshold", 0.0);                          // Part of the termination condition for the pose optimisation.
  m_poseOptimisationGradientThreshold = m_settings->get_first_value<double>(settingsNamespace + "poseOptimisationGradientThreshold", 1e-6);                     // Part of the termination condition for the pose optimisation.
  m_poseOptimisationInlierThreshold = m_settings->get_first_value<float>(settingsNamespace + "poseOptimizationInlierThreshold", 0.2f);                          // In m.
  m_poseOptimisationMaxIterations = m_settings->get_first_value<uint32_t>(settingsNamespace + "poseOptimisationMaxIterations", 100);                            // Maximum number of LM iterations.
  m_poseOptimisationStepThreshold = m_settings->get_first_value<double>(settingsNamespace + "poseOptimisationStepThreshold", 0.0);                              // Part of the termination condition for the pose optimisation.
  m_poseUpdate = m_settings->get_first_value<bool>(settingsNamespace + "poseUpdate", true);                                                                     // Whether or not to optimise the poses with LM.
  m_printTimers = m_settings->get_first_value<bool>(settingsNamespace + "printTimers", false);                                                                  // Whether or not to print the timers for each phase.
  m_ransacInliersPerIteration = m_settings->get_first_value<uint32_t>(settingsNamespace + "ransacInliersPerIteration", 500);                                    // The number of inliers sampled in each P-RANSAC iteration.
  m_useAllModesPerLeafInPoseHypothesisGeneration = m_settings->get_first_value<bool>(settingsNamespace + "useAllModesPerLeafInPoseHypothesisGeneration", true); // If false, use the first mode only (representing the largest cluster).
  m_usePredictionCovarianceForPoseOptimization = m_settings->get_first_value<bool>(settingsNamespace + "usePredictionCovarianceForPoseOptimization", true);     // If false, use L2.

  // Each RANSAC iteration after the initial cull adds m_ransacInliersPerIteration inliers to the set, so we allocate enough space for all of them up-front.
  m_nbMaxInliers = m_ransacInliersPerIteration * static_cast<uint32_t>(std::ceil(log2(m_maxPoseCandidatesAfterCull)));

  // We can only update the candidate poses if ALGLIB is available. Check and throw an exception otherwise.
#ifndef WITH_ALGLIB
  if(m_poseUpdate)
  {
    throw std::runtime_error("Error: Enabling poseUpdate requires ALGLIB. Reconfigure in CMake with the WITH_ALGLIB option set to ON.");
  }
#endif

  // Allocate memory.
  const MemoryBlockFactory& mbf = MemoryBlockFactory::instance();
  m_inlierRasterIndicesBlock = mbf.make_block<int>(m_nbMaxInliers);
  m_inliersMaskImage = mbf.make_image<int>();
  m_poseCandidates = mbf.make_block<PoseCandidate>(m_maxPoseCandidates);

  const uint32_t poseOptimisationBufferSize = static_cast<uint32_t>(m_nbMaxInliers * m_maxPoseCandidates);
  m_poseOptimisationCameraPoints = mbf.make_block<Vector4f>(poseOptimisationBufferSize);
  m_poseOptimisationPredictedModes = mbf.make_block<Keypoint3DColourCluster>(poseOptimisationBufferSize);

#ifdef ENABLE_TIMERS
  // Force the average timers to on as well if we want verbose printing.
  m_printTimers = true;
#endif

  // Set up the remaining timers.
  const int maxRansacIterations = static_cast<int>(ceil(log2(m_maxPoseCandidatesAfterCull)));
  for(int i = 1; i <= maxRansacIterations; ++i)
  {
    m_timerInlierSampling.push_back(AverageTimer("Inlier Sampling " + boost::lexical_cast<std::string>(i)));
    m_timerPrepareOptimisation.push_back(AverageTimer("Prepare Optimisation " + boost::lexical_cast<std::string>(i)));
    m_timerOptimisation.push_back(AverageTimer("Optimisation " + boost::lexical_cast<std::string>(i)));
    m_timerComputeEnergy.push_back(AverageTimer("Energy Computation " + boost::lexical_cast<std::string>(i)));
  }
}

//#################### DESTRUCTOR ####################

PreemptiveRansac::~PreemptiveRansac()
{
  if(m_printTimers)
  {
    print_timer(m_timerTotal);
    print_timer(m_timerCandidateGeneration);
    print_timer(m_timerFirstTrim);
    print_timer(m_timerFirstComputeEnergy);

    for(size_t i = 0; i < m_timerInlierSampling.size(); ++i)
    {
      print_timer(m_timerInlierSampling[i]);
      print_timer(m_timerComputeEnergy[i]);
      print_timer(m_timerPrepareOptimisation[i]);
      print_timer(m_timerOptimisation[i]);
    }
  }
}

//#################### PROTECTED ABSTRACT MEMBER FUNCTIONS ####################

void PreemptiveRansac::update_candidate_poses()
{
  // Note: This is a default implementation of the abstract function - it is intended to be called / overridden by derived classes.
  const int nbPoseCandidates = static_cast<int>(m_poseCandidates->dataSize);

#ifdef WITH_OPENMP
  #pragma omp parallel for schedule(dynamic)
#endif
  for(int i = 0; i < nbPoseCandidates; ++i)
  {
    update_candidate_pose(i);
  }
}

//#################### PUBLIC MEMBER FUNCTIONS ####################

boost::optional<PoseCandidate> PreemptiveRansac::estimate_pose(const Keypoint3DColourImage_CPtr& keypointsImage, const ScorePredictionsImage_CPtr& predictionsImage)
{
  /*
  Note: In this function and in the virtual functions of the CPU and CUDA subclasses, we directly access and overwrite
        the dataSize member of several MemoryBlock variables instead of keeping a separate "valid size" variable.
        This is done on purpose since the Update*From* functions of MemoryBlock/Images only move the first dataSize
        elements of the block. By changing the number to the actual number of elements to move we can gain a slight
        speed-up of the system.
  */

  m_timerTotal.start_sync();

  // Copy the keypoints and predictions images into member variables to avoid explicitly passing them to every function.
  m_keypointsImage = keypointsImage;
  m_predictionsImage = predictionsImage;

  // Step 1: Generate the initial pose candidates.
  {
#ifdef ENABLE_TIMERS
    boost::timer::auto_cpu_timer t(6, "generating initial candidates: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif
    m_timerCandidateGeneration.start_nosync(); // No need to synchronize the GPU again.
    generate_pose_candidates();
    m_timerCandidateGeneration.stop_sync();
  }

  // Reset the number of inliers ready for the new pose estimation.
  {
    const bool resetMask = false;
    reset_inliers(resetMask);
  }

  // Step 2: If necessary, aggressively cull the initial candidates to reduce the computational cost of the remaining steps.
  if(m_poseCandidates->dataSize > m_maxPoseCandidatesAfterCull)
  {
    m_timerFirstTrim.start_sync();
#ifdef ENABLE_TIMERS
    boost::timer::auto_cpu_timer t(6, "first trim: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif

    // Step 2(a): First, sample a set of points from the input data to use to evaluate the quality of each candidate.
    {
#ifdef ENABLE_TIMERS
      boost::timer::auto_cpu_timer t(6, "sample inliers: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif
      const bool useMask = false; // no mask for the first pass
      sample_inliers(useMask);
    }

    // Step 2(b): Then, evaluate the candidates and sort them in non-increasing order of quality.
    {
#ifdef ENABLE_TIMERS
      boost::timer::auto_cpu_timer t(6, "compute energies and sort: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif
      m_timerFirstComputeEnergy.start_sync();
      compute_energies_and_sort();
      m_timerFirstComputeEnergy.stop_sync();
    }

    // Step 2(c): Finally, trim the number of candidates down to the maximum number allowed. Since we previously sorted
    //            the candidates by quality, this has the effect of keeping only the best ones.
    m_poseCandidates->dataSize = m_maxPoseCandidatesAfterCull;

    m_timerFirstTrim.stop_nosync(); // No need to synchronize the GPU again.
  }

  m_poseCandidatesAfterCull = static_cast<uint32_t>(m_poseCandidates->dataSize);

#ifdef ENABLE_TIMERS
  boost::timer::auto_cpu_timer t(6, "ransac: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif

  // Step 3: Reset the inlier mask and clear any inliers that might have been selected in a previous invocation of the method.
  {
    const bool resetMask = true;
    reset_inliers(resetMask);
  }

  // Step 4: Run preemptive RANSAC until only a single candidate remains.
  int iteration = 0;
  while(m_poseCandidates->dataSize > 1)
  {
#ifdef ENABLE_TIMERS
    boost::timer::auto_cpu_timer t(6, "ransac iteration: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif

#if 0
    std::cout << candidates.size() << " camera(s) remaining" << std::endl;
#endif

    // Step 4(a): Sample a set of keypoints from the input image. Record that thay have been selected in the mask image, to avoid selecting them again.
    m_timerInlierSampling[iteration].start_sync();
    const bool useMask = true;
    sample_inliers(useMask);
    m_timerInlierSampling[iteration].stop_sync();

    // Step 4(b): If pose update is enabled, optimise all remaining candidates, taking into account the newly selected inliers.
    if(m_poseUpdate)
    {
      m_timerPrepareOptimisation[iteration].start_nosync(); // No need to synchronize the GPU again.
      prepare_inliers_for_optimisation();
      m_timerPrepareOptimisation[iteration].stop_sync();

#ifdef ENABLE_TIMERS
      boost::timer::auto_cpu_timer t(6, "continuous optimization: %ws wall, %us user + %ss system = %ts CPU (%p%)\n");
#endif
      m_timerOptimisation[iteration].start_nosync(); // No need to synchronize the GPU again.
      update_candidate_poses();
      m_timerOptimisation[iteration].stop_sync();
    }

    // Step 4(c): Compute the energy for each candidate and sort them in non-increasing order of quality.
    m_timerComputeEnergy[iteration].start_nosync(); // No need to synchronize the GPU again.
    compute_energies_and_sort();
    m_timerComputeEnergy[iteration].stop_sync();

    // Step 4(d): Remove the worse half of the candidates.
    m_poseCandidates->dataSize /= 2;

    ++iteration;
  }

  // If we initially generated a single candidate, the update step above wouldn't have been executed (zero iterations). Force its execution.
  if(m_poseUpdate && iteration == 0 && m_poseCandidates->dataSize == 1)
  {
    // Sample some inliers.
    m_timerInlierSampling[iteration].start_sync();
    sample_inliers(true);
    m_timerInlierSampling[iteration].stop_sync();

    // Having selected the inlier points, find the best associated modes to use during optimisation.
    m_timerPrepareOptimisation[iteration].start_nosync(); // No need to synchronize the GPU again.
    prepare_inliers_for_optimisation();
    m_timerPrepareOptimisation[iteration].stop_sync();

    // Run the optimisation.
    m_timerOptimisation[iteration].start_nosync(); // No need to synchronize the GPU again.
    update_candidate_poses();
    m_timerOptimisation[iteration].stop_sync();
  }

  m_timerTotal.stop_nosync(); // No need to synchronize the GPU again.

  // Make sure the pose candidates available on the host are up to date.
  update_host_pose_candidates();

  // Step 5: If we managed to generate at least one candidate, return the best one.
  const PoseCandidate *candidates = m_poseCandidates->GetData(MEMORYDEVICE_CPU);
  return m_poseCandidates->dataSize > 0 ? boost::optional<PoseCandidate>(candidates[0]) : boost::none;
}

void PreemptiveRansac::get_best_poses(std::vector<PoseCandidate>& poseCandidates) const
{
  // Set up the output container.
  poseCandidates.clear();
  poseCandidates.reserve(m_poseCandidatesAfterCull);

  // Copy all the poses that survived the initial cull. They are ordered in blocks: the first one is the one returned by
  // estimate_pose, the second is the one removed after the last RANSAC iteration, the third and fourth are removed in the
  // iteration before (whilst they are not in a specific order, they are worse than those in positions 0 and 1), etc.
  const PoseCandidate *candidates = m_poseCandidates->GetData(MEMORYDEVICE_CPU);
  for(uint32_t poseIdx = 0; poseIdx < m_poseCandidatesAfterCull; ++poseIdx)
  {
    poseCandidates.push_back(candidates[poseIdx]);
  }
}

uint32_t PreemptiveRansac::get_min_nb_required_points() const
{
  // We need at least the number of inliers required for a RANSAC iteration.
  return m_ransacInliersPerIteration;
}

//#################### PROTECTED MEMBER FUNCTIONS ####################

void PreemptiveRansac::compute_candidate_poses_kabsch()
{
  // We assume that the data on the CPU is up-to-date (the CUDA subclass must ensure this).
  // This function will probably go away as soon as we implement a shared SVD solver.
  const int nbPoseCandidates = static_cast<int>(m_poseCandidates->dataSize);
  PoseCandidate *poseCandidates = m_poseCandidates->GetData(MEMORYDEVICE_CPU);

#if 0
  std::cout << "Generated " << nbPoseCandidates << " candidates." << std::endl;
#endif

  // For each candidate:
#ifdef WITH_OPENMP
  #pragma omp parallel for
#endif
  for(int candidateIdx = 0; candidateIdx < nbPoseCandidates; ++candidateIdx)
  {
    PoseCandidate& candidate = poseCandidates[candidateIdx];

    // Copy the camera and world space points into two Eigen matrices (one point per column in each) on which we can run the Kabsch algorithm.
    Eigen::Matrix3f cameraPoints;
    Eigen::Matrix3f worldPoints;
    for(int i = 0; i < PoseCandidate::KABSCH_CORRESPONDENCES_NEEDED; ++i)
    {
      cameraPoints.col(i) = Eigen::Map<const Eigen::Vector3f>(candidate.pointsCamera[i].v);
      worldPoints.col(i) = Eigen::Map<const Eigen::Vector3f>(candidate.pointsWorld[i].v);
    }

    // Run the Kabsch algorithm and store the resulting camera -> world transformation in the candidate's cameraPose matrix.
    Eigen::Map<Eigen::Matrix4f>(candidate.cameraPose.m) = GeometryUtil::estimate_rigid_transform(cameraPoints, worldPoints);
  }
}

void PreemptiveRansac::reset_inliers(bool resetMask)
{
  if(resetMask)
  {
    m_inliersMaskImage->ChangeDims(m_keypointsImage->noDims); // happens only once (i.e. a no-op on subsequent occasions)
    m_inliersMaskImage->Clear();
  }

  m_inlierRasterIndicesBlock->dataSize = 0;
}

bool PreemptiveRansac::update_candidate_pose(int candidateIdx) const
#ifdef WITH_ALGLIB
try
{
  // Fill in the struct that will be passed to the optimiser.
  PointsForLM ptsForLM;
  ptsForLM.nbPoints = static_cast<uint32_t>(m_inlierRasterIndicesBlock->dataSize);                          // The current number of inlier points.
  const uint32_t candidateOffset = ptsForLM.nbPoints * candidateIdx;                                        // The linearised offset in the pose optimisation buffers.
  ptsForLM.cameraPoints = m_poseOptimisationCameraPoints->GetData(MEMORYDEVICE_CPU) + candidateOffset;      // Pointers to the data for this candidate.
  ptsForLM.predictedModes = m_poseOptimisationPredictedModes->GetData(MEMORYDEVICE_CPU) + candidateOffset;

  // Look up the pose candidate. The assumption is that all of the pose candidates have already been copied
  // across to the CPU (non-CPU subclasses must ensure this). The plan is ultimately to reimplement the
  // optimisation using shared code, but for now everything is done on the CPU.
  PoseCandidate& poseCandidate = m_poseCandidates->GetData(MEMORYDEVICE_CPU)[candidateIdx];

  // Convert the candidate's current pose to a 6D twist vector that can be optimised by alglib.
  alglib::real_1d_array xi = make_twist_from_pose(poseCandidate.cameraPose);

  // Set up the optimiser itself.
  alglib::minlmstate state;
  const double differentiationStep = 0.0001;
#if 1
  alglib::minlmcreatev(6, 1, xi, differentiationStep, state);
#else
  alglib::minlmcreatevj(6, 1, xi, state);
#endif
  alglib::minlmsetcond(state, m_poseOptimisationGradientThreshold, m_poseOptimisationEnergyThreshold, m_poseOptimisationStepThreshold, m_poseOptimisationMaxIterations);

  // Run the optimiser.
  if (m_usePredictionCovarianceForPoseOptimization)
  {
    alglib::minlmoptimize(state, alglib_func_mahalanobis, alglib_jac_mahalanobis, alglib_rep, &ptsForLM);
  }
  else
  {
    alglib::minlmoptimize(state, alglib_func_l2, alglib_jac_l2, alglib_rep, &ptsForLM);
  }

  // Extract the results of the optimisation.
  alglib::minlmreport report;
  alglib::minlmresults(state, xi, report);
  const bool succeeded = report.terminationtype >= 0;

  // Iff the optimisation succeeded, update the candidate's pose.
  if(succeeded)
  {
    poseCandidate.cameraPose = make_pose_from_twist(xi).GetM();
  }

  return succeeded;
}
catch(const alglib::ap_error& e)
{
  std::cout << "ALGLIB failed the optimisation for pose candidate: " << candidateIdx << ". Reason: " << e.msg << "\n";
  return false;
}
#else
{
  throw std::runtime_error("Error: Cannot update candidate pose. Reconfigure in CMake with the WITH_ALGLIB option set to ON.");
}
#endif

//#################### PRIVATE MEMBER FUNCTIONS ####################

void PreemptiveRansac::update_host_pose_candidates() const
{
  // No-op by default
}

//#################### PRIVATE STATIC MEMBER FUNCTIONS ####################

#ifdef WITH_ALGLIB
void PreemptiveRansac::alglib_func_l2(const alglib::real_1d_array& xi, alglib::real_1d_array& phi, void *pts)
{
  phi[0] = compute_energy_l2(make_pose_from_twist(xi), *reinterpret_cast<PointsForLM*>(pts));
}

void PreemptiveRansac::alglib_func_mahalanobis(const alglib::real_1d_array& xi, alglib::real_1d_array& phi, void *pts)
{
  phi[0] = compute_energy_mahalanobis(make_pose_from_twist(xi), *reinterpret_cast<PointsForLM*>(pts));
}

void PreemptiveRansac::alglib_jac_l2(const alglib::real_1d_array& xi, alglib::real_1d_array& phi, alglib::real_2d_array& jac, void *pts)
{
  phi[0] = compute_energy_l2(make_pose_from_twist(xi), *reinterpret_cast<PointsForLM*>(pts), jac[0]);
}

void PreemptiveRansac::alglib_jac_mahalanobis(const alglib::real_1d_array& xi, alglib::real_1d_array& phi, alglib::real_2d_array& jac, void *pts)
{
  phi[0] = compute_energy_mahalanobis(make_pose_from_twist(xi), *reinterpret_cast<PointsForLM*>(pts), jac[0]);
}

void PreemptiveRansac::alglib_rep(const alglib::real_1d_array& xi, double phi, void *pts)
{
  return;
}
#endif

double PreemptiveRansac::compute_energy_l2(const ORUtils::SE3Pose& candidateCameraPose, const PointsForLM& pts, double *jac)
{
  double res = 0.0;

  // If we're computing the Jacobian of the 6D twist vector, initially reset it to zero.
  if(jac)
  {
    for(int i = 0; i < 6; ++i)
    {
      jac[i] = 0.0;
    }
  }

  // For each point under consideration:
  for(uint32_t i = 0; i < pts.nbPoints; ++i)
  {
    // If the point's position in camera space is invalid, skip it.
    if(pts.cameraPoints[i].w == 0.0f) continue;

    // Compute the difference between the point's position in world space (i) as predicted by the camera
    // pose and its position in camera space, and (ii) as predicted by the position of the chosen mode.
    const Vector3f cameraPoint = pts.cameraPoints[i].toVector3();
    const Vector3f transformedPt = candidateCameraPose.GetM() * cameraPoint;
    const Vector3f diff = transformedPt - pts.predictedModes[i].position;

    // Based on this difference, add an L2 distance-based error term to the resulting energy.
#if 1
    const double err = dot(diff, diff); // squared L2 distance
#else
    const double err = length(diff);    // unsquared L2 distance
#endif

    res += err;

    // If we're computing the Jacobian of the 6D twist vector, update it as per equation (10.23) in
    // "A tutorial on SE(3) transformation parameterizations and on-manifold optimization" (Blanco).
    if(jac)
    {
#if 1
      const Vector3f normGradient = 2 * diff;   // for squared L2 distance
#else
      const Vector3f normGradient = diff / err; // for unsquared L2 distance
#endif

      const Vector3f poseGradient[6] = {
        Vector3f(1.0f, 0.0f, 0.0f),
        Vector3f(0.0f, 1.0f, 0.0f),
        Vector3f(0.0f, 0.0f, 1.0f),
        -Vector3f(0.0f, transformedPt.z, -transformedPt.y),
        -Vector3f(-transformedPt.z, 0.0f, transformedPt.x),
        -Vector3f(transformedPt.y, -transformedPt.x, 0.0f),
      };

      for(int i = 0; i < 6; ++i)
      {
        jac[i] += dot(normGradient, poseGradient[i]);
      }
    }
  }

  return res;
}

double PreemptiveRansac::compute_energy_mahalanobis(const ORUtils::SE3Pose& candidateCameraPose, const PointsForLM& pts, double *jac)
{
  double res = 0.0;

  // If we're computing the Jacobian of the 6D twist vector, initially reset it to zero.
  if(jac)
  {
    for(int i = 0; i < 6; ++i)
    {
      jac[i] = 0.0;
    }
  }

  // For each point under consideration:
  for(uint32_t i = 0; i < pts.nbPoints; ++i)
  {
    // If the point's position in camera space is invalid, skip it.
    if(pts.cameraPoints[i].w == 0.0f) continue;

    // Compute the difference between the point's position in world space (i) as predicted by the camera
    // pose and its position in camera space, and (ii) as predicted by the position of the chosen mode.
    const Vector3f transformedPt = candidateCameraPose.GetM() * pts.cameraPoints[i].toVector3();
    const Vector3f diff = transformedPt - pts.predictedModes[i].position;

    // Based on this difference, add a Mahalanobis distance-based error term to the resulting energy.
    // See also: https://en.wikipedia.org/wiki/Mahalanobis_distance.
    const Vector3f invCovarianceTimesDiff = pts.predictedModes[i].positionInvCovariance * diff;
#if 1
    const float err = dot(diff, invCovarianceTimesDiff);        // squared Mahalanobis distance
#else
    const float err = sqrtf(dot(diff, invCovarianceTimesDiff)); // unsquared Mahalanobis distance
#endif

    res += err;

    // If we're computing the Jacobian of the 6D twist vector, update it accordingly.
    if(jac)
    {
#if 1
      const Vector3f normGradient = 2 * invCovarianceTimesDiff;   // for squared Mahalanobis distance
#else
      const Vector3f normGradient = invCovarianceTimesDiff / err; // for unsquared Mahalanobis distance
#endif

      const Vector3f poseGradient[6] = {
        Vector3f(1.0f, 0.0f, 0.0f),
        Vector3f(0.0f, 1.0f, 0.0f),
        Vector3f(0.0f, 0.0f, 1.0f),
        -Vector3f(0.0f, transformedPt.z, -transformedPt.y),
        -Vector3f(-transformedPt.z, 0.0f, transformedPt.x),
        -Vector3f(transformedPt.y, -transformedPt.x, 0.0f),
      };

      for(int i = 0; i < 6; ++i)
      {
        jac[i] += dot(normGradient, poseGradient[i]);
      }
    }
  }

  return res;
}

#ifdef WITH_ALGLIB
ORUtils::SE3Pose PreemptiveRansac::make_pose_from_twist(const alglib::real_1d_array& xi)
{
  return ORUtils::SE3Pose(
    static_cast<float>(xi[0]),
    static_cast<float>(xi[1]),
    static_cast<float>(xi[2]),
    static_cast<float>(xi[3]),
    static_cast<float>(xi[4]),
    static_cast<float>(xi[5])
  );
}

alglib::real_1d_array PreemptiveRansac::make_twist_from_pose(const ORUtils::SE3Pose& pose)
{
  alglib::real_1d_array xi;
  xi.setlength(6);
  for(int i = 0; i < 6; ++i)
  {
    xi[i] = static_cast<double>(pose.GetParams()[i]);
  }
  return xi;
}
#endif

void PreemptiveRansac::print_timer(const AverageTimer& timer)
{
  std::cout << timer.name() << ": " << timer.count() << " times, avg: " << timer.average_duration() << ".\n";
}

}


// Copyrights(c) 2017-2018, The BitcoinFlame Project
// Copyrights(c) 2014-2017, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
//    conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
//    of conditions and the following disclaimer in the documentation and/or other
//    materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers

#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/shared_ptr.hpp>

#include "common/varint.h"
#include "warnings.h"
#include "crypto.h"
#include "hash.h"

namespace crypto {

  using std::abort;
  using std::int32_t;
  using std::int64_t;
  using std::size_t;
  using std::uint32_t;
  using std::uint64_t;

  extern "C" {
#include "crypto-ops.h"
#include "random.h"
  }

  boost::mutex random_lock;

  static inline unsigned char *operator &(ec_point &point) {
    return &reinterpret_cast<unsigned char &>(point);
  }

  static inline const unsigned char *operator &(const ec_point &point) {
    return &reinterpret_cast<const unsigned char &>(point);
  }

  static inline unsigned char *operator &(ec_scalar &scalar) {
    return &reinterpret_cast<unsigned char &>(scalar);
  }

  static inline const unsigned char *operator &(const ec_scalar &scalar) {
    return &reinterpret_cast<const unsigned char &>(scalar);
  }

  /* generate a random 32-byte (256-bit) integer and copy it to res */
  static inline void random_scalar_not_thread_safe(ec_scalar &res) {
    unsigned char tmp[64];
    generate_random_bytes_not_thread_safe(64, tmp);
    sc_reduce(tmp);
    memcpy(&res, tmp, 32);
  }
  static inline void random_scalar(ec_scalar &res) {
    boost::lock_guard<boost::mutex> lock(random_lock);
    random_scalar_not_thread_safe(res);
  }

  static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) {
    cn_fast_hash(data, length, reinterpret_cast<hash &>(res));
    sc_reduce32(&res);
  }

  /*
   * generate public and secret keys from a random 256-bit integer
   * TODO: allow specifiying random value (for wallet recovery)
   *
   */
  secret_key crypto_ops::generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key, bool recover) {
    ge_p3 point;

    secret_key rng;

    if (recover)
    {
      rng = recovery_key;
    }
    else
    {
      random_scalar(rng);
    }
    sec = rng;
    sc_reduce32(&sec);  // reduce in case second round of keys (sendkeys)

    ge_scalarmult_base(&point, &sec);
    ge_p3_tobytes(&pub, &point);

    return rng;
  }

  bool crypto_ops::check_key(const public_key &key) {
    ge_p3 point;
    return ge_frombytes_vartime(&point, &key) == 0;
  }

  bool crypto_ops::secret_key_to_public_key(const secret_key &sec, public_key &pub) {
    ge_p3 point;
    if (sc_check(&sec) != 0) {
      return false;
    }
    ge_scalarmult_base(&point, &sec);
    ge_p3_tobytes(&pub, &point);
    return true;
  }

  bool crypto_ops::generate_key_derivation(const public_key &key1, const secret_key &key2, key_derivation &derivation) {
    ge_p3 point;
    ge_p2 point2;
    ge_p1p1 point3;
    assert(sc_check(&key2) == 0);
    if (ge_frombytes_vartime(&point, &key1) != 0) {
      return false;
    }
    ge_scalarmult(&point2, &key2, &point);
    ge_mul8(&point3, &point2);
    ge_p1p1_to_p2(&point2, &point3);
    ge_tobytes(&derivation, &point2);
    return true;
  }

  void crypto_ops::derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res) {
    struct {
      key_derivation derivation;
      char output_index[(sizeof(size_t) * 8 + 6) / 7];
    } buf;
    char *end = buf.output_index;
    buf.derivation = derivation;
    tools::write_varint(end, output_index);
    assert(end <= buf.output_index + sizeof buf.output_index);
    hash_to_scalar(&buf, end - reinterpret_cast<char *>(&buf), res);
  }

  bool crypto_ops::derive_public_key(const key_derivation &derivation, size_t output_index,
    const public_key &base, public_key &derived_key) {
    ec_scalar scalar;
    ge_p3 point1;
    ge_p3 point2;
    ge_cached point3;
    ge_p1p1 point4;
    ge_p2 point5;
    if (ge_frombytes_vartime(&point1, &base) != 0) {
      return false;
    }
    derivation_to_scalar(derivation, output_index, scalar);
    ge_scalarmult_base(&point2, &scalar);
    ge_p3_to_cached(&point3, &point2);
    ge_add(&point4, &point1, &point3);
    ge_p1p1_to_p2(&point5, &point4);
    ge_tobytes(&derived_key, &point5);
    return true;
  }

  void crypto_ops::derive_secret_key(const key_derivation &derivation, size_t output_index,
    const secret_key &base, secret_key &derived_key) {
    ec_scalar scalar;
    assert(sc_check(&base) == 0);
    derivation_to_scalar(derivation, output_index, scalar);
    sc_add(&derived_key, &base, &scalar);
  }

  struct s_comm {
    hash h;
    ec_point key;
    ec_point comm;
  };

  struct s_comm_2 {
    hash msg;
    ec_point D;
    ec_point X;
    ec_point Y;
  };

  void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) {
    ge_p3 tmp3;
    ec_scalar k;
    s_comm buf;
#if !defined(NDEBUG)
    {
      ge_p3 t;
      public_key t2;
      assert(sc_check(&sec) == 0);
      ge_scalarmult_base(&t, &sec);
      ge_p3_tobytes(&t2, &t);
      assert(pub == t2);
    }
#endif
    buf.h = prefix_hash;
    buf.key = pub;
    random_scalar(k);
    ge_scalarmult_base(&tmp3, &k);
    ge_p3_tobytes(&buf.comm, &tmp3);
    hash_to_scalar(&buf, sizeof(s_comm), sig.c);
    sc_mulsub(&sig.r, &sig.c, &sec, &k);
  }

  bool crypto_ops::check_signature(const hash &prefix_hash, const public_key &pub, const signature &sig) {
    ge_p2 tmp2;
    ge_p3 tmp3;
    ec_scalar c;
    s_comm buf;
    assert(check_key(pub));
    buf.h = prefix_hash;
    buf.key = pub;
    if (ge_frombytes_vartime(&tmp3, &pub) != 0) {
      return false;
    }
    if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) {
      return false;
    }
    ge_double_scalarmult_base_vartime(&tmp2, &sig.c, &tmp3, &sig.r);
    ge_tobytes(&buf.comm, &tmp2);
    hash_to_scalar(&buf, sizeof(s_comm), c);
    sc_sub(&c, &c, &sig.c);
    return sc_isnonzero(&c) == 0;
  }

  void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) {
    // sanity check
    ge_p3 R_p3;
    ge_p3 A_p3;
    ge_p3 D_p3;
    if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid");
    if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid");
    if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid");
#if !defined(NDEBUG)
    {
      assert(sc_check(&r) == 0);
      // check R == r*G
      ge_p3 dbg_R_p3;
      ge_scalarmult_base(&dbg_R_p3, &r);
      public_key dbg_R;
      ge_p3_tobytes(&dbg_R, &dbg_R_p3);
      assert(R == dbg_R);
      // check D == r*A
      ge_p2 dbg_D_p2;
      ge_scalarmult(&dbg_D_p2, &r, &A_p3);
      public_key dbg_D;
      ge_tobytes(&dbg_D, &dbg_D_p2);
      assert(D == dbg_D);
    }
#endif

    // pick random k
    ec_scalar k;
    random_scalar(k);

    // compute X = k*G
    ge_p3 X_p3;
    ge_scalarmult_base(&X_p3, &k);

    // compute Y = k*A
    ge_p2 Y_p2;
    ge_scalarmult(&Y_p2, &k, &A_p3);

    // sig.c = Hs(Msg || D || X || Y)
    s_comm_2 buf;
    buf.msg = prefix_hash;
    buf.D = D;
    ge_p3_tobytes(&buf.X, &X_p3);
    ge_tobytes(&buf.Y, &Y_p2);
    hash_to_scalar(&buf, sizeof(s_comm_2), sig.c);

    // sig.r = k - sig.c*r
    sc_mulsub(&sig.r, &sig.c, &r, &k);
  }

  bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) {
    // sanity check
    ge_p3 R_p3;
    ge_p3 A_p3;
    ge_p3 D_p3;
    if (ge_frombytes_vartime(&R_p3, &R) != 0) return false;
    if (ge_frombytes_vartime(&A_p3, &A) != 0) return false;
    if (ge_frombytes_vartime(&D_p3, &D) != 0) return false;
    if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false;

    // compute sig.c*R
    ge_p2 cR_p2;
    ge_scalarmult(&cR_p2, &sig.c, &R_p3);

    // compute sig.r*G
    ge_p3 rG_p3;
    ge_scalarmult_base(&rG_p3, &sig.r);

    // compute sig.c*D
    ge_p2 cD_p2;
    ge_scalarmult(&cD_p2, &sig.c, &D_p3);

    // compute sig.r*A
    ge_p2 rA_p2;
    ge_scalarmult(&rA_p2, &sig.r, &A_p3);

    // compute X = sig.c*R + sig.r*G
    public_key cR;
    ge_tobytes(&cR, &cR_p2);
    ge_p3 cR_p3;
    if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false;
    ge_cached rG_cached;
    ge_p3_to_cached(&rG_cached, &rG_p3);
    ge_p1p1 X_p1p1;
    ge_add(&X_p1p1, &cR_p3, &rG_cached);
    ge_p2 X_p2;
    ge_p1p1_to_p2(&X_p2, &X_p1p1);

    // compute Y = sig.c*D + sig.r*A
    public_key cD;
    public_key rA;
    ge_tobytes(&cD, &cD_p2);
    ge_tobytes(&rA, &rA_p2);
    ge_p3 cD_p3;
    ge_p3 rA_p3;
    if (ge_frombytes_vartime(&cD_p3, &cD) != 0) return false;
    if (ge_frombytes_vartime(&rA_p3, &rA) != 0) return false;
    ge_cached rA_cached;
    ge_p3_to_cached(&rA_cached, &rA_p3);
    ge_p1p1 Y_p1p1;
    ge_add(&Y_p1p1, &cD_p3, &rA_cached);
    ge_p2 Y_p2;
    ge_p1p1_to_p2(&Y_p2, &Y_p1p1);

    // compute c2 = Hs(Msg || D || X || Y)
    s_comm_2 buf;
    buf.msg = prefix_hash;
    buf.D = D;
    ge_tobytes(&buf.X, &X_p2);
    ge_tobytes(&buf.Y, &Y_p2);
    ec_scalar c2;
    hash_to_scalar(&buf, sizeof(s_comm_2), c2);

    // test if c2 == sig.c
    sc_sub(&c2, &c2, &sig.c);
    return sc_isnonzero(&c2) == 0;
  }

  static void hash_to_ec(const public_key &key, ge_p3 &res) {
    hash h;
    ge_p2 point;
    ge_p1p1 point2;
    cn_fast_hash(std::addressof(key), sizeof(public_key), h);
    ge_fromfe_frombytes_vartime(&point, reinterpret_cast<const unsigned char *>(&h));
    ge_mul8(&point2, &point);
    ge_p1p1_to_p3(&res, &point2);
  }

  void crypto_ops::generate_key_image(const public_key &pub, const secret_key &sec, key_image &image) {
    ge_p3 point;
    ge_p2 point2;
    assert(sc_check(&sec) == 0);
    hash_to_ec(pub, point);
    ge_scalarmult(&point2, &sec, &point);
    ge_tobytes(&image, &point2);
  }

PUSH_WARNINGS
DISABLE_VS_WARNINGS(4200)
  struct ec_point_pair {
    ec_point a, b;
  };
  struct rs_comm {
    hash h;
    struct ec_point_pair ab[];
  };
POP_WARNINGS

  static inline size_t rs_comm_size(size_t pubs_count) {
    return sizeof(rs_comm) + pubs_count * sizeof(ec_point_pair);
  }

  void crypto_ops::generate_ring_signature(const hash &prefix_hash, const key_image &image,
    const public_key *const *pubs, size_t pubs_count,
    const secret_key &sec, size_t sec_index,
    signature *sig) {
    size_t i;
    ge_p3 image_unp;
    ge_dsmp image_pre;
    ec_scalar sum, k, h;
    boost::shared_ptr<rs_comm> buf(reinterpret_cast<rs_comm *>(malloc(rs_comm_size(pubs_count))), free);
    if (!buf)
      abort();
    assert(sec_index < pubs_count);
#if !defined(NDEBUG)
    {
      ge_p3 t;
      public_key t2;
      key_image t3;
      assert(sc_check(&sec) == 0);
      ge_scalarmult_base(&t, &sec);
      ge_p3_tobytes(&t2, &t);
      assert(*pubs[sec_index] == t2);
      generate_key_image(*pubs[sec_index], sec, t3);
      assert(image == t3);
      for (i = 0; i < pubs_count; i++) {
        assert(check_key(*pubs[i]));
      }
    }
#endif
    if (ge_frombytes_vartime(&image_unp, &image) != 0) {
      abort();
    }
    ge_dsm_precomp(image_pre, &image_unp);
    sc_0(&sum);
    buf->h = prefix_hash;
    for (i = 0; i < pubs_count; i++) {
      ge_p2 tmp2;
      ge_p3 tmp3;
      if (i == sec_index) {
        random_scalar(k);
        ge_scalarmult_base(&tmp3, &k);
        ge_p3_tobytes(&buf->ab[i].a, &tmp3);
        hash_to_ec(*pubs[i], tmp3);
        ge_scalarmult(&tmp2, &k, &tmp3);
        ge_tobytes(&buf->ab[i].b, &tmp2);
      } else {
        random_scalar(sig[i].c);
        random_scalar(sig[i].r);
        if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) {
          abort();
        }
        ge_double_scalarmult_base_vartime(&tmp2, &sig[i].c, &tmp3, &sig[i].r);
        ge_tobytes(&buf->ab[i].a, &tmp2);
        hash_to_ec(*pubs[i], tmp3);
        ge_double_scalarmult_precomp_vartime(&tmp2, &sig[i].r, &tmp3, &sig[i].c, image_pre);
        ge_tobytes(&buf->ab[i].b, &tmp2);
        sc_add(&sum, &sum, &sig[i].c);
      }
    }
    hash_to_scalar(buf.get(), rs_comm_size(pubs_count), h);
    sc_sub(&sig[sec_index].c, &h, &sum);
    sc_mulsub(&sig[sec_index].r, &sig[sec_index].c, &sec, &k);
  }

  bool crypto_ops::check_ring_signature(const hash &prefix_hash, const key_image &image,
    const public_key *const *pubs, size_t pubs_count,
    const signature *sig) {
    size_t i;
    ge_p3 image_unp;
    ge_dsmp image_pre;
    ec_scalar sum, h;
    boost::shared_ptr<rs_comm> buf(reinterpret_cast<rs_comm *>(malloc(rs_comm_size(pubs_count))), free);
    if (!buf)
      return false;
#if !defined(NDEBUG)
    for (i = 0; i < pubs_count; i++) {
      assert(check_key(*pubs[i]));
    }
#endif
    if (ge_frombytes_vartime(&image_unp, &image) != 0) {
      return false;
    }
    ge_dsm_precomp(image_pre, &image_unp);
    sc_0(&sum);
    buf->h = prefix_hash;
    for (i = 0; i < pubs_count; i++) {
      ge_p2 tmp2;
      ge_p3 tmp3;
      if (sc_check(&sig[i].c) != 0 || sc_check(&sig[i].r) != 0) {
        return false;
      }
      if (ge_frombytes_vartime(&tmp3, &*pubs[i]) != 0) {
        return false;
      }
      ge_double_scalarmult_base_vartime(&tmp2, &sig[i].c, &tmp3, &sig[i].r);
      ge_tobytes(&buf->ab[i].a, &tmp2);
      hash_to_ec(*pubs[i], tmp3);
      ge_double_scalarmult_precomp_vartime(&tmp2, &sig[i].r, &tmp3, &sig[i].c, image_pre);
      ge_tobytes(&buf->ab[i].b, &tmp2);
      sc_add(&sum, &sum, &sig[i].c);
    }
    hash_to_scalar(buf.get(), rs_comm_size(pubs_count), h);
    sc_sub(&h, &h, &sum);
    return sc_isnonzero(&h) == 0;
  }
}


/**
 * \file
 * \copyright
 * Copyright (c) 2012-2022, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 *
 */

#include "PhreeqcIO.h"

#include <IPhreeqc.h>

#include <boost/algorithm/string.hpp>
#include <cmath>
#include <fstream>
#include <iomanip>
#include <numeric>

#include "BaseLib/Algorithm.h"
#include "BaseLib/ConfigTreeUtil.h"
#include "MaterialLib/MPL/Medium.h"
#include "MathLib/LinAlg/Eigen/EigenVector.h"
#include "MathLib/LinAlg/LinAlg.h"
#include "MeshLib/Mesh.h"
#include "PhreeqcIOData/AqueousSolution.h"
#include "PhreeqcIOData/ChemicalSystem.h"
#include "PhreeqcIOData/Dump.h"
#include "PhreeqcIOData/EquilibriumReactant.h"
#include "PhreeqcIOData/Exchange.h"
#include "PhreeqcIOData/KineticReactant.h"
#include "PhreeqcIOData/Knobs.h"
#include "PhreeqcIOData/Output.h"
#include "PhreeqcIOData/ReactionRate.h"
#include "PhreeqcIOData/Surface.h"
#include "PhreeqcIOData/UserPunch.h"

namespace ChemistryLib
{
namespace PhreeqcIOData
{
namespace
{
template <class... Ts>
struct overloaded : Ts...
{
    using Ts::operator()...;
};
template <class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

template <typename DataBlock>
std::ostream& operator<<(std::ostream& os,
                         std::vector<DataBlock> const& data_blocks)
{
    std::copy(data_blocks.begin(), data_blocks.end(),
              std::ostream_iterator<DataBlock>(os));
    return os;
}

void setAqueousSolution(std::vector<double> const& concentrations,
                        GlobalIndexType const& chemical_system_id,
                        AqueousSolution& aqueous_solution)
{
    // components
    auto& components = aqueous_solution.components;
    for (unsigned component_id = 0; component_id < components.size();
         ++component_id)
    {
        MathLib::LinAlg::setLocalAccessibleVector(
            *components[component_id].amount);
        components[component_id].amount->set(chemical_system_id,
                                             concentrations[component_id]);
    }

    // pH
    MathLib::LinAlg::setLocalAccessibleVector(*aqueous_solution.pH);
    aqueous_solution.pH->set(chemical_system_id, concentrations.back());
}

template <typename Reactant>
void initializeReactantMolality(Reactant& reactant,
                                GlobalIndexType const& chemical_system_id,
                                MaterialPropertyLib::Phase const& solid_phase,
                                MaterialPropertyLib::Phase const& liquid_phase,
                                MaterialPropertyLib::Medium const& medium,
                                ParameterLib::SpatialPosition const& pos,
                                double const t)
{
    auto const& solid_constituent = solid_phase.component(reactant.name);

    if (solid_constituent.hasProperty(
            MaterialPropertyLib::PropertyType::molality))
    {
        auto const molality =
            solid_constituent[MaterialPropertyLib::PropertyType::molality]
                .template initialValue<double>(pos, t);

        (*reactant.molality)[chemical_system_id] = molality;
        (*reactant.molality_prev)[chemical_system_id] = molality;
    }
    else
    {
        auto const volume_fraction =
            solid_constituent
                [MaterialPropertyLib::PropertyType::volume_fraction]
                    .template initialValue<double>(pos, t);

        (*reactant.volume_fraction)[chemical_system_id] = volume_fraction;

        (*reactant.volume_fraction_prev)[chemical_system_id] = volume_fraction;

        auto const fluid_density =
            liquid_phase[MaterialPropertyLib::PropertyType::density]
                .template initialValue<double>(pos, t);

        auto const porosity =
            medium[MaterialPropertyLib::PropertyType::porosity]
                .template initialValue<double>(pos, t);

        auto const molar_volume =
            solid_constituent[MaterialPropertyLib::PropertyType::molar_volume]
                .template initialValue<double>(pos, t);

        (*reactant.molality)[chemical_system_id] =
            volume_fraction / fluid_density / porosity / molar_volume;

        (*reactant.molality_prev)[chemical_system_id] =
            (*reactant.molality)[chemical_system_id];
    }
}

template <typename Reactant>
void setReactantMolality(Reactant& reactant,
                         GlobalIndexType const& chemical_system_id,
                         MaterialPropertyLib::Phase const& solid_phase,
                         MaterialPropertyLib::Phase const& liquid_phase,
                         MaterialPropertyLib::VariableArray const& vars,
                         ParameterLib::SpatialPosition const& pos,
                         double const t, double const dt)
{
    auto const& solid_constituent = solid_phase.component(reactant.name);

    if (solid_constituent.hasProperty(
            MaterialPropertyLib::PropertyType::molality))
    {
        (*reactant.molality_prev)[chemical_system_id] =
            (*reactant.molality)[chemical_system_id];

        return;
    }

    auto const volume_fraction =
        (*reactant.volume_fraction)[chemical_system_id];

    (*reactant.volume_fraction_prev)[chemical_system_id] =
        (*reactant.volume_fraction)[chemical_system_id];

    auto const fluid_density =
        liquid_phase[MaterialPropertyLib::PropertyType::density]
            .template value<double>(vars, pos, t, dt);

    auto const porosity = std::get<double>(
        vars[static_cast<int>(MaterialPropertyLib::Variable::porosity)]);

    auto const molar_volume =
        solid_constituent[MaterialPropertyLib::PropertyType::molar_volume]
            .template value<double>(vars, pos, t, dt);

    (*reactant.molality)[chemical_system_id] =
        volume_fraction / fluid_density / porosity / molar_volume;

    (*reactant.molality_prev)[chemical_system_id] =
        (*reactant.molality)[chemical_system_id];
}

template <typename Site>
void initializeSiteMolality(Site& site,
                            GlobalIndexType const& chemical_system_id,
                            MaterialPropertyLib::Phase const& solid_phase,
                            ParameterLib::SpatialPosition const& pos,
                            double const t)
{
    auto const& solid_constituent = solid_phase.component(site.name);

    auto const molality =
        solid_constituent[MaterialPropertyLib::PropertyType::molality]
            .template initialValue<double>(pos, t);

    (*site.molality)[chemical_system_id] = molality;
}

template <typename Reactant>
void updateReactantVolumeFraction(Reactant& reactant,
                                  GlobalIndexType const& chemical_system_id,
                                  MaterialPropertyLib::Medium const& medium,
                                  ParameterLib::SpatialPosition const& pos,
                                  double const porosity, double const t,
                                  double const dt)
{
    auto const& solid_phase = medium.phase("Solid");
    auto const& liquid_phase = medium.phase("AqueousLiquid");

    MaterialPropertyLib::VariableArray vars;

    auto const liquid_density =
        liquid_phase[MaterialPropertyLib::PropertyType::density]
            .template value<double>(vars, pos, t, dt);

    auto const& solid_constituent = solid_phase.component(reactant.name);

    if (solid_constituent.hasProperty(
            MaterialPropertyLib::PropertyType::molality))
    {
        return;
    }

    auto const molar_volume =
        solid_constituent[MaterialPropertyLib::PropertyType::molar_volume]
            .template value<double>(vars, pos, t, dt);

    (*reactant.volume_fraction)[chemical_system_id] +=
        ((*reactant.molality)[chemical_system_id] -
         (*reactant.molality_prev)[chemical_system_id]) *
        liquid_density * porosity * molar_volume;
}

template <typename Reactant>
void setPorosityPostReaction(Reactant& reactant,
                             GlobalIndexType const& chemical_system_id,
                             MaterialPropertyLib::Medium const& medium,
                             double& porosity)
{
    auto const& solid_phase = medium.phase("Solid");

    auto const& solid_constituent = solid_phase.component(reactant.name);

    if (solid_constituent.hasProperty(
            MaterialPropertyLib::PropertyType::molality))
    {
        return;
    }

    porosity -= ((*reactant.volume_fraction)[chemical_system_id] -
                 (*reactant.volume_fraction_prev)[chemical_system_id]);
}

template <typename Reactant>
static double averageReactantMolality(
    Reactant const& reactant,
    std::vector<GlobalIndexType> const& chemical_system_indices)
{
    double const sum = std::accumulate(
        chemical_system_indices.begin(), chemical_system_indices.end(), 0.0,
        [&](double const s, GlobalIndexType const id)
        { return s + (*reactant.molality)[id]; });
    return sum / chemical_system_indices.size();
}
}  // namespace

PhreeqcIO::PhreeqcIO(MeshLib::Mesh const& mesh,
                     GlobalLinearSolver& linear_solver,
                     std::string const& project_file_name,
                     std::string&& database,
                     std::unique_ptr<ChemicalSystem>&& chemical_system,
                     std::vector<ReactionRate>&& reaction_rates,
                     std::unique_ptr<UserPunch>&& user_punch,
                     std::unique_ptr<Output>&& output,
                     std::unique_ptr<Dump>&& dump,
                     Knobs&& knobs)
    : ChemicalSolverInterface(mesh, linear_solver),
      _phreeqc_input_file(project_file_name + "_phreeqc.inp"),
      _database(std::move(database)),
      _chemical_system(std::move(chemical_system)),
      _reaction_rates(std::move(reaction_rates)),
      _user_punch(std::move(user_punch)),
      _output(std::move(output)),
      _dump(std::move(dump)),
      _knobs(std::move(knobs))
{
    // initialize phreeqc instance
    if (CreateIPhreeqc() != phreeqc_instance_id)
    {
        OGS_FATAL(
            "Failed to initialize phreeqc instance, due to lack of memory.");
    }

    // load specified thermodynamic database
    if (LoadDatabase(phreeqc_instance_id, _database.c_str()) != IPQ_OK)
    {
        OGS_FATAL(
            "Failed in loading the specified thermodynamic database file: "
            "{:s}.",
            _database);
    }

    if (SetSelectedOutputFileOn(phreeqc_instance_id, 1) != IPQ_OK)
    {
        OGS_FATAL(
            "Failed to fly the flag for the specified file {:s} where phreeqc "
            "will write output.",
            _output->basic_output_setups.output_file);
    }

    if (_dump)
    {
        // Chemical composition of the aqueous solution of last time step will
        // be written into .dmp file once the second function argument is set to
        // one.
        SetDumpFileOn(phreeqc_instance_id, 1);
    }
}

PhreeqcIO::~PhreeqcIO()
{
    DestroyIPhreeqc(phreeqc_instance_id);
}

void PhreeqcIO::initialize()
{
    _num_chemical_systems = chemical_system_index_map.size();

    _chemical_system->initialize(_num_chemical_systems);

    if (_user_punch)
    {
        _user_punch->initialize(_num_chemical_systems);
    }
}

void PhreeqcIO::initializeChemicalSystemConcrete(
    std::vector<double> const& concentrations,
    GlobalIndexType const& chemical_system_id,
    MaterialPropertyLib::Medium const& medium,
    ParameterLib::SpatialPosition const& pos,
    double const t)
{
    setAqueousSolution(concentrations, chemical_system_id,
                       *_chemical_system->aqueous_solution);

    auto const& solid_phase = medium.phase("Solid");
    auto const& liquid_phase = medium.phase("AqueousLiquid");

    for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
    {
        initializeReactantMolality(kinetic_reactant, chemical_system_id,
                                   solid_phase, liquid_phase, medium, pos, t);
    }

    for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
    {
        initializeReactantMolality(equilibrium_reactant, chemical_system_id,
                                   solid_phase, liquid_phase, medium, pos, t);
    }

    for (auto& exchanger : _chemical_system->exchangers)
    {
        initializeSiteMolality(exchanger, chemical_system_id, solid_phase, pos,
                               t);
    }

    for (auto& surface_site : _chemical_system->surface)
    {
        if (auto const surface_site_ptr =
                std::get_if<MoleBasedSurfaceSite>(&surface_site))
        {
            initializeSiteMolality(*surface_site_ptr, chemical_system_id,
                                   solid_phase, pos, t);
        }
    }
}

void PhreeqcIO::setChemicalSystemConcrete(
    std::vector<double> const& concentrations,
    GlobalIndexType const& chemical_system_id,
    MaterialPropertyLib::Medium const* medium,
    MaterialPropertyLib::VariableArray const& vars,
    ParameterLib::SpatialPosition const& pos, double const t, double const dt)
{
    setAqueousSolution(concentrations, chemical_system_id,
                       *_chemical_system->aqueous_solution);

    auto const& solid_phase = medium->phase("Solid");
    auto const& liquid_phase = medium->phase("AqueousLiquid");

    for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
    {
        setReactantMolality(kinetic_reactant, chemical_system_id, solid_phase,
                            liquid_phase, vars, pos, t, dt);
    }

    for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
    {
        setReactantMolality(equilibrium_reactant, chemical_system_id,
                            solid_phase, liquid_phase, vars, pos, t, dt);
    }
}

void PhreeqcIO::executeSpeciationCalculation(double const dt)
{
    writeInputsToFile(dt);

    callPhreeqc();

    readOutputsFromFile();
}

std::vector<GlobalVector*> PhreeqcIO::getIntPtProcessSolutions() const
{
    auto const& aqueous_solution = _chemical_system->aqueous_solution;
    std::vector<GlobalVector*> int_pt_process_solutions;
    int_pt_process_solutions.reserve(aqueous_solution->components.size() + 1);

    std::transform(aqueous_solution->components.begin(),
                   aqueous_solution->components.end(),
                   std::back_inserter(int_pt_process_solutions),
                   [](auto const& c) { return c.amount.get(); });

    int_pt_process_solutions.push_back(aqueous_solution->pH.get());

    return int_pt_process_solutions;
}

double PhreeqcIO::getConcentration(
    int const component_id, GlobalIndexType const chemical_system_id) const
{
    auto const& aqueous_solution = *_chemical_system->aqueous_solution;
    auto& components = aqueous_solution.components;
    auto const& pH = *aqueous_solution.pH;

    return component_id < static_cast<int>(components.size())
               ? components[component_id].amount->get(chemical_system_id)
               : pH.get(chemical_system_id);
}

void PhreeqcIO::setAqueousSolutionsPrevFromDumpFile()
{
    if (!_dump)
    {
        return;
    }

    auto const& dump_file = _dump->dump_file;
    std::ifstream in(dump_file);
    if (!in)
    {
        // return if phreeqc dump file doesn't exist. Normally, this happens in
        // the first time step.
        return;
    }

    _dump->readDumpFile(in, _num_chemical_systems);

    if (!in)
    {
        OGS_FATAL("Error when reading phreeqc dump file '{:s}'", dump_file);
    }

    in.close();
}

void PhreeqcIO::writeInputsToFile(double const dt)
{
    DBUG("Writing phreeqc inputs into file '{:s}'.", _phreeqc_input_file);
    std::ofstream out(_phreeqc_input_file, std::ofstream::out);

    if (!out)
    {
        OGS_FATAL("Could not open file '{:s}' for writing phreeqc inputs.",
                  _phreeqc_input_file);
    }

    out << std::scientific
        << std::setprecision(std::numeric_limits<double>::digits10);
    out << (*this << dt);

    if (!out)
    {
        OGS_FATAL("Failed in generating phreeqc input file '{:s}'.",
                  _phreeqc_input_file);
    }

    out.close();
}

std::ostream& operator<<(std::ostream& os, PhreeqcIO const& phreeqc_io)
{
    bool const fixing_pe =
        phreeqc_io._chemical_system->aqueous_solution->fixing_pe;
    if (fixing_pe)
    {
        os << "PHASES\n"
           << "Fix_pe\n"
           << "e- = e-\n"
           << "log_k 0.0\n\n";
    }

    os << phreeqc_io._knobs << "\n";

    os << *phreeqc_io._output << "\n";

    auto const& user_punch = phreeqc_io._user_punch;
    if (user_punch)
    {
        os << *user_punch << "\n";
    }

    auto const& reaction_rates = phreeqc_io._reaction_rates;
    if (!reaction_rates.empty())
    {
        os << "RATES\n";
        os << reaction_rates << "\n";
    }

    for (std::size_t chemical_system_id = 0;
         chemical_system_id < phreeqc_io._num_chemical_systems;
         ++chemical_system_id)
    {
        os << "SOLUTION " << chemical_system_id + 1 << "\n";
        phreeqc_io._chemical_system->aqueous_solution->print(
            os, chemical_system_id);

        auto const& dump = phreeqc_io._dump;
        if (dump)
        {
            auto const& aqueous_solutions_prev = dump->aqueous_solutions_prev;
            if (!aqueous_solutions_prev.empty())
            {
                os << aqueous_solutions_prev[chemical_system_id] << "\n\n";
            }
        }

        os << "USE solution none\n";
        os << "END\n\n";

        os << "USE solution " << chemical_system_id + 1 << "\n\n";

        auto const& equilibrium_reactants =
            phreeqc_io._chemical_system->equilibrium_reactants;
        if (!equilibrium_reactants.empty() || fixing_pe)
        {
            os << "EQUILIBRIUM_PHASES " << chemical_system_id + 1 << "\n";
            for (auto const& equilibrium_reactant : equilibrium_reactants)
            {
                equilibrium_reactant.print(os, chemical_system_id);
            }
            fixing_pe
                ? os << "Fix_pe "
                     << -phreeqc_io._chemical_system->aqueous_solution->pe0
                     << " O2(g)\n\n"
                : os << "\n";
        }

        auto const& kinetic_reactants =
            phreeqc_io._chemical_system->kinetic_reactants;
        if (!kinetic_reactants.empty())
        {
            os << "KINETICS " << chemical_system_id + 1 << "\n";
            for (auto const& kinetic_reactant : kinetic_reactants)
            {
                kinetic_reactant.print(os, chemical_system_id);
            }
            os << "-steps " << phreeqc_io._dt << "\n\n";
        }

        auto const& surface = phreeqc_io._chemical_system->surface;
        if (!surface.empty())
        {
            os << "SURFACE " << chemical_system_id + 1 << "\n";
            std::size_t aqueous_solution_id =
                dump->aqueous_solutions_prev.empty()
                    ? chemical_system_id + 1
                    : phreeqc_io._num_chemical_systems + chemical_system_id + 1;
            os << "-equilibrate with solution " << aqueous_solution_id << "\n";

            // print unit
            if (std::holds_alternative<DensityBasedSurfaceSite>(
                    surface.front()))
            {
                os << "-sites_units density\n";
            }
            else
            {
                os << "-sites_units absolute\n";
            }

            for (auto const& surface_site : surface)
            {
                std::visit(
                    overloaded{[&os](DensityBasedSurfaceSite const& s)
                               {
                                   os << s.name << " " << s.site_density << " "
                                      << s.specific_surface_area << " "
                                      << s.mass << "\n";
                               },
                               [&os, chemical_system_id](
                                   MoleBasedSurfaceSite const& s) {
                                   os << s.name << " "
                                      << (*s.molality)[chemical_system_id]
                                      << "\n";
                               }},
                    surface_site);
            }

            // overlook the effect of the buildup of charges onto the surface
            if (std::holds_alternative<MoleBasedSurfaceSite>(surface.front()))
            {
                os << "-no_edl\n";
            }

            os << "SAVE solution " << chemical_system_id + 1 << "\n";
        }

        auto const& exchangers = phreeqc_io._chemical_system->exchangers;
        if (!exchangers.empty())
        {
            os << "EXCHANGE " << chemical_system_id + 1 << "\n";
            std::size_t const aqueous_solution_id =
                dump->aqueous_solutions_prev.empty()
                    ? chemical_system_id + 1
                    : phreeqc_io._num_chemical_systems + chemical_system_id + 1;
            os << "-equilibrate with solution " << aqueous_solution_id << "\n";
            for (auto const& exchanger : exchangers)
            {
                exchanger.print(os, chemical_system_id);
            }
            os << "SAVE solution " << chemical_system_id + 1 << "\n";
        }

        os << "END\n\n";
    }

    auto const& dump = phreeqc_io._dump;
    if (dump)
    {
        dump->print(os, phreeqc_io._num_chemical_systems);
    }

    return os;
}

void PhreeqcIO::callPhreeqc()
{
    INFO("Phreeqc: Executing chemical calculation.");
    if (RunFile(phreeqc_instance_id, _phreeqc_input_file.c_str()) != IPQ_OK)
    {
        OutputErrorString(phreeqc_instance_id);
        OGS_FATAL(
            "Failed in performing speciation calculation with the generated "
            "phreeqc input file '{:s}'.",
            _phreeqc_input_file);
    }
}

void PhreeqcIO::readOutputsFromFile()
{
    auto const& basic_output_setups = _output->basic_output_setups;
    auto const& phreeqc_result_file = basic_output_setups.output_file;
    DBUG("Reading phreeqc results from file '{:s}'.", phreeqc_result_file);
    std::ifstream in(phreeqc_result_file);

    if (!in)
    {
        OGS_FATAL("Could not open phreeqc result file '{:s}'.",
                  phreeqc_result_file);
    }

    in >> *this;

    if (!in)
    {
        OGS_FATAL("Error when reading phreeqc result file '{:s}'",
                  phreeqc_result_file);
    }

    in.close();
}

std::istream& operator>>(std::istream& in, PhreeqcIO& phreeqc_io)
{
    // Skip the headline
    in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    std::string line;
    auto const& output = *phreeqc_io._output;
    auto const& dropped_item_ids = output.dropped_item_ids;

    auto const& surface = phreeqc_io._chemical_system->surface;
    auto const& exchangers = phreeqc_io._chemical_system->exchangers;

    int const num_skipped_lines =
        1 + (!surface.empty() ? 1 : 0) + (!exchangers.empty() ? 1 : 0);

    auto& equilibrium_reactants =
        phreeqc_io._chemical_system->equilibrium_reactants;
    auto& kinetic_reactants = phreeqc_io._chemical_system->kinetic_reactants;

    for (std::size_t chemical_system_id = 0;
         chemical_system_id < phreeqc_io._num_chemical_systems;
         ++chemical_system_id)
    {
        // Skip equilibrium calculation result of initial solution
        for (int i = 0; i < num_skipped_lines; ++i)
        {
            in.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }

        // Get calculation result of the solution after the reaction
        if (!std::getline(in, line))
        {
            OGS_FATAL(
                "Error when reading calculation result of Solution {:d} "
                "after the reaction.",
                chemical_system_id);
        }

        std::vector<double> accepted_items;
        std::vector<std::string> items;
        boost::trim_if(line, boost::is_any_of("\t "));
        boost::algorithm::split(items, line, boost::is_any_of("\t "),
                                boost::token_compress_on);
        for (int item_id = 0; item_id < static_cast<int>(items.size());
             ++item_id)
        {
            if (std::find(dropped_item_ids.begin(), dropped_item_ids.end(),
                          item_id) == dropped_item_ids.end())
            {
                double value;
                try
                {
                    value = std::stod(items[item_id]);
                }
                catch (const std::invalid_argument& e)
                {
                    OGS_FATAL(
                        "Invalid argument. Could not convert string '{:s}' to "
                        "double for chemical system {:d}, column {:d}. "
                        "Exception '{:s}' was thrown.",
                        items[item_id], chemical_system_id + 1, item_id,
                        e.what());
                }
                catch (const std::out_of_range& e)
                {
                    OGS_FATAL(
                        "Out of range error. Could not convert string "
                        "'{:s}' to double for chemical system {:d}, column "
                        "{:d}. Exception '{:s}' was thrown.",
                        items[item_id], chemical_system_id + 1, item_id,
                        e.what());
                }
                accepted_items.push_back(value);
            }
        }
        assert(accepted_items.size() == output.accepted_items.size());

        auto& aqueous_solution = phreeqc_io._chemical_system->aqueous_solution;
        auto& components = aqueous_solution->components;
        auto& user_punch = phreeqc_io._user_punch;
        for (int item_id = 0; item_id < static_cast<int>(accepted_items.size());
             ++item_id)
        {
            auto const& accepted_item = output.accepted_items[item_id];
            auto const& item_name = accepted_item.name;

            auto compare_by_name = [&item_name](auto const& item)
            { return item.name == item_name; };

            switch (accepted_item.item_type)
            {
                case ItemType::pH:
                {
                    // Update pH value
                    MathLib::LinAlg::setLocalAccessibleVector(
                        *aqueous_solution->pH);
                    aqueous_solution->pH->set(
                        chemical_system_id,
                        std::pow(10, -accepted_items[item_id]));
                    break;
                }
                case ItemType::pe:
                {
                    // Update pe value
                    (*aqueous_solution->pe)[chemical_system_id] =
                        accepted_items[item_id];
                    break;
                }
                case ItemType::Component:
                {
                    // Update component concentrations
                    auto& component = BaseLib::findElementOrError(
                        components.begin(), components.end(), compare_by_name,
                        "Could not find component '" + item_name + "'.");
                    MathLib::LinAlg::setLocalAccessibleVector(
                        *component.amount);
                    component.amount->set(chemical_system_id,
                                          accepted_items[item_id]);
                    break;
                }
                case ItemType::EquilibriumReactant:
                {
                    // Update amounts of equilibrium reactant
                    auto& equilibrium_reactant = BaseLib::findElementOrError(
                        equilibrium_reactants.begin(),
                        equilibrium_reactants.end(), compare_by_name,
                        "Could not find equilibrium reactant '" + item_name +
                            "'.");
                    (*equilibrium_reactant.molality)[chemical_system_id] =
                        accepted_items[item_id];
                    break;
                }
                case ItemType::KineticReactant:
                {
                    // Update amounts of kinetic reactants
                    auto& kinetic_reactant = BaseLib::findElementOrError(
                        kinetic_reactants.begin(), kinetic_reactants.end(),
                        compare_by_name,
                        "Could not find kinetic reactant '" + item_name + "'.");
                    (*kinetic_reactant.molality)[chemical_system_id] =
                        accepted_items[item_id];
                    break;
                }
                case ItemType::SecondaryVariable:
                {
                    assert(user_punch);
                    auto& secondary_variables = user_punch->secondary_variables;
                    // Update values of secondary variables
                    auto& secondary_variable = BaseLib::findElementOrError(
                        secondary_variables.begin(), secondary_variables.end(),
                        compare_by_name,
                        "Could not find secondary variable '" + item_name +
                            "'.");
                    (*secondary_variable.value)[chemical_system_id] =
                        accepted_items[item_id];
                    break;
                }
            }
        }
    }

    return in;
}

std::vector<std::string> const PhreeqcIO::getComponentList() const
{
    std::vector<std::string> component_names;
    auto const& components = _chemical_system->aqueous_solution->components;
    std::transform(components.begin(), components.end(),
                   std::back_inserter(component_names),
                   [](auto const& c) { return c.name; });

    component_names.push_back("H");

    return component_names;
}

void PhreeqcIO::updateVolumeFractionPostReaction(
    GlobalIndexType const& chemical_system_id,
    MaterialPropertyLib::Medium const& medium,
    ParameterLib::SpatialPosition const& pos, double const porosity,
    double const t, double const dt)
{
    for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
    {
        updateReactantVolumeFraction(kinetic_reactant, chemical_system_id,
                                     medium, pos, porosity, t, dt);
    }

    for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
    {
        updateReactantVolumeFraction(equilibrium_reactant, chemical_system_id,
                                     medium, pos, porosity, t, dt);
    }
}

void PhreeqcIO::updatePorosityPostReaction(
    GlobalIndexType const& chemical_system_id,
    MaterialPropertyLib::Medium const& medium,
    double& porosity)
{
    for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
    {
        setPorosityPostReaction(kinetic_reactant, chemical_system_id, medium,
                                porosity);
    }

    for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
    {
        setPorosityPostReaction(equilibrium_reactant, chemical_system_id,
                                medium, porosity);
    }
}

void PhreeqcIO::computeSecondaryVariable(
    std::size_t const ele_id,
    std::vector<GlobalIndexType> const& chemical_system_indices)
{
    for (auto& kinetic_reactant : _chemical_system->kinetic_reactants)
    {
        (*kinetic_reactant.mesh_prop_molality)[ele_id] =
            averageReactantMolality(kinetic_reactant, chemical_system_indices);
    }

    for (auto& equilibrium_reactant : _chemical_system->equilibrium_reactants)
    {
        (*equilibrium_reactant.mesh_prop_molality)[ele_id] =
            averageReactantMolality(equilibrium_reactant,
                                    chemical_system_indices);
    }
}
}  // namespace PhreeqcIOData
}  // namespace ChemistryLib


#pragma once
#include <cstdint>
#include <ostream>
#include <filesystem>
#include <memory>
#include <Eigen/Dense>

#ifndef ORBTOOL_CONV
#include <foundation/PxVec3.h>
#include <characterkinematic/PxExtended.h>
#include <foundation/PxQuat.h>
#endif

#if defined _DEBUG

extern const char* monitor_file;
extern unsigned    monitor_line;

struct AllocEntry
{
	const void* alloc;
	const char* file;
	unsigned line;
	unsigned size;
};

struct AllocDictionary {
	AllocEntry* allocs;
	unsigned size;
	unsigned capacity;
};

static AllocDictionary alloc_dict;

extern bool prepare_monitoring();

static const auto monitoring_prepared = prepare_monitoring();

extern void grow_dictionary();

extern void shutdown_monitoring();

extern void log_allocations();

extern void monitor_allocation(const void* alloc, unsigned size, const char* file, unsigned line);

extern void monitor_deallocation(const void* alloc);

extern bool prepare_alloc(const char* file, unsigned line);

inline void* operator new(std::size_t size)
{
	auto alloc = malloc(size);
    auto fLen = strlen(monitor_file);
    char* file = (char*)malloc(fLen);
    std::memcpy(file, monitor_file, fLen);
	monitor_allocation(alloc, size, file, monitor_line);
	return alloc;
}

inline void operator delete(void* alloc)
{
	monitor_deallocation(alloc);
	free(alloc);
}

#define new prepare_alloc(__FILE__, __LINE__) ? 0 : new

#else

static void prepare_monitoring()
{
}

static void shutdown_monitoring()
{
}

#endif

#if defined (ORBIT_DIRECTX_11) || (ORBIT_DIRECTX_12)

#include <Windows.h>
#include <wrl/client.h>

namespace orbit
{
	namespace wrl = Microsoft::WRL;

	template<typename COM>
	using ComPtr = wrl::ComPtr<COM>;
}

#endif

#include <DirectXMath.h>

namespace orbit
{
#if defined (ORBIT_DIRECTX_11) || (ORBIT_DIRECTX_12)
    using namespace DirectX;
#endif

    using namespace Eigen;
    namespace fs = std::filesystem;

#ifndef ORBTOOL_CONV

    using namespace physx;

#endif

    // Objects in Orbit are identified by their unique-ID.
    using ResourceId = uint64_t;

    // A Slot can have context-dependant meaning. I.e. a constant buffer slot for a
    // shader resource. Or a Slot in the input layout.
    using Slot = uint32_t;

    class OpenGL_4_0 {};
    class DirectX_11 {};
    class DirectX_12 {};

    using Index = size_t;
    using Offset = size_t;

    using ShaderIndex = uint32_t;
    using MaterialFlags = uint32_t;

    using Byte = uint8_t;

    using Roughness = float;

    template<typename T>
    using SPtr = std::shared_ptr<T>;
    template<typename T>
    using UPtr = std::unique_ptr<T>;

    template<typename T>
    class Math
    {
    public:
        static constexpr T PI     = static_cast<T>(3.141592653589793238462643383279);
        static constexpr T PIDIV4 = static_cast<T>(PI * .25);
        static constexpr T PIDIV2 = static_cast<T>(PI * .5);
        static constexpr T TWO_PI = static_cast<T>(PI * 2.);

        static constexpr T AlignUp(const T& in, const T& alignment)
        {
            static_assert(std::is_integral<T>::value && "Math<T>::AlignUp() T must be integral type.");
            return (in + alignment - 1) & ~(alignment - 1);
        }
        static constexpr T AlignDown(const T& in, const T& alignment)
        {
            static_assert(std::is_integral<T>::value && "Math<T>::AlignDown() T must be integral type.");
            return in & ~(alignment - 1);
        }
        static constexpr Matrix<T, 4, 4> LookAt(
            const Matrix<T, 3, 1>& eye,
            const Matrix<T, 3, 1>& target,
            const Matrix<T, 3, 1>& up)
        {
            auto zaxis = (target - eye).normalized();
            auto xaxis = up.cross(zaxis).normalized();
            auto yaxis = zaxis.cross(xaxis);

            Matrix<T,4,4> matrix = Matrix<T, 4, 4>::Zero();
            matrix.block<3, 1>(0, 0) = xaxis;
            matrix.block<3, 1>(0, 1) = yaxis;
            matrix.block<3, 1>(0, 2) = zaxis;
            matrix(3, 0) = -xaxis.dot(eye);
            matrix(3, 1) = -yaxis.dot(eye);
            matrix(3, 2) = -zaxis.dot(eye);
            matrix(3, 3) = 1;

            return matrix;
        }
        static constexpr Matrix<T, 4, 4> Perspective(
            T vFOV,
            T aspectRatio,
            T nearZ,
            T farZ)
        {
            const auto d = 1.f / (nearZ - farZ);
            const auto a = 1.f / tan(.5f * vFOV);

            Matrix4f m = Matrix4f::Zero();
            m(0, 0) = a / aspectRatio;
            m(1, 1) = a;
            m(2, 2) = -(nearZ + farZ) * d;
            m(3, 2) = nearZ * farZ * d;
            m(2, 3) = 1;
            return m;
        }

        static constexpr Matrix<T, 4, 4> Orthogonal(T nearZ, T farZ, Vector4f normal)
        {
            const auto d = 1.f / (nearZ - farZ);
            Matrix4f m = Matrix4f::Identity() - normal * normal.transpose();
            m(2, 2) = -(nearZ + farZ) * d;
            m(3, 2) = nearZ * farZ * d;
            m(2, 3) = 1;
            return m;
        }

#ifndef ORBTOOL_CONV

        static constexpr PxVec3 EigenToPx3(const Vector3<T>& in)
        {
            return PxVec3(
                static_cast<float>(in.x()),
                static_cast<float>(in.y()),
                static_cast<float>(in.z())
            );
        }

        static constexpr Vector3<T> PxToEigen(const PxVec3& in)
        {
            return Vector3<T>{
                static_cast<T>(in.x),
                static_cast<T>(in.y),
                static_cast<T>(in.z)
            };
        }

        static constexpr Quaternion<T> PxToEigen(const PxQuat& in)
        {
            return Quaternion<T>{
                static_cast<T>(in.x),
                static_cast<T>(in.y),
                static_cast<T>(in.z),
                static_cast<T>(in.w)
            };
        }

        static constexpr Vector3<T> PxToEigen(const PxExtendedVec3& in)
        {
            return Vector3<T>{
                static_cast<T>(in.x),
                static_cast<T>(in.y),
                static_cast<T>(in.z)
            };
        }

        static constexpr PxQuat EigenToPx(const Quaternion<T>& in)
        {
            return PxQuat(
                static_cast<float>(in.x()),
                static_cast<float>(in.x()),
                static_cast<float>(in.z()),
                static_cast<float>(in.w())
            );
        }
#endif

    };

}

#ifdef ORBIT_OPENGL
  #ifndef GL_GLEXT_PROTOTYPES
    #define GL_GLEXT_PROTOTYPES 1
  #endif

  #include <GL/glut.h>
  #include <GL/gl.h>
#elif defined(ORBIT_DIRECTX_11)
  #include <d3d11.h>
  //#include <d3dx11.h>
#elif defined(ORBIT_DIRECTX_11)
  #include <d3d12.h>
  #include <d3dx12.h>
#endif