// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2021.
//
// This software is released under a three-clause BSD license:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * 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.
//  * Neither the name of any author or any participating institution
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission.
// For a full list of authors, refer to the file AUTHORS.
// --------------------------------------------------------------------------
// 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 ANY OF THE AUTHORS OR THE CONTRIBUTING
// INSTITUTIONS 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.
//
// --------------------------------------------------------------------------
// $Maintainer: Timo Sachsenberg $
// $Authors: Eva Lange $
// --------------------------------------------------------------------------
#include <algorithm>
#include <cmath>

#include <boost/math/special_functions/acosh.hpp>

#include <OpenMS/TRANSFORMATIONS/RAW2PEAK/OptimizePick.h>

using std::max;

namespace OpenMS
{

  OptimizePick::OptimizePick(
      const struct OptimizationFunctions::PenaltyFactors & penalties,
      const int max_iteration)
  {

    penalties_ = penalties;

    max_iteration_ = max_iteration;

#ifdef DEBUG_PEAK_PICKING
    std::cout << "max iteration " << max_iteration_
              << "\n penalty factor pos " << penalties.pos
              << "\n penalty factor left width " << penalties.lWidth
              << "\n penalty factor right width " << penalties.rWidth
              << std::endl;
#endif

  }

  OptimizePick::~OptimizePick()
  {
  }

  void OptimizePick::optimize(std::vector<PeakShape> & peaks, Data & data)
  {
    if (peaks.empty())
      return;

    size_t global_peak_number = 0;
    data.peaks.assign(peaks.begin(), peaks.end());

    size_t num_dimensions = 4 * data.peaks.size();
    Eigen::VectorXd x_init (num_dimensions);
    x_init.setZero();
    // We have to initialize the parameters for the optimization
    for (size_t i = 0; i < data.peaks.size(); i++)
    {
      PeakShape current_peak = data.peaks[i];
      double h  = current_peak.height;
      double wl = current_peak.left_width;
      double wr = current_peak.right_width;
      double p  = current_peak.mz_position;
      if (boost::math::isnan(wl))
      {
        data.peaks[i].left_width = 1;
        wl = 1.;
      }
      if (boost::math::isnan(wr))
      {
        data.peaks[i].right_width = 1;
        wr = 1.;
      }
      x_init(4 * i) = h;
      x_init(4 * i + 1) = wl;
      x_init(4 * i + 2) = wr;
      x_init(4 * i + 3) = p;
    }

    data.penalties = penalties_;

    unsigned num_data_points = std::max(data.positions.size() + 1, num_dimensions);
    OptPeakFunctor functor (num_dimensions, num_data_points, &data);
    Eigen::LevenbergMarquardt<OptPeakFunctor> lmSolver (functor);
    lmSolver.parameters.maxfev = max_iteration_;
    Eigen::LevenbergMarquardtSpace::Status status = lmSolver.minimize(x_init);
    //the states are poorly documented. after checking the source, we believe that
    //all states except NotStarted, Running and ImproperInputParameters are good
    //termination states.
    if (status <= Eigen::LevenbergMarquardtSpace::ImproperInputParameters)
    {
        throw Exception::UnableToFit(__FILE__, __LINE__, OPENMS_PRETTY_FUNCTION, "UnableToFit-OptimizePeak:", "Could not fit the data: Error " + String(status));
    }

    // iterate over all peaks and store the optimized values in peaks
    for (size_t current_peak = 0; current_peak < data.peaks.size(); current_peak++)
    {
      // Store the current parameters for this peak
      peaks[global_peak_number + current_peak].height =  x_init(4 * current_peak);
      peaks[global_peak_number + current_peak].mz_position = x_init(4 * current_peak + 3);
      peaks[global_peak_number + current_peak].left_width = x_init(4 * current_peak + 1);
      peaks[global_peak_number + current_peak].right_width = x_init(4 * current_peak + 2);

      // compute the area
      // is it a Lorentz or a Sech - Peak?
      if (peaks[global_peak_number + current_peak].type == PeakShape::LORENTZ_PEAK)
      {
        PeakShape p = peaks[global_peak_number + current_peak];
        double x_left_endpoint = p.mz_position - 1 / p.left_width * sqrt(p.height / 1 - 1);
        double x_right_endpoint = p.mz_position + 1 / p.right_width * sqrt(p.height / 1 - 1);
        double area_left = -p.height / p.left_width * atan(p.left_width * (x_left_endpoint - p.mz_position));
        double area_right = -p.height / p.right_width * atan(p.right_width * (p.mz_position - x_right_endpoint));
        peaks[global_peak_number + current_peak].area = area_left + area_right;
#ifdef DEBUG_PEAK_PICKING
        std::cout << "Lorentz " << area_left << " " << area_right
                  << " " << peaks[global_peak_number + current_peak].area << std::endl;
#endif
      }
      else  //It's a Sech - Peak
      {
        PeakShape p = peaks[global_peak_number + current_peak];
        double x_left_endpoint = p.mz_position - 1 / p.left_width * boost::math::acosh(sqrt(p.height / 0.001));
        double x_right_endpoint = p.mz_position + 1 / p.right_width * boost::math::acosh(sqrt(p.height / 0.001));
        double area_left = p.height / p.left_width * (sinh(p.left_width * (p.mz_position - x_left_endpoint)) / cosh(p.left_width * (p.mz_position - x_left_endpoint)));
        double area_right = -p.height / p.right_width * (sinh(p.right_width * (p.mz_position - x_right_endpoint)) / cosh(p.right_width * (p.mz_position - x_right_endpoint)));
        peaks[global_peak_number + current_peak].area = area_left + area_right;
#ifdef DEBUG_PEAK_PICKING
        std::cout << "Sech " << area_left << " " << area_right
                  << " " << peaks[global_peak_number + current_peak].area << std::endl;
        std::cout << p.mz_position << " " << x_left_endpoint << " " << x_right_endpoint << std::endl;
#endif
      }
    }
    //global_peak_number += data.peaks.size();

  }

  int OptimizePick::OptPeakFunctor::operator()(const Eigen::VectorXd &x, Eigen::VectorXd &fvec)
  {
    const std::vector<double> & signal = m_data->signal;
    const std::vector<double> & positions = m_data->positions;
    const std::vector<PeakShape> & peaks = m_data->peaks;
    const OptimizationFunctions::PenaltyFactors & penalties = m_data->penalties;
    // iterate over all points of the signal
    for (size_t current_point = 0; current_point < positions.size(); current_point++)
    {
      double computed_signal = 0.;
      double current_position = positions[current_point];
      double experimental_signal = signal[current_point];

      // iterate over all peaks
      for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
      {
        // Store the current parameters for this peak
        double p_height = x(4 * current_peak);
        double p_position = x(4 * current_peak + 3);
        double p_width
            = (current_position <= p_position) ? x(4 * current_peak + 1)
                                 : x(4 * current_peak + 2);

        // is it a Lorentz or a Sech - Peak?
        if (peaks[current_peak].type == PeakShape::LORENTZ_PEAK)
        {
          computed_signal += p_height / (1. + pow(p_width * (current_position - p_position), 2));
        }
        else // It's a Sech - Peak
        {
          computed_signal += p_height / pow(cosh(p_width * (current_position - p_position)), 2);
        }
      }
      fvec(current_point) = computed_signal - experimental_signal;
    }

    double penalty = 0.;
    double penalty_pos    = penalties.pos;
    double penalty_lwidth = penalties.lWidth;
    double penalty_rwidth = penalties.rWidth;

    // iterate over all peaks again to compute the penalties
    for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
    {
      double old_position = peaks[current_peak].mz_position;
      double old_width_l = peaks[current_peak].left_width;
      double old_width_r = peaks[current_peak].right_width;
      double p_position = x(4 * current_peak + 3);
      double p_width_l = x(4 * current_peak + 1);
      double p_width_r = x(4 * current_peak + 2);

      //penalty += pow(p_position - old_position, 2) + pow(p_width_l - old_width_l, 2) + pow(p_width_r - old_width_r, 2);
      penalty += penalty_pos * pow(p_position - old_position, 2)
        + penalty_lwidth * pow(p_width_l - old_width_l, 2)
        + penalty_rwidth * pow(p_width_r - old_width_r, 2);
    }

    fvec(positions.size()) = 100 * penalty;

    return 0;
  }
  // compute Jacobian matrix for the different parameters
  int OptimizePick::OptPeakFunctor::df(const Eigen::VectorXd &x, Eigen::MatrixXd &J)
  {
    std::cout << "rows: " << J.rows() << " columns: " << J.cols() << std::endl;//DEBUG
    const std::vector<double> & positions = m_data->positions;
    const std::vector<PeakShape> & peaks = m_data->peaks;
    const OptimizationFunctions::PenaltyFactors & penalties = m_data->penalties;
    // iterate over all points of the signal
    for (size_t current_point = 0; current_point < positions.size(); current_point++)
    {
      double current_position = positions[current_point];

      // iterate over all peaks
      for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
      {
        // Store the current parameters for this peak
        double p_height = x(4 * current_peak);
        double p_position = x(4 * current_peak + 3);
        double p_width = (current_position <= p_position) ? x(4 * current_peak + 1)
                          : x(4 * current_peak + 2);

        // is it a Lorentz or a Sech - Peak?
        if (peaks[current_peak].type == PeakShape::LORENTZ_PEAK)
        {
          double diff = current_position - p_position;
          double denom_inv = 1. / (1. + pow(p_width * diff, 2));

          double ddl_left = (current_position <= p_position)
              ? -2 * p_height * pow(diff, 2) * p_width * pow(denom_inv, 2) : 0;

          double ddl_right = (current_position  > p_position)
              ? -2 * p_height * pow(diff, 2) * p_width * pow(denom_inv, 2) : 0;

          double ddx0 = -2 * p_height * pow(p_width, 2) * diff * pow(denom_inv, 2);

          J(current_point, 4 * current_peak) = denom_inv;
          J(current_point, 4 * current_peak + 1) = ddl_left;
          J(current_point, 4 * current_peak + 2) = ddl_right;
          J(current_point, 4 * current_peak + 3) = ddx0;
        }
        else // It's a Sech - Peak
        {
          double diff = current_position - p_position;
          double denom_inv = 1. / cosh(p_width * diff);

          // The remaining computations are not stable if denom_inv == 0. In that case, we are far away from the peak
          // and can assume that all derivatives vanish
          double sinh_term = (fabs(denom_inv) < 1e-6) ? 0.0 : sinh(p_width * diff);
          double ddl_left  = (current_position <= p_position)
              ? -2 * p_height * sinh_term * diff * pow(denom_inv, 3) : 0;
          double ddl_right = (current_position  > p_position)
              ? -2 * p_height * sinh_term * diff * pow(denom_inv, 3) : 0;
          double ddx0      = 2 * p_height * p_width * sinh_term * pow(denom_inv, 3);

          J(current_point, 4 * current_peak) = pow(denom_inv, 2);
          J(current_point, 4 * current_peak + 1) = ddl_left;
          J(current_point, 4 * current_peak + 2) = ddl_right;
          J(current_point, 4 * current_peak + 3) = ddx0;
        }
      }
    }

    // Now iterate over all peaks again to compute the penalties.
    for (size_t current_peak = 0; current_peak < peaks.size(); current_peak++)
    {
      double p_width_left = x(4 * current_peak + 1);
      double p_width_right = x(4 * current_peak + 2);
      double p_position = x(4 * current_peak + 3);

      double old_width_left = peaks[current_peak].left_width;
      double old_width_right = peaks[current_peak].right_width;
      double old_position = peaks[current_peak].mz_position;

      double penalty_l = 2. * penalties.lWidth * (p_width_left - old_width_left);
      double penalty_r = 2. * penalties.rWidth * (p_width_right - old_width_right);
      double penalty_p = 0;
      if (fabs(p_position - old_position) < 0.2)
      {
        penalty_p = 2. * penalties.pos * (p_position - old_position);
      }

      J(positions.size(), 4 * current_peak) = 0.;
      J(positions.size(), 4 * current_peak + 1) = 100 * penalty_l;
      J(positions.size(), 4 * current_peak + 2) = 100 * penalty_r;
      J(positions.size(), 4 * current_peak + 3) = 100 * penalty_p;
    }
    return 0;
  }
}//namespace


#include <istream>
#include <string>
#include <tuple>
#include <utility>
#include <algorithm>
#include <iterator>
#include <memory>
#include <stdexcept>
# if __cplusplus >= 201703L
#   include <type_traits>
# else
#   include <boost/type_traits/is_nothrow_swappable.hpp>
# endif

#include <boost/lexical_cast.hpp>

#include <boost/range/empty.hpp>
#include <boost/range/size.hpp>

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>

#ifndef BRA_NO_MPI
# include <yampi/communicator.hpp>
# include <yampi/environment.hpp>
#endif // BRA_NO_MPI

#include <ket/qubit.hpp>
#include <ket/control.hpp>
#include <ket/utility/integer_log2.hpp>
#include <ket/utility/integer_exp2.hpp>
#include <ket/utility/generate_phase_coefficients.hpp>

#include <bra/gates.hpp>
#include <bra/state.hpp>
#include <bra/gate/gate.hpp>
#include <bra/gate/hadamard.hpp>
#include <bra/gate/pauli_x.hpp>
#include <bra/gate/pauli_y.hpp>
#include <bra/gate/pauli_z.hpp>
#include <bra/gate/s_gate.hpp>
#include <bra/gate/adj_s_gate.hpp>
#include <bra/gate/t_gate.hpp>
#include <bra/gate/adj_t_gate.hpp>
#include <bra/gate/u1.hpp>
#include <bra/gate/u2.hpp>
#include <bra/gate/u3.hpp>
#include <bra/gate/phase_shift.hpp>
#include <bra/gate/adj_phase_shift.hpp>
#include <bra/gate/x_rotation_half_pi.hpp>
#include <bra/gate/adj_x_rotation_half_pi.hpp>
#include <bra/gate/y_rotation_half_pi.hpp>
#include <bra/gate/adj_y_rotation_half_pi.hpp>
#include <bra/gate/controlled_not.hpp>
#include <bra/gate/controlled_phase_shift.hpp>
#include <bra/gate/adj_controlled_phase_shift.hpp>
#include <bra/gate/controlled_v.hpp>
#include <bra/gate/adj_controlled_v.hpp>
#include <bra/gate/toffoli.hpp>
#include <bra/gate/projective_measurement.hpp>
#include <bra/gate/measurement.hpp>
#include <bra/gate/generate_events.hpp>
#include <bra/gate/shor_box.hpp>
#include <bra/gate/clear.hpp>
#include <bra/gate/set.hpp>
#include <bra/gate/depolarizing_channel.hpp>
#include <bra/gate/exit.hpp>

# if __cplusplus >= 201703L
#   define BRA_is_nothrow_swappable std::is_nothrow_swappable
# else
#   define BRA_is_nothrow_swappable boost::is_nothrow_swappable
# endif

namespace bra
{
  unsupported_mnemonic_error::unsupported_mnemonic_error(std::string const& mnemonic)
    : std::runtime_error{(mnemonic + " is not supported").c_str()}
  { }

  wrong_mnemonics_error::wrong_mnemonics_error(::bra::gates::columns_type const& columns)
    : std::runtime_error{generate_what_string(columns).c_str()}
  { }

  std::string wrong_mnemonics_error::generate_what_string(::bra::gates::columns_type const& columns)
  {
    auto result = std::string{};

    auto const last = columns.end();
    for (auto iter = columns.begin(); iter != last; ++iter)
    {
      result += *iter;
      result += " ";
    }

    return result;
  }

#ifndef BRA_NO_MPI
  wrong_mpi_communicator_size_error::wrong_mpi_communicator_size_error()
    : std::runtime_error{"communicator size is wrong"}
  { }
#endif // BRA_NO_MPI

#ifndef BRA_NO_MPI
  gates::gates()
    : data_{}, num_qubits_{}, num_lqubits_{}, num_uqubits_{}, num_processes_per_unit_{1u},
      initial_state_value_{}, initial_permutation_{}, phase_coefficients_{}, root_{}
  { }

  gates::gates(gates::allocator_type const& allocator)
    : data_{allocator}, num_qubits_{}, num_lqubits_{}, num_uqubits_{}, num_processes_per_unit_{1u},
      initial_state_value_{}, initial_permutation_{}, phase_coefficients_{}, root_{}
  { }

  gates::gates(gates&& other, gates::allocator_type const& allocator)
      : data_{std::move(other.data_), allocator},
        num_qubits_{std::move(other.num_qubits_)},
        num_lqubits_{std::move(other.num_lqubits_)},
        num_uqubits_{std::move(other.num_uqubits_)},
        num_processes_per_unit_{std::move(other.num_processes_per_unit_)},
        initial_state_value_{std::move(other.initial_state_value_)},
        initial_permutation_{std::move(other.initial_permutation_)},
        phase_coefficients_{std::move(other.phase_coefficients_)},
        root_{std::move(other.root_)}
  { }
#else // BRA_NO_MPI
  gates::gates()
    : data_{}, num_qubits_{},
      initial_state_value_{}, phase_coefficients_{}
  { }

  gates::gates(gates::allocator_type const& allocator)
    : data_{allocator}, num_qubits_{},
      initial_state_value_{}, phase_coefficients_{}
  { }

  gates::gates(gates&& other, gates::allocator_type const& allocator)
      : data_{std::move(other.data_), allocator},
        num_qubits_{std::move(other.num_qubits_)},
        initial_state_value_{std::move(other.initial_state_value_)},
        phase_coefficients_{std::move(other.phase_coefficients_)}
  { }
#endif // BRA_NO_MPI

#ifndef BRA_NO_MPI
  gates::gates(
    std::istream& input_stream,
    bit_integer_type num_uqubits, unsigned int num_processes_per_unit,
    yampi::environment const& environment,
    yampi::rank const root, yampi::communicator const& communicator,
    size_type const num_reserved_gates)
    : data_{}, num_qubits_{}, num_lqubits_{},
      num_uqubits_{num_uqubits}, num_processes_per_unit_{num_processes_per_unit},
      initial_state_value_{}, initial_permutation_{}, phase_coefficients_{}, root_{root}
  {
    assert(num_processes_per_unit >= 1u);
    assign(input_stream, environment, communicator, num_reserved_gates);
  }
#else // BRA_NO_MPI
  gates::gates(std::istream& input_stream)
    : data_{}, num_qubits_{},
      initial_state_value_{}, phase_coefficients_{}
  { assign(input_stream, size_type{0u}); }

  gates::gates(std::istream& input_stream, size_type const num_reserved_gates)
    : data_{}, num_qubits_{},
      initial_state_value_{}, phase_coefficients_{}
  { assign(input_stream, num_reserved_gates); }
#endif // BRA_NO_MPI

  bool gates::operator==(gates const& other) const
  {
#ifndef BRA_NO_MPI
    return data_ == other.data_
      and num_qubits_ == other.num_qubits_
      and num_lqubits_ == other.num_lqubits_
      and num_uqubits_ == other.num_uqubits_
      and num_processes_per_unit_ == other.num_processes_per_unit_
      and initial_state_value_ == other.initial_state_value_
      and initial_permutation_ == other.initial_permutation_
      and phase_coefficients_ == other.phase_coefficients_
      and root_ == other.root_;
#else // BRA_NO_MPI
    return data_ == other.data_
      and num_qubits_ == other.num_qubits_
      and initial_state_value_ == other.initial_state_value_
      and phase_coefficients_ == other.phase_coefficients_;
#endif // BRA_NO_MPI
  }

#ifndef BRA_NO_MPI
  void gates::num_qubits(
    bit_integer_type const new_num_qubits,
    yampi::communicator const& communicator, yampi::environment const& environment)
  {
    auto const num_gqubits
      = ket::utility::integer_log2<bit_integer_type>(
          communicator.size(environment) / num_processes_per_unit_);
    set_num_qubits_params(new_num_qubits - num_gqubits - num_uqubits_, num_gqubits, communicator, environment);
  }

  void gates::num_lqubits(
    bit_integer_type const new_num_lqubits,
    yampi::communicator const& communicator, yampi::environment const& environment)
  {
    set_num_qubits_params(
      new_num_lqubits,
      ket::utility::integer_log2<bit_integer_type>(
        communicator.size(environment) / num_processes_per_unit_),
      communicator, environment);
  }
#else // BRA_NO_MPI
  void gates::num_qubits(bit_integer_type const new_num_qubits)
  { set_num_qubits_params(new_num_qubits); }
#endif // BRA_NO_MPI

#ifndef BRA_NO_MPI
  void gates::set_num_qubits_params(
    bit_integer_type const new_num_lqubits, bit_integer_type const num_gqubits,
    yampi::communicator const& communicator, yampi::environment const& environment)
  {
    if (ket::utility::integer_exp2<bit_integer_type>(num_gqubits) * num_processes_per_unit_
        != static_cast<bit_integer_type>(communicator.size(environment)))
      throw wrong_mpi_communicator_size_error{};

    num_lqubits_ = new_num_lqubits;
    num_qubits_ = new_num_lqubits + num_uqubits_ + num_gqubits;
    ket::utility::generate_phase_coefficients(phase_coefficients_, num_qubits_);

    initial_permutation_.clear();
    initial_permutation_.reserve(num_qubits_);
    for (auto bit = bit_integer_type{0u}; bit < num_qubits_; ++bit)
      initial_permutation_.push_back(permutated_qubit_type{bit});
  }
#else // BRA_NO_MPI
  void gates::set_num_qubits_params(bit_integer_type const new_num_qubits)
  {
    num_qubits_ = new_num_qubits;
    ket::utility::generate_phase_coefficients(phase_coefficients_, num_qubits_);
  }
#endif // BRA_NO_MPI

#ifndef BRA_NO_MPI
  void gates::assign(
    std::istream& input_stream, yampi::environment const& environment,
    yampi::communicator const& communicator, size_type const num_reserved_gates)
#else // BRA_NO_MPI
  void gates::assign(std::istream& input_stream, size_type const num_reserved_gates)
#endif // BRA_NO_MPI
  {
    data_.clear();
    data_.reserve(num_reserved_gates);

    auto line = std::string{};
    auto columns = columns_type{};
    columns.reserve(10u);

    while (std::getline(input_stream, line))
    {
      if (line.empty())
        continue;

      line.erase(std::find(line.begin(), line.end(), '!'), line.end());
      boost::algorithm::trim(line);
      if (line.empty())
        continue;

      boost::algorithm::split(
        columns, line, boost::algorithm::is_space(),
        boost::algorithm::token_compress_on);

      if (boost::empty(columns))
        continue;

      boost::algorithm::to_upper(columns.front());
      auto const& first_mnemonic = columns.front();
      if (first_mnemonic == "QUBITS")
      {
#ifndef BRA_NO_MPI
        num_qubits(
          static_cast< ::bra::state::bit_integer_type >(read_num_qubits(columns)),
          communicator, environment);
#else // BRA_NO_MPI
        num_qubits(
          static_cast< ::bra::state::bit_integer_type >(read_num_qubits(columns)));
#endif // BRA_NO_MPI
      }
      else if (first_mnemonic == "INITIAL") // INITIAL STATE
        initial_state_value_
          = static_cast< ::bra::state::state_integer_type >(read_initial_state_value(columns));
      else if (first_mnemonic == "MPIPROCESSES")
      {
        read_num_mpi_processes(columns);
        // ignore this statement
      }
      else if (first_mnemonic == "MPISWAPBUFFER")
      {
        read_mpi_buffer_size(columns);
        // ignore this statement
      }
      else if (first_mnemonic == "BIT") // BIT ASSIGNMENT
      {
        auto const statement = read_bit_statement(columns);

        if (statement == ::bra::bit_statement::assignment)
        {
#ifndef BRA_NO_MPI
          initial_permutation_ = read_initial_permutation(columns);
#endif
        }
      }
      else if (first_mnemonic == "PERMUTATION")
        throw unsupported_mnemonic_error{first_mnemonic};
      else if (first_mnemonic == "RANDOM") // RANDOM PERMUTATION
        throw unsupported_mnemonic_error{first_mnemonic};
      else if (first_mnemonic == "I")
        continue;
      else if (first_mnemonic == "H")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::hadamard{read_hadamard(columns)}});
      else if (first_mnemonic == "X")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::pauli_x{read_pauli_x(columns)}});
      else if (first_mnemonic == "Y")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::pauli_y{read_pauli_y(columns)}});
      else if (first_mnemonic == "Z")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::pauli_z{read_pauli_z(columns)}});
      else if (first_mnemonic == "S")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::s_gate{
              phase_coefficients_[2u], read_s_gate(columns)}});
      else if (first_mnemonic == "S+")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_s_gate{
              phase_coefficients_[2u], read_adj_s_gate(columns)}});
      else if (first_mnemonic == "T")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::t_gate{
              phase_coefficients_[3u], read_t_gate(columns)}});
      else if (first_mnemonic == "T+")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_t_gate{
              phase_coefficients_[3u], read_adj_t_gate(columns)}});
      else if (first_mnemonic == "U1")
      {
        auto target = qubit_type{};
        auto phase = real_type{};
        std::tie(target, phase) = read_u1(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::u1{phase, target}});
      }
      else if (first_mnemonic == "U2")
      {
        auto target = qubit_type{};
        auto phase1 = real_type{};
        auto phase2 = real_type{};
        std::tie(target, phase1, phase2) = read_u2(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::u2{phase1, phase2, target}});
      }
      else if (first_mnemonic == "U3")
      {
        auto target = qubit_type{};
        auto phase1 = real_type{};
        auto phase2 = real_type{};
        auto phase3 = real_type{};
        std::tie(target, phase1, phase2, phase3) = read_u3(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::u3{phase1, phase2, phase3, target}});
      }
      else if (first_mnemonic == "R" or first_mnemonic == "+R")
      {
        auto target = qubit_type{};
        auto phase_exponent = int{};
        std::tie(target, phase_exponent) = read_phase_shift(columns);

        if (phase_exponent >= 0)
          data_.push_back(std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::phase_shift{
              phase_exponent, phase_coefficients_[phase_exponent], target}});
        else
        {
          phase_exponent *= -1;
          data_.push_back(std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_phase_shift{
              phase_exponent, phase_coefficients_[phase_exponent], target}});
        }
      }
      else if (first_mnemonic == "-R")
      {
        auto target = qubit_type{};
        auto phase_exponent = int{};
        std::tie(target, phase_exponent) = read_phase_shift(columns);

        if (phase_exponent >= 0)
          data_.push_back(std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_phase_shift{
              phase_exponent, phase_coefficients_[phase_exponent], target}});
        else
        {
          phase_exponent *= -1;
          data_.push_back(std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::phase_shift{
              phase_exponent, phase_coefficients_[phase_exponent], target}});
        }
      }
      else if (first_mnemonic == "+X")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::x_rotation_half_pi{read_x_rotation_half_pi(columns)}});
      else if (first_mnemonic == "-X")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_x_rotation_half_pi{read_adj_x_rotation_half_pi(columns)}});
      else if (first_mnemonic == "+Y")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::y_rotation_half_pi{read_y_rotation_half_pi(columns)}});
      else if (first_mnemonic == "-Y")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::adj_y_rotation_half_pi{read_adj_y_rotation_half_pi(columns)}});
      else if (first_mnemonic == "CNOT")
      {
        auto control = control_qubit_type{};
        auto target = qubit_type{};
        std::tie(control, target) = read_controlled_not(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::controlled_not{target, control}});
      }
      else if (first_mnemonic == "U")
      {
        auto control = control_qubit_type{};
        auto target = qubit_type{};
        auto phase_exponent = int{};
        std::tie(control, target, phase_exponent) = read_controlled_phase_shift(columns);

        if (phase_exponent >= 0)
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::controlled_phase_shift{
                phase_exponent, phase_coefficients_[phase_exponent], target, control}});
        else
        {
          phase_exponent *= -1;
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::adj_controlled_phase_shift{
                phase_exponent, phase_coefficients_[phase_exponent], target, control}});
        }
      }
      else if (first_mnemonic == "V")
      {
        auto control = control_qubit_type{};
        auto target = qubit_type{};
        auto phase_exponent = int{};
        std::tie(control, target, phase_exponent) = read_controlled_v(columns);

        if (phase_exponent >= 0)
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::controlled_v{
                phase_exponent, phase_coefficients_[phase_exponent], target, control}});
        else
        {
          phase_exponent *= -1;
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::adj_controlled_v{
                phase_exponent, phase_coefficients_[phase_exponent], target, control}});
        }
      }
      else if (first_mnemonic == "TOFFOLI")
      {
        auto control1 = control_qubit_type{};
        auto control2 = control_qubit_type{};
        auto target = qubit_type{};
        std::tie(control1, control2, target) = read_toffoli(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::toffoli{target, control1, control2}});
      }
      else if (first_mnemonic == "M")
      {
#ifndef BRA_NO_MPI
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::projective_measurement{read_projective_measurement(columns), root_}});
#else // BRA_NO_MPI
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::projective_measurement{read_projective_measurement(columns)}});
#endif // BRA_NO_MPI
      }
      else if (first_mnemonic == "SHORBOX")
      {
        auto num_exponent_qubits = bit_integer_type{};
        auto divisor = state_integer_type{};
        auto base = state_integer_type{};
        std::tie(num_exponent_qubits, divisor, base) = read_shor_box(columns);

        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::shor_box{num_exponent_qubits, divisor, base}});
      }
      else if (first_mnemonic == "BEGIN") // BEGIN MEASUREMENT/LEARNING MACHINE
      {
        auto const statement = read_begin_statement(columns);

        if (statement == ::bra::begin_statement::measurement)
        {
#ifndef BRA_NO_MPI
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{new ::bra::gate::measurement{root_}});
#else // BRA_NO_MPI
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{new ::bra::gate::measurement{}});
#endif // BRA_NO_MPI
        }
        else if (statement == ::bra::begin_statement::learning_machine)
          throw unsupported_mnemonic_error{first_mnemonic};
      }
      else if (first_mnemonic == "DO") // DO MEASUREMENT
      {
        /*
        auto const statement = read_do_statement(columns);

        if (statement == do_statement::error)
          throw wrong_mnemonics_error{columns};
        else if (statement == do_statement::measurement)
          throw unsupported_mnemonic_error{first_mnemonic};
          */
        throw unsupported_mnemonic_error{first_mnemonic};
      }
      else if (first_mnemonic == "END") // END MEASUREMENT/LEARNING MACHINE
      {
        /*
        auto const statement = read_end_statement(columns);

        if (statement == ::bra::end_statement::measurement)
        {
#ifndef BRA_NO_MPI
          data_.push_back(std::make_unique< ::bra::gate::measurement >(root_));
#else // BRA_NO_MPI
          data_.push_back(std::make_unique< ::bra::gate::measurement >());
#endif // BRA_NO_MPI
        }
        else if (statement == ::bra::end_statement::learning_machine)
          throw unsupported_mnemonic_error{first_mnemonic};
*/
        throw unsupported_mnemonic_error{first_mnemonic};
      }
      else if (first_mnemonic == "GENERATE") // GENERATE EVENTS
      {
        auto statement = ::bra::generate_statement{};
        auto num_events = int{};
        auto seed = int{};
        std::tie(statement, num_events, seed) = read_generate_statement(columns);

        if (statement == ::bra::generate_statement::events)
        {
#ifndef BRA_NO_MPI
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::generate_events{root_, num_events, seed}});
#else // BRA_NO_MPI
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::generate_events{num_events, seed}});
#endif // BRA_NO_MPI
          break;
        }
      }
      else if (first_mnemonic == "CLEAR")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::clear{read_clear(columns)}});
      else if (first_mnemonic == "SET")
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{
            new ::bra::gate::set{read_set(columns)}});
      else if (first_mnemonic == "DEPOLARIZING")
      {
        auto statement = ::bra::depolarizing_statement{};
        auto px = real_type{};
        auto py = real_type{};
        auto pz = real_type{};
        auto seed = int{};
        std::tie(statement, px, py, pz, seed) = read_depolarizing_statement(columns);

        if (statement == ::bra::depolarizing_statement::channel)
          data_.push_back(
            std::unique_ptr< ::bra::gate::gate >{
              new ::bra::gate::depolarizing_channel{px, py, pz, seed}});
        else
          throw unsupported_mnemonic_error{first_mnemonic};
      }
      else if (first_mnemonic == "EXIT")
      {
        if (boost::size(columns) != 1u)
          throw wrong_mnemonics_error{columns};

#ifndef BRA_NO_MPI
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{new ::bra::gate::exit{root_}});
#else // BRA_NO_MPI
        data_.push_back(
          std::unique_ptr< ::bra::gate::gate >{new ::bra::gate::exit{}});
#endif // BRA_NO_MPI
        break;
      }
      else
        throw unsupported_mnemonic_error{first_mnemonic};
    }
  }

  void gates::swap(gates& other)
    noexcept(
      BRA_is_nothrow_swappable<data_type>::value
      and BRA_is_nothrow_swappable<bit_integer_type>::value
      and BRA_is_nothrow_swappable<state_integer_type>::value
      and BRA_is_nothrow_swappable<qubit_type>::value)
  {
    using std::swap;
#ifndef BRA_NO_MPI
    swap(data_, other.data_);
    swap(num_qubits_, other.num_qubits_);
    swap(num_lqubits_, other.num_lqubits_);
    swap(initial_state_value_, other.initial_state_value_);
    swap(initial_permutation_, other.initial_permutation_);
    swap(phase_coefficients_, other.phase_coefficients_);
    swap(root_, other.root_);
#else // BRA_NO_MPI
    swap(data_, other.data_);
    swap(num_qubits_, other.num_qubits_);
    swap(initial_state_value_, other.initial_state_value_);
    swap(phase_coefficients_, other.phase_coefficients_);
#endif // BRA_NO_MPI
  }

  gates::bit_integer_type gates::read_num_qubits(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 2u)
      throw ::bra::wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    return boost::lexical_cast<bit_integer_type>(*++iter);
  }

  gates::state_integer_type gates::read_initial_state_value(gates::columns_type& columns) const
  {
    if (boost::size(columns) != 3u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    boost::algorithm::to_upper(*++iter);
    if (columns[1] != "STATE")
      throw wrong_mnemonics_error{columns};

    return boost::lexical_cast<state_integer_type>(*++iter);
  }

  gates::bit_integer_type gates::read_num_mpi_processes(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 2u)
      throw ::bra::wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    return boost::lexical_cast<bit_integer_type>(*++iter);
  }

  gates::state_integer_type gates::read_mpi_buffer_size(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 2u)
      throw ::bra::wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    return boost::lexical_cast<state_integer_type>(*++iter);
  }

#ifndef BRA_NO_MPI
  std::vector<gates::permutated_qubit_type>
  gates::read_initial_permutation(gates::columns_type const& columns) const
  {
    auto result = std::vector<permutated_qubit_type>{};
    result.reserve(boost::size(columns)-2u);

    auto iter = std::begin(columns);
    ++iter;
    ++iter;

    auto const last = std::end(columns);
    for (; iter != last; ++iter)
      result.push_back(static_cast<permutated_qubit_type>(boost::lexical_cast<bit_integer_type>(*iter)));

    return result;
  }
#endif

  gates::qubit_type gates::read_target(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 2u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);

    return ket::make_qubit<state_integer_type>(target);
  }

  std::tuple<gates::qubit_type, gates::real_type>
  gates::read_target_phase(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 3u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const phase = boost::lexical_cast<real_type>(*++iter);

    return std::make_tuple(ket::make_qubit<state_integer_type>(target), phase);
  }

  std::tuple<gates::qubit_type, gates::real_type, gates::real_type>
  gates::read_target_2phases(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 4u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const phase1 = boost::lexical_cast<real_type>(*++iter);
    auto const phase2 = boost::lexical_cast<real_type>(*++iter);

    return std::make_tuple(ket::make_qubit<state_integer_type>(target), phase1, phase2);
  }

  std::tuple<gates::qubit_type, gates::real_type, gates::real_type, gates::real_type>
  gates::read_target_3phases(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 5u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const phase1 = boost::lexical_cast<real_type>(*++iter);
    auto const phase2 = boost::lexical_cast<real_type>(*++iter);
    auto const phase3 = boost::lexical_cast<real_type>(*++iter);

    return std::make_tuple(ket::make_qubit<state_integer_type>(target), phase1, phase2, phase3);
  }

  std::tuple<gates::qubit_type, int> gates::read_target_phaseexp(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 3u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const phase_exponent = boost::lexical_cast<int>(*++iter);

    return std::make_tuple(ket::make_qubit<state_integer_type>(target), phase_exponent);
  }

  std::tuple<gates::control_qubit_type, gates::qubit_type> gates::read_control_target(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 3u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const control = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);

    return std::make_tuple(
      ket::make_control(ket::make_qubit<state_integer_type>(control)),
      ket::make_qubit<state_integer_type>(target));
  }

  std::tuple<gates::control_qubit_type, gates::qubit_type, int>
  gates::read_control_target_phaseexp(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 4u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const control = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const phase_exponent = boost::lexical_cast<int>(*++iter);

    return std::make_tuple(
      ket::make_control(ket::make_qubit<state_integer_type>(control)),
      ket::make_qubit<state_integer_type>(target),
      phase_exponent);
  }

  std::tuple<gates::control_qubit_type, gates::control_qubit_type, gates::qubit_type>
  gates::read_2controls_target(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 4u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const control1 = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const control2 = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const target = boost::lexical_cast<bit_integer_type>(*++iter);

    return std::make_tuple(
      ket::make_control(ket::make_qubit<state_integer_type>(control1)),
      ket::make_control(ket::make_qubit<state_integer_type>(control2)),
      ket::make_qubit<state_integer_type>(target));
  }

  ::bra::begin_statement gates::read_begin_statement(gates::columns_type& columns) const
  {
    auto const column_size = boost::size(columns);

    if (column_size <= 1u or column_size >= 4u)
      throw wrong_mnemonics_error{columns};

    if (column_size == 3u)
    {
      auto iter = std::begin(columns);
      boost::algorithm::to_upper(*++iter);
      if (*iter == "LEARNING")
      {
        boost::algorithm::to_upper(*++iter);

        if (*iter == "MACHINE")
          return ::bra::begin_statement::learning_machine;
        else
          throw wrong_mnemonics_error{columns};
      }
      else
        throw wrong_mnemonics_error{columns};
    }

    // if (column_size == 2u)
    auto iter = std::begin(columns);
    boost::algorithm::to_upper(*++iter);

    if (*iter != "MEASUREMENT")
      throw wrong_mnemonics_error{columns};

    return ::bra::begin_statement::measurement;
  }

  ::bra::bit_statement gates::read_bit_statement(gates::columns_type& columns) const
  {
    if (boost::size(columns) <= 1u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    boost::algorithm::to_upper(*++iter);

    if (*iter != "ASSIGNMENT")
      throw wrong_mnemonics_error{columns};

    return ::bra::bit_statement::assignment;
  }

  std::tuple<gates::bit_integer_type, gates::state_integer_type, gates::state_integer_type>
  gates::read_shor_box(gates::columns_type const& columns) const
  {
    if (boost::size(columns) != 4u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    auto const num_exponent_qubits = boost::lexical_cast<bit_integer_type>(*++iter);
    auto const divisor = boost::lexical_cast<state_integer_type>(*++iter);
    auto const base = boost::lexical_cast<state_integer_type>(*++iter);

    return std::make_tuple(num_exponent_qubits, divisor, base);
  }

  std::tuple< ::bra::generate_statement, int, int > gates::read_generate_statement(gates::columns_type& columns) const
  {
    if (boost::size(columns) != 4u)
      throw wrong_mnemonics_error{columns};

    auto iter = std::begin(columns);
    boost::algorithm::to_upper(*++iter);

    if (*iter != "EVENTS")
      throw wrong_mnemonics_error{columns};

    auto const num_events = boost::lexical_cast<int>(*++iter);
    auto const seed = boost::lexical_cast<int>(*++iter);
    return std::make_tuple(::bra::generate_statement::events, num_events, seed);
  }

  std::tuple< ::bra::depolarizing_statement, gates::real_type, gates::real_type, gates::real_type, int >
  gates::read_depolarizing_statement(gates::columns_type& columns) const
  {
    if (boost::size(columns) <= 2u)
      throw wrong_mnemonics_error{columns};

    auto iter = columns.cbegin();
    auto present_string = std::string{*++iter}; // present_string == "CHANNEL"
    boost::algorithm::to_upper(present_string);

    if (present_string != "CHANNEL")
      throw wrong_mnemonics_error{columns};

    auto px = real_type{};
    auto py = real_type{};
    auto pz = real_type{};
    auto seed = -1;
    auto is_px_checked = false;
    auto is_py_checked = false;
    auto is_pz_checked = false;
    auto is_seed_checked = false;

    present_string = *++iter; // present_string == "P_*" or "P_*=" or "P_*=0.xxx" or "P_*=0.xxx," or "P_*=0.xxx,..."
    auto probability_string = std::string{"    "};
    auto const last = columns.cend();
    while (iter != last)
    {
      auto string_found = std::find(present_string.cbegin(), present_string.cend(), '=');
      probability_string.assign(present_string.cbegin(), string_found);
      boost::algorithm::to_upper(probability_string);
      if (probability_string == "P_X")
      {
        if (is_px_checked)
          throw wrong_mnemonics_error{columns};

        ::bra::gates_detail::read_depolarizing_statement(px, present_string, iter, last, string_found, columns);
        if (px < 0.0 or px > 1.0)
          throw wrong_mnemonics_error{columns};

        is_px_checked = true;
      }
      else if (probability_string == "P_Y")
      {
        if (is_py_checked)
          throw wrong_mnemonics_error{columns};

        ::bra::gates_detail::read_depolarizing_statement(py, present_string, iter, last, string_found, columns);
        if (py < 0.0 or py > 1.0)
          throw wrong_mnemonics_error{columns};

        is_py_checked = true;
      }
      else if (probability_string == "P_Z")
      {
        if (is_pz_checked)
          throw wrong_mnemonics_error{columns};

        ::bra::gates_detail::read_depolarizing_statement(pz, present_string, iter, last, string_found, columns);
        if (pz < 0.0 or pz > 1.0)
          throw wrong_mnemonics_error{columns};

        is_pz_checked = true;
      }
      else if (probability_string == "SEED")
      {
        if (is_seed_checked)
          throw wrong_mnemonics_error{columns};

        ::bra::gates_detail::read_depolarizing_statement(seed, present_string, iter, last, string_found, columns);
        is_seed_checked = true;
      }
      else
        throw wrong_mnemonics_error{columns};
    }

    if (is_px_checked and is_py_checked and is_pz_checked and px + py + pz > 1.0)
      throw wrong_mnemonics_error{columns};
    else if (is_px_checked and is_py_checked and not is_pz_checked and px + py < 1.0)
      pz = 1.0 - px - py;
    else if (is_px_checked and not is_py_checked and is_pz_checked and px + pz < 1.0)
      py = 1.0 - px - pz;
    else if (not is_px_checked and is_py_checked and is_pz_checked and py + pz < 1.0)
      px = 1.0 - py - pz;
    else
      throw wrong_mnemonics_error{columns};

    return std::make_tuple(::bra::depolarizing_statement::channel, px, py, pz, seed);
  }
} // namespace bra

# undef BRA_is_nothrow_swappable


//
// $Id: IsotopeCalculator.cpp 2051 2010-06-15 18:39:13Z chambm $
//
//
// Original author: Darren Kessner <darren@proteowizard.org>
//
// Copyright 2006 Louis Warschaw Prostate Cancer Center
//   Cedars Sinai Medical Center, Los Angeles, California  90048
//
// 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.
//

#define PWIZ_SOURCE

#include "IsotopeCalculator.hpp"
#include "IsotopeTable.hpp"
#include "Ion.hpp"
#include "pwiz/utility/misc/Std.hpp"
#include <boost/ptr_container/ptr_vector.hpp>

namespace pwiz {
namespace chemistry {

using boost::ptr_vector;

class IsotopeCalculator::Impl
{
    public:

    Impl(double abundanceCutoff, double massPrecision);

    MassDistribution distribution(const Formula& formula,
                                  int chargeState,
                                  int normalization) const;
    private:

    double abundanceCutoff_;
    double massPrecision_;

    ptr_vector<IsotopeTable> tableStorage_;
    typedef map<Element::Type, const IsotopeTable*> TableMap;
    TableMap tableMap_;

    void initializeIsotopeTables();
    MassDistribution distributionManually(Element::Type e, int atomCount) const;
};

IsotopeCalculator::Impl::Impl(double abundanceCutoff, double massPrecision)
:   abundanceCutoff_(abundanceCutoff),
    massPrecision_(massPrecision)
{
    initializeIsotopeTables();
}

namespace {

bool hasLessMass(const MassAbundance& a, const MassAbundance& b)
{
    return a.mass < b.mass;
}

MassAbundance coalesce(const MassAbundance& a, const MassAbundance& b)
{
    double abundance = a.abundance + b.abundance;
    double mass = (a.abundance*a.mass + b.abundance*b.mass) / abundance;
    return MassAbundance(mass, abundance);
}

class Distinct
{
    public:

    Distinct(double mass, double precision)
    :   mass_(mass), precision_(precision)
    {}

    bool operator()(const MassAbundance& ma)
    {
        return fabs(ma.mass - mass_) > precision_;
    }

    private:
    double mass_;
    double precision_;
};

MassDistribution coalesceDistribution(const MassDistribution& md, double precision)
{
    // assumes MassDistribution md is sorted by mass!

    MassDistribution result;

    for (MassDistribution::const_iterator it=md.begin(); it!=md.end();)
    {
        Distinct distinct(it->mass, precision);
        MassDistribution::const_iterator next = find_if(it, md.end(), distinct);
        result.push_back(accumulate(it, next, MassAbundance(), coalesce));
        it = next;
    }

    return result;
}

class Convolve
{
    public:

    Convolve(double cutoff = 0)
    :   cutoff_(cutoff)
    {}

    MassDistribution operator()(const MassDistribution& m, const MassDistribution& n)
    {
        if (m.empty()) return n;
        if (n.empty()) return m;

        MassDistribution result;

        // we could break out of these loops early if the mass distributions
        // are sorted by abundance

        for (MassDistribution::const_iterator i=m.begin(); i!=m.end(); ++i)
        for (MassDistribution::const_iterator j=n.begin(); j!=n.end(); ++j)
        {
            double mass = i->mass + j->mass;
            double abundance = i->abundance * j->abundance;
            if (abundance > cutoff_)
                result.push_back(MassAbundance(mass, abundance));
        }

        return result;
    }

    private:
    double cutoff_;
};

class Ionize
{
    public:

    Ionize(int chargeState)
    :   chargeState_(chargeState)
    {}

    void operator()(MassAbundance& ma) const
    {
        ma.mass = Ion::mz(ma.mass, chargeState_);
    }

    private:
    int chargeState_;
};

void normalize(MassDistribution& md, int normalization)
{
    if (md.empty())
        return;

    double abundanceScale = 1;

    if (normalization & IsotopeCalculator::NormalizeAbundance)
    {
        double sumSquaredAbundances = 0;
        for (MassDistribution::iterator it=md.begin(); it!=md.end(); ++it)
        {
            double a = it->abundance;
            sumSquaredAbundances += a*a;
        }

        abundanceScale = sqrt(sumSquaredAbundances);
    }

    double massShift = (normalization & IsotopeCalculator::NormalizeMass) ? md[0].mass : 0;

    for (MassDistribution::iterator it=md.begin(); it!=md.end(); ++it)
    {
        it->mass -= massShift;
        it->abundance /= abundanceScale;
    }
}

} // namespace

MassDistribution IsotopeCalculator::Impl::distribution(const Formula& formula,
                                                       int chargeState,
                                                       int normalization) const
{
    // collect the distributions for each element in the formula

    vector<MassDistribution> distributions;

    Formula::Map formulaData = formula.data();
    for (Formula::Map::const_iterator it=formulaData.begin(); it!=formulaData.end(); ++it)
    {
        Element::Type e = it->first;
        int atomCount = it->second;

        TableMap::const_iterator table = tableMap_.find(e);
        if (table != tableMap_.end())
            distributions.push_back(table->second->distribution(atomCount));
        else
            distributions.push_back(distributionManually(e, atomCount));
    }

    // coalesce each elemental distribution

    vector<MassDistribution> coalescedDistributions;
    for (vector<MassDistribution>::iterator it=distributions.begin(); it!=distributions.end(); ++it)
        coalescedDistributions.push_back(coalesceDistribution(*it, massPrecision_));

    // combine the distributions and sort by mass

    MassDistribution combined = accumulate(coalescedDistributions.begin(), coalescedDistributions.end(),
                                           MassDistribution(), Convolve(abundanceCutoff_));

    sort(combined.begin(), combined.end(), hasLessMass);

    MassDistribution result = coalesceDistribution(combined, massPrecision_);

    // adjust for charge state

    if (chargeState)
        for_each(result.begin(), result.end(), Ionize(chargeState));

    // normalize if requested

    if (normalization)
        normalize(result, normalization);

    return result;
}

namespace {

struct TableInfo
{
    Element::Type element;
    int maxAtomCount;
};

TableInfo tableInfo_[] =
{
    {Element::C, 5000},
    {Element::H, 8000},
    {Element::N, 1500},
    {Element::O, 1500},
    {Element::S, 50},
};

const int tableInfoSize_ = sizeof(tableInfo_)/sizeof(TableInfo);

} // namespace

void IsotopeCalculator::Impl::initializeIsotopeTables()
{
    for (TableInfo* it=tableInfo_; it!=tableInfo_+tableInfoSize_; ++it)
    {
        IsotopeTable* temp(new IsotopeTable(Element::Info::record(it->element).isotopes,
                                            it->maxAtomCount,
                                            abundanceCutoff_));
        tableMap_[it->element] = temp; // store pointer in the map
        tableStorage_.push_back(temp); // maintain ownership in the ptr_vector
    }
}

MassDistribution IsotopeCalculator::Impl::distributionManually(Element::Type e,
                                                                        int atomCount) const
{
    throw runtime_error("[IsotopeCalculator::distribution()] No table for element "
        + Element::Info::record(e).symbol);
}

PWIZ_API_DECL IsotopeCalculator::IsotopeCalculator(double abundanceCutoff, double massPrecision)
:   impl_(new Impl(abundanceCutoff, massPrecision))
{}

PWIZ_API_DECL IsotopeCalculator::~IsotopeCalculator(){} // auto destruction of impl_

PWIZ_API_DECL
MassDistribution IsotopeCalculator::distribution(const Formula& formula,
                                                 int chargeState,
                                                 int normalization) const
{
    return impl_->distribution(formula, chargeState, normalization);
}

} // namespace chemistry
} // namespace pwiz


/*
Copyright 2020 Dennis Rohde

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 <vector>
#include <limits>

#include <boost/chrono/include.hpp>

#include "frechet.hpp"

namespace Frechet {

namespace Continuous {

distance_t epsilon = 0.001;
bool round = true;

std::string Distance::repr() const {
    std::stringstream ss;
    ss << value;
    return ss.str();
}

Distance distance(const Curve &curve1, const Curve &curve2) {
    if ((curve1.complexity() < 2) or (curve2.complexity() < 2)) {
        std::cerr << "WARNING: comparison possible only for curves of at least two points" << std::endl;
        Distance result;
        result.value = std::numeric_limits<distance_t>::signaling_NaN();
        return result;
    }
    if (curve1.dimensions() != curve2.dimensions()) {
        std::cerr << "WARNING: comparison possible only for curves of equal number of dimensions" << std::endl;
        Distance result;
        result.value = std::numeric_limits<distance_t>::signaling_NaN();
        return result;
    }

    auto start = boost::chrono::process_real_cpu_clock::now();
    const auto lb = std::sqrt(std::max(curve1[0].dist_sqr(curve2[0]), curve1[curve1.complexity()-1].dist_sqr(curve2[curve2.complexity()-1])));
    const auto ub = _greedy_upper_bound(curve1, curve2);
    auto end = boost::chrono::process_real_cpu_clock::now();

    #if DEBUG
    std::cout << "narrowed to [" << lb << ", " << ub.value << "]" << std::endl;
    #endif

    auto dist = _distance(curve1, curve2, ub, lb);
    dist.time_bounds = (end-start).count() / 1000000000.0;
    if (round) dist.value =  std::round(dist.value * 1e3) / 1e3;

    return dist;
}

Distance _distance(const Curve &curve1, const Curve &curve2, distance_t ub, distance_t lb) {
    Distance result;
    auto start = boost::chrono::process_real_cpu_clock::now();

    distance_t split = (ub + lb)/2;
    std::size_t number_searches = 0;

    if (ub - lb > epsilon) {
        auto infty = std::numeric_limits<distance_t>::infinity();
        std::vector<std::vector<distance_t>> reachable1(curve1.complexity()-1, std::vector<distance_t>(curve2.complexity(), infty));
        std::vector<std::vector<distance_t>> reachable2(curve1.complexity(), std::vector<distance_t>(curve2.complexity()-1, infty));

        std::vector<std::vector<Interval>> free_intervals1(curve2.complexity(), std::vector<Interval>(curve1.complexity(), Interval()));
        std::vector<std::vector<Interval>> free_intervals2(curve1.complexity(), std::vector<Interval>(curve2.complexity(), Interval()));

        if (std::isnan(lb) or std::isnan(ub)) {
            result.value = std::numeric_limits<distance_t>::signaling_NaN();
            return result;
        }

        //Binary search over the feasible distances
        while (ub - lb > epsilon) {
            ++number_searches;
            split = (ub + lb)/2;
            auto isLessThan = _less_than_or_equal(split, curve1, curve2, reachable1, reachable2, free_intervals1, free_intervals2);
            if (isLessThan) {
                ub = split;
            }
            else {
                lb = split;
            }
            #if DEBUG
            std::cout << "narrowed to [" << lb << ", " << ub << "]" << std::endl;
            #endif
        }
    }

    distance_t value = (ub + lb)/2.;
    auto end = boost::chrono::process_real_cpu_clock::now();
    result.value = value;
    result.time_searches = (end-start).count() / 1000000000.0;
    result.number_searches = number_searches;
    return result;
}

bool _less_than_or_equal(const distance_t distance, Curve const& curve1, Curve const& curve2,
        std::vector<std::vector<distance_t>> &reachable1, std::vector<std::vector<distance_t>> &reachable2,
        std::vector<std::vector<Interval>> &free_intervals1, std::vector<std::vector<Interval>> &free_intervals2) {
    assert(curve1.complexity() >= 2);
    assert(curve2.complexity() >= 2);

    distance_t dist_sqr = distance * distance;
    auto infty = std::numeric_limits<distance_t>::infinity();

    if (curve1[0].dist_sqr(curve2[0]) > dist_sqr or curve1.back().dist_sqr(curve2.back()) > dist_sqr) return false;

    for (auto &elem: reachable1) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = infty;
        }
    }

    for (auto &elem: reachable2) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = infty;
        }
    }

    for (auto &elem: free_intervals1) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = Interval();
        }
    }

    for (auto &elem: free_intervals2) {
        #pragma omp parallel for
        for (curve_size_t i = 0; i < elem.size(); ++i) {
            elem[i] = Interval();
        }
    }

    for (curve_size_t i = 0; i < curve1.complexity() - 1; ++i) {
        reachable1[i][0] = 0.;
        if (curve2[0].dist_sqr(curve1[i+1]) > dist_sqr) break;
    }

    for (curve_size_t j = 0; j < curve2.complexity() - 1; ++j) {
        reachable2[0][j] = 0.;
        if (curve1[0].dist_sqr(curve2[j+1]) > dist_sqr) break;
    }

    #pragma omp parallel for collapse(2)
    for (curve_size_t i = 0; i < curve1.complexity(); ++i) {
        for (curve_size_t j = 0; j < curve2.complexity(); ++j) {
            if ((i < curve1.complexity() - 1) and (j > 0)) {
                free_intervals1[j][i] = curve2[j].intersection_interval(dist_sqr, curve1[i], curve1[i+1]);
            }
            if ((j < curve2.complexity() - 1) and (i > 0)) {
                free_intervals2[i][j] = curve1[i].intersection_interval(dist_sqr, curve2[j], curve2[j+1]);
            }
        }
    }

    for (curve_size_t i = 0; i < curve1.complexity(); ++i) {
        for (curve_size_t j = 0; j < curve2.complexity(); ++j) {
            if ((i < curve1.complexity() - 1) and (j > 0)) {
                if (not free_intervals1[j][i].is_empty()) {
                    if (reachable2[i][j-1] != infty) {
                        reachable1[i][j] = free_intervals1[j][i].begin();
                    }
                    else if (reachable1[i][j-1] <= free_intervals1[j][i].end()) {
                        reachable1[i][j] = std::max(free_intervals1[j][i].begin(), reachable1[i][j-1]);
                    }
                }
            }
            if ((j < curve2.complexity() - 1) and (i > 0)) {
                if (not free_intervals2[i][j].is_empty()) {
                    if (reachable1[i-1][j] != infty) {
                        reachable2[i][j] = free_intervals2[i][j].begin();
                    }
                    else if (reachable2[i-1][j] <= free_intervals2[i][j].end()) {
                        reachable2[i][j] = std::max(free_intervals2[i][j].begin(), reachable2[i-1][j]);
                    }
                }
            }
        }
    }

    assert((reachable1.back().back() < infty) == (reachable2.back().back() < infty));

    return reachable1.back().back() < infty;
}

distance_t _greedy_upper_bound(const Curve &curve1, const Curve &curve2) {
    distance_t result = 0;

    const curve_size_t len1 = curve1.complexity(), len2 = curve2.complexity();
    curve_size_t i = 0, j = 0;

    while ((i < len1 - 1) and (j < len2 - 1)) {
        result = std::max(result, curve1[i].dist_sqr(curve2[j]));

        distance_t dist1 = curve1[i+1].dist_sqr(curve2[j]),
            dist2 = curve1[i].dist_sqr(curve2[j+1]),
            dist3 = curve1[i+1].dist_sqr(curve2[j+1]);

        if ((dist1 <= dist2) and (dist1 <= dist3)) ++i;
        else if ((dist2 <= dist1) and (dist2 <= dist3)) ++j;
        else {
            ++i;
            ++j;
        }
    }

    while (i < len1) result = std::max(result, curve1[i++].dist_sqr(curve2[j]));

    --i;

    while (j < len2) result = std::max(result, curve1[i].dist_sqr(curve2[j++]));

    return std::sqrt(result);
}

} // end namespace Continuous

namespace Discrete {

std::string Distance::repr() const {
    std::stringstream ss;
    ss << value;
    return ss.str();
}

Distance distance(const Curve &curve1, const Curve &curve2) {
    Distance result;
    auto start = boost::chrono::process_real_cpu_clock::now();
    std::vector<std::vector<distance_t>> a(curve1.complexity(), std::vector<distance_t>(curve2.complexity(), -1));
    auto value = std::sqrt(_dp(a, curve1.complexity() - 1, curve2.complexity() - 1, curve1, curve2));
    auto end = boost::chrono::process_real_cpu_clock::now();
    result.time = (end-start).count() / 1000000000.0;
    result.value = value;
    return result;

}

distance_t _dp(std::vector<std::vector<distance_t>> &a, const curve_size_t i, const curve_size_t j, const Curve &curve1, const Curve &curve2) {
    if (a[i][j] > -1) return a[i][j];
    else if (i == 0 and j == 0) return curve1[i].dist_sqr(curve2[j]);
    else if (i > 0 and j == 0) return std::max(_dp(a, i-1, 0, curve1, curve2), curve1[i].dist_sqr(curve2[j]));
    else if (i == 0 and j > 0) return std::max(_dp(a, 0, j-1, curve1, curve2), curve1[i].dist_sqr(curve2[j]));
    else {
        a[i][j] = std::max(
                    std::min(
                        std::min(_dp(a, i-1, j, curve1, curve2),
                            _dp(a, i-1, j-1, curve1, curve2)),
                        _dp(a, i, j-1, curve1, curve2)),
                    curve1[i].dist_sqr(curve2[j]));
    }
    return a[i][j];
}

} // end namespace Discrete

} // end namespace Frechet