// --------------------------------------------------------------------------
//                   OpenMS -- Open-Source Mass Spectrometry
// --------------------------------------------------------------------------
// Copyright The OpenMS Team -- Eberhard Karls University Tuebingen,
// ETH Zurich, and Freie Universitaet Berlin 2002-2015.
//
// 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: Chris Bielow $
// $Authors: Chris Bielow $
// --------------------------------------------------------------------------
//

#include <OpenMS/ANALYSIS/QUANTITATION/ItraqQuantifier.h>

// default isotope correction (via A^-1)
// NNLS isotope correction
#include <OpenMS/MATH/MISC/NonNegativeLeastSquaresSolver.h>

#include <OpenMS/DATASTRUCTURES/StringListUtils.h>
#include <OpenMS/DATASTRUCTURES/Utils/MatrixUtils.h>
#include <OpenMS/ANALYSIS/QUANTITATION/ProteinInference.h>
#include <OpenMS/ANALYSIS/ID/IDMapper.h>

#include <Eigen/Core>
#include <Eigen/LU>

#include <limits>

//#define ITRAQ_DEBUG 1

namespace OpenMS
{
  ItraqQuantifier::ItraqQuantifier() :
    DefaultParamHandler("ItraqQuantifier"),
    itraq_type_(FOURPLEX),
    isotope_corrections_()
  {
    initIsotopeCorrections_();
    setDefaultParams_();
  }

  ItraqQuantifier::ItraqQuantifier(Int itraq_type) :
    DefaultParamHandler("ItraqQuantifier"),
    itraq_type_(itraq_type),
    isotope_corrections_()
  {
    initIsotopeCorrections_();
    setDefaultParams_();
  }

  ItraqQuantifier::ItraqQuantifier(Int itraq_type, const Param& param) :
    DefaultParamHandler("ItraqQuantifier"),
    itraq_type_(itraq_type),
    isotope_corrections_()
  {
    initIsotopeCorrections_();
    setDefaultParams_();
    setParameters(param);
    updateMembers_();
  }

  ItraqQuantifier::ItraqQuantifier(const ItraqQuantifier& cp) :
    DefaultParamHandler(cp),
    ItraqConstants(cp),
    itraq_type_(cp.itraq_type_),
    channel_map_(cp.channel_map_),
    isotope_corrections_(cp.isotope_corrections_)
  {
  }

  ItraqQuantifier& ItraqQuantifier::operator=(const ItraqQuantifier& rhs)
  {
    if (this == &rhs)
      return *this;

    DefaultParamHandler::operator=(rhs);
    ItraqConstants::operator=(rhs);
    itraq_type_ = rhs.itraq_type_;
    channel_map_ = rhs.channel_map_;
    isotope_corrections_ = rhs.isotope_corrections_;

    return *this;
  }

  bool ItraqQuantifier::isIdentityCorrectionMatrix_(const Matrix<double>& channel_frequency) const
  {
    // check if we have an identity matrix
    bool isIdentity = true;
    for (Size i = 0; i < channel_frequency.cols(); ++i)
    {
      if (channel_frequency.getValue(i, i) != 1.0)
      {
        isIdentity = false;
        break;
      }
    }
    return isIdentity;
  }

  void ItraqQuantifier::run(const ConsensusMap& consensus_map_in, ConsensusMap& consensus_map_out)
  {
    // new stats
    stats_ = ItraqQuantifierStats();
    stats_.channel_count = CHANNEL_COUNT[itraq_type_];
    if (consensus_map_in.empty())
    {
      LOG_WARN << "Warning: Empty iTRAQ container. No quantitative information available!" << std::endl;
      return;
    }

    reconstructChannelInfo_(consensus_map_in);
    consensus_map_out = consensus_map_in;

    // first do isotope correction
    if (String(param_.getValue("isotope_correction")) == "true")
    {
      // translate isotope_corrections_ to a channel_frequency matrix
      Matrix<double> channel_frequency = ItraqConstants::translateIsotopeMatrix(itraq_type_, isotope_corrections_);

      // if it is an identity matrix, performing isotope correction makes no sense
      if (isIdentityCorrectionMatrix_(channel_frequency))
      {
        throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier: The given isotope correction matrix is an identity matrix leading to no correction. Please provide a valid isotope_correction matrix as it was provided with the iTRAQ/TMT kit!");
      }

#ifdef ITRAQ_DEBUG
      std::cout << "channel_frequency matrix: \n" << channel_frequency << "\n" << std::endl;
#endif

      // ISOTOPE CORRECTION: this solves the system naively via matrix inversion
      EigenMatrixXdPtr m(convertOpenMSMatrix2EigenMatrixXd(channel_frequency));
      Eigen::FullPivLU<Eigen::MatrixXd> ludecomp(*m);
      Eigen::VectorXd b;
      b.resize(CHANNEL_COUNT[itraq_type_]);
      b.setZero();
      std::vector<double> vec_x(CHANNEL_COUNT[itraq_type_], 0);

      if (!ludecomp.isInvertible())
      {
        throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier: Invalid entry in Param 'isotope_correction_values'; the Matrix is not invertible!");
      }

      LOG_INFO << "SOLVING isotope correction via NNLS\n";

      Matrix<double> m_b(CHANNEL_COUNT[itraq_type_], 1);
      Matrix<double> m_x(CHANNEL_COUNT[itraq_type_], 1);

      // correct all consensus elements
      for (size_t i = 0; i < consensus_map_out.size(); ++i)
      {
#ifdef ITRAQ_DEBUG
        std::cout << "\nMAP element  #### " << i << " #### \n" << std::endl;
#endif

        consensus_map_out[i].clear(); // delete only the consensus handles
        // fill b vector
        for (ConsensusFeature::HandleSetType::const_iterator it_elements = consensus_map_in[i].getFeatures().begin();
             it_elements != consensus_map_in[i].getFeatures().end();
             ++it_elements)
        {

          //find channel_id of current element
          Int index = Int(consensus_map_in.getFileDescriptions().find(it_elements->getMapIndex())->second.getMetaValue("channel_id"));
#ifdef ITRAQ_DEBUG
          std::cout << "  map_index " << it_elements->getMapIndex() << "-> id " << index << " with intensity " << it_elements->getIntensity() << "\n" << std::endl;
#endif

          // this is deprecated, but serves as quality measurement
          b(index) = it_elements->getIntensity();
          m_b(index, 0) = it_elements->getIntensity();
        }

        // solve
        Eigen::MatrixXd matrix_x = ludecomp.solve(b);
        // check if a solution exists
        if (!((*m) * matrix_x).isApprox(b))
        {
          throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier: Invalid entry in Param 'isotope_correction_values'; Cannot multiply!");
        }
        Int status = NonNegativeLeastSquaresSolver::solve(channel_frequency, m_b, m_x);
        if (status != NonNegativeLeastSquaresSolver::SOLVED)
        {
          throw Exception::FailedAPICall(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier: Failed to find least-squares fit!");
        }

        Size s_negative(0);
        Size s_different_count(0); // happens when naive solution is negative in other channels
        double s_different_intensity(0);
        // ISOTOPE CORRECTION: compare solutions of Matrix inversion vs. NNLS
        for (Size index = 0; index < (Size)CHANNEL_COUNT[itraq_type_]; ++index)
        {
          if (matrix_x(index) < 0.0)
          {
            ++s_negative;
          }
          else if (std::fabs(m_x(index, 0) - matrix_x(index)) > 0.000001)
          {
            ++s_different_count;
            s_different_intensity += std::fabs(m_x(index, 0) - matrix_x(index));
          }
        }

        if (s_negative == 0 && s_different_count > 0) // solutions are inconsistent, despite being positive! This should not happen!
        {
          throw Exception::Postcondition(__FILE__, __LINE__, __PRETTY_FUNCTION__, "Isotope correction values of alternative method differ!");
        }

        // update global stats
        stats_.iso_number_reporter_negative += s_negative;
        stats_.iso_number_reporter_different += s_different_count;
        stats_.iso_solution_different_intensity += s_different_intensity;

        // write back the values to the map
        Peak2D::IntensityType cf_intensity(0);
        for (ConsensusFeature::HandleSetType::const_iterator it_elements = consensus_map_in[i].begin();
             it_elements != consensus_map_in[i].end();
             ++it_elements)
        {
          FeatureHandle handle = *it_elements;
          //find channel_id of current element
          Int index = Int(consensus_map_out.getFileDescriptions()[it_elements->getMapIndex()].getMetaValue("channel_id"));

          handle.setIntensity(Peak2D::IntensityType(m_x(index, 0)));

          consensus_map_out[i].insert(handle);

          cf_intensity += handle.getIntensity(); // sum up all channels for CF

#ifdef ITRAQ_DEBUG
          std::cout <<  it_elements->getIntensity() << " -> " << handle.getIntensity() << std::endl;
#endif
        }
        consensus_map_out[i].setIntensity(cf_intensity); // set overall intensity of CF (sum of all channels)

        if (s_negative > 0)
        {
          ++stats_.iso_number_ms2_negative;
          stats_.iso_total_intensity_negative += cf_intensity;
        }
      }

    } // ! isotope_correction
    else
    {
      LOG_WARN << "Warning: Due to deactivated isotope-correction labeling statistics will be based on raw intensities, which might give too optimistic results." << std::endl;
    }

    stats_.number_ms2_total = consensus_map_out.size();
    // ------------------------------
    // Labeling efficiency statistics
    // ------------------------------
    std::map<Size, Size> empty_channel;

    for (size_t i = 0; i < consensus_map_out.size(); ++i)
    {
      // is whole scan empty?!
      if (consensus_map_out[i].getIntensity() == 0)
        ++stats_.number_ms2_empty;

      // look at single reporters
      for (ConsensusFeature::HandleSetType::const_iterator it_elements = consensus_map_out[i].begin();
           it_elements != consensus_map_out[i].end();
           ++it_elements)
      {
        if (it_elements->getIntensity() == 0)
        {
          Int ch_index = consensus_map_out.getFileDescriptions()[it_elements->getMapIndex()].getMetaValue("channel_name");
          ++empty_channel[ch_index];
        }
      }
    }
    LOG_INFO << "iTRAQ: skipped " << stats_.number_ms2_empty << " of " << consensus_map_out.size() << " selected scans due to lack of iTRAQ information:\n";
    consensus_map_out.setMetaValue("itraq:scans_noquant", stats_.number_ms2_empty);
    consensus_map_out.setMetaValue("itraq:scans_total", consensus_map_out.size());

    stats_.empty_channels = empty_channel;

    LOG_INFO << "iTRAQ: channels with signal\n";
    for (std::map<Size, Size>::const_iterator it_m = empty_channel.begin(); it_m != empty_channel.end(); ++it_m)
    {
      LOG_INFO << "      channel " << it_m->first << ": " << (consensus_map_out.size() - it_m->second) << " / " <<  consensus_map_out.size() << " (" << ((consensus_map_out.size() - it_m->second) * 100 / consensus_map_out.size()) << "%)\n";
      consensus_map_out.setMetaValue(String("itraq:quantifyable_ch") + it_m->first, (consensus_map_out.size() - it_m->second));
    }

    // ****************************
    // ** find reference channel **
    // ****************************
    Int reference_channel = Int(param_.getValue("channel_reference"));
    if (itraq_type_ == ItraqConstants::FOURPLEX && (reference_channel < 114 || reference_channel > 117))
    {
      throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier:Invalid entry in Param 'channel_reference'; Valid channels for 4plex are 114-117!");
    }
    else if (itraq_type_ == ItraqConstants::EIGHTPLEX && (reference_channel < 113 || reference_channel > 121))
    {
      throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier:Invalid entry in Param 'channel_reference'; Valid channels for 8plex are 113-121!");
    }
    else if (itraq_type_ == ItraqConstants::TMT_SIXPLEX && (reference_channel < 126 || reference_channel > 131))
    {
      throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier:Invalid entry in Param 'channel_reference'; Valid channels for TMT-6plex are 126-131!");
    }

#ifdef ITRAQ_DEBUG
    std::cout << "reference_channel is: " << reference_channel  << std::endl;
#endif

    // determine reference channel as vector index
    Map<Size, Size> map_to_vectorindex;
    Size ref_mapid = 0;
    Size index = 0;
    for (ConsensusMap::FileDescriptions::const_iterator file_it = consensus_map_out.getFileDescriptions().begin();
         file_it != consensus_map_out.getFileDescriptions().end();
         ++file_it)
    {
      if ((Int) file_it->second.getMetaValue("channel_name") == reference_channel)
      {
        ref_mapid = file_it->first;
#ifdef ITRAQ_DEBUG
        std::cout << "reference_map_id is: " << ref_mapid <<  std::endl;
#endif
      }
      map_to_vectorindex[file_it->first] = index;
      ++index;
    }

    // ** NORMALIZATION ** //

    // normalize median of channel-to-reference ratio to 1
    if (String(param_.getValue("do_normalization")) == "true")
    {
      if (channel_map_.has(reference_channel))
      {
        std::vector<std::vector<double> > peptide_ratios;
        // this is a control (the normalization factors should be about the same)
        std::vector<std::vector<double> > peptide_intensities;

        // build mapping of map_index to ratio_array_index
        peptide_ratios.resize(channel_map_.size());
        peptide_intensities.resize(channel_map_.size());

        //build up ratios for each peptide of non-reference channels
        ConsensusFeature::HandleSetType::iterator ref_it;
        Peak2D::IntensityType ref_intensity;
        for (size_t i = 0; i < consensus_map_out.size(); ++i)
        {
          // find reference index (this is inefficient to do every time,
          // but the most robust against anyone who tries to change the internals of ConsensusFeature):
          ref_it = consensus_map_out[i].end();
          for (ConsensusFeature::HandleSetType::iterator it_elements = consensus_map_out[i].begin();
               it_elements != consensus_map_out[i].end();
               ++it_elements)
          {
            if ((Int) consensus_map_out.getFileDescriptions()[it_elements->getMapIndex()].getMetaValue("channel_name") == reference_channel)
            {
              ref_it = it_elements;
              break;
            }
          }

          // reference channel not found in this ConsensusFeature
          if (ref_it == consensus_map_out[i].end())
          {
            LOG_ERROR << "ItraqQuantifier::run() WARNING: ConsensusFeature " << i << " does not have a reference channel! Skipping" << std::endl;
            continue;
          }

          ref_intensity = ref_it->getIntensity();

          // now collect the ratios and intensities
          for (ConsensusFeature::HandleSetType::iterator it_elements = consensus_map_out[i].begin();
               it_elements != consensus_map_out[i].end();
               ++it_elements)
          {
            if (ref_intensity == 0) //avoid nan's and inf's
            {
              if (it_elements->getIntensity() == 0) // 0/0 will give 'nan'
              {
                //so leave it out completely (there is no information to be gained)
              }
              else // x/0 is 'inf' but std::sort() has problems with that
              {
                peptide_ratios[map_to_vectorindex[it_elements->getMapIndex()]].push_back(std::numeric_limits<double>::max());
              }
            }
            else // everything seems fine
            {
              peptide_ratios[map_to_vectorindex[it_elements->getMapIndex()]].push_back(it_elements->getIntensity() / ref_intensity);
            }

            // control
            peptide_intensities[map_to_vectorindex[it_elements->getMapIndex()]].push_back(it_elements->getIntensity());
          }
        } // ! collect ratios

        double max_deviation_from_control = 0;
        // find MEDIAN of ratios for each channel (store as 0th element in sorted vector)
        for (Map<Size, Size>::const_iterator it_map = map_to_vectorindex.begin(); it_map != map_to_vectorindex.end(); ++it_map)
        {
          // sort vector (partial_sort might improve performance here)
          std::sort(peptide_ratios[it_map->second].begin(), peptide_ratios[it_map->second].end());
          // save median as first element
          peptide_ratios[it_map->second][0] = peptide_ratios[it_map->second][peptide_ratios[it_map->second].size() / 2];

          // sort control (intensities)
          std::sort(peptide_intensities[it_map->second].begin(), peptide_intensities[it_map->second].end());
          // find MEDIAN of control-method (intensities) for each channel
          peptide_intensities[it_map->second][0] = peptide_intensities[it_map->second][peptide_intensities[it_map->second].size() / 2] /
                                                   peptide_intensities[ref_mapid][peptide_intensities[ref_mapid].size() / 2];
          //#ifdef ITRAQ_DEBUG
          LOG_INFO << "iTRAQ-normalize:  map-id " << (it_map->first) << " has factor " << (peptide_ratios[it_map->second][0]) << " (control: " << (peptide_intensities[it_map->second][0]) << ")" << std::endl;
          //#endif
          double dev = (peptide_ratios[it_map->second][0] - peptide_intensities[it_map->second][0]) / peptide_ratios[it_map->second][0];
          if (fabs(max_deviation_from_control) < fabs(dev))
          {
            max_deviation_from_control = dev;
          }
        }

        LOG_INFO << "iTRAQ-normalization: max ratio deviation of alternative method is " << (max_deviation_from_control * 100) << "%\n";

#ifdef ITRAQ_DEBUG
        std::cout << "debug OUTPUT\n";
        for (Size i = 1; i < peptide_ratios[0].size(); ++i)
        {
          if (i == peptide_intensities[0].size() / 2)
          {
            std::cout << "++++++++++ median: \n";
          }
          for (Size j = 0; j < peptide_ratios.size(); ++j)
          {
            std::cout << peptide_ratios[j][i] << " ";
          }
          std::cout << " -- int -- ";
          for (Size j = 0; j < peptide_intensities.size(); ++j)
          {
            std::cout << peptide_intensities[j][i] << " ";
          }
          if (i == peptide_intensities[0].size() / 2)
          {
            std::cout << "\n----------- median: ";
          }
          std::cout << "\n";
        }
#endif

        // adjust intensity ratios
        for (size_t i = 0; i < consensus_map_out.size(); ++i)
        {
          // find reference index (this is inefficient to do every time,
          // but the most robust against anyone who tries to change the internals of ConsensusFeature):
          ref_it = consensus_map_out[i].end();
          for (ConsensusFeature::HandleSetType::iterator it_elements = consensus_map_out[i].begin();
               it_elements != consensus_map_out[i].end();
               ++it_elements)
          {
            if ((Int) consensus_map_out.getFileDescriptions()[it_elements->getMapIndex()].getMetaValue("channel_name") == reference_channel)
            {
              ref_it = it_elements;
              break;
            }
          }

          // reference channel not found in this ConsensusFeature
          if (ref_it == consensus_map_out[i].end())
          {
            continue;
          }

          ref_intensity = ref_it->getIntensity();

          // now adjust the ratios
          ConsensusFeature cf = consensus_map_out[i];
          cf.clear(); // delete its handles
          for (ConsensusFeature::HandleSetType::iterator it_elements = consensus_map_out[i].begin();
               it_elements != consensus_map_out[i].end();
               ++it_elements)
          {
            FeatureHandle hd = *it_elements;
            if (it_elements == ref_it)
            {
              hd.setIntensity(1);
            }
            else // divide current intensity by normalization factor (which was stored at position 0)
            {
              hd.setIntensity(hd.getIntensity() / peptide_ratios[map_to_vectorindex[it_elements->getMapIndex()]][0]);
            }
            cf.insert(hd);
          }
          // replace consensusFeature with updated intensity
          consensus_map_out[i] = cf;
        } // ! adjust ratios

      } // ! ref_channel valid
      else
      {
        throw Exception::InvalidParameter(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier::run() Parameter 'channel_reference' does not name a valid channel!");
      }
    } // !do_normalization

    // ** PEPTIDE PROTEIN MAPPING ** //

    consensus_map_out.setExperimentType("itraq");

    return;
  }

  ItraqQuantifier::ItraqQuantifierStats ItraqQuantifier::getStats() const
  {
    return stats_;
  }

  void ItraqQuantifier::setDefaultParams_()
  {
    // choose default and documentation depending on itraq/tmt, since we can provide no stable default for TMT
    defaults_.setValue("isotope_correction",
                       (itraq_type_ == TMT_SIXPLEX
                        ? "false"
                        : "true"),
                       (itraq_type_ == TMT_SIXPLEX
                        ? "Enable isotope correction (highly recommended). Note that you need to provide a correction matrix (see isotope_correction:tmt-6plex otherwise the tool will fail."
                        : "Enable isotope correction (highly recommended)."),
                       ListUtils::create<String>("advanced"));
    defaults_.setValidStrings("isotope_correction", ListUtils::create<String>("true,false"));

    defaults_.setValue("do_normalization", "false", "Normalize channels? Done by using the Median of Ratios (every channel / Reference). Also the ratio of medians (from any channel and reference) is provided as control measure!", ListUtils::create<String>("advanced"));
    defaults_.setValidStrings("do_normalization", ListUtils::create<String>("true,false"));

    if (itraq_type_ == TMT_SIXPLEX)
    {
      defaults_.setValue("isotope_correction:tmt-6plex",
                         ItraqConstants::getIsotopeMatrixAsStringList(ItraqConstants::TMT_SIXPLEX, isotope_corrections_),
                         "Override default values (see Documentation); use the following format: <channel>:<-2Da>/<-1Da>/<+1Da>/<+2Da> ; e.g. '126:0/0.3/4/0' , '128:0.1/0.3/3/0.2'.",
                         ListUtils::create<String>("advanced"));
    }
    else
    {
      defaults_.setValue("isotope_correction:4plex",
                         ItraqConstants::getIsotopeMatrixAsStringList(ItraqConstants::FOURPLEX, isotope_corrections_),
                         "Override default values (see Documentation); use the following format: <channel>:<-2Da>/<-1Da>/<+1Da>/<+2Da> ; e.g. '114:0/0.3/4/0' , '116:0.1/0.3/3/0.2'.",
                         ListUtils::create<String>("advanced"));
      defaults_.setValue("isotope_correction:8plex",
                         ItraqConstants::getIsotopeMatrixAsStringList(ItraqConstants::EIGHTPLEX, isotope_corrections_),
                         "Override default values (see Documentation); use the following format: <channel>:<-2Da>/<-1Da>/<+1Da>/<+2Da> ; e.g. '114:0/0.3/4/0' , '116:0.1/0.3/3/0.2'.",
                         ListUtils::create<String>("advanced"));
    }

    defaults_.setSectionDescription("isotope_correction",
                                    (itraq_type_ == TMT_SIXPLEX
                                     ? "Isotope correction matrices for tmt-6plex."
                                     : "Isotope correction matrices for 4plex and 8plex. Only one of them will be used (depending on iTRAQ mode)."));

    // for 4 & 8 plex. Max value is again checked during runtime
    defaults_.setValue("channel_reference",
                       (itraq_type_ != TMT_SIXPLEX
                        ? 114
                        : 126),
                       (itraq_type_ != TMT_SIXPLEX
                        ? "Number of the reference channel (114-117 for 4plex)."
                        : "Number of the reference channel (126-131)."));
    if (itraq_type_ == TMT_SIXPLEX)
    {
      defaults_.setMinInt("channel_reference", 126);
      defaults_.setMaxInt("channel_reference", 131);
    }
    else if (itraq_type_ == FOURPLEX)
    {
      defaults_.setMinInt("channel_reference", 114);
      defaults_.setMaxInt("channel_reference", 117);
    }
    else // EIGHTPLEX
    {
      defaults_.setMinInt("channel_reference", 113);
      defaults_.setMaxInt("channel_reference", 121);
    }

    defaultsToParam_();
  }

  void ItraqQuantifier::updateMembers_()
  {
    StringList channels;
    // update isotope_corrections_ Matrix with custom values
    if (itraq_type_ == ItraqConstants::FOURPLEX)
    {
      channels = param_.getValue("isotope_correction:4plex");
    }
    else if (itraq_type_ == ItraqConstants::EIGHTPLEX)
    {
      channels = param_.getValue("isotope_correction:8plex");
    }
    else if (itraq_type_ == ItraqConstants::TMT_SIXPLEX)
    {
      channels = param_.getValue("isotope_correction:tmt-6plex");
    }

    if (channels.size() > 0)
    {
      ItraqConstants::updateIsotopeMatrixFromStringList(itraq_type_, channels, isotope_corrections_);
    }
  }

  /// initialize
  void ItraqQuantifier::initIsotopeCorrections_()
  {
    isotope_corrections_.resize(3);
    isotope_corrections_[0].setMatrix<4, 4>(ItraqConstants::ISOTOPECORRECTIONS_FOURPLEX);
    isotope_corrections_[1].setMatrix<8, 4>(ItraqConstants::ISOTOPECORRECTIONS_EIGHTPLEX);
    isotope_corrections_[2].setMatrix<6, 4>(ItraqConstants::ISOTOPECORRECTIONS_TMT_SIXPLEX);
  }

  /// extract channel information (active channels, names, etc) from ConsensusMap
  void ItraqQuantifier::reconstructChannelInfo_(const ConsensusMap& consensus_map)
  {
    channel_map_.clear();

    for (ConsensusMap::FileDescriptions::const_iterator file_it = consensus_map.getFileDescriptions().begin();
         file_it != consensus_map.getFileDescriptions().end();
         ++file_it)
    {
      if (file_it->second.metaValueExists("channel_name"))
      {
        ChannelInfo info;
        // fill info
        info.name = file_it->second.getMetaValue("channel_name");
        info.id = file_it->second.getMetaValue("channel_id");
        info.description = file_it->second.getMetaValue("channel_description");
        info.center = file_it->second.getMetaValue("channel_center");
        info.active = (String(file_it->second.getMetaValue("channel_active")) == "true" ? true : false);
        channel_map_[info.name] = info;
#ifdef ITRAQ_DEBUG
        std::cout << " setting info.name " << (info.name) << " and id " << (info.id) << std::endl;
#endif
      }
      else
      {
        throw Exception::MissingInformation(__FILE__, __LINE__, __PRETTY_FUNCTION__, "ItraqQuantifier::reconstructChannelInfo_ The ConsensusMap provided is missing MetaInfo from ItraqChannelExtractor!");
      }
    }
  }

  std::ostream& operator<<(std::ostream& os, const ItraqQuantifier::ItraqQuantifierStats& stats)
  {
    os << "name\tvalue\t(value in %)\n";
    os << "# channels\t" << stats.channel_count << "\tNA\n";
    os << "# spectra total\t" << stats.number_ms2_total << "\tNA\n";
    os << "# spectra negative\t" << stats.iso_number_reporter_negative << "\tNA\n";
    os << "# negative reporter intensity\t" << stats.iso_number_reporter_negative << "\tNA\n";
    os << "# alternative positive reporter intensity\t" << stats.iso_number_reporter_different << "\tNA\n";
    os << "total intensity (affected spectra)\t" << stats.iso_total_intensity_negative << "\tNA\n";
    os << "total intensity difference (affected spectra)\t" << stats.iso_solution_different_intensity << "\t" << (stats.iso_solution_different_intensity * 100 / stats.iso_total_intensity_negative) << "\n";

    for (std::map<Size, Size>::const_iterator it_m = stats.empty_channels.begin(); it_m != stats.empty_channels.end(); ++it_m)
    {
      os << "labeling_efficiency_channel_" << it_m->first << "\t" << (stats.number_ms2_total - it_m->second) << "\t" << ((stats.number_ms2_total - it_m->second) * 100 / stats.number_ms2_total) << "\n";
    }

    return os;
  }

}


#ifndef DART_NEURAL_CONSTRAINT_MATRICES_HPP_
#define DART_NEURAL_CONSTRAINT_MATRICES_HPP_

#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include <Eigen/Dense>

#include "dart/neural/DifferentiableContactConstraint.hpp"
#include "dart/neural/NeuralConstants.hpp"
#include "dart/neural/NeuralUtils.hpp"
#include "dart/performance/PerformanceLog.hpp"
#include "dart/simulation/World.hpp"

namespace dart {

namespace constraint {
class ConstrainedGroup;
class ConstraintBase;
} // namespace constraint

namespace dynamics {
class Skeleton;
} // namespace dynamics

using namespace performance;

namespace neural {

enum ConstraintMapping
{
  CLAMPING = -1,
  NOT_CLAMPING = -2,
  ILLEGAL = -3,
  IRRELEVANT = -4
};

/// This class pairs with a ConstrainedGroup, to save all the constraint
/// matrices and related info for that ConstrainedGroup, so that we can
/// construct full Jacobian matrices or run backprop later.
class ConstrainedGroupGradientMatrices
{
public:
  ConstrainedGroupGradientMatrices(
      constraint::ConstrainedGroup& group, s_t timeStep);

  /// This is a constructor for test mocks
  ConstrainedGroupGradientMatrices(
      int numDofs, int numConstraintDim, s_t timeStep);

  /// This gets called during the setup of the ConstrainedGroupGradientMatrices
  /// at each constraint. This must be called before constructMatrices(), and
  /// must be called exactly once for each constraint.
  void registerConstraint(
      const std::shared_ptr<constraint::ConstraintBase>& constraint);

  /// This mocks registering a constaint. Useful for testing.
  void mockRegisterConstraint(
      s_t restitutionCoeff, s_t penetrationHackVel);

  /// This gets called during the setup of the ConstrainedGroupGradientMatrices
  /// at each constraint's dimension. It gets called _after_ the system has
  /// already applied a measurement impulse to that constraint dimension, and
  /// measured some velocity changes. This must be called before
  /// constructMatrices(), and must be called exactly once for each constraint's
  /// dimension.
  void measureConstraintImpulse(
      const std::shared_ptr<constraint::ConstraintBase>& constraint,
      std::size_t constraintIndex);

  /// This will attempt to quickly solve an LCP by exploiting locality in the
  /// solution. Assuming we were initialized at the last solution, there's
  /// actually a good chance that we're still in all the same force categories.
  /// This gets called before constructMatrices(), so it assumes no matrices
  /// exist yet. Returns true if successful, false otherwise.
  bool attemptFastSolveLCP(
      simulation::World* world,
      Eigen::VectorXs& mX,
      const Eigen::VectorXs& A,
      const Eigen::VectorXs& mHi,
      const Eigen::VectorXs& mLo,
      const Eigen::VectorXs& mB,
      const Eigen::VectorXs& mFIndex);

  /// This mocks measuring a constraint impulse. Useful for testing.
  void mockMeasureConstraintImpulse(Eigen::VectorXs massedImpulseTest);

  /// This gets called during the setup of the ConstrainedGroupGradientMatrices
  /// after the LCP has run, with the result from the LCP solver.
  void registerLCPResults(
      Eigen::VectorXs mX,
      Eigen::VectorXs hi,
      Eigen::VectorXs lo,
      Eigen::VectorXi fIndex,
      Eigen::VectorXs b,
      Eigen::VectorXs aColNorms,
      Eigen::MatrixXs A,
      s_t constraintForceMixingConstant,
      bool deliberatelyIgnoreFriction);

  /// If possible (because A is rank-deficient), this changes mX to be the
  /// least-squares minimal solution. This makes mX unique for a given set of
  /// inputs, rather than leaving the exact solution undefined. This can also be
  /// used to short-circuit an LCP solve before it even needs to start, by using
  /// the previous LCP solution as a "close enough" guess that can then be
  /// cleaned up by this method and made exact. To faccilitate that use case,
  /// this method returns true if it's found a valid solution, whether it
  /// changed anything or not, and false if the solution is invalid.
  bool opportunisticallyStandardizeResults(
      simulation::World* world, Eigen::VectorXs& mX);

  /// This returns true if the proposed mX is consistent with our recorded LCP
  /// construction
  bool isSolutionValid(const Eigen::VectorXs& mX);

  /// This gets called by constructMatrices()
  void deduplicateConstraints();

  /// This gets called during the setup of the ConstrainedGroupGradientMatrices
  /// after registerLCPResults(). This can only
  /// be called once, and after this is called you cannot call
  /// measureConstraintImpulse() again!
  void constructMatrices(
      simulation::World* world,
      Eigen::VectorXi overrideClasses = Eigen::VectorXi::Zero(0));

  /// This computes and returns the whole vel-vel jacobian for this group. For
  /// backprop, you don't actually need this matrix, you can compute backprop
  /// directly. This is here if you want access to the full Jacobian for some
  /// reason.
  Eigen::MatrixXs getVelVelJacobian(
      simulation::WorldPtr world, PerformanceLog* perfLog = nullptr);

  /// This computes and returns the whole pos-vel jacobian. For backprop, you
  /// don't actually need this matrix, you can compute backprop directly. This
  /// is here if you want access to the full Jacobian for some reason.
  Eigen::MatrixXs getPosVelJacobian(
      simulation::WorldPtr world, PerformanceLog* perfLog = nullptr);

  /// This computes and returns the whole force-vel jacobian for this group. For
  /// backprop, you don't actually need this matrix, you can compute backprop
  /// directly. This is here if you want access to the full Jacobian for some
  /// reason.
  Eigen::MatrixXs getControlForceVelJacobian(
      simulation::WorldPtr world, PerformanceLog* perfLog = nullptr);

  /// This computes and returns the whole pos-pos jacobian for this group. For
  /// backprop, you don't actually need this matrix, you can compute backprop
  /// directly. This is here if you want access to the full Jacobian for some
  /// reason.
  Eigen::MatrixXs getPosPosJacobian(
      simulation::WorldPtr world, PerformanceLog* perfLog = nullptr);

  /// This computes and returns the whole vel-pos jacobian for this group. For
  /// backprop, you don't actually need this matrix, you can compute backprop
  /// directly. This is here if you want access to the full Jacobian for some
  /// reason.
  Eigen::MatrixXs getVelPosJacobian(
      simulation::WorldPtr world, PerformanceLog* perfLog = nullptr);

  /// This returns the [dC(pos,vel)/dpos] for the group, a block diagonal
  /// concatenation of the skeleton [dC(pos,vel)/dpos] matrices.
  Eigen::MatrixXs getPosCJacobian(simulation::WorldPtr world);

  /// This returns the [dC(pos,vel)/dvel] for the group, a block diagonal
  /// concatenation of the skeleton [dC(pos,vel)/dvel] matrices.
  Eigen::MatrixXs getVelCJacobian(simulation::WorldPtr world);

  /// This returns the mass matrix for the group, a block diagonal
  /// concatenation of the skeleton mass matrices.
  Eigen::MatrixXs getMassMatrix(simulation::WorldPtr world);

  /// This returns the inverse mass matrix for the group, a block diagonal
  /// concatenation of the skeleton inverse mass matrices.
  Eigen::MatrixXs getInvMassMatrix(simulation::WorldPtr world);

  /// This returns the block diagonal matrix where each skeleton's joints
  /// integration scheme is reflected.
  Eigen::MatrixXs getJointsPosPosJacobian(simulation::WorldPtr world);

  /// This returns the block diagonal matrix where each skeleton's joints
  /// integration scheme is reflected.
  Eigen::MatrixXs getJointsVelPosJacobian(simulation::WorldPtr world);

  /// This computes and returns the component of the pos-pos and pos-vel
  /// jacobians due to bounce approximation. For backprop, you don't actually
  /// need this matrix, you can compute backprop directly. This is here if you
  /// want access to the full Jacobian for some reason.
  Eigen::MatrixXs getBounceApproximationJacobian(PerformanceLog* perfLog);

  /// This computes and returns the whole pos-vel jacobian. For backprop, you
  /// don't actually need this matrix, you can compute backprop directly. This
  /// is here if you want access to the full Jacobian for some reason.
  Eigen::MatrixXs getVelJacobianWrt(
      simulation::WorldPtr world, WithRespectTo* wrt);

  /// This returns the jacobian of constraint force, holding everyhing constant
  /// except the value of WithRespectTo
  Eigen::MatrixXs getJacobianOfConstraintForce(
      simulation::WorldPtr world, WithRespectTo* wrt);

  /// This returns the analytical expression for the Jacobian of Q*b, holding b
  /// constant, if there are some upper-bound indices
  Eigen::MatrixXs dQ_WithUB(
      simulation::WorldPtr world,
      const Eigen::MatrixXs& Minv,
      const Eigen::MatrixXs& A_c,
      const Eigen::MatrixXs& E,
      const Eigen::MatrixXs& A_c_ub_E,
      Eigen::VectorXs rhs,
      WithRespectTo* wrt);

  /// This returns the analytical expression for the Jacobian of Q^T*b, holding
  /// b constant, if there are some upper-bound indices
  Eigen::MatrixXs dQT_WithUB(
      simulation::WorldPtr world,
      const Eigen::MatrixXs& Minv,
      const Eigen::MatrixXs& A_c,
      const Eigen::MatrixXs& E,
      const Eigen::MatrixXs& A_ub,
      Eigen::VectorXs rhs,
      WithRespectTo* wrt);

  /// This returns the analytical expression for the Jacobian of Q*b, holding b
  /// constant, if there are no upper-bound indices
  Eigen::MatrixXs dQ_WithoutUB(
      simulation::WorldPtr world,
      const Eigen::MatrixXs& Minv,
      const Eigen::MatrixXs& A_c,
      Eigen::VectorXs rhs,
      WithRespectTo* wrt);

  /// This returns the vector of constants that get added to the diagonal of Q
  /// to guarantee that Q is full-rank
  Eigen::VectorXs& getConstraintForceMixingDiagonal();

  /// This returns the jacobian of Q^{-1}b, holding b constant, with respect to
  /// wrt
  Eigen::MatrixXs getJacobianOfLCPConstraintMatrixClampingSubset(
      simulation::WorldPtr world, Eigen::VectorXs b, WithRespectTo* wrt);

  /// This returns the jacobian of b (from Q^{-1}b) with respect to wrt
  Eigen::MatrixXs getJacobianOfLCPOffsetClampingSubset(
      simulation::WorldPtr world, WithRespectTo* wrt);

  /// This returns the subset of the A matrix used by the original LCP for just
  /// the clamping constraints. It relates constraint force to constraint
  /// acceleration. It's a mass matrix, just in a weird frame.
  void computeLCPConstraintMatrixClampingSubset(
      simulation::WorldPtr world,
      Eigen::MatrixXs& Q,
      const Eigen::MatrixXs& A_c);

  /// This returns the subset of the b vector used by the original LCP for just
  /// the clamping constraints. It's just the relative velocity at the clamping
  /// contact points.
  void computeLCPOffsetClampingSubset(
      simulation::WorldPtr world,
      Eigen::VectorXs& b,
      const Eigen::MatrixXs& A_c);

  /// This computes and returns an estimate of the constraint impulses for the
  /// clamping constraints. This is based on a linear approximation of the
  /// constraint impulses.
  Eigen::VectorXs estimateClampingConstraintImpulses(
      simulation::WorldPtr world, const Eigen::MatrixXs& A_c);

  /// This returns the jacobian of M^{-1}(pos, inertia) * tau, holding
  /// everything constant except the value of WithRespectTo
  Eigen::MatrixXs getJacobianOfMinv(
      simulation::WorldPtr world, Eigen::VectorXs tau, WithRespectTo* wrt);

  /// This returns the jacobian of C(pos, inertia, vel), holding everything
  /// constant except the value of WithRespectTo
  Eigen::MatrixXs getJacobianOfC(
      simulation::WorldPtr world, WithRespectTo* wrt);

  /// This computes the Jacobian of A_c*f0 with respect to position using
  /// impulse tests.
  Eigen::MatrixXs getJacobianOfClampingConstraints(
      simulation::WorldPtr world, Eigen::VectorXs f0);

  /// This computes the Jacobian of A_c^T*v0 with respect to position using
  /// impulse tests.
  Eigen::MatrixXs getJacobianOfClampingConstraintsTranspose(
      simulation::WorldPtr world, Eigen::VectorXs v0);

  /// This computes the Jacobian of A_ub*E*f0 with respect to position using
  /// impulse tests.
  Eigen::MatrixXs getJacobianOfUpperBoundConstraints(
      simulation::WorldPtr world, Eigen::VectorXs f0);

  /// This computes the Jacobian of A_ub^T*E*v0 with respect to position using
  /// impulse tests.
  Eigen::MatrixXs getJacobianOfUpperBoundConstraintsTranspose(
      simulation::WorldPtr world, Eigen::VectorXs v0);

  /// This computes the implicit backprop without forming intermediate
  /// Jacobians. It takes a LossGradient with the position and velocity vectors
  /// filled it, though the loss with respect to torque is ignored and can be
  /// null. It returns a LossGradient with all three values filled in, position,
  /// velocity, and torque.
  void backprop(
      simulation::WorldPtr world,
      LossGradient& thisTimestepLoss,
      const LossGradient& nextTimestepLoss,
      bool exploreAlternateStrategies = false);

  /// This zeros out any components of the gradient that would want to push us
  /// out of the box-bounds encoded in the world for pos, vel, or force.
  void clipLossGradientsToBounds(
      simulation::WorldPtr world,
      Eigen::VectorXs& lossWrtPos,
      Eigen::VectorXs& lossWrtVel,
      Eigen::VectorXs& lossWrtForce);

  /// This replaces x with the result of M*x in place, without explicitly
  /// forming M
  Eigen::VectorXs implicitMultiplyByMassMatrix(
      simulation::WorldPtr world, const Eigen::VectorXs& x);

  /// This replaces x with the result of Minv*x in place, without explicitly
  /// forming Minv
  Eigen::VectorXs implicitMultiplyByInvMassMatrix(
      simulation::WorldPtr world, const Eigen::VectorXs& x);

  const Eigen::MatrixXs& getAllConstraintMatrix() const;

  const Eigen::MatrixXs& getClampingConstraintMatrix() const;

  const Eigen::MatrixXs& getMassedClampingConstraintMatrix() const;

  const Eigen::MatrixXs& getUpperBoundConstraintMatrix() const;

  const Eigen::MatrixXs& getMassedUpperBoundConstraintMatrix() const;

  const Eigen::MatrixXs& getUpperBoundMappingMatrix() const;

  const Eigen::MatrixXs& getBouncingConstraintMatrix() const;

  /// These was the mX() vector used to construct this. Pretty much only here
  /// for testing.
  const Eigen::VectorXs& getContactConstraintImpulses() const;

  /// These was the fIndex() vector used to construct this. Pretty much only
  /// here for testing.
  const Eigen::VectorXi& getContactConstraintMappings() const;

  /// Returns the restitution coefficiennts at each clamping contact point.
  const Eigen::VectorXs& getBounceDiagonals() const;

  /// Returns the contact distances at each clamping contact point.
  const Eigen::VectorXs& getRestitutionDiagonals() const;

  /// Returns the penetration correction hack "bounce" (or 0 if the contact is
  /// not inter-penetrating or is actively bouncing) at each contact point.
  const Eigen::VectorXs& getPenetrationCorrectionVelocities() const;

  /// This is the subset of the A matrix from the original LCP that corresponds
  /// to clamping indices.
  const Eigen::MatrixXs& getClampingAMatrix() const;

  /// Returns the constraint impulses along the clamping constraints
  const Eigen::VectorXs& getClampingConstraintImpulses() const;

  /// Returns the relative velocities along the clamping constraints
  const Eigen::VectorXs& getClampingConstraintRelativeVels() const;

  /// Returns the velocity change caused by the illegal impulses from the LCP
  const Eigen::VectorXs& getVelocityDueToIllegalImpulses() const;

  /// Returns the torques applied pre-step
  const Eigen::VectorXs& getPreStepTorques() const;

  /// Returns the velocity pre-step
  const Eigen::VectorXs& getPreStepVelocity() const;

  /// Returns the velocity pre-LCP
  const Eigen::VectorXs& getPreLCPVelocity() const;

  /// Returns the M^{-1} matrix from pre-step
  const Eigen::MatrixXs& getMinv() const;

  /// Get the coriolis and gravity forces
  const Eigen::VectorXs getCoriolisAndGravityAndExternalForces(
      simulation::WorldPtr world) const;

  /// This is like `getClampingConstraintMatrix()` or
  /// `getUpperBoundConstraintMatrix()`, except that it returns all the columns
  /// instead of just a subset.
  Eigen::MatrixXs getFullConstraintMatrix(simulation::World* world) const;

  std::size_t getNumDOFs() const;

  std::size_t getNumConstraintDim() const;

  const std::vector<std::string>& getSkeletons() const;

  const std::vector<std::shared_ptr<DifferentiableContactConstraint>>&
  getDifferentiableConstraints() const;

  const std::vector<std::shared_ptr<DifferentiableContactConstraint>>&
  getClampingConstraints() const;

  const std::vector<std::shared_ptr<DifferentiableContactConstraint>>&
  getUpperBoundConstraints() const;

  /// Returns true if we were able to standardize our LCP results, false if we
  /// weren't
  bool areResultsStandardized() const;

  /// This computes and returns the jacobian of M^{-1}(pos, inertia) * tau by
  /// finite differences. This is SUPER SLOW, and is only here for testing.
  Eigen::MatrixXs finiteDifferenceJacobianOfMinv(
      simulation::WorldPtr world,
      Eigen::VectorXs tau,
      WithRespectTo* wrt,
      bool useRidders = false);

  /// This computes and returns the jacobian of M^{-1}(pos, inertia) * tau by
  /// finite differences. This is SUPER SLOW, and is only here for testing.
  Eigen::MatrixXs finiteDifferenceRiddersJacobianOfMinv(
      simulation::WorldPtr world, Eigen::VectorXs tau, WithRespectTo* wrt);

  /// This computes and returns the jacobian of C(pos, inertia, vel) by finite
  /// differences. This is SUPER SLOW, and is only here for testing.
  Eigen::MatrixXs finiteDifferenceJacobianOfC(
      simulation::WorldPtr world, WithRespectTo* wrt, bool useRidders = true);

  /// This computes and returns the jacobian of C(pos, inertia, vel) by finite
  /// differences. This is SUPER SLOW, and is only here for testing.
  Eigen::MatrixXs finiteDifferenceRiddersJacobianOfC(
      simulation::WorldPtr world, WithRespectTo* wrt);

private:
  std::size_t getWrtDim(simulation::WorldPtr world, WithRespectTo* wrt);

  Eigen::VectorXs getWrt(simulation::WorldPtr world, WithRespectTo* wrt);

  void setWrt(
      simulation::WorldPtr world, WithRespectTo* wrt, Eigen::VectorXs v);

  /// Gets the skeletons associated with this constrained group in vector form
  std::vector<std::shared_ptr<dynamics::Skeleton>> getSkeletons(
      simulation::WorldPtr world);

public:
  /// This is only true after we've called constructMatrices(). It's a useful
  /// flag to ensure we don't call it twice.
  bool mFinalized;

  /// This is a constant value we added to the diagonal entries of the A matrix,
  /// which we only do if we were having problems because A is low rank.
  /// Applying CFM amounts to softening the contact constraints on this
  /// timestep.
  s_t mConstraintForceMixingConstant;

  bool mConstraintForceMixingDiagonalDirty;
  Eigen::VectorXs mConstraintForceMixingDiagonal;

  /// This flag gets set if we needed to ignore the friction indices in order to
  /// solve the LCP. This can happen because boxed LCPs that we use to solve
  /// friction aren't guaranteed to be solvable.
  bool mDeliberatelyIgnoreFriction;

  /// Impulse test matrix for all the constraints (only initialized in debug
  /// mode)
  Eigen::MatrixXs mAllConstraintMatrix;

  /// Impulse test matrix for the clamping constraints
  Eigen::MatrixXs mClampingConstraintMatrix;

  /// Massed impulse test matrix for the clamping constraints
  Eigen::MatrixXs mMassedClampingConstraintMatrix;

  /// Impulse test matrix for the upper bound constraints
  Eigen::MatrixXs mUpperBoundConstraintMatrix;

  /// Massed impulse test matrix for the upper bound constraints
  Eigen::MatrixXs mMassedUpperBoundConstraintMatrix;

  /// Mapping matrix for upper bound constraints
  Eigen::MatrixXs mUpperBoundMappingMatrix;

  /// Impulse test matrix for the bouncing constraints
  Eigen::MatrixXs mBouncingConstraintMatrix;

  /// This is the vector of the coefficients on the diagonal of the bounce
  /// matrix. These are 1+restitutionCoeff[i]
  Eigen::VectorXs mBounceDiagonals;

  /// This is the vector of the coefficients sized for just the bounces.
  Eigen::VectorXs mRestitutionDiagonals;

  /// This is the vector of velocity changes due to any impulses from the LCP
  /// solver that are illegal (out of their legal bounds).
  Eigen::VectorXs mVelocityDueToIllegalImpulses;

  /// This is the vector of constraint impulses for all the clamping
  /// constraints. It's key for computing Jacobians through quantities that
  /// change the mass matrix.
  Eigen::VectorXs mClampingConstraintImpulses;

  /// This is just useful for testing the gradient computations
  Eigen::VectorXs mClampingConstraintRelativeVels;

  /// This is just useful for testing the gradient computations
  Eigen::VectorXi mContactConstraintMappings;

  /// This is just useful for testing the gradient computations
  Eigen::VectorXs mPenetrationCorrectionVelocitiesVec;

  /// This is the subset of the A matrix from the original LCP that corresponds
  /// to clamping indices.
  Eigen::MatrixXs mClampingAMatrix;

  /// This is the inverse mass matrix computed in the constuctor
  Eigen::MatrixXs mMinv;

  /// These are the torques being applied, computed in the constuctor
  Eigen::VectorXs mPreStepTorques;

  /// These are the pre-step velocities, computed in the constuctor
  Eigen::VectorXs mPreStepVelocities;

  /// These are the pre-LCP velocities, computed in the constuctor
  Eigen::VectorXs mPreLCPVelocities;

  /// These are the names of skeletons that are covered by this constraint group
  std::vector<std::string> mSkeletons;

  /// For each index in the original force vector, this either points to an
  /// index in the clamping vector, or it contains -1 to indicate the index was
  /// not clamping.
  std::vector<int> mClampingIndex;

  /// For each index in the original force vector, this either points to an
  /// index in the upper bound vector, or it contains -1 to indicate the index
  /// was not clamping.
  std::vector<int> mUpperBoundIndex;

  /// This is the global timestep length. This is included here because it shows
  /// up as a constant in some of the matrices.
  s_t mTimeStep;

  /// This is the total DOFs for this ConstrainedGroup
  std::size_t mNumDOFs;

  /// This is the number of total dimensions on all the constraints
  std::size_t mNumConstraintDim;

  /// These are the offsets into the total degrees of freedom for each skeleton
  std::unordered_map<std::string, std::size_t> mSkeletonOffset;

  /// This is all the constraints, in order that they were registered
  std::vector<std::shared_ptr<constraint::ConstraintBase>> mConstraints;

  /// This gives the index into the constraint at mConstraints[i] that
  /// constraint i represents
  std::vector<int> mConstraintIndices;

  /// These are all the constraints
  std::vector<std::shared_ptr<DifferentiableContactConstraint>>
      mDifferentiableConstraints;

  /// These are just the clamping constraints
  std::vector<std::shared_ptr<DifferentiableContactConstraint>>
      mClampingConstraints;

  /// These are just the upper bound constraints
  std::vector<std::shared_ptr<DifferentiableContactConstraint>>
      mUpperBoundConstraints;

  /// This is set to true when we were able to standardize the LCP output. It's
  /// false if we were invalid for some reason.
  bool mStandardizedResults;

  /// These are public to enable unit testing
public:
  /// This holds the coefficient of restitution for each constraint on this
  /// group.
  std::vector<s_t> mRestitutionCoeffs;

  /// This holds the penetration correction velocities for each constraint in
  /// this group.
  std::vector<s_t> mPenetrationCorrectionVelocities;

  /// These are all the values from the original LCP
  Eigen::VectorXs mX;
  Eigen::VectorXs mHi;
  Eigen::VectorXs mLo;
  Eigen::VectorXi mFIndex;
  Eigen::VectorXs mB;
  Eigen::VectorXs mAColNorms;
  Eigen::MatrixXs mA;

  /// These are values from our stabilization, for debugging
  // TODO: <remove>
  Eigen::VectorXs mStabilizationPos;
  Eigen::VectorXs mStabilizationVel;
  // TODO: </remove>
  Eigen::MatrixXs mStabilizationQ;
  Eigen::VectorXs mStabilizationB;

  /// This holds the outputs of the impulse tests we run to create the
  /// constraint matrices. We shuffle these vectors into the columns of
  /// mClampingConstraintMatrix and mUpperBoundConstraintMatrix depending on the
  /// values of the LCP solution. We also discard many of these vectors.
  ///
  /// mImpulseTests[k] holds the k'th constraint's impulse test, which is
  /// a concatenated vector of the results for each skeleton in the group.
  std::vector<Eigen::VectorXs> mMassedImpulseTests;
};

} // namespace neural
} // namespace dart

#endif


#pragma once

#include <ostream>
#include <boost/graph/adjacency_list.hpp>
#include "periodic.hpp"
#include "ctab.hpp" // MOL file format (aka CTable)

struct AtomVertex{
    Code code;
// temporaries for DFS that is used to find linear descriptors
    boost::default_color_type color;
    int path;
// deduced as part of FCSP algorithm
    int valence; // effective valence
    int piE; // number of PI-electrons
    bool inAromaCycle; // is part of aromatic cycle?
    AtomVertex(){}
    AtomVertex(Code code_):
        code(code_), path(0), valence(0), piE(0), inAromaCycle(false){}
};

struct Bound{
    int type; //
    Bound(){}
    Bound(int type_) :type(type_){}
};

template<typename V, typename E>
using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, V, E>;

using ChemGraph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, AtomVertex, Bound>;
using vd = ChemGraph::vertex_descriptor;
using ed = ChemGraph::edge_descriptor;

ChemGraph toGraph(CTab& tab);
ChemGraph& addHydrogen(ChemGraph& graph);
int getValence(ChemGraph& graph, ChemGraph::vertex_descriptor vertex);
void dumpGraph(ChemGraph& graph, std::ostream& out);

template<class T, class EdgeMap>
std::vector<vd> cycleToChain(std::vector<T>& ic, EdgeMap&& mapper)
{
    typename std::vector<vd> vc;
    auto seed = mapper(ic.front());
    vc.push_back(seed.first);
    vc.push_back(seed.second);
    while (vc.front() != vc.back())
    {
        bool found = false;
        for (auto e : ic)
        {
            auto p = mapper(e);
            //has common vertex with back of chain and not == second one
            if (p.first == vc.back() && p.second != vc[vc.size() - 2])
                vc.push_back(p.second);
            else if (p.second == vc.back() && p.first != vc[vc.size() - 2])
                vc.push_back(p.first);
            //has common vertex with front of chain and not == second one
            else if (p.first == vc[0] && p.second != vc[1])
                vc.insert(vc.begin(), p.second);
            else if (p.second == vc[0] && p.first != vc[1])
                vc.insert(vc.begin(), p.first);
            else
                continue;
            found = true;
            break;
        }
        assert(found);
    }
    vc.pop_back();
    return vc;
}

template<class T>
inline bool operator<(const std::pair<T,T>& a, std::pair<T,T>& b){
    return a.first < b.first || (a.first == b.first && a.second < b.second);
}

struct Cycle{
public:
    std::vector<vd> chain;
    std::vector<std::pair<vd, vd>> edges; // ordered vertices (first<second)
    bool aromatic_;
public:
    // From set of edges
    Cycle(std::vector<std::pair<vd, vd>> edges_);
    // From chain of vertices
    Cycle(std::vector<vd> chain);
    // True if there is intersection of this and that cycle
    bool intersects(const Cycle& that)const;
    // Get set of edges of the intersection of this and that
    std::vector<std::pair<vd,vd>> intersection(const Cycle& c)const;
    // True if this cylce is aromatic
    bool aromatic()const{ return aromatic_; }
    // Chemical notion of size - number of edges
    size_t size()const{ return edges.size(); }
    // sets aromatic flags on atoms and cycle itself iff aromatic
    Cycle& markAromatic(ChemGraph& graph);
    // output as chain
    friend std::ostream& operator<<(std::ostream& stream, const Cycle& cycle);
};

std::ostream& operator<<(std::ostream& stream, const Cycle& cycle);

// Obtain minimal cycle basis
std::vector<Cycle> minimalCycleBasis(ChemGraph& graph);

// Impl class
template<class Vertex, class Edge>
struct ConnectedComponents{
    using G = Graph<Vertex,Edge>;
    ConnectedComponents(G& graph):
    components(), g(graph),labels(boost::num_vertices(graph)), label(0){
        findComponents();
    }
    std::vector<G> components;
private:
    void findComponents(){
            using namespace boost;
            auto const V = num_vertices(g);
        for(size_t v=0; v<V; v++){
            if(!labels[v]){
                ++label; //start new component
                dfs(v);
            }
        }
        if(label == 1){
            components.push_back(g);
            return;
        }
        components.resize(label);
        // for each component map global --> component
        std::vector<std::vector<size_t>> g2c(label);
        for(auto & vec : g2c)
            vec.resize(V);

        // map vertices to components and record positions
        for(size_t v=0; v<V; v++){
            int lbl = labels[v] - 1;
            g2c[lbl][v] = add_vertex(g[v], components[lbl]);
        }
        // use map to copy over edges
        for(size_t v=0; v<V; v++){
                auto adj = adjacent_vertices(v, g);
            for(auto p=adj.first; p!=adj.second; p++){
                    auto w = *p;
                if (w < v){ //deduplicate
                    continue;
                }
                int lbl = labels[v] - 1;
                auto e = edge(v, w, g).first;
                add_edge(g2c[lbl][v], g2c[lbl][w], g[e], components[lbl]);
            }
        }
    }

    void dfs(size_t v){
        labels[v] = label;
        auto adj = adjacent_vertices(v, g);
        for(auto p = adj.first; p != adj.second; p++){
                auto w = *p;
            if(!labels[w]){
                dfs(w);
            }
        }
    }
    G& g;
    std::vector<int> labels;
    int label;
};

// Will copy g if it's the only component
template<class V, class E>
std::vector<Graph<V,E>> connectedComponents(Graph<V,E> &g)
{
    return ConnectedComponents<V,E>(g).components;
}