#ifndef FK_IK_H
#define FK_IK_H

/***** Included Libs *****/

#include "helpers.hpp"

/** Basic ROS **/
#include <ros/ros.h>
#include <ros/package.h>
#include <ros/xmlrpc_manager.h>

/** FK/IK/MP **/
#include <boost/array.hpp>
#include <trac_ik/trac_ik.hpp>
#include <kdl/chainiksolverpos_nr_jl.hpp>
#include <kdl_parser/kdl_parser.hpp>

/** Messages **/
// ROS Standard
#include <std_msgs/Float32MultiArray.h>
// Custom
#include <ur_fk_ik/FK.h>
#include <ur_fk_ik/IK.h>

/*************** class FK_IK_Service *********************************************************************************/

class FK_IK_Service{

/***** Public *****/ public:

ros::ServiceServer FKservice;
ros::ServiceServer IKservice;

FK_IK_Service( ros::NodeHandle& _nh );

bool /*-*/ check_q( const KDL::JntArray& q );
KDL::Frame calc_FK( const boost::array<double,6>& jointConfig );

boost::array<double,7> calc_IK( const boost::array<double,22>& bigIKarr );

bool FK_cb( ur_fk_ik::FK::Request& req, ur_fk_ik::FK::Response& rsp );
bool IK_cb( ur_fk_ik::IK::Request& req, ur_fk_ik::IK::Response& rsp );

bool init_services();

~FK_IK_Service();

/***** Protected *****/ protected:

/** ROS Node **/
ros::NodeHandle _nh;

/** JSON Params **/
string FK_srv_topicName ,
       IK_srv_topicName ,
       URDF_param       ,
       SRDF_param       ,
       URDF_contents    ,
       SRDF_contents    ,
       base_link_name   ,
       end_link_name    ,
       _PKG_NAME        = "ur_fk_ik";
bool paramsOK;

/** Robot Description **/
KDL::Chain    chain; // ---- Kin chain
u_char /*--*/ N_joints; // # of joints in the kin chain
KDL::JntArray ll , // ------ lower joint limits
              ul ; // ------ upper joint limits

/** FK Solver **/
KDL::ChainFkSolverPos_recursive* fk_solver; //(chain); // Forward kin. solver

/** IK Solver and Params **/

/* Stage 1 */
TRAC_IK::TRAC_IK* tracik_solver = nullptr;
u_short /*----*/ N_IKsamples;
double /*-----*/ IK_timeout   ,
                 IK_epsilon   ,
                 IK_seed_fuzz ;

/* Stage 2 */
TRAC_IK::TRAC_IK* tracik_solver2 = nullptr;
u_short /*----*/ N_IKsamples2;
double /*-----*/ IK_timeout2   ,
                 IK_epsilon2   ,
                 IK_seed_fuzz2 ;

/** Internal Functions **/
bool setup_kin_chain();
bool fetch_params();
bool setup_FK();
bool setup_IK();

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
};

#endif


#include "dynet/nodes.h"
#include "dynet/dynet.h"
#include "dynet/training.h"
#include "dynet/timing.h"
#include "dynet/rnn.h"
#include "dynet/gru.h"
#include "dynet/lstm.h"
#include "dynet/dict.h"
#include "dynet/expr.h"
#include "getpid.h"

#include <iostream>
#include <fstream>
#include <sstream>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

using namespace std;
using namespace dynet;

float pdrop = 0.5;
unsigned LAYERS = 1;
unsigned INPUT_DIM = 128;
unsigned HIDDEN_DIM = 128;
unsigned TAG_HIDDEN_DIM = 32;
unsigned TAG_DIM = 32;
unsigned TAG_SIZE = 0;
unsigned VOCAB_SIZE = 0;

bool eval = false;
dynet::Dict d;
dynet::Dict td;
int kNONE;
int kSOS;
int kEOS;

template <class Builder>
struct RNNLanguageModel {
  LookupParameter p_w;
  Parameter p_l2th;
  Parameter p_r2th;
  Parameter p_thbias;

  Parameter p_th2t;
  Parameter p_tbias;
  Builder l2rbuilder;
  Builder r2lbuilder;
  explicit RNNLanguageModel(Model& model) :
      l2rbuilder(LAYERS, INPUT_DIM, HIDDEN_DIM, &model),
      r2lbuilder(LAYERS, INPUT_DIM, HIDDEN_DIM, &model) {
    p_w = model.add_lookup_parameters(VOCAB_SIZE, {INPUT_DIM});
    p_l2th = model.add_parameters({TAG_HIDDEN_DIM, HIDDEN_DIM});
    p_r2th = model.add_parameters({TAG_HIDDEN_DIM, HIDDEN_DIM});
    p_thbias = model.add_parameters({TAG_HIDDEN_DIM});

    p_th2t = model.add_parameters({TAG_SIZE, TAG_HIDDEN_DIM});
    p_tbias = model.add_parameters({TAG_SIZE});
  }

  // return Expression of total loss
  Expression BuildTaggingGraph(const vector<int>& sent, const vector<int>& tags, ComputationGraph& cg, double* cor = 0, unsigned* ntagged = 0) {
    const unsigned slen = sent.size();
    l2rbuilder.new_graph(cg);  // reset RNN builder for new graph
    l2rbuilder.start_new_sequence();
    r2lbuilder.new_graph(cg);  // reset RNN builder for new graph
    r2lbuilder.start_new_sequence();
    Expression i_l2th = parameter(cg, p_l2th);
    Expression i_r2th = parameter(cg, p_r2th);
    Expression i_thbias = parameter(cg, p_thbias);
    Expression i_th2t = parameter(cg, p_th2t);
    Expression i_tbias = parameter(cg, p_tbias);
    vector<Expression> errs;
    vector<Expression> i_words(slen);
    vector<Expression> fwds(slen);
    vector<Expression> revs(slen);

    // read sequence from left to right
    l2rbuilder.add_input(lookup(cg, p_w, kSOS));
    for (unsigned t = 0; t < slen; ++t) {
      i_words[t] = lookup(cg, p_w, sent[t]);
      if (!eval) { i_words[t] = noise(i_words[t], 0.1); }
      fwds[t] = l2rbuilder.add_input(i_words[t]);
    }

    // read sequence from right to left
    r2lbuilder.add_input(lookup(cg, p_w, kEOS));
    for (unsigned t = 0; t < slen; ++t)
      revs[slen - t - 1] = r2lbuilder.add_input(i_words[slen - t - 1]);

    for (unsigned t = 0; t < slen; ++t) {
      if (tags[t] != kNONE) {
        if (ntagged) (*ntagged)++;
        Expression i_th = tanh(affine_transform({i_thbias, i_l2th, fwds[t], i_r2th, revs[t]}));
        //if (!eval) { i_th = dropout(i_th, pdrop); }
        Expression i_t = affine_transform({i_tbias, i_th2t, i_th});
        if (cor) {
          vector<float> dist = as_vector(cg.incremental_forward(i_t));
          double best = -9e99;
          int besti = -1;
          for (int i = 0; i < dist.size(); ++i) {
            if (dist[i] > best) { best = dist[i]; besti = i; }
          }
          if (tags[t] == besti) (*cor)++;
        }
        if (tags[t] != kNONE) {
          Expression i_err = pickneglogsoftmax(i_t, tags[t]);
          errs.push_back(i_err);
        }
      }
    }
    return sum(errs);
  }
};

int main(int argc, char** argv) {
  dynet::initialize(argc, argv);
  if (argc != 3 && argc != 4) {
    cerr << "Usage: " << argv[0] << " corpus.txt dev.txt [model.params]\n";
    return 1;
  }
  kNONE = td.convert("*");
  kSOS = d.convert("<s>");
  kEOS = d.convert("</s>");
  vector<pair<vector<int>,vector<int>>> training, dev;
  string line;
  int tlc = 0;
  int ttoks = 0;
  cerr << "Reading training data from " << argv[1] << "...\n";
  {
    ifstream in(argv[1]);
    assert(in);
    while(getline(in, line)) {
      ++tlc;
      int nc = 0;
      vector<int> x,y;
      read_sentence_pair(line, &x, &d, &y, &td);
      assert(x.size() == y.size());
      if (x.size() == 0) { cerr << line << endl; abort(); }
      training.push_back(make_pair(x,y));
      for (unsigned i = 0; i < y.size(); ++i) {
        if (y[i] != kNONE) { ++nc; }
      }
      if (nc == 0) {
        cerr << "No tagged tokens in line " << tlc << endl;
        abort();
      }
      ttoks += x.size();
    }
    cerr << tlc << " lines, " << ttoks << " tokens, " << d.size() << " types\n";
    cerr << "Tags: " << td.size() << endl;
  }
  d.freeze(); // no new word types allowed
  td.freeze(); // no new tag types allowed
  VOCAB_SIZE = d.size();
  TAG_SIZE = td.size();

  int dlc = 0;
  int dtoks = 0;
  cerr << "Reading dev data from " << argv[2] << "...\n";
  {
    ifstream in(argv[2]);
    assert(in);
    while(getline(in, line)) {
      ++dlc;
      vector<int> x,y;
      read_sentence_pair(line, &x, &d, &y, &td);
      assert(x.size() == y.size());
      dev.push_back(make_pair(x,y));
      dtoks += x.size();
    }
    cerr << dlc << " lines, " << dtoks << " tokens\n";
  }
  ostringstream os;
  os << "tagger"
     << '_' << LAYERS
     << '_' << INPUT_DIM
     << '_' << HIDDEN_DIM
     << "-pid" << getpid() << ".params";
  const string fname = os.str();
  cerr << "Parameters will be written to: " << fname << endl;
  double best = 9e+99;

  Model model;
  bool use_momentum = true;
  Trainer* sgd = nullptr;
  if (use_momentum)
    sgd = new MomentumSGDTrainer(&model);
  else
    sgd = new SimpleSGDTrainer(&model);

  RNNLanguageModel<LSTMBuilder> lm(model);
  //RNNLanguageModel<SimpleRNNBuilder> lm(model);
  if (argc == 4) {
    string fname = argv[3];
    ifstream in(fname);
    boost::archive::text_iarchive ia(in);
    ia >> model;
  }

  unsigned report_every_i = 50;
  unsigned dev_every_i_reports = 25;
  unsigned si = training.size();
  vector<unsigned> order(training.size());
  for (unsigned i = 0; i < order.size(); ++i) order[i] = i;
  bool first = true;
  int report = 0;
  unsigned lines = 0;
  while(1) {
    Timer iteration("completed in");
    double loss = 0;
    unsigned ttags = 0;
    double correct = 0;
    for (unsigned i = 0; i < report_every_i; ++i) {
      if (si == training.size()) {
        si = 0;
        if (first) { first = false; } else { sgd->update_epoch(); }
        cerr << "**SHUFFLE\n";
        shuffle(order.begin(), order.end(), *rndeng);
      }

      // build graph for this instance
      ComputationGraph cg;
      auto& sent = training[order[si]];
      ++si;
      Expression loss_expr = lm.BuildTaggingGraph(sent.first, sent.second, cg, &correct, &ttags);
      loss += as_scalar(cg.forward(loss_expr));
      cg.backward(loss_expr);
      sgd->update(1.0);
      ++lines;
    }
    sgd->status();
    cerr << " E = " << (loss / ttags) << " ppl=" << exp(loss / ttags) << " (acc=" << (correct / ttags) << ") ";

    // show score on dev data?
    report++;
    if (report % dev_every_i_reports == 0) {
      double dloss = 0;
      unsigned dtags = 0;
      double dcorr = 0;
      eval = true;
      //lm.p_th2t->scale_parameters(pdrop);
      for (auto& sent : dev) {
        ComputationGraph cg;
        Expression loss_expr = lm.BuildTaggingGraph(sent.first, sent.second, cg, &dcorr, &dtags);
        dloss += as_scalar(cg.forward(loss_expr));
      }
      //lm.p_th2t->scale_parameters(1/pdrop);
      eval = false;
      if (dloss < best) {
        best = dloss;
        ofstream out(fname);
        boost::archive::text_oarchive oa(out);
        oa << model;
      }
      cerr << "\n***DEV [epoch=" << (lines / (double)training.size()) << "] E = " << (dloss / dtags) << " ppl=" << exp(dloss / dtags) << " acc=" << (dcorr / dtags) << ' ';
    }
  }
  delete sgd;
}


/*
 * local.cc
 *
 *  Copyright (C) 2013 Diamond Light Source
 *
 *  Author: James Parkhurst
 *
 *  This code is distributed under the BSD license, a copy of which is
 *  included in the root directory of this package.
 */
#include <boost/python.hpp>
#include <boost/python/def.hpp>
#include <dials/algorithms/image/threshold/local.h>

namespace dials { namespace algorithms { namespace boost_python {

  using namespace boost::python;

  template <typename FloatType>
  void local_threshold_suite() {
    def("niblack", &niblack<FloatType>, (arg("image"), arg("size"), arg("n_sigma")));

    def(
      "sauvola", &sauvola<FloatType>, (arg("image"), arg("size"), arg("k"), arg("r")));

    def("index_of_dispersion",
        &index_of_dispersion<FloatType>,
        (arg("image"), arg("size"), arg("n_sigma")));

    def("index_of_dispersion_masked",
        &index_of_dispersion_masked<FloatType>,
        (arg("image"), arg("mask"), arg("size"), arg("min_count"), arg("n_sigma")));

    def("gain",
        &gain<FloatType>,
        (arg("image"),
         arg("mask"),
         arg("gain"),
         arg("size"),
         arg("min_count"),
         arg("n_sigma")));

    def("dispersion",
        &dispersion<FloatType>,
        (arg("image"),
         arg("mask"),
         arg("size"),
         arg("n_sigma_b"),
         arg("n_sigma_s"),
         arg("min_count")));

    def("dispersion_w_gain",
        &dispersion_w_gain<FloatType>,
        (arg("image"),
         arg("mask"),
         arg("gain"),
         arg("size"),
         arg("n_sigma_b"),
         arg("n_sigma_s"),
         arg("min_count")));
  }

  void export_local() {
    local_threshold_suite<float>();
    local_threshold_suite<double>();

    class_<DispersionThreshold>("DispersionThreshold", no_init)
      .def(init<int2, int2, double, double, double, int>())
      .def("__call__", &DispersionThreshold::threshold<int>)
      .def("__call__", &DispersionThreshold::threshold<double>)
      .def("__call__", &DispersionThreshold::threshold_w_gain<int>)
      .def("__call__", &DispersionThreshold::threshold_w_gain<double>);

    class_<DispersionThresholdDebug>("DispersionThresholdDebug", no_init)
      .def(init<const af::const_ref<double, af::c_grid<2> > &,
                const af::const_ref<bool, af::c_grid<2> > &,
                int2,
                double,
                double,
                double,
                int>((arg("image"),
                      arg("mask"),
                      arg("size"),
                      arg("n_sigma_b"),
                      arg("n_sigma_s"),
                      arg("threshold"),
                      arg("min_count"))))
      .def(init<const af::const_ref<double, af::c_grid<2> > &,
                const af::const_ref<bool, af::c_grid<2> > &,
                const af::const_ref<double, af::c_grid<2> > &,
                int2,
                double,
                double,
                double,
                int>((arg("image"),
                      arg("mask"),
                      arg("gain"),
                      arg("size"),
                      arg("n_sigma_b"),
                      arg("n_sigma_s"),
                      arg("threshold"),
                      arg("min_count"))))
      .def("mean", &DispersionThresholdDebug::mean)
      .def("variance", &DispersionThresholdDebug::variance)
      .def("index_of_dispersion", &DispersionThresholdDebug::index_of_dispersion)
      .def("global_mask", &DispersionThresholdDebug::global_mask)
      .def("cv_mask", &DispersionThresholdDebug::cv_mask)
      .def("value_mask", &DispersionThresholdDebug::value_mask)
      .def("final_mask", &DispersionThresholdDebug::final_mask);

    class_<DispersionExtendedThresholdDebug>("DispersionExtendedThresholdDebug",
                                             no_init)
      .def(init<const af::const_ref<double, af::c_grid<2> > &,
                const af::const_ref<bool, af::c_grid<2> > &,
                int2,
                double,
                double,
                double,
                int>((arg("image"),
                      arg("mask"),
                      arg("size"),
                      arg("n_sigma_b"),
                      arg("n_sigma_s"),
                      arg("threshold"),
                      arg("min_count"))))
      .def(init<const af::const_ref<double, af::c_grid<2> > &,
                const af::const_ref<bool, af::c_grid<2> > &,
                const af::const_ref<double, af::c_grid<2> > &,
                int2,
                double,
                double,
                double,
                int>((arg("image"),
                      arg("mask"),
                      arg("gain"),
                      arg("size"),
                      arg("n_sigma_b"),
                      arg("n_sigma_s"),
                      arg("threshold"),
                      arg("min_count"))))
      .def("mean", &DispersionExtendedThresholdDebug::mean)
      .def("variance", &DispersionExtendedThresholdDebug::variance)
      .def("index_of_dispersion",
           &DispersionExtendedThresholdDebug::index_of_dispersion)
      .def("global_mask", &DispersionExtendedThresholdDebug::global_mask)
      .def("cv_mask", &DispersionExtendedThresholdDebug::cv_mask)
      .def("value_mask", &DispersionExtendedThresholdDebug::value_mask)
      .def("final_mask", &DispersionExtendedThresholdDebug::final_mask);

    class_<DispersionExtendedThreshold>("DispersionExtendedThreshold", no_init)
      .def(init<int2, int2, double, double, double, int>())
      /* .def("__call__", &DispersionExtendedThreshold::threshold<int>) */
      .def("__call__", &DispersionExtendedThreshold::threshold<double>)
      /* .def("__call__", &DispersionExtendedThreshold::threshold_w_gain<int>) */
      .def("__call__", &DispersionExtendedThreshold::threshold_w_gain<double>);
  }

}}}  // namespace dials::algorithms::boost_python


#pragma once

#include <boost/mp11/integral.hpp>
#include <boost/mp11/detail/mp_plus.hpp>
#include <utility>

namespace util
{

using namespace boost::mp11;

namespace detail
{

    template <class Index = mp_size_t<0>>
    struct for_each_argument
    {
        template <class F>
        static void run(F&& f) {}

        template <class F, class Arg, class... Args>
        static void run(F&& f, Arg&& arg, Args&&... args)
        {
            std::forward<F>(f)(Index::value, std::forward<Arg>(arg));

            for_each_argument<mp_plus<Index, mp_size_t<1>>>::run(std::forward<F>(f), std::forward<Args>(args)...);
        }
    };

}

template <class F, class... Args>
void for_each_argument(F&& f, Args&&... args)
{
    detail::for_each_argument<mp_size_t<0>>::run(std::forward<F>(f), std::forward<Args>(args)...);
}

}


/******************************************************************************\
* Author: Matthew Beauregard Smith                                             *
* Affiliation: The University of Texas at Austin                               *
* Department: Oden Institute and Institute for Cellular and Molecular Biology  *
* PI: Edward Marcotte                                                          *
* Project: Protein Fluorosequencing                                            *
\******************************************************************************/

// Boost unit test framework (recommended to be the first include):
#include <boost/test/unit_test.hpp>

// File under test:
#include "edman-transition.h"

// Local project headers:
#include "common/error-model.h"
#include "hmm/fit/error-model-fitter.h"

namespace whatprot {

namespace {
using boost::unit_test::tolerance;
const double TOL = 0.000000001;
}  // namespace

BOOST_AUTO_TEST_SUITE(hmm_suite)
BOOST_AUTO_TEST_SUITE(step_suite)
BOOST_AUTO_TEST_SUITE(edman_transition_suite)

BOOST_AUTO_TEST_CASE(constructor_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    BOOST_TEST(et.p_edman_failure == p_fail);
    // Should also test that the DyeSeq and DyeTrack were copied over, but this
    // would require equality operators for those classes which I don't want to
    // write right now. Anyways this should be covered by the forward tests.
}

BOOST_AUTO_TEST_CASE(forward_in_place_trivial_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 1;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 1.0;  // loc is {0, 0}
    loc[0] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 1.0 * p_fail);  // loc is {0, 0}
    loc[0] = 1;
    BOOST_TEST(psv.tensor[loc] == 1.0 * p_pop);  // loc is {1, 0}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_trivial_test, *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 1;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 1;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 1.0;  // loc is {0, 0}
//     loc[0] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 1.0 * p_fail);  // loc is {0, 0}
//     loc[0] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 1.0 * p_pop);  // loc is {1, 0}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_basic_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_pop);  // loc is {1, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.7 * p_pop);  // loc is {1, 1}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_basic_test, *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 1;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 2;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.7;  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
//     loc[1] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_pop);  // loc is {1, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.7 * p_pop);  // loc is {1, 1}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_more_edmans_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 4;
    shape[1] = 1;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.2;  // loc is {0, 0}
    loc[0] = 1;
    psv.tensor[loc] = 0.3;  // loc is {1, 0}
    loc[0] = 2;
    psv.tensor[loc] = 0.5;  // loc is {2, 0}
    loc[0] = 3;
    psv.tensor[loc] = -1000.0;  // loc is {3, 0} -- to be ignored.
    int edmans = 2;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 3);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0}
    loc[0] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_pop + 0.3 * p_fail);  // loc is {1, 0}
    loc[0] = 2;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_pop + 0.5 * p_fail);  // loc is {2, 0}
    loc[0] = 3;
    BOOST_TEST(psv.tensor[loc] == 0.5 * p_pop);  // loc is {3, 0}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_more_edmans_test, *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 3;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 4;
//     shape[1] = 1;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.2;  // loc is {0, 0}
//     loc[0] = 1;
//     psv1.tensor[loc] = 0.3;  // loc is {1, 0}
//     loc[0] = 2;
//     psv1.tensor[loc] = 0.5;  // loc is {2, 0}
//     loc[0] = 3;
//     psv1.tensor[loc] = -1000.0;  // loc is {3, 0} -- to be ignored.
//     int edmans = 2;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 3);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0}
//     loc[0] = 1;
//     BOOST_TEST(psv2.tensor[loc]
//                == 0.2 * p_pop + 0.3 * p_fail);  // loc is {1, 0}
//     loc[0] = 2;
//     BOOST_TEST(psv2.tensor[loc]
//                == 0.3 * p_pop + 0.5 * p_fail);  // loc is {2, 0}
//     loc[0] = 3;
//     BOOST_TEST(psv2.tensor[loc]
//                == 0.5 * p_pop);  // loc is {3, 0}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_multiple_dye_colors_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0, 1} -- to be ignored.
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1, 1} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.4 * p_fail);  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.1 * p_pop);  // loc is {1, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_pop);  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_pop);  // loc is {1, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.4 * p_pop);  // loc is {1, 1, 1}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_multiple_dye_colors_test,
//                      *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 1;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 3;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 2;
//     shape[2] = 2;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.1;  // loc is {0, 0, 0}
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.2;  // loc is {0, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 1, 0}
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.4;  // loc is {0, 1, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     loc[2] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0, 0} -- ignore this
//     loc[2] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0, 1} -- ignore this
//     loc[1] = 1;
//     loc[2] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1, 0} -- to be ignored.
//     loc[2] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1, 1} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 1, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.4 * p_fail);  // loc is {0, 1, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.1 * p_pop);  // loc is {1, 0, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_pop);  // loc is {1, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_pop);  // loc is {1, 1, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.4 * p_pop);  // loc is {1, 1, 1}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_irrelevant_dye_seq_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, ".0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_pop);  // loc is {1, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.7 * p_pop);  // loc is {1, 1}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_irrelevant_dye_seq_test,
// *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 1;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, ".0");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 2;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.7;  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
//     loc[1] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_pop);  // loc is {1, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.7 * p_pop);  // loc is {1, 1}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_one_dye_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == (0.3 + 0.7) * p_pop);  // loc is {1, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {1, 1}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_one_dye_first_edman_test,
//                      *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 1;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "0");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 2;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.7;  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
//     loc[1] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.7 * p_fail);  // loc is {0, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == (0.3 + 0.7) * p_pop);  // loc is {1, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {1, 1}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_two_dyes_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 1;
    DyeSeq ds(num_channels, "00");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 3;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 0.4;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.5;  // loc is {1, 1}
    loc[1] = 2;
    psv.tensor[loc] = 0.0;  // loc is {1, 2} -- 1 edman can't have 2 dyes.
    loc[0] = 2;
    loc[1] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {2, 0} -- to be ignored.
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1} -- to be ignored.
    loc[1] = 2;
    psv.tensor[loc] = -1000.0;  // loc is {2, 2} -- to be ignored.
    int edmans = 1;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 2);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_fail);  // loc is {0, 1}
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    // loc is {1, 0}
    BOOST_TEST(psv.tensor[loc] == (0.1 + 0.2 / 2.0) * p_pop + 0.4 * p_fail);
    loc[1] = 1;
    // loc is {1, 1}
    BOOST_TEST(psv.tensor[loc] == (0.2 / 2.0 + 0.3) * p_pop + 0.5 * p_fail);
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {1, 2}
    loc[0] = 2;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == (0.4 + 0.5) * p_pop);  // loc is {2, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {2, 1}
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {2, 2}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_two_dyes_second_edman_test,
//                      *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 2;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "00");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 3;
//     shape[1] = 3;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.1;  // loc is {0, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.2;  // loc is {0, 1}
//     loc[1] = 2;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 2}
//     loc[0] = 1;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.4;  // loc is {1, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.5;  // loc is {1, 1}
//     loc[1] = 2;
//     psv1.tensor[loc] = 0.0;  // loc is {1, 2} -- 1 edman can't have 2 dyes.
//     loc[0] = 2;
//     loc[1] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 0} -- to be ignored.
//     loc[1] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 1} -- to be ignored.
//     loc[1] = 2;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 2} -- to be ignored.
//     int edmans = 1;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 2);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_fail);  // loc is {0, 1}
//     loc[1] = 2;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 2}
//     loc[0] = 1;
//     loc[1] = 0;
//     // loc is {1, 0}
//     BOOST_TEST(psv2.tensor[loc] == (0.1 + 0.2 / 2.0) * p_pop + 0.4 * p_fail);
//     loc[1] = 1;
//     // loc is {1, 1}
//     BOOST_TEST(psv2.tensor[loc] == (0.2 / 2.0 + 0.3) * p_pop + 0.5 * p_fail);
//     loc[1] = 2;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {1, 2}
//     loc[0] = 2;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == (0.4 + 0.5) * p_pop);  // loc is {2, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {2, 1}
//     loc[1] = 2;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {2, 2}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_three_dyes_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "000");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 4;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[1] = 3;
    psv.tensor[loc] = 0.4;  // loc is {0, 3}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    loc[1] = 2;
    psv.tensor[loc] = -1000.0;  // loc is {1, 2} -- to be ignored.
    loc[1] = 3;
    psv.tensor[loc] = -1000.0;  // loc is {1, 3} -- to be ignored.
    int edmans = 0;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_fail);  // loc is {0, 1}
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 2}
    loc[1] = 3;
    BOOST_TEST(psv.tensor[loc] == 0.4 * p_fail);  // loc is {0, 3}
    loc[0] = 1;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == (0.1 + 0.2 / 3.0) * p_pop);  // loc is {1, 0}
    loc[1] = 1;
    // loc is {1, 1}
    BOOST_TEST(psv.tensor[loc] == (0.2 * 2.0 / 3.0 + 0.3 * 2.0 / 3.0) * p_pop);
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == (0.3 / 3.0 + 0.4) * p_pop);  // loc is {1, 2}
    loc[1] = 3;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {1, 3}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_three_dyes_first_edman_test,
//                      *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 3;
//     int num_channels = 1;
//     DyeSeq ds(num_channels, "000");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 2;
//     int* shape = new int[order];
//     shape[0] = 2;
//     shape[1] = 4;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     psv1.tensor[loc] = 0.1;  // loc is {0, 0}
//     loc[1] = 1;
//     psv1.tensor[loc] = 0.2;  // loc is {0, 1}
//     loc[1] = 2;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 2}
//     loc[1] = 3;
//     psv1.tensor[loc] = 0.4;  // loc is {0, 3}
//     loc[0] = 1;
//     loc[1] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 0} -- to be ignored.
//     loc[1] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
//     loc[1] = 2;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 2} -- to be ignored.
//     loc[1] = 3;
//     psv1.tensor[loc] = -1000.0;  // loc is {1, 3} -- to be ignored.
//     int edmans = 0;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 1);
//     loc[0] = 0;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0}
//     loc[1] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_fail);  // loc is {0, 1}
//     loc[1] = 2;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 2}
//     loc[1] = 3;
//     BOOST_TEST(psv2.tensor[loc] == 0.4 * p_fail);  // loc is {0, 3}
//     loc[0] = 1;
//     loc[1] = 0;
//     BOOST_TEST(psv2.tensor[loc]
//                == (0.1 + 0.2 / 3.0) * p_pop);  // loc is {1, 0}
//     loc[1] = 1;
//     // loc is {1, 1}
//     BOOST_TEST(psv2.tensor[loc]
//                == (0.2 * 2.0 / 3.0 + 0.3 * 2.0 / 3.0) * p_pop);
//     loc[1] = 2;
//     BOOST_TEST(psv2.tensor[loc]
//                == (0.3 / 3.0 + 0.4) * p_pop);  // loc is {1, 2}
//     loc[1] = 3;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {1, 3}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(forward_in_place_two_dye_colors_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 2;
    DyeSeq ds(num_channels, "01");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.5;  // loc is {1, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.6;  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 0.0;  // loc is {1, 1, 0} -- impossible state.
    loc[2] = 1;
    psv.tensor[loc] = 0.0;  // loc is {1, 1, 1} -- impossible state.
    loc[0] = 2;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {2, 0, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 0, 1} -- to be ignored.
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1, 1} -- to be ignored.
    int edmans = 1;
    et.forward(&edmans, &psv);
    BOOST_TEST(edmans == 2);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.3 * p_fail);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.4 * p_fail);  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    // loc is {1, 0, 0}
    BOOST_TEST(psv.tensor[loc] == (0.1 + 0.3) * p_pop + 0.5 * p_fail);
    loc[2] = 1;
    // loc is {1, 0, 1}
    BOOST_TEST(psv.tensor[loc] == (0.2 + 0.4) * p_pop + 0.6 * p_fail);
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {1, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {1, 1, 1}
    loc[0] = 2;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == (0.5 + 0.6) * p_pop);  // loc is {2, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {2, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {2, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc] == 0.0);  // loc is {2, 1, 1}
    delete[] loc;
}

// BOOST_AUTO_TEST_CASE(forward_new_tsr_two_dye_colors_second_edman_test,
//                      *tolerance(TOL)) {
//     double p_fail = 0.05;
//     double p_pop = 0.95;
//     int num_timesteps = 2;
//     int num_channels = 2;
//     DyeSeq ds(num_channels, "01");
//     DyeTrack dt(num_timesteps, num_channels, ds);
//     EdmanTransition et(p_fail, ds, dt);
//     int order = 3;
//     int* shape = new int[order];
//     shape[0] = 3;
//     shape[1] = 2;
//     shape[2] = 2;
//     PeptideStateVector psv1(order, shape);
//     PeptideStateVector psv2(order, shape);
//     delete[] shape;
//     int* loc = new int[order];
//     loc[0] = 0;
//     loc[1] = 0;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.1;  // loc is {0, 0, 0}
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.2;  // loc is {0, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.3;  // loc is {0, 1, 0}
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.4;  // loc is {0, 1, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.5;  // loc is {1, 0, 0}
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.6;  // loc is {1, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     psv1.tensor[loc] = 0.0;  // loc is {1, 1, 0} -- impossible state.
//     loc[2] = 1;
//     psv1.tensor[loc] = 0.0;  // loc is {1, 1, 1} -- impossible state.
//     loc[0] = 2;
//     loc[1] = 0;
//     loc[2] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 0, 0} -- to be ignored
//     loc[2] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 0, 1} -- to be ignored
//     loc[1] = 1;
//     loc[2] = 0;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 1, 0} -- to be ignored
//     loc[2] = 1;
//     psv1.tensor[loc] = -1000.0;  // loc is {2, 1, 1} -- to be ignored.
//     int edmans = 1;
//     et.forward(tsr1, &edmans, &psv2);
//     BOOST_TEST(edmans == 2);
//     loc[0] = 0;
//     loc[1] = 0;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.1 * p_fail);  // loc is {0, 0, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.2 * p_fail);  // loc is {0, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.3 * p_fail);  // loc is {0, 1, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.4 * p_fail);  // loc is {0, 1, 1}
//     loc[0] = 1;
//     loc[1] = 0;
//     loc[2] = 0;  // loc is {1, 0, 0}
//     BOOST_TEST(psv2.tensor[loc] == (0.1 + 0.3) * p_pop + 0.5 * p_fail);
//     loc[2] = 1;
//     // loc is {1, 0, 1}
//     BOOST_TEST(psv2.tensor[loc] == (0.2 + 0.4) * p_pop + 0.6 * p_fail);
//     loc[1] = 1;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {1, 1, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {1, 1, 1}
//     loc[0] = 2;
//     loc[1] = 0;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == (0.5 + 0.6) * p_pop);  // loc is {2, 0, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {2, 0, 1}
//     loc[1] = 1;
//     loc[2] = 0;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {2, 1, 0}
//     loc[2] = 1;
//     BOOST_TEST(psv2.tensor[loc] == 0.0);  // loc is {2, 1, 1}
//     delete[] loc;
// }

BOOST_AUTO_TEST_CASE(backward_in_place_trivial_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 1;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[0] = 1;
    psv.tensor[loc] = 0.7;  // loc is {1, 0}
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.3 + p_pop * 0.7);  // loc is {0, 0}
    // We ignore the value at {1, 0}, it doesn't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_trivial_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 1;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[0] = 1;
    psv1.tensor[loc] = 0.7;  // loc is {1, 0}
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 0.7);  // loc is {0, 0}
    // We ignore the value at {1, 0}, it doesn't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_basic_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = 1.77;  // loc is {1, 1}
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.77);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_basic_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv1.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 1.77;  // loc is {1, 1}
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.77);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_more_edmans_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 4;
    shape[1] = 1;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.2;  // loc is {0, 0}
    loc[0] = 1;
    psv.tensor[loc] = 0.3;  // loc is {1, 0}
    loc[0] = 2;
    psv.tensor[loc] = 0.5;  // loc is {2, 0}
    loc[0] = 3;
    psv.tensor[loc] = 0.7;  // loc is {3, 0}
    int edmans = 3;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 2);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.2 + p_pop * 0.3);  // loc is {0, 0}
    loc[0] = 1;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.3 + p_pop * 0.5);  // loc is {1, 0}
    loc[0] = 2;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.5 + p_pop * 0.7);  // loc is {2, 0}
    // We ignore the value at {3, 0}, it doesn't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_more_edmans_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 4;
    shape[1] = 1;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.2;  // loc is {0, 0}
    loc[0] = 1;
    psv1.tensor[loc] = 0.3;  // loc is {1, 0}
    loc[0] = 2;
    psv1.tensor[loc] = 0.5;  // loc is {2, 0}
    loc[0] = 3;
    psv1.tensor[loc] = 0.7;  // loc is {3, 0}
    int edmans = 3;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 2);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.2 + p_pop * 0.3);  // loc is {0, 0}
    loc[0] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 0.5);  // loc is {1, 0}
    loc[0] = 2;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.5 + p_pop * 0.7);  // loc is {2, 0}
    // We ignore the value at {3, 0}, it doesn't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_multiple_dye_colors_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 1.11;  // loc is {1, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 1.22;  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 1.33;  // loc is {1, 1, 0}
    loc[2] = 1;
    psv.tensor[loc] = 1.44;  // loc is {1, 1, 1}
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.1 + p_pop * 1.11);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.2 + p_pop * 1.22);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.4 + p_pop * 1.44);  // loc is {0, 1, 1}
    // We ignore the values at {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, and {1, 1, 1}
    // they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_multiple_dye_colors_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv1.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv1.tensor[loc] = 1.11;  // loc is {1, 0, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 1.22;  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv1.tensor[loc] = 1.33;  // loc is {1, 1, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 1.44;  // loc is {1, 1, 1}
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.1 + p_pop * 1.11);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.2 + p_pop * 1.22);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.4 + p_pop * 1.44);  // loc is {0, 1, 1}
    // We ignore the values at {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, and {1, 1, 1}
    // they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_irrelevant_dye_seq_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, ".0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = 1.77;  // loc is {1, 1}
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.77);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_irrelevant_dye_seq_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, ".0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv1.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 1.77;  // loc is {1, 1}
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.77);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_one_dye_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.33);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_one_dye_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "0");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.7;  // loc is {0, 1}
    loc[0] = 1;
    loc[1] = 0;
    psv1.tensor[loc] = 1.33;  // loc is {1, 0}
    loc[1] = 1;
    psv1.tensor[loc] = -1000.0;  // loc is {1, 1} -- to be ignored.
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 1.33);  // loc is {0, 0}
    loc[1] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.7 + p_pop * 1.33);  // loc is {0, 1}
    // We ignore the values at {1, 0} and {1, 1}, they don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_two_dyes_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 1;
    DyeSeq ds(num_channels, "00");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 3;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 0.4;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.5;  // loc is {1, 1}
    loc[1] = 2;
    psv.tensor[loc] = -1000.0;  // loc is {1, 2} -- to be ignored.
    loc[0] = 2;
    loc[1] = 0;
    psv.tensor[loc] = 0.6;  // loc is {2, 0}
    loc[1] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1} -- to be ignored.
    loc[1] = 2;
    psv.tensor[loc] = -1000.0;  // loc is {2, 2} -- to be ignored.
    int edmans = 2;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.1 + p_pop * 0.4);  // loc is {0, 0}
    loc[1] = 1;
    // loc is {0, 1}
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.2 + p_pop * (0.5 / 2.0 + 0.4 / 2.0));
    loc[1] = 2;
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.3 + p_pop * 0.5);  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    // loc is {1, 0}
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.4 + p_pop * 0.6);
    loc[1] = 1;
    // loc is {1, 1}
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.5 + p_pop * 0.6);
    // We ignore the values at {1, 2}, {2, 0}, {2, 1}, and {2, 2}.
    // They don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_two_dyes_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 1;
    DyeSeq ds(num_channels, "00");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 3;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv1.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    psv1.tensor[loc] = 0.4;  // loc is {1, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.5;  // loc is {1, 1}
    loc[1] = 2;
    psv1.tensor[loc] = -1000.0;  // loc is {1, 2} -- to be ignored.
    loc[0] = 2;
    loc[1] = 0;
    psv1.tensor[loc] = 0.6;  // loc is {2, 0}
    loc[1] = 1;
    psv1.tensor[loc] = -1000.0;  // loc is {2, 1} -- to be ignored.
    loc[1] = 2;
    psv1.tensor[loc] = -1000.0;  // loc is {2, 2} -- to be ignored.
    int edmans = 2;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.1 + p_pop * 0.4);  // loc is {0, 0}
    loc[1] = 1;
    // loc is {0, 1}
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.2 + p_pop * (0.5 / 2.0 + 0.4 / 2.0));
    loc[1] = 2;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 0.5);  // loc is {0, 2}
    loc[0] = 1;
    loc[1] = 0;
    // loc is {1, 0}
    BOOST_TEST(psv2.tensor[loc] == p_fail * 0.4 + p_pop * 0.6);
    loc[1] = 1;
    // loc is {1, 1}
    BOOST_TEST(psv2.tensor[loc] == p_fail * 0.5 + p_pop * 0.6);
    // We ignore the values at {1, 2}, {2, 0}, {2, 1}, and {2, 2}.
    // They don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_three_dyes_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "000");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 4;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[1] = 3;
    psv.tensor[loc] = 0.4;  // loc is {0, 3}
    loc[0] = 1;
    loc[1] = 0;
    psv.tensor[loc] = 1.11;  // loc is {1, 0}
    loc[1] = 1;
    psv.tensor[loc] = 1.22;  // loc is {1, 1}
    loc[1] = 2;
    psv.tensor[loc] = 1.33;  // loc is {1, 2}
    loc[1] = 3;
    psv.tensor[loc] = -1000.0;  // loc is {1, 3} -- to be ignored.
    int edmans = 1;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.1 + p_pop * 1.11);  // loc is {0, 0}
    loc[1] = 1;
    // loc is {0, 1}
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.2 + p_pop * (1.11 * 1.0 / 3.0 + 1.22 * 2.0 / 3.0));
    loc[1] = 2;
    // loc is {0, 2}
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * (1.22 * 2.0 / 3.0 + 1.33 * 1.0 / 3.0));
    loc[1] = 3;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.4 + p_pop * 1.33);  // loc is {0, 3}
    // We ignore the values at {1, 0}, {1, 1}, {1, 2}, and {1, 3}.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_three_dyes_first_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 3;
    int num_channels = 1;
    DyeSeq ds(num_channels, "000");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 4;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    psv1.tensor[loc] = 0.1;  // loc is {0, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 0.2;  // loc is {0, 1}
    loc[1] = 2;
    psv1.tensor[loc] = 0.3;  // loc is {0, 2}
    loc[1] = 3;
    psv1.tensor[loc] = 0.4;  // loc is {0, 3}
    loc[0] = 1;
    loc[1] = 0;
    psv1.tensor[loc] = 1.11;  // loc is {1, 0}
    loc[1] = 1;
    psv1.tensor[loc] = 1.22;  // loc is {1, 1}
    loc[1] = 2;
    psv1.tensor[loc] = 1.33;  // loc is {1, 2}
    loc[1] = 3;
    psv1.tensor[loc] = -1000.0;  // loc is {1, 3} -- to be ignored.
    int edmans = 1;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 0);
    loc[0] = 0;
    loc[1] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.1 + p_pop * 1.11);  // loc is {0, 0}
    loc[1] = 1;
    // loc is {0, 1}
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.2 + p_pop * (1.11 * 1.0 / 3.0 + 1.22 * 2.0 / 3.0));
    loc[1] = 2;
    // loc is {0, 2}
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * (1.22 * 2.0 / 3.0 + 1.33 * 1.0 / 3.0));
    loc[1] = 3;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.4 + p_pop * 1.33);  // loc is {0, 3}
    // We ignore the values at {1, 0}, {1, 1}, {1, 2}, and {1, 3}.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_in_place_two_dye_colors_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 2;
    DyeSeq ds(num_channels, "01");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.5;  // loc is {1, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = 0.6;  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {1, 1, 1} -- to be ignored.
    loc[0] = 2;
    loc[1] = 0;
    loc[2] = 0;
    psv.tensor[loc] = 0.7;  // loc is {2, 0, 0}
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 0, 1} -- to be ignored.
    loc[1] = 1;
    loc[2] = 0;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv.tensor[loc] = -1000.0;  // loc is {2, 1, 1} -- to be ignored.
    int edmans = 2;
    et.backward(psv, &edmans, &psv);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.1 + p_pop * 0.5);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.2 + p_pop * 0.6);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.3 + p_pop * 0.5);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv.tensor[loc]
               == p_fail * 0.4 + p_pop * 0.6);  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    // loc is {1, 0, 0}
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.5 + p_pop * 0.7);
    loc[2] = 1;
    // loc is {1, 0, 1}
    BOOST_TEST(psv.tensor[loc] == p_fail * 0.6 + p_pop * 0.7);
    // We ignore the values at {1, 1, 0}, {1, 1, 1}, {2, 0, 0}, {2, 0, 1},
    // {2, 1, 0}, and {2, 1, 1}. They don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(backward_new_tsr_two_dye_colors_second_edman_test,
                     *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 2;
    int num_channels = 2;
    DyeSeq ds(num_channels, "01");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 3;
    int* shape = new int[order];
    shape[0] = 3;
    shape[1] = 2;
    shape[2] = 2;
    PeptideStateVector psv1(order, shape);
    PeptideStateVector psv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    psv1.tensor[loc] = 0.1;  // loc is {0, 0, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 0.2;  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv1.tensor[loc] = 0.3;  // loc is {0, 1, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 0.4;  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    psv1.tensor[loc] = 0.5;  // loc is {1, 0, 0}
    loc[2] = 1;
    psv1.tensor[loc] = 0.6;  // loc is {1, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    psv1.tensor[loc] = -1000.0;  // loc is {1, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv1.tensor[loc] = -1000.0;  // loc is {1, 1, 1} -- to be ignored.
    loc[0] = 2;
    loc[1] = 0;
    loc[2] = 0;
    psv1.tensor[loc] = 0.7;  // loc is {2, 0, 0}
    loc[2] = 1;
    psv1.tensor[loc] = -1000.0;  // loc is {2, 0, 1} -- to be ignored.
    loc[1] = 1;
    loc[2] = 0;
    psv1.tensor[loc] = -1000.0;  // loc is {2, 1, 0} -- to be ignored.
    loc[2] = 1;
    psv1.tensor[loc] = -1000.0;  // loc is {2, 1, 1} -- to be ignored.
    int edmans = 2;
    et.backward(psv1, &edmans, &psv2);
    BOOST_TEST(edmans == 1);
    loc[0] = 0;
    loc[1] = 0;
    loc[2] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.1 + p_pop * 0.5);  // loc is {0, 0, 0}
    loc[2] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.2 + p_pop * 0.6);  // loc is {0, 0, 1}
    loc[1] = 1;
    loc[2] = 0;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.3 + p_pop * 0.5);  // loc is {0, 1, 0}
    loc[2] = 1;
    BOOST_TEST(psv2.tensor[loc]
               == p_fail * 0.4 + p_pop * 0.6);  // loc is {0, 1, 1}
    loc[0] = 1;
    loc[1] = 0;
    loc[2] = 0;
    // loc is {1, 0, 0}
    BOOST_TEST(psv2.tensor[loc] == p_fail * 0.5 + p_pop * 0.7);
    loc[2] = 1;
    // loc is {1, 0, 1}
    BOOST_TEST(psv2.tensor[loc] == p_fail * 0.6 + p_pop * 0.7);
    // We ignore the values at {1, 1, 0}, {1, 1, 1}, {2, 0, 0}, {2, 0, 1},
    // {2, 1, 0}, and {2, 1, 1}. They don't matter.
    delete[] loc;
}

BOOST_AUTO_TEST_CASE(improve_fit_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector fpsv(order, shape);
    PeptideStateVector bpsv(order, shape);
    PeptideStateVector nbpsv(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    fpsv.tensor[loc] = 0.61;  // loc is {0, 0}
    bpsv.tensor[loc] = 0.51;
    nbpsv.tensor[loc] = 0.41;
    loc[1] = 1;
    fpsv.tensor[loc] = 0.91;  // loc is {0, 1}
    bpsv.tensor[loc] = 0.81;
    nbpsv.tensor[loc] = 0.71;
    loc[0] = 1;
    loc[1] = 0;
    fpsv.tensor[loc] = 0.62;  // loc is {1, 0}
    bpsv.tensor[loc] = 0.52;
    nbpsv.tensor[loc] = 0.42;
    loc[1] = 1;
    fpsv.tensor[loc] = 0.92;  // loc is {1, 1}
    bpsv.tensor[loc] = 0.82;
    nbpsv.tensor[loc] = 0.72;
    delete[] loc;
    int edmans = 0;
    double probability = 1.0;
    ErrorModelFitter emf;
    et.improve_fit(fpsv, bpsv, nbpsv, edmans, probability, &emf);
    BOOST_TEST(emf.p_edman_failure_fit.get()
               == (0.91 * p_fail * 0.71) / (0.91 * 0.81));
}

BOOST_AUTO_TEST_CASE(improve_fit_twice_test, *tolerance(TOL)) {
    double p_fail = 0.05;
    double p_pop = 0.95;
    int num_timesteps = 1;
    int num_channels = 1;
    DyeSeq ds(num_channels, "");
    DyeTrack dt(num_timesteps, num_channels, ds);
    EdmanTransition et(p_fail, ds, dt);
    int order = 2;
    int* shape = new int[order];
    shape[0] = 2;
    shape[1] = 2;
    PeptideStateVector fpsv1(order, shape);
    PeptideStateVector bpsv1(order, shape);
    PeptideStateVector nbpsv1(order, shape);
    PeptideStateVector fpsv2(order, shape);
    PeptideStateVector bpsv2(order, shape);
    PeptideStateVector nbpsv2(order, shape);
    delete[] shape;
    int* loc = new int[order];
    loc[0] = 0;
    loc[1] = 0;
    fpsv1.tensor[loc] = 0.31;  // loc is {0, 0}
    bpsv1.tensor[loc] = 0.331;
    nbpsv1.tensor[loc] = 0.21;
    fpsv2.tensor[loc] = 0.221;
    bpsv2.tensor[loc] = 0.11;
    nbpsv2.tensor[loc] = 0.111;
    loc[1] = 1;
    fpsv1.tensor[loc] = 0.91;  // loc is {0, 1}
    bpsv1.tensor[loc] = 0.81;
    nbpsv1.tensor[loc] = 0.71;
    fpsv2.tensor[loc] = 0.61;
    bpsv2.tensor[loc] = 0.51;
    nbpsv2.tensor[loc] = 0.41;
    loc[0] = 1;
    loc[1] = 0;
    fpsv1.tensor[loc] = 0.32;  // loc is {1, 0}
    bpsv1.tensor[loc] = 0.332;
    nbpsv1.tensor[loc] = 0.22;
    fpsv2.tensor[loc] = 0.222;
    bpsv2.tensor[loc] = 0.12;
    nbpsv2.tensor[loc] = 0.112;
    loc[1] = 1;
    fpsv1.tensor[loc] = 0.92;  // loc is {1, 1}
    bpsv1.tensor[loc] = 0.82;
    nbpsv1.tensor[loc] = 0.72;
    fpsv2.tensor[loc] = 0.92;
    bpsv2.tensor[loc] = 0.82;
    nbpsv2.tensor[loc] = 0.72;
    delete[] loc;
    int edmans = 0;
    double prob1 = 0.12345;
    double prob2 = 0.98765;
    ErrorModelFitter emf;
    et.improve_fit(fpsv1, bpsv1, nbpsv1, edmans, prob1, &emf);
    et.improve_fit(fpsv2, bpsv2, nbpsv2, edmans, prob2, &emf);
    BOOST_TEST(emf.p_edman_failure_fit.get()
               == (0.91 * p_fail * 0.71 / prob1 + 0.61 * p_fail * 0.41 / prob2)
                          / (0.91 * 0.81 / prob1 + 0.61 * 0.51 / prob2));
}

BOOST_AUTO_TEST_SUITE_END()  // edman_transition_suite
BOOST_AUTO_TEST_SUITE_END()  // step_suite
BOOST_AUTO_TEST_SUITE_END()  // hmm_suite

}  // namespace whatprot