/* Siconos is a program dedicated to modeling, simulation and control
 * of non smooth dynamical systems.
 *
 * Copyright 2016 INRIA.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/
#include "OSNSMultipleImpact.hpp"
#include "LagrangianDS.hpp"
#include "MultipleImpactNSL.hpp"
#include "Simulation.hpp"
#include "ioMatrix.hpp"
#include <boost/numeric/ublas/io.hpp>
#include <boost/progress.hpp>
#include "OSNSMatrix.hpp"
#include "Model.hpp"
#include "NonSmoothDynamicalSystem.hpp"

//Default constructor
OSNSMultipleImpact::OSNSMultipleImpact(): LinearOSNS()
{
  _typeCompLaw = "BiStiffness";
  _nStepSave = 100;
  _tolImpact = DEFAULT__tolImpact;
  _Tol_Vel = DEFAULT_TOL_VEL;
  _Tol_Ener = DEFAULT_TOL_ENER;
  _ZeroVel_EndIm = DEFAULT_TOL_VEL;
  _ZeroEner_EndIm = DEFAULT_TOL_ENER;
  _saveData = false;
  _sizeDataSave = 1000;
  _nStepMax = 100000;
  _stepMinSave = 1;
  _stepMaxSave = _nStepMax;
  _namefile = "DataMultipleImpact.dat";
}
//------------------------------ -------------------------------------------------------------
OSNSMultipleImpact::OSNSMultipleImpact(std::string newTypeLaw, double newDelP = 1.0e-5): LinearOSNS()
{
  _typeCompLaw = newTypeLaw;
  _deltaP = newDelP;
  _nStepSave = 100;
  _tolImpact = DEFAULT__tolImpact;
  _Tol_Vel = DEFAULT_TOL_VEL;
  _Tol_Ener = DEFAULT_TOL_ENER;
  _ZeroVel_EndIm = DEFAULT_TOL_VEL;
  _ZeroEner_EndIm = DEFAULT_TOL_ENER;
  _saveData = false;
  _namefile = "DataMultipleImpact.dat";
  _sizeDataSave = 1000;
  _nStepMax = 100000;
  _stepMinSave = 1;
  _stepMaxSave = _nStepMax;
  if ((_typeCompLaw != "MonoStiffness") && (_typeCompLaw != "BiStiffness"))
    RuntimeException::selfThrow("OSNSMultipleImpact::_typeCompLaw type of the compliance model must be either MonoStiffness or BiStiffness!");
}
//-------------------------------------------------------------------------------------------------
OSNSMultipleImpact::~OSNSMultipleImpact() {}
//------------------------------------------------------------------------------------------------

void OSNSMultipleImpact::setTolImpact(double newTolZero)
{
  _tolImpact = newTolZero;
};

void OSNSMultipleImpact::SetSaveData(bool var)
{
  _saveData = var;
};

void OSNSMultipleImpact::SetNameOutput(std::string file_name)
{
  _namefile = file_name;
};

void OSNSMultipleImpact::SetTolVel(double _var)
{
  _Tol_Vel = _var;
};

void OSNSMultipleImpact::SetTolEner(double _var)
{
  _Tol_Ener = _var;
};

void OSNSMultipleImpact::SetZeroVelEndImp(double _var)
{
  _ZeroVel_EndIm = _var;
};

void OSNSMultipleImpact::SetZeroEnerEndImp(double _var)
{
  _ZeroEner_EndIm = _var;
};

void OSNSMultipleImpact::SetNstepSave(unsigned int var)
{
  _nStepSave = var;
};

void OSNSMultipleImpact::SetNstepMax(unsigned int var)
{
  _nStepMax = var;
};

void OSNSMultipleImpact::SetStepMinMaxSave(unsigned int var1, unsigned int var2)
{
  _stepMinSave = var1;
  _stepMaxSave = var2;
}

void OSNSMultipleImpact::set_typeCompLaw(std::string newTypeLaw)
{
  _typeCompLaw = newTypeLaw;
  if ((_typeCompLaw != "MonoStiffness") && (_typeCompLaw != "BiStiffness"))
    RuntimeException::selfThrow("OSNSMultipleImpact::_typeCompLaw type of the compliance model must be either MonoStiffness or BiStiffness!");
};

void OSNSMultipleImpact::SetSizeDataSave(unsigned int var)
{
  _sizeDataSave = var;
}
//---------------------------------------------------------------------------------------------------
void OSNSMultipleImpact::WriteVectorIntoMatrix(const SiconosVector m, const unsigned int pos_row, const unsigned int pos_col)
{
  for (unsigned int i = 0; i < m.size(); ++i)
  {
    (*_DataMatrix)(pos_row, pos_col + i) = m(i);
  }
}
//----------------------------------------------------------------------------------------------------
bool OSNSMultipleImpact::isZero(double Var)
{
  if (std::abs(Var) <= _tolImpact)
    return true;
  else
    return false;
}
//------------------------------------------------------------------------------------------------
bool OSNSMultipleImpact::isVelNegative(double Var)
{
  if (Var < - _Tol_Vel)
    return true;
  else
    return false;
}
//-------------------------------------------------------------------------------------------------

bool OSNSMultipleImpact::isEnerZero(double Var)
{
  if (std::abs(Var) <= _Tol_Ener)
    return true;
  else
    return false;
}
//--------------------------------------------------------------------------------------------------
unsigned int OSNSMultipleImpact::EstimateNdataCols()
{
  unsigned int _numberCols = 1;
  // Number of columns for data at contacts
  SP::InteractionsGraph indexSet = simulation()->indexSet(0); // get indexSet[0]
  InteractionsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = indexSet->vertices(); ui != uiend; ++ui)
  {
    //_numberCols = _numberCols + 3*(indexSet->bundle(*ui)->interaction()->nonSmoothLaw()->size()) + 1;
    _numberCols = _numberCols + (indexSet->bundle(*ui)->getSizeOfY());
  }
  // Number of columns for data at particles
  SP::DynamicalSystemsGraph DSG = simulation()->nonSmoothDynamicalSystem()->dynamicalSystems();
  DynamicalSystemsGraph::VIterator dsi, dsiend;
  for (std11::tie(dsi, dsiend) = DSG->vertices(); dsi != dsiend; ++dsi)
  {
    _numberCols = _numberCols + (DSG->bundle(*dsi)->dimension());
  }
  return(_numberCols);
}
//-----------------------------------------------------------------------------------------------
void OSNSMultipleImpact::AllocateMemory()
{
  if (!_velocityContact)
    _velocityContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_velocityContact->size() != maxSize())
      _velocityContact->resize(maxSize());
  };
  //
  if (!_oldVelocityContact)
    _oldVelocityContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_oldVelocityContact->size() != maxSize())
      _oldVelocityContact->resize(maxSize());
  };
  //
  if (! _energyContact)
    _energyContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_energyContact->size() != maxSize())
      _energyContact->resize(maxSize());
  };
  //
  if (!_WorkcContact)
    _WorkcContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_WorkcContact->size() != maxSize())
      _WorkcContact->resize(maxSize());
  };
  //
  if (!_distributionVector)
    _distributionVector.reset(new SiconosVector(maxSize()));
  else
  {
    if (_distributionVector->size() != maxSize())
      _distributionVector->resize(maxSize());
  };
  //
  if (!_stateContact)
    _stateContact.reset(new IndexInt(maxSize()));
  else
  {
    if (_stateContact->size() != maxSize())
      _stateContact->resize(maxSize());
  };
  //
  if (!_Kcontact)
    _Kcontact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_Kcontact->size() != maxSize())
      _Kcontact->resize(maxSize());
  };
  //
  if (!_restitutionContact)
    _restitutionContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_restitutionContact->size() != maxSize())
      _restitutionContact->resize(maxSize());
  };
  //
  if (!_elasticyCoefficientcontact)
    _elasticyCoefficientcontact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_elasticyCoefficientcontact->size() != maxSize())
      _elasticyCoefficientcontact->resize(maxSize());
  };
  if (!_tolImpulseContact)
    _tolImpulseContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_tolImpulseContact->size() != maxSize())
      _tolImpulseContact->resize(maxSize());
  };
  //
  if (!_deltaImpulseContact)
    _deltaImpulseContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_deltaImpulseContact->size() != maxSize())
      _deltaImpulseContact->resize(maxSize());
  };
  //
  if (!_impulseContactUpdate)
    _impulseContactUpdate.reset(new SiconosVector(maxSize()));
  else
  {
    if (_impulseContactUpdate->size() != maxSize())
      _impulseContactUpdate->resize(maxSize());
  }
  //
  if (!_forceContact)
    _forceContact.reset(new SiconosVector(maxSize()));
  else
  {
    if (_forceContact->size() != maxSize())
      _forceContact->resize(maxSize());
  };
  // for the data matrix
  unsigned int _numberCols = EstimateNdataCols();
  if (!_DataMatrix)
    _DataMatrix.reset(new SimpleMatrix(_sizeDataSave, _numberCols));
  else
  {
    if ((_DataMatrix->size(0) != _sizeDataSave) || (_DataMatrix->size(1) != _numberCols))
      _DataMatrix->resize(_sizeDataSave, _numberCols);
  }
}
//=====================================================================================
void OSNSMultipleImpact::BuildParaContact()
{
  SP::InteractionsGraph indexSet = simulation()->indexSet(1); // get indexSet[1]
  //Loop over the Interactionof the indexSet(1)
  InteractionsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = indexSet->vertices(); ui != uiend; ++ui)
  {
    SP::Interaction inter = indexSet->bundle(*ui);
    SP::NonSmoothLaw nslaw = inter->nslaw();
    SP::MultipleImpactNSL Mulnslaw = std11::dynamic_pointer_cast<MultipleImpactNSL>(nslaw);
    assert(Mulnslaw && "In OSNSMultipleImpact::BuildStiffResCofVec, non-smooth law used must be MultipleImpactNSL!!!");
    // Get the relative position of inter-interactionBlock in the vector _velocityContact
    unsigned int pos = _M->getPositionOfInteractionBlock(*inter);
    (*_restitutionContact)(pos) = Mulnslaw->ResCof();
    (*_Kcontact)(pos) = Mulnslaw->Stiff();
    (*_elasticyCoefficientcontact)(pos) = Mulnslaw->ElasCof();
  }
  /*
    std::cout << " Restitution coefficients: " <<std::endl;
    _restitutionContact->display();
    std::cout << "Stiffnesses: " <<std::endl;
    _Kcontact->display();
    std::cout << "Elasticity coeffients at contacts: " <<std::endl;
    _elasticyCoefficientcontact->display();
  */

}
//========================================================================================
void OSNSMultipleImpact::PreComputeImpact()
{
  //1. Get the number of contacts and bodies involved in the impact
  if (indexSetLevel() != 1)
    RuntimeException::selfThrow("OSNSMultipleImpact::PreComputeImpact==> the levelMin must be equal to 1 in the multiple impact model !!");
  SP::InteractionsGraph indexSet = simulation()->indexSet(indexSetLevel()); // get indexSet[1]
  _nContact = indexSet->size();
  //2. Compute matrix _M
  SP::Topology topology = simulation()->nonSmoothDynamicalSystem()->topology();
  bool isLinear = simulation()->nonSmoothDynamicalSystem()->isLinear();
  if (!_hasBeenUpdated || !isLinear)
  {
    // Computes new _unitaryBlocks if required
    updateInteractionBlocks();
    // Updates matrix M
    _M->fill(indexSet, !_hasBeenUpdated);
    _sizeOutput = _M->size();
  }
  if (_nContact != _sizeOutput)
    RuntimeException::selfThrow("OSNSMultipleImpact::ComputeWMinvWtrans: number of contacts different from the size of output--> this case is not yet implemented!");
  //3. Checks size of vectors
  if (_velocityContact->size() != _sizeOutput)
  {
    _velocityContact->resize(_sizeOutput);
  }
  _velocityContact->zero();
  //
  if (_oldVelocityContact->size() != _sizeOutput)
  {
    _oldVelocityContact->resize(_sizeOutput);
  }
  _oldVelocityContact->zero();
  //
  if (_energyContact->size() != _sizeOutput)
  {
    _energyContact->resize(_sizeOutput);
  }
  _energyContact->zero();
  //
  if (_WorkcContact->size() != _sizeOutput)
  {
    _WorkcContact->resize(_sizeOutput);
  }
  _WorkcContact->zero();
  //
  if (_distributionVector->size() != _sizeOutput)
  {
    _distributionVector->resize(_sizeOutput);
  }
  _distributionVector->zero();
  //
  if (_stateContact->size() != _sizeOutput)
  {
    _stateContact->resize(_sizeOutput);
  }
  //
  if (_Kcontact->size() != _sizeOutput)
  {
    _Kcontact->resize(_sizeOutput);
  }
  _Kcontact->zero();
  //
  if (_restitutionContact->size() != _sizeOutput)
  {
    _restitutionContact->resize(_sizeOutput);
  }
  _restitutionContact->zero();
  //
  if (_elasticyCoefficientcontact->size() != _sizeOutput)
  {
    _elasticyCoefficientcontact->resize(_sizeOutput);
  }
  _elasticyCoefficientcontact->zero();
  //
  if (_tolImpulseContact->size() != _sizeOutput)
  {
    _tolImpulseContact->resize(_sizeOutput);
  }
  _tolImpulseContact->zero();
  //
  if (_deltaImpulseContact->size() != _sizeOutput)
  {
    _deltaImpulseContact->resize(_sizeOutput);
  }
  _deltaImpulseContact->zero();
  //
  if (_impulseContactUpdate->size() != _sizeOutput)
  {
    _impulseContactUpdate->resize(_sizeOutput);
  }
  _impulseContactUpdate->zero();
  //
  if (_forceContact->size() != _sizeOutput)
  {
    _forceContact->resize(_sizeOutput);
  }
  _forceContact->zero();
  //4. Initialize the relative velocity, potential energy, impulse at contacts
  InitializeInput();
  //5. Build the vectors of stifnesseses, of restitution coefficients, and of elaticity coefficients
  BuildParaContact();
}
//=======================================================================================
void OSNSMultipleImpact::InitializeInput()
{
  //Loop over alls Interactioninvolved in the indexSet[1]
  SP::InteractionsGraph indexSet = simulation()->indexSet(indexSetLevel()); // get indexSet[1]
  InteractionsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = indexSet->vertices(); ui != uiend; ++ui)
  {
    SP::Interaction inter = indexSet->bundle(*ui);
    //SP::SiconosVector Vc0 = inter->y(1); // Relative velocity at beginning of impact
    SP::SiconosVector Vc0 = inter->yOld(1); // Relative velocity at beginning of impact
    unsigned int pos_inter = _M->getPositionOfInteractionBlock(*inter);
    setBlock(*Vc0, _velocityContact, Vc0->size(), 0, pos_inter);
    SP::SiconosVector ener0(new SiconosVector(Vc0->size()));
    ener0->zero(); // We suppose that the initial potential energy before impact is equal to zero at any contact
    // at the beginning of impact
    setBlock(*ener0, _energyContact, ener0->size(), 0, pos_inter);
    //SP::SiconosVector impulse0= (inter)->lambda(1))->vector(inter->number());
    SP::SiconosVector impulse0(new SiconosVector(Vc0->size()));
    impulse0->zero(); // We suppose that the impulse before impact is equal to zero at any contact
    // at the beginning of impact
    setBlock(*impulse0, _tolImpulseContact, impulse0->size(), 0, pos_inter);
  };
  /*
    std::cout << "Initial relative velocity at contacts" <<std::endl;
    _velocityContact->display();
    std::cout<< "Initial energy at contacts" <<std::endl;
    _energyContact->display();
    std::cout << "Impulse at contact" <<std::endl;
    _tolImpulseContact->display();
  */

}
//=========================================================================================
void OSNSMultipleImpact::initialize(SP::Simulation sim)
{

  // General initialize for OneStepNSProblem
  OneStepNSProblem::initialize(sim);
  // Allocate the memory
  AllocateMemory();
  // get topology
  SP::Topology topology = simulation()->nonSmoothDynamicalSystem()->topology();
  // Note that _interactionBlocks is up to date since updateInteractionBlocks
  // has been called during OneStepNSProblem::initialize()

  if (! _M)
  {
    if (_MStorageType == 0)
      _M.reset(new OSNSMatrix(maxSize(), 0));

    else // if(_MStorageType == 1) size = number of _interactionBlocks
      // = number of Interactionin the largest considered indexSet
      _M.reset(new OSNSMatrix(simulation()->indexSet(indexSetLevel())->size(), 1));
  }

};
//========================================================================================
void OSNSMultipleImpact::PrimConVelocity()
{
  getMin(*_velocityContact, _relativeVelocityPrimaryContact, _primaryContactId);
  _energyPrimaryContact = (*_energyContact)(_primaryContactId);
  if (!isVelNegative(_relativeVelocityPrimaryContact))
  {
    RuntimeException::selfThrow("OSNSMultipleImpact::PrimConVelocity, the velocity at the primary contact must be negative !!");
  }
  /*
    std::cout << "Primary contact according to relative velocity: " << _primaryContactId <<std::endl;
    std::cout << "Relative velocity at the primary contact: " << _relativeVelocityPrimaryContact <<std::endl;
    std::cout << "Potential energy at the primary contact: " << _energyPrimaryContact <<std::endl;
  */
}
//=======================================================================================
void OSNSMultipleImpact::PrimConEnergy()
{
  getMax(*_energyContact, _energyPrimaryContact, _primaryContactId);
  _relativeVelocityPrimaryContact = (*_velocityContact)(_primaryContactId);
  if (_energyPrimaryContact < 0.0)
  {
    RuntimeException::selfThrow("OSNSMultipleImpact::PrimConEnergy the potential energy at the primary contact must be positive !!");
  }
  /*
    std::cout << "Primary contact according to potenial energy: " << _primaryContactId <<std::endl;
    std::cout << "Relative velocity at the primary contact: " << _relativeVelocityPrimaryContact <<std::endl;
    std::cout << "Potential energy at the primary contact: " << _energyPrimaryContact <<std::endl;
  */

}
//======================================================================================
bool OSNSMultipleImpact::IsEnermaxZero()
{
  double MaxEner;
  unsigned int IdMax;
  getMax(*_energyContact, MaxEner, IdMax);
  if (isEnerZero(MaxEner))
    return true;
  else
    return false;
}
//======================================================================================
bool OSNSMultipleImpact::IsVcminNegative()
{
  double MinVelCon;
  unsigned int IdConVmin;
  getMin(*_velocityContact, MinVelCon, IdConVmin);
  if (isVelNegative(MinVelCon))
    return true;
  else
    return false;
}
//=======================================================================================
void OSNSMultipleImpact::Check_stateContact()
{
  for (unsigned int i = 0; i < _nContact; ++i)
  {
    if (isEnerZero((*_energyContact)(i))) // potential energy is zero
    {
      if (!isVelNegative((*_velocityContact)(i))) // relative velocity is positive or equal to zero
        (*_stateContact)[i] = 0; // no impact at this contact
      else  // impact happens without potential energy
      {
        (*_stateContact)[i] = 1;
      }
    }
    else // impact happens with not zero potential energy
    {
      if ((*_stateContact)[i] != 2)
      {
        (*_stateContact)[i] = 2;
      }
    }
  }
}
//=======================================================================================
bool OSNSMultipleImpact::IsMulImpactTerminate()
{
  _IsImpactEnd = true;
  for (unsigned int i = 0; i < _nContact; ++i)
  {
    if (((*_energyContact)(i) > _ZeroEner_EndIm) || ((*_velocityContact)(i) < -_ZeroVel_EndIm)) // if potential energy is not equal to zero or the relative velocity is negative
    {
      _IsImpactEnd = false;
    }
  }
  return _IsImpactEnd;
  //   bool var = true;
  //   for(unsigned int i = 0; i < _nContact;++i)
  //     {
  //       if ((*_stateContact)[i] != 0)
  //         {
  //           var = false;
  //           break;
  //         };
  //     };
  //   return var;
  //
  //cout << "Is the multiple impacts is terminated: " << _IsImpactEnd <<std::endl;
  //
}
//=======================================================================================
void OSNSMultipleImpact::SelectPrimaContact()
{
  if (IsEnermaxZero()) // case of no potential energy at any contact
  {
    PrimConVelocity(); // Select the primary contact according to the relative velocity at contact
    _isPrimaryContactEnergy = false;
  }
  else
  {
    PrimConEnergy(); // Select the primary contact according to the potential energy at contacts
    _isPrimaryContactEnergy = true;
  }
  //
  // std::cout << "The primary contact is :" << _primaryContactId <<std::endl;
  // std::cout << "Is the primary contact is selected according to the potential energy: " << _isPrimaryContactEnergy <<std::endl;
}
//=======================================================================================
void OSNSMultipleImpact::Compute_distributionVector()
{
  //Case 1: if no potential energy at any contact
  double _ratio_mu, ratio_stiff, ratio_ener;
  double mu_prima = (*_elasticyCoefficientcontact)(_primaryContactId); // Elasticity coefficient at the primary contact
  double stiff_prima = (*_Kcontact)(_primaryContactId);     // Stiffness at the primary contact
  double _mu, _stiff, _vel, _energy;
  if (!_isPrimaryContactEnergy) // case of primary contact selected according to the relative velocity
  {
    double ratio_vel;
    for (unsigned int i = 0; i < _nContact; ++i)
    {
      if ((*_stateContact)[i] != 0) // the impact can takes place at this contact
      {
        _mu = (*_elasticyCoefficientcontact)(i); // Elasticity coefficient at the current contact
        _stiff = (*_Kcontact)(i);     // Stiffness at the current contact
        _vel = (*_velocityContact)(i);     // Relative velocity at the current contact
        _ratio_mu = (std::pow(_mu + 1.0, (_mu / (_mu + 1.0)))) / (std::pow(mu_prima + 1.0, (mu_prima / (mu_prima + 1.0))));
        ratio_stiff = (std::pow(_stiff, (1.0 / (1.0 + _mu)))) / (std::pow(stiff_prima, (1.0 / (1.0 + mu_prima))));
        if (!isVelNegative(_vel))
        {
          RuntimeException::selfThrow("OSNSMultipleImpact::Compute_distributionVector, the relative velocity when particle starts to impact must be negative!!");
        }

        ratio_vel = (std::pow(std::fabs(_vel), (_mu / (_mu + 1.0)))) / (std::pow(std::fabs(_relativeVelocityPrimaryContact), (mu_prima / (1.0 + mu_prima))));
        (*_distributionVector)(i) = std::pow((_ratio_mu * ratio_stiff * ratio_vel), (1.0 + _mu)) * std::pow(_deltaP, ((_mu - mu_prima) / (1.0 + mu_prima)));
      }
      else
      {
        (*_distributionVector)(i) = 0.0;
      }
      if ((*_distributionVector)(i) < 0.0)
        RuntimeException::selfThrow("OSNSMultipleImpact::Compute_distributionVector the component of _distributionVector must be positive !!");
    };
  }
  //Case 2: case of primary contact selected according to the potential energy
  else
  {
    for (unsigned int i = 0; i < _nContact; ++i)
    {
      //
      _mu = (*_elasticyCoefficientcontact)(i);
      _stiff = (*_Kcontact)(i);
      _ratio_mu = (std::pow(_mu + 1.0, (_mu / (_mu + 1.0)))) / (std::pow(mu_prima + 1.0, (mu_prima / (mu_prima + 1.0))));
      ratio_stiff = (std::pow(_stiff, (1.0 / (1.0 + _mu)))) / (std::pow(stiff_prima, (1.0 / (1.0 + mu_prima))));
      if ((*_stateContact)[i] == 1) // no potential energy at this contact, including the contacts at which impact repeats
      {
        if (!isVelNegative((*_velocityContact)(i)))
        {
          RuntimeException::selfThrow("OSNSMultipleImpact::Compute_distributionVector, the pre-impact velocity must be negative!!");
        }
        else
        {
          _vel = (*_velocityContact)(i);
          ratio_ener = (std::pow(std::fabs(_vel * _deltaP), (_mu / (_mu + 1.0)))) / (std::pow(_energyPrimaryContact, (mu_prima / (mu_prima + 1.0))));
          //
          // std::cout << "_ratio_m: " << _ratio_mu <<std::endl;
          // std::cout << "Stiff: " << _stiff <<std::endl;
          // std::cout << "ratio_stiff: " << ratio_stiff <<std::endl;
          // std::cout << "energy ratio: " << ratio_ener <<std::endl;

          //
          (*_distributionVector)(i) = std::pow((_ratio_mu * ratio_stiff * ratio_ener), (1.0 + _mu));
        }
      }
      else if ((*_stateContact)[i] == 2) // potential is not zero at this contact
      {
        _energy = (*_energyContact)(i); // Potential energy at the current contact
        ratio_ener = (std::pow(_energy, (_mu / (_mu + 1.0)))) / (std::pow(_energyPrimaryContact, (mu_prima / (mu_prima + 1.0))));
        (*_distributionVector)(i) = _ratio_mu * ratio_stiff * ratio_ener;
      }
      else // no impact at this contact
      {
        (*_distributionVector)(i) = 0.0;
      };
      if ((*_distributionVector)(i) < 0.0)
        RuntimeException::selfThrow("OSNSMultipleImpact::Compute_distributionVector the component of _distributionVector must be positive !!");
    };
  };
}
//=======================================================================================
void OSNSMultipleImpact::ComputeImpulseContact()
{
  (*_deltaImpulseContact) = (*_distributionVector) * _deltaP;
  (*_tolImpulseContact) = (*_tolImpulseContact) + (*_deltaImpulseContact);
  (*_impulseContactUpdate) = (*_impulseContactUpdate) + (*_deltaImpulseContact);
  // Compute the contact force
  double PowCompLaw;
  for (unsigned int i = 0; i < _nContact; ++i)
  {
    PowCompLaw = (*_elasticyCoefficientcontact)(i);
    if (isEnerZero((*_energyContact)(i))) // if potential energy at this contact is zero
    {
      if (isVelNegative((*_velocityContact)(i))) // if the relative velocity at contact is negative
      {
        (*_forceContact)(i) = std::pow((1.0 + PowCompLaw), PowCompLaw / (1.0 + PowCompLaw)) * std::pow((*_Kcontact)(i), 1.0 / (1.0 + PowCompLaw)) * std::pow((std::fabs((*_velocityContact)(i)) * (*_deltaImpulseContact)(i)), PowCompLaw / (1.0 + PowCompLaw));
      }
      else
      {
        (*_forceContact)(i) = 0.0;
      };
    }
    else
    {
      (*_forceContact)(i) = std::pow((1.0 + PowCompLaw), PowCompLaw / (1.0 + PowCompLaw)) * std::pow((*_Kcontact)(i), 1.0 / (1.0 + PowCompLaw)) * std::pow((*_energyContact)(i), PowCompLaw / (1.0 + PowCompLaw));
    }
    if ((*_forceContact)(i) < 0.0)
    {
      RuntimeException::selfThrow("OSNSMultipleImpact::ComputeImpulseContact, the contact force must be positive or equal to zero!!!");
    }
  };
}
//=======================================================================================
void OSNSMultipleImpact::Compute_velocityContact()
{
  (*_oldVelocityContact) = (*_velocityContact); //save the relative velocity at the beginning of the step
  (*_velocityContact) = (*_velocityContact) + prod(*(_M->defaultMatrix()), *_deltaImpulseContact); // compute the relative velocity at the end of the step
  //
  /*
    std::cout << "Relative velocity at contacts at the beginning of step:" <<std::endl;
    _oldVelocityContact->display();
    std::cout << "Relative velocity at contacts at the end of step:" <<std::endl;
    _velocityContact->display();
  */
  //
}
//=======================================================================================
void OSNSMultipleImpact::Compute_energyContact()
{
  if (_typeCompLaw == "BiStiffness")
    // For Bistiffness model
  {
    for (unsigned int i = 0; i < _nContact; ++i)
    {
      if ((0.5 * ((*_oldVelocityContact)(i) + (*_velocityContact)(i))) <= 0.0) // Contact located in the compression phase
      {
        (*_energyContact)(i) = (*_energyContact)(i) - 0.5 * ((*_oldVelocityContact)(i) + (*_velocityContact)(i)) * ((*_deltaImpulseContact)(i));
      }
      else                       // Contact located in the expansion phase
      {
        if (!isZero((*_restitutionContact)(i)))
        {
          (*_energyContact)(i) = (*_energyContact)(i) - (1.0 / std::pow((*_restitutionContact)(i), 2)) * 0.5 * ((*_oldVelocityContact)(i) + (*_velocityContact)(i)) * ((*_deltaImpulseContact)(i));
          //
          if ((*_energyContact)(i) <  0.0)
          {
            (*_energyContact)(i) = 0.0;
          };
        }
        else // restitution coefficient equal to zero
        {
          (*_energyContact)(i) = 0.0; // In this case, no potential energy at contacts when the contact is located in the compression phase
        }
      };
      //
      if ((*_energyContact)(i) <  0.0)
      {
        RuntimeException::selfThrow("OSNSMultipleImpact::Compute_energyContact, the potential energy during compression phase must be positive!!!");
      };
    };
  }
  else
    // For the mono-stiffness model
  {
    for (unsigned int i = 0; i < _nContact; ++i)
    {
      //1: dertermine the work done by the last compression phase at contacts (nessessary for the mono-stiffness compliance model)
      // if Vc(k) < 0 and Vc(k +1) >= 0 ==> transition from the compression phase to the expansion phase, Wc = E(k)
      if (((*_oldVelocityContact)(i) < 0.0) && ((*_velocityContact)(i) >= 0.0))
      {
        (*_WorkcContact)(i) = (*_energyContact)(i);
      };
      //2: Calculate the potential energy at the end of stap
      (*_energyContact)(i) = (*_energyContact)(i) - 0.5 * ((*_oldVelocityContact)(i) + (*_velocityContact)(i)) * ((*_deltaImpulseContact)(i));
      //3: Check if the termination condition is verified or not (if Vc(k+1) > 0.0 and E(k+1) <= (1-e^2)*Wc). If yes, discard the potential energy
      // in order to respect the energetic constraint
      if (((*_stateContact)[i] == 2) && (((*_velocityContact)(i) > 0.0) && ((*_energyContact)(i) <= ((1.0 - std::pow((*_restitutionContact)(i), 2)) * (*_WorkcContact)(i)))))
      {
        (*_energyContact)(i) = 0.0; // potential energy at this contact is completely dissipated before the compression phase finishes
      };
    };
  }
  /*

    std::cout << "Potential energy at contacts at the end of step:" <<std::endl;
    _energyContact->display();
    std::cout << "Work done during the compression phase at contacts" <<std::endl;
    _WorkcContact->display();

  */
}
//======================================================================================
void OSNSMultipleImpact::UpdateDuringImpact()
{
  //1. Copy _velocityContact/_deltaImpulseContact into the vector y/lambda for Interactions
  SP::InteractionsGraph indexSet = simulation()->indexSet(indexSetLevel());
  // y and lambda vectors
  SP::SiconosVector lambda;
  SP::SiconosVector y;
  // === Loop through "active" Interactions (ie present in indexSets[1]) ===
  unsigned int pos;
  InteractionsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = indexSet->vertices(); ui != uiend; ++ui)
  {
    Interaction& inter = *indexSet->bundle(*ui);
    // Get the relative position of inter-interactionBlock in the vector _velocityContact/_tolImpulseContact
    pos = _M->getPositionOfInteractionBlock(inter);
    // Get Y and Lambda for the current Interaction
    y = inter.y(inputOutputLevel());
    lambda = inter.lambda(inputOutputLevel());
    // Copy _velocityContact/_tolImpulseContact, starting from index pos into y/lambda
    // save into y !!
    setBlock(*_velocityContact, y, y->size(), pos, 0);
    // saved into lambda[1] !!
    setBlock(*_impulseContactUpdate, lambda, lambda->size(), pos, 0);
    //setBlock(*_deltaImpulseContact, lambda, lambda->size(), pos, 0);
  };
  //2. Update the Input[1], state of DS systems, Output[1]
  simulation()->update(inputOutputLevel());
  _impulseContactUpdate->zero(); // reset input[1] to zero after each update
}
//--------------------------------------------------------------------------------------
void OSNSMultipleImpact::SaveDataOneStep(unsigned int _ithPoint)
{
  // Save the total impulse at the primary contacts (time-like independent variable) and the time evolution during impact
  if (_ithPoint >= _DataMatrix->size(0))
    RuntimeException::selfThrow("In OSNSMultipleImpact::ComputeImpact, number of points saved exceeds the size of matrix allocated!!!");
  //(*_DataMatrix)(_ithPoint,0) = _timeVariable;
  (*_DataMatrix)(_ithPoint, 0) = _impulseVariable;
  // Save the data related to UnitaryRelations
  SP::InteractionsGraph indexSet0 = simulation()->indexSet(0);
  SP::InteractionsGraph indexSet1 = simulation()->indexSet(indexSetLevel());
  unsigned int pos;
  InteractionsGraph::VIterator ui, uiend;
  unsigned int col_pos = 1;
  for (std11::tie(ui, uiend) = indexSet0->vertices(); ui != uiend; ++ui)
  {
    SP::Interaction inter = indexSet0->bundle(*ui);
    SP::SiconosVector ydot = inter->y(1);
    SP::SiconosVector P_inter(new SiconosVector(inter->getSizeOfY()));
    SP::SiconosVector F_inter(new SiconosVector(inter->getSizeOfY()));
    SP::SiconosVector E_inter(new SiconosVector(1));
    if (indexSet1->is_vertex(inter)) // if Interaction belongs to the IndexSet[1]
    {
      pos = _M->getPositionOfInteractionBlock(*inter);
      setBlock(*_tolImpulseContact, P_inter, P_inter->size(), pos, 0);
      setBlock(*_forceContact, F_inter, F_inter->size(), pos, 0);
      setBlock(*_energyContact, E_inter, E_inter->size(), pos, 0);
    }
    else
    {
      P_inter->zero();   // no impulse at this Interaction
      F_inter->zero();   // no force at this Interaction
      E_inter->zero();   // no potential at this Interaction
    };
    // Write the force at the Interaction
    WriteVectorIntoMatrix(*F_inter, _ithPoint, col_pos);
    //WriteVectorIntoMatrix(*P_inter, _ithPoint, col_pos);
    //WriteVectorIntoMatrix(*E_inter, _ithPoint, col_pos);
    col_pos = col_pos + F_inter->size();
  } // Save the data related to DS
  SP::DynamicalSystemsGraph DSG = simulation()->nonSmoothDynamicalSystem()->dynamicalSystems();
  DynamicalSystemsGraph::VIterator dsi, dsiend;
  for (std11::tie(dsi, dsiend) = DSG->vertices(); dsi != dsiend; ++dsi)
  {
    SP::DynamicalSystem ds = DSG->bundle(*dsi); // DS
    SP::LagrangianDS Lagds = std11::dynamic_pointer_cast<LagrangianDS>(ds);
    SP::SiconosVector qdot = Lagds->velocity();
    // Write

    WriteVectorIntoMatrix(*qdot, _ithPoint, col_pos);
    col_pos = col_pos + qdot->size();
  }
}
//=======================================================================================
void OSNSMultipleImpact::ComputeImpact()
{
  _impulseVariable = 0.0;
  _timeVariable = 0.0;
  unsigned int number_step = 1;
  unsigned int point_save = 0;
  unsigned int _counterstepsave = 0;
  // Show computation progress
  //cout << "*********** Impact computation progress *************" <<std::endl;
  //boost::progress_display show_progress(_nStepMax);
  /*
     std::cout << "----------Before multiple impacts computation---------------" <<std::endl;
     std::cout << "Velocity at contacts: ";
     _velocityContact->display();
     std::cout << "Impulse at contact: ";
     _tolImpulseContact->display();
  */
  //cout << "-------------------Multiple impacts computation starts:-----------------------" <<std::endl;
  // First save at the beginning of impact computation
  if ((_saveData) && (_stepMinSave == 1))
  {
    SaveDataOneStep(point_save); // Save the data
    point_save++;
  }
  //
  while (1 != 0)
  {
    // std::cout << "==================Step==================:  " << number_step <<std::endl;
    // std::cout << "Impulse variable: " << _impulseVariable <<std::endl;
    // std::cout << "_timeVariable: " << _timeVariable <<std::endl;

    //Step 1: check the state at contacts
    Check_stateContact();
    //Step 2: check if the multiple impact is terminated or not
    if (IsMulImpactTerminate()) // multiple impact terminated
    {
      if (_saveData) // Save the date at the end of impact
      {
        UpdateDuringImpact(); // Update state of dynamical system
        SaveDataOneStep(point_save); // Save the data
      }
      break;
    }
    // Select the primary contact
    SelectPrimaContact();
    //Step 3: compute the vector of distributing law
    Compute_distributionVector();
    //Step 4: compute the increment of normal impulse and the total impulse at contacts
    ComputeImpulseContact();
    // Step 5: compute the relative velocity at contacts
    Compute_velocityContact();
    // Step 6: compute the potential energy at contacts
    Compute_energyContact();
    //Step 7: Update the time-like variable
    ++number_step;
    ++_counterstepsave;
    //++show_progress;
    _impulseVariable = _impulseVariable + _deltaP;
    _timeVariable = _timeVariable + _deltaP / (*_forceContact)(_primaryContactId);
    // Step 8: update the state of DS and output during impact and write data into output file at the beginning of each step
    if ((_saveData) & (_counterstepsave >= _nStepSave))
    {
      if ((number_step >= _stepMinSave) && (number_step <= _stepMaxSave))
      {
        UpdateDuringImpact(); // Update state of dynamical system
        SaveDataOneStep(point_save); // Save the data
        point_save++;
        _counterstepsave = 0; // reset the counter to 0
      }
    }
    //
    if (number_step > _nStepMax)
    {
      RuntimeException::selfThrow("In OSNSMultipleImpact::ComputeImpact, number of integration steps perfomed exceeds the maximal number of steps allowed!!!");
      //cout << "Causion: so long computation, the computation is stopped even when the impact is not yet terminated!!! " <<std::endl;
      break;
    }
    // std::cout << "Distribution vector: ";
    // _distributionVector->display();
    // std::cout << "Incremental Impulse: ";
    // _deltaImpulseContact->display();
    // std::cout << "Impulse at contact: ";
    // _tolImpulseContact->display();
    // std::cout << "Velocity at contacts: ";
    // _velocityContact->display();
    // std::cout << "Potential energy at contacts: ";
    // _energyContact->display();

  }

  //
  // std::cout << "*****************Impact computation is terminated******************" <<std::endl;
  // std::cout << "Number of integration steps: " << number_step <<std::endl;
  // std::cout << "Velocity at contacts: ";
  // _velocityContact->display();
  // std::cout << "Impulse at contact: ";
  // _tolImpulseContact->display();
  // std::cout << "Duration of the multiple impacts process: " << _timeVariable << " s" <<std::endl;

  // Close the stream file
  if (_saveData)
  {
    ioMatrix::write(_namefile.c_str(), "ascii", *_DataMatrix, "noDim");
  }
}
//=======================================================================================
void OSNSMultipleImpact::PostComputeImpact()
{
  // === Get index set from Topology ===
  SP::InteractionsGraph indexSet = simulation()->indexSet(indexSetLevel());
  // y and lambda vectors
  SP::SiconosVector lambda;
  SP::SiconosVector y;
  // === Loop through "active" Interactions (ie present in indexSets[1]) ===
  unsigned int pos;
  InteractionsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = indexSet->vertices(); ui != uiend; ++ui)
  {
    Interaction& inter = *indexSet->bundle(*ui);
    // Get the relative position of inter-interactionBlock in the vector _velocityContact/_tolImpulseContact
    pos = _M->getPositionOfInteractionBlock(inter);
    // Get Y and Lambda for the current Interaction
    y = inter.y(inputOutputLevel());
    lambda = inter.lambda(inputOutputLevel());
    // Copy _velocityContact/_tolImpulseContact, starting from index pos into y/lambda
    // save into y !!
    setBlock(*_velocityContact, y, y->size(), pos, 0);// Warning: yEquivalent is
    // saved into lambda[1] !!
    setBlock(*_impulseContactUpdate, lambda, lambda->size(), pos, 0);
    // If the update is performed at the end of the impact process, we update the total normal impulse at contacts
    // from the beginning to the end of impact (vector _tolImpulseContact). Otherwise, we must reset the lambda[1] to zero because
    // the post-impact velocity has been calculated during impact
    // if (!_saveData) // we update the impact state at the end of impact
    //   {
    //     // Copy _velocityContact/_tolImpulseContact, starting from index pos into y/lambda
    //     // save into y !!
    //     setBlock(*_velocityContact, y, y->size(), pos, 0);// Warning: yEquivalent is
    //     // saved into lambda[1] !!
    //     setBlock(*_tolImpulseContact, lambda, lambda->size(), pos, 0);
    //   }
    // else //
    //   lambda->zero();
  }
}
//========================================================================================
int OSNSMultipleImpact::compute(double time)
{
  // Pre-compute for impact
  PreComputeImpact();
  // solve the multiple impacts
  if ((_nContact != 0) && IsVcminNegative()) // if there is at least one contact and the vilocity before impact is negative
  {
    ComputeImpact();
  };
  // Post-compute for multiple impacts
  PostComputeImpact();
  return  0;
}

//========================================================================================
void OSNSMultipleImpact::display() const
{
  std::cout << "<<<<<<<<<<<<<<<<< Information about the multiple impact >>>>>>>>>>>>>>>>>>>>>" <<std::endl;
  std::cout << "Type of the contact compliance law: " << _typeCompLaw <<std::endl;
  std::cout << "Number of contacts involved into impacts: " << _nContact <<std::endl;
  std::cout << "Step size used: " << _deltaP <<std::endl;
  std::cout << "Primary impulse at the end of impact: " << _impulseVariable <<std::endl;
  std::cout << "Duration of the multiple impacs process: " << _timeVariable <<std::endl;
  // Display post-impact velocities
  SP::DynamicalSystemsGraph DSG0 = simulation()->nonSmoothDynamicalSystem()->topology()->dSG(0);
  DynamicalSystemsGraph::VIterator ui, uiend;
  for (std11::tie(ui, uiend) = DSG0->vertices(); ui != uiend; ++ui)
  {
    SP::DynamicalSystem ds = DSG0->bundle(*ui);
    SP::LagrangianDS lag_ds = std11::dynamic_pointer_cast<LagrangianDS>(ds);
    std::cout << "DS number: " << ds->number() <<std::endl;
    std::cout << "Pre-impact velocity: ";
    (lag_ds->velocityMemory()->getSiconosVector(1))->display();
    std::cout << "Post-impact velocity: ";
    (lag_ds->velocity())->display();
  }
  // Display impulses at contact points
  SP::InteractionsGraph IndexSet0 = simulation()->nonSmoothDynamicalSystem()->topology()->indexSet(0);
  InteractionsGraph::VIterator vi, viend;
  for (std11::tie(vi, viend) = IndexSet0->vertices(); vi != viend; ++vi)
  {
    SP::Interaction inter = IndexSet0->bundle(*vi);
    std::cout << "Impulse at contact point " << inter->number() << ":";
    (inter->lambda(1))->display();
  }
};


//此源码被清华学神尹成大魔王专业翻译分析并修改
//尹成QQ77025077
//尹成微信18510341407
//尹成所在QQ群721929980
//尹成邮箱 yinc13@mails.tsinghua.edu.cn
//尹成毕业于清华大学,微软区块链领域全球最有价值专家
//https://mvp.microsoft.com/zh-cn/PublicProfile/4033620
//版权所有（c）2017-2018比特币核心开发商
//根据MIT软件许可证分发，请参见随附的
//文件复制或http://www.opensource.org/licenses/mit-license.php。

#include <wallet/coinselection.h>

#include <util/system.h>
#include <util/moneystr.h>

#include <boost/optional.hpp>

//降阶比较器
struct {
    bool operator()(const OutputGroup& a, const OutputGroup& b) const
    {
        return a.effective_value > b.effective_value;
    }
} descending;

/*
 *这是Murch设计的分支绑定硬币选择算法。它搜索输入
 *设置可支付支出目标，且不超过支出目标
 *创建和支出变更输出的成本。该算法对二进制文件使用深度优先搜索。
 *树。在二叉树中，每个节点对应于utxo的包含或省略。UTXOS
 *按其有效值排序，并根据包含内容对树进行确定性探索。
 *先分支。在每个节点上，算法检查选择是否在目标范围内。
 *虽然选择未达到目标范围，但包含更多的utxo。当选定内容
 *值超出目标范围，可省略从此选择派生的完整子树。
 *此时，取消选择最后一个包含的utxo，并探索相应的省略分支。
 *相反。搜索完成树后或经过有限的尝试后，搜索结束。
 *
 *找到一个解决方案后，搜索会继续搜索更好的解决方案。最好的
 *通过最小化浪费指标来选择解决方案。废物指标定义为
 *以给定的费率减去长期预期成本来花费当前投入
 *输入，加上所选金额超出支出目标：
 *
 *浪费=选择总计-目标+输入×（当前频率-长期频率）
 *
 *该算法使用了两个额外的优化。展望跟踪
 *未经探索的utxos。如果lookahead指示目标范围，则不探索子树。
 *无法联系。此外，无需测试等效组合。这让我们
 *跳过测试是否包含与省略项的有效值和浪费相匹配的utxo。
 *前身。
 *
 *分支定界算法在Murch的硕士论文中有详细描述：
 *https://murch.one/wp-content/uploads/2016/11/erhardt2016coinselection.pdf
 *
 *@param const std:：vector<cinputcoin>&utxo_pool我们正在选择的utxos集。
 *这些utxo将按有效值和cinputcoins的降序排序。
 *值是它们的有效值。
 *@param const camount&target_value这是我们要选择的值。它是较低的
 *范围的界限。
 *@param const camount&cost_change这是创建和花费更改输出的成本。
 *这个加上目标值是范围的上限。
 *@param std:：set<cinputcoin>&out\set->这是一组cinputcoins的输出参数
 *已选定。
 *@param camount&value_ret->这是Cinputcoins总值的输出参数
 *已选定。
 *@param camount not_input_fees->输出和固定大小需要支付的费用
 *开销（版本、锁定时间、标记和标志）
 **/

static const size_t TOTAL_TRIES = 100000;

bool SelectCoinsBnB(std::vector<OutputGroup>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees)
{
    out_set.clear();
    CAmount curr_value = 0;

std::vector<bool> curr_selection; //在此索引中选择utxo
    curr_selection.reserve(utxo_pool.size());
    CAmount actual_target = not_input_fees + target_value;

//计算当前可用值
    CAmount curr_available_value = 0;
    for (const OutputGroup& utxo : utxo_pool) {
//断言这个utxo不是负数。它不应该是负数，有效值计算应该删除它。
        assert(utxo.effective_value > 0);
        curr_available_value += utxo.effective_value;
    }
    if (curr_available_value < actual_target) {
        return false;
    }

//分类utxo_池
    std::sort(utxo_pool.begin(), utxo_pool.end(), descending);

    CAmount curr_waste = 0;
    std::vector<bool> best_selection;
    CAmount best_waste = MAX_MONEY;

//选择utxos的深度优先搜索循环
    for (size_t i = 0; i < TOTAL_TRIES; ++i) {
//开始回溯的条件
        bool backtrack = false;
if (curr_value + curr_available_value < actual_target ||                //无法达到当前可用价值中剩余金额的目标。
curr_value > actual_target + cost_of_change ||    //所选值超出范围，请返回并尝试其他分支
(curr_waste > best_waste && (utxo_pool.at(0).fee - utxo_pool.at(0).long_term_fee) > 0)) { //不要选择那些我们知道如果浪费增加会更浪费的东西。
            backtrack = true;
} else if (curr_value >= actual_target) {       //所选值在范围内
curr_waste += (curr_value - actual_target); //这是为进行以下比较而添加到废物中的超额价值。
//如果长期费用高于当前费用，在检查后再添加一个utxo可以减少浪费。
//但是，我们不会去探索这一点，因为只有当我们达到目标时，才会对废物进行优化。
//价值。再加上任何一个utxo都只会烧掉utxo，而这完全是要收费的。所以我们不会
//探索更多的UTXO，以避免像那样烧钱。
            if (curr_waste <= best_waste) {
                best_selection = curr_selection;
                best_selection.resize(utxo_pool.size());
                best_waste = curr_waste;
            }
curr_waste -= (curr_value - actual_target); //去掉多余的价值，因为我们现在要选择不同的硬币
            backtrack = true;
        }

//回溯，向后移动
        if (backtrack) {
//向后走，找到最后一个包含的utxo，它仍然需要遍历其省略分支。
            while (!curr_selection.empty() && !curr_selection.back()) {
                curr_selection.pop_back();
                curr_available_value += utxo_pool.at(curr_selection.size()).effective_value;
            }

if (curr_selection.empty()) { //我们已经走回了第一个utxo，没有一个分支是不受欢迎的。已搜索所有解决方案
                break;
            }

//输出包含在以前的迭代中，请尝试立即排除。
            curr_selection.back() = false;
            OutputGroup& utxo = utxo_pool.at(curr_selection.size() - 1);
            curr_value -= utxo.effective_value;
            curr_waste -= utxo.fee - utxo.long_term_fee;
} else { //向前走，继续沿着这条路走
            OutputGroup& utxo = utxo_pool.at(curr_selection.size());

//将此utxo从当前可用的utxo值中删除
            curr_available_value -= utxo.effective_value;

//如果先前的utxo具有相同的值和相同的浪费并且被排除在外，则避免搜索分支。因为费用与
//长期费用是一样的，我们只需要检查其中一个值是否匹配就可以知道浪费是一样的。
            if (!curr_selection.empty() && !curr_selection.back() &&
                utxo.effective_value == utxo_pool.at(curr_selection.size() - 1).effective_value &&
                utxo.fee == utxo_pool.at(curr_selection.size() - 1).fee) {
                curr_selection.push_back(false);
            } else {
//包容性分支第一（最大的第一次勘探）
                curr_selection.push_back(true);
                curr_value += utxo.effective_value;
                curr_waste += utxo.fee - utxo.long_term_fee;
            }
        }
    }

//检查解决方案
    if (best_selection.empty()) {
        return false;
    }

//集合输出集合
    value_ret = 0;
    for (size_t i = 0; i < best_selection.size(); ++i) {
        if (best_selection.at(i)) {
            util::insert(out_set, utxo_pool.at(i).m_outputs);
            value_ret += utxo_pool.at(i).m_value;
        }
    }

    return true;
}

static void ApproximateBestSubset(const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
                                  std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
{
    std::vector<char> vfIncluded;

    vfBest.assign(groups.size(), true);
    nBest = nTotalLower;

    FastRandomContext insecure_rand;

    for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
    {
        vfIncluded.assign(groups.size(), false);
        CAmount nTotal = 0;
        bool fReachedTarget = false;
        for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
        {
            for (unsigned int i = 0; i < groups.size(); i++)
            {
//这里的解算器使用随机算法，
//随机性不是真正的安全目的，只是
//需要防止退化行为，这很重要
//RNG很快。我们不使用常数随机序列，
//因为通过
//随机选择。
                if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
                {
                    nTotal += groups[i].m_value;
                    vfIncluded[i] = true;
                    if (nTotal >= nTargetValue)
                    {
                        fReachedTarget = true;
                        if (nTotal < nBest)
                        {
                            nBest = nTotal;
                            vfBest = vfIncluded;
                        }
                        nTotal -= groups[i].m_value;
                        vfIncluded[i] = false;
                    }
                }
            }
        }
    }
}

bool KnapsackSolver(const CAmount& nTargetValue, std::vector<OutputGroup>& groups, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet)
{
    setCoinsRet.clear();
    nValueRet = 0;

//小于目标值的列表
    boost::optional<OutputGroup> lowest_larger;
    std::vector<OutputGroup> applicable_groups;
    CAmount nTotalLower = 0;

    Shuffle(groups.begin(), groups.end(), FastRandomContext());

    for (const OutputGroup& group : groups) {
        if (group.m_value == nTargetValue) {
            util::insert(setCoinsRet, group.m_outputs);
            nValueRet += group.m_value;
            return true;
        } else if (group.m_value < nTargetValue + MIN_CHANGE) {
            applicable_groups.push_back(group);
            nTotalLower += group.m_value;
        } else if (!lowest_larger || group.m_value < lowest_larger->m_value) {
            lowest_larger = group;
        }
    }

    if (nTotalLower == nTargetValue) {
        for (const auto& group : applicable_groups) {
            util::insert(setCoinsRet, group.m_outputs);
            nValueRet += group.m_value;
        }
        return true;
    }

    if (nTotalLower < nTargetValue) {
        if (!lowest_larger) return false;
        util::insert(setCoinsRet, lowest_larger->m_outputs);
        nValueRet += lowest_larger->m_value;
        return true;
    }

//用随机逼近法求解子集和
    std::sort(applicable_groups.begin(), applicable_groups.end(), descending);
    std::vector<char> vfBest;
    CAmount nBest;

    ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
    if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
        ApproximateBestSubset(applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
    }

//如果我们有一个更大的硬币（或者随机近似没有找到一个好的解决方案，
//或者下一个更大的硬币更近），把更大的硬币还给我
    if (lowest_larger &&
        ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || lowest_larger->m_value <= nBest)) {
        util::insert(setCoinsRet, lowest_larger->m_outputs);
        nValueRet += lowest_larger->m_value;
    } else {
        for (unsigned int i = 0; i < applicable_groups.size(); i++) {
            if (vfBest[i]) {
                util::insert(setCoinsRet, applicable_groups[i].m_outputs);
                nValueRet += applicable_groups[i].m_value;
            }
        }

        if (LogAcceptCategory(BCLog::SELECTCOINS)) {
            /*打印（bclog:：selectcoins，“selectcoins（）最佳子集：”）；/*续*/
            for（unsigned int i=0；i<applicable_groups.size（）；i++）
                如果（vfbest[i]）；
                    logprint（bclog:：selectcoins，“%s”，formatmoney（适用的_groups[i].m_value））；/*续*/

                }
            }
            LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
        }
    }

    return true;
}

/*************************************************************************

 输出组

 *************************************************************************/

void OutputGroup::Insert(const CInputCoin& output, int depth, bool from_me, size_t ancestors, size_t descendants) {
    m_outputs.push_back(output);
    m_from_me &= from_me;
    m_value += output.effective_value;
    m_depth = std::min(m_depth, depth);
//这里的祖先表达了新硬币最终拥有的祖先数量，也就是说
//总和，而不是最大值；在多个输入的情况下，这将高估
//有共同的祖先
    m_ancestors += ancestors;
//后代是从上一个祖先看到的计数，而不是从上一个祖先看到的后代。
//硬币本身；因此，这个值被计算为最大值，而不是总和。
    m_descendants = std::max(m_descendants, descendants);
    effective_value = m_value;
}

std::vector<CInputCoin>::iterator OutputGroup::Discard(const CInputCoin& output) {
    auto it = m_outputs.begin();
    while (it != m_outputs.end() && it->outpoint != output.outpoint) ++it;
    if (it == m_outputs.end()) return it;
    m_value -= output.effective_value;
    effective_value -= output.effective_value;
    return m_outputs.erase(it);
}

bool OutputGroup::EligibleForSpending(const CoinEligibilityFilter& eligibility_filter) const
{
    return m_depth >= (m_from_me ? eligibility_filter.conf_mine : eligibility_filter.conf_theirs)
        && m_ancestors <= eligibility_filter.max_ancestors
        && m_descendants <= eligibility_filter.max_descendants;
}


/************************************************************************
 *                                                                      *
 * Copyright (C) 2012 OVSM/IPGP                                         *
 *                                                                      *
 * This program is free software: you can redistribute it and/or modify *
 * it under the terms of the GNU General Public License as published by *
 * the Free Software Foundation, either version 3 of the License, or    *
 * (at your option) any later version.                                  *
 *                                                                      *
 * This program is distributed in the hope that it will be useful,      *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
 * GNU General Public License for more details.                         *
 *                                                                      *
 * This program is part of 'Projet TSUAREG - INTERREG IV Caraïbes'.     *
 * It has been co-financed by the European Union and le Ministère de    *
 * l'Ecologie, du Développement Durable, des Transports et du Logement. *
 *                                                                      *
 ************************************************************************/

#define SEISCOMP_COMPONENT Md

#include "md.h"
#include "l4c1hz.h"
#include <seiscomp3/processing/waveformprocessor.h>
#include <seiscomp3/math/filter/stalta.h>
#include <seiscomp3/math/filter/iirfilter.h>
#include <seiscomp3/math/filter/butterworth.h>
#include <seiscomp3/logging/log.h>
#include <seiscomp3/core/strings.h>
#include <seiscomp3/seismology/magnitudes.h>
#include <seiscomp3/math/mean.h>
#include <seiscomp3/math/filter/seismometers.h>
#include <seiscomp3/math/restitution/fft.h>
#include <seiscomp3/math/geo.h>
#include <iostream>
#include <boost/bind.hpp>
#include <unistd.h>

#define _DEPTH_MAX 200.0
#define _SIGNAL_WINDOW_END 30.0
#define _LINEAR_CORRECTION 1.0
#define _OFFSET 0.0
#define _SNR_MIN 1.2
#define _DELTA_MAX 400.0
#define _MD_MAX 5.0
#define _FMA -0.87
#define _FMB 2.0
#define _FMD 0.0035
#define _FMF 0.0
#define _FMZ 0.0
#define _STACOR 0.0 //! not fully implemented !
/**
 * Seismometer selection
 * 1 for Wood-Anderson
 * 2 for Seismometer 5 sec
 * 3 for WWSSN LP ? filter
 * 4 for WWSSN SP? filter
 * 5 for Generic Seismometer ? filter
 * 6 for Butterworth Low Pass ? filter
 * 7 for Butterwoth High Pass ? filter
 * 8 for Butterworth Band Pass ? filter
 * 9 for L4C 1Hz seismometer
 **/
#define _SEISMO 9
#define _BUTTERWORTH ""

ADD_SC_PLUGIN("Md duration magnitude plugin", "IPGP <www.ipgp.fr>", 0, 1, 1)

#define AMPTAG "[Amp] [Md]"
#define MAGTAG "[Mag] [Md]"

using namespace Seiscomp;
using namespace Seiscomp::Math;
using namespace Seiscomp::Processing;

/*----[ AMPLITUDE PROCESSOR CLASS ]----*/

IMPLEMENT_SC_CLASS_DERIVED(AmplitudeProcessor_Md, AmplitudeProcessor, "AmplitudeProcessor_Md");
REGISTER_AMPLITUDEPROCESSOR(AmplitudeProcessor_Md, "Md");

struct ampConfig {

		double DEPTH_MAX;
		double SIGNAL_WINDOW_END;
		double SNR_MIN;
		double DELTA_MAX;
		double MD_MAX;
		double FMA;
		double FMB;
		double FMD;
		double FMF;
		double FMZ;
		double STACOR;
		int SEISMO;
		std::string BUTTERWORTH;
};
ampConfig aFile;

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AmplitudeProcessor_Md::AmplitudeProcessor_Md() :
		AmplitudeProcessor("Md") {

	setSignalStart(0.);
	setSignalEnd(150.);
	setMinSNR(aFile.SNR_MIN);
	setMaxDist(8);
	_computeAbsMax = true;
	_isInitialized = false;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AmplitudeProcessor_Md::AmplitudeProcessor_Md(const Core::Time& trigger) :
		AmplitudeProcessor(trigger, "Md") {

	setSignalStart(0.);
	setSignalEnd(150.);
	setMinSNR(aFile.SNR_MIN);
	setMaxDist(8);
	_computeAbsMax = true;
	_isInitialized = false;

	computeTimeWindow();
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
bool AmplitudeProcessor_Md::setup(const Settings& settings) {

	if ( !AmplitudeProcessor::setup(settings) )
		return false;

	bool isButterworth = false;
	try {
		aFile.SEISMO = settings.getInt("md.seismo");
		std::string type;
		switch ( aFile.SEISMO ) {
			case 1:
				type = "WoodAnderson";
			break;
			case 2:
				type = "Seismo5sec";
			break;
			case 3:
				type = "WWSSN LP";
			break;
			case 4:
				type = "WWSSN SP";
			break;
			case 5:
				type = "Generic Seismometer";
			break;
			case 6:
				type = "Butterworth Low Pass";
				isButterworth = true;
			break;
			case 7:
				type = "Butterworth High Pass";
				isButterworth = true;
			break;
			case 8:
				type = "Butterworth Band Pass";
				isButterworth = true;
			break;
			case 9:
				type = "L4C 1Hz Seismometer";
			break;
			default:
				break;
		}
		SEISCOMP_DEBUG("%s sets SEISMO to  %s [%s.%s]", AMPTAG, type.c_str(),
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.SEISMO = _SEISMO;
		SEISCOMP_ERROR("%s can not read SEISMO value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	if ( isButterworth == true ) {
		try {
			aFile.BUTTERWORTH = settings.getString("md.butterworth");
			SEISCOMP_DEBUG("%s sets Butterworth filter to  %s [%s.%s]", AMPTAG,
			    aFile.BUTTERWORTH.c_str(), settings.networkCode.c_str(),
			    settings.stationCode.c_str());
		}
		catch ( ... ) {
			aFile.BUTTERWORTH = _BUTTERWORTH;
			SEISCOMP_ERROR("%s can not read Butterworth filter value from configuration file [%s.%s]", AMPTAG,
			    settings.networkCode.c_str(), settings.stationCode.c_str());
		}
	}

	try {
		aFile.DEPTH_MAX = settings.getDouble("md.depthmax");
		SEISCOMP_DEBUG("%s sets DEPTH MAX to  %.2f [%s.%s]", AMPTAG, aFile.DEPTH_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.DEPTH_MAX = _DEPTH_MAX;
		SEISCOMP_ERROR("%s can not read DEPTH MAX value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.DELTA_MAX = settings.getDouble("md.deltamax");
		SEISCOMP_DEBUG("%s sets DELTA MAX to  %.2f [%s.%s]", AMPTAG, aFile.DELTA_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.DELTA_MAX = _DELTA_MAX;
		SEISCOMP_ERROR("%s can not read DELTA MAX value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.SNR_MIN = settings.getDouble("md.snrmin");
		SEISCOMP_DEBUG("%s sets SNR MIN to  %.2f [%s.%s]", AMPTAG, aFile.SNR_MIN,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.SNR_MIN = _SNR_MIN;
		SEISCOMP_ERROR("%s can not read SNR MIN value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.MD_MAX = settings.getDouble("md.mdmax");
		SEISCOMP_DEBUG("%s sets MD MAX to  %.2f [%s.%s]", AMPTAG, aFile.MD_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.MD_MAX = _MD_MAX;
		SEISCOMP_ERROR("%s can not read MD MAX value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.FMA = settings.getDouble("md.fma");
		SEISCOMP_DEBUG("%s sets FMA to  %.4f [%s.%s]", AMPTAG, aFile.FMA,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.FMA = _FMA;
		SEISCOMP_ERROR("%s can not read FMA value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.FMB = settings.getDouble("md.fmb");
		SEISCOMP_DEBUG("%s sets FMB to  %.4f [%s.%s]", AMPTAG, aFile.FMB,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.FMB = _FMB;
		SEISCOMP_ERROR("%s can not read FMB value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.FMD = settings.getDouble("md.fmd");
		SEISCOMP_DEBUG("%s sets FMD to  %.4f [%s.%s]", AMPTAG, aFile.FMD,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.FMD = _FMD;
		SEISCOMP_ERROR("%s can not read FMD value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.FMF = settings.getDouble("md.fmf");
		SEISCOMP_DEBUG("%s sets FMF to  %.4f [%s.%s]", AMPTAG, aFile.FMF,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.FMF = _FMF;
		SEISCOMP_ERROR("%s can not read FMF value from configuration file [%s.%s]", AMPTAG,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		aFile.FMZ = settings.getDouble("md.fmz");
		SEISCOMP_DEBUG("%s sets FMZ to  %.4f [%s.%s]", AMPTAG, aFile.FMZ,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		aFile.FMZ = _FMZ;
		SEISCOMP_ERROR("%s can not read FMZ value from configuration file [%s.%s]",
		    AMPTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	_isInitialized = true;

	return true;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
void AmplitudeProcessor_Md::initFilter(double fsamp) {

	if ( !_enableResponses ) {

		SEISCOMP_DEBUG("Using custom responses");

		Math::Filtering::InPlaceFilter<double>* f;
		switch ( aFile.SEISMO ) {
			case 1:
				AmplitudeProcessor::setFilter(new Filtering::IIR::WoodAndersonFilter<
				        double>(Velocity));
			break;
			case 2:
				AmplitudeProcessor::setFilter(new Filtering::IIR::Seismometer5secFilter<
				        double>(Velocity));
			break;
			case 3:
				AmplitudeProcessor::setFilter(new Filtering::IIR::WWSSN_LP_Filter<
				        double>(Velocity));
			break;
			case 4:
				AmplitudeProcessor::setFilter(new Filtering::IIR::WWSSN_SP_Filter<
				        double>(Velocity));
			break;
			case 5:
				AmplitudeProcessor::setFilter(new Filtering::IIR::GenericSeismometer<
				        double>(Velocity));
			break;
			case 6:
				f = new Math::Filtering::IIR::ButterworthLowpass<double>(3, 1, 15);
				AmplitudeProcessor::setFilter(f);
			break;
			case 7:
				f = new Math::Filtering::IIR::ButterworthHighpass<double>(3, 1, 15);
				AmplitudeProcessor::setFilter(f);
			break;
			case 8:
				f = new Math::Filtering::IIR::ButterworthBandpass<double>(3, 1, 15, 1, true);
				AmplitudeProcessor::setFilter(f);
			break;
			case 9:
				AmplitudeProcessor::setFilter(new Filtering::IIR::L4C_1Hz_Filter<
				        double>(Velocity));
			break;
			default:
				SEISCOMP_ERROR("%s can not initialize the chosen filter, "
					"please review your configuration file", AMPTAG);
			break;
		}
	}
	else
		AmplitudeProcessor::setFilter(NULL);

	AmplitudeProcessor::initFilter(fsamp);
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
int AmplitudeProcessor_Md::capabilities() const {
	return MeasureType;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
AmplitudeProcessor::IDList
AmplitudeProcessor_Md::capabilityParameters(Capability cap) const {

	if ( cap == MeasureType ) {
		IDList params;
		params.push_back("AbsMax");
		params.push_back("MinMax");
		return params;
	}

	return AmplitudeProcessor::capabilityParameters(cap);
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
bool AmplitudeProcessor_Md::setParameter(Capability cap,
                                         const std::string& value) {

	if ( cap == MeasureType ) {

		if ( value == "AbsMax" ) {
			_computeAbsMax = true;
			return true;
		}
		else if ( value == "MinMax" ) {
			_computeAbsMax = false;
			return true;
		}
		return false;
	}

	return AmplitudeProcessor::setParameter(cap, value);
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
bool AmplitudeProcessor_Md::deconvolveData(Response* resp,
                                           DoubleArray& data,
                                           int numberOfIntegrations) {
	if ( numberOfIntegrations < -1 )
		return false;

	SEISCOMP_DEBUG("Inside deconvolve function");

	double m, n;
	Math::Restitution::FFT::TransferFunctionPtr tf =
		resp->getTransferFunction(numberOfIntegrations < 0 ? 0 : numberOfIntegrations);

	if ( !tf )
		return false;

	Math::GroundMotion gm;

	if ( numberOfIntegrations < 0 )
		gm = Math::Displacement;
	else
		gm = Math::Velocity;

	Math::Restitution::FFT::TransferFunctionPtr cascade;
	Math::SeismometerResponse::WoodAnderson woodAndersonResp(gm);
	Math::SeismometerResponse::Seismometer5sec seis5sResp(gm);
	Math::SeismometerResponse::L4C_1Hz l4c1hzResp(gm);

	Math::Restitution::FFT::PolesAndZeros woodAnderson(woodAndersonResp);
	Math::Restitution::FFT::PolesAndZeros seis5sec(seis5sResp);
	Math::Restitution::FFT::PolesAndZeros l4c1hz(l4c1hzResp);

	SEISCOMP_DEBUG("SEISMO = %d", aFile.SEISMO);

	switch ( aFile.SEISMO ) {
		case 1:
			cascade = *tf / woodAnderson;
		break;
		case 2:
			cascade = *tf / seis5sec;
		break;
		case 9:
			SEISCOMP_INFO("%s Applying filter L4C 1Hz to data", AMPTAG);
			cascade = *tf / l4c1hz;
		break;
		default:
			cascade = tf;
			SEISCOMP_INFO("%s No seismometer specified, no signal reconvolution performed", AMPTAG);
			return false;
		break;
	}

	// Remove linear trend
	Math::Statistics::computeLinearTrend(data.size(), data.typedData(), m, n);
	Math::Statistics::detrend(data.size(), data.typedData(), m, n);

	return Math::Restitution::transformFFT(data.size(), data.typedData(),
	    _stream.fsamp, cascade.get(), _config.respTaper, _config.respMinFreq,
	    _config.respMaxFreq);
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
bool AmplitudeProcessor_Md::computeAmplitude(const DoubleArray& data, size_t i1,
                                             size_t i2, size_t si1, size_t si2,
                                             double offset, AmplitudeIndex* dt,
                                             AmplitudeValue* amplitude,
                                             double* period, double* snr) {

	double amax, Imax, ofs_sig, amp_sig;
	DoubleArrayPtr d;

	if ( *snr < aFile.SNR_MIN )
		SEISCOMP_DEBUG("%s computed SNR is under configured SNR MIN", AMPTAG);

	if ( _computeAbsMax ) {
		size_t imax = find_absmax(data.size(), data.typedData(), si1, si2, offset);
		amax = fabs(data[imax] - offset);
		dt->index = imax;
	}
	else {
		int lmin, lmax;
		find_minmax(lmin, lmax, data.size(), data.typedData(), si1, si2, offset);
		amax = data[lmax] - data[lmin];
		dt->index = (lmin + lmax) * 0.5;
		dt->begin = lmin - dt->index;
		dt->end = lmax - dt->index;
	}

	Imax = dt->index;

	SEISCOMP_DEBUG("%s Amplitude max: %.2f", AMPTAG, amax);

	//! searching for Coda second by second through the end of the window
	//! TODO: elevate accuracy by using a nanometers scale (maybe)
	unsigned int i = si1;
	bool hasEndSignal = false;
	double calculatedSnr = -1;

	for (i = (int) Imax; i < i2; i = i + 1 * (int) _stream.fsamp) {

		int window_end = i + 1 * (int) _stream.fsamp;
		d = static_cast<DoubleArray*>(data.slice(i, window_end));

		//! computes pre-arrival offset
		ofs_sig = d->median();

		//! computes rms after removing offset
		amp_sig = 2 * d->rms(ofs_sig);

		if ( amp_sig / *_noiseAmplitude <= aFile.SNR_MIN ) {
			SEISCOMP_DEBUG("%s End of signal found! (%.2f <= %.2f)", AMPTAG,
			    (amp_sig / *_noiseAmplitude), aFile.SNR_MIN);
			hasEndSignal = true;
			calculatedSnr = amp_sig / *_noiseAmplitude;
			break;
		}
	}

	if ( !hasEndSignal ) {
		SEISCOMP_ERROR("%s SNR stayed over configured SNR_MIN! (%.2f > %.2f), "
			"skipping magnitude calculation for this station", AMPTAG,
		    calculatedSnr, aFile.SNR_MIN);
		return false;
	}

	dt->index = i;

	//amplitude->value = 2 * amp_sig; //! actually it would have to be max. peak-to-peak
	amplitude->value = amp_sig;

	if ( _streamConfig[_usedComponent].gain != 0.0 )
		amplitude->value /= _streamConfig[_usedComponent].gain;
	else {
		setStatus(MissingGain, 0.0);
		return false;
	}

	// Convert m/s to nm/s
	amplitude->value *= 1.E09;

	*period = i - i1 + (_config.signalBegin * _stream.fsamp);

	SEISCOMP_DEBUG("%s calculated event amplitude = %.2f", AMPTAG, amplitude->value);
	SEISCOMP_DEBUG("%s calculated signal end at %.2f ms from P phase", AMPTAG, *period);

	return true;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
double AmplitudeProcessor_Md::timeWindowLength(double distance_deg) const {

	if ( !_isInitialized ) {

		aFile.MD_MAX = _MD_MAX;
		aFile.FMA = _FMA;
		aFile.FMZ = _FMZ;
		aFile.DEPTH_MAX = _DEPTH_MAX;
		aFile.STACOR = _STACOR;
		aFile.FMD = _FMD;
		aFile.FMB = _FMB;
		aFile.FMF = _FMF;
		aFile.SNR_MIN = _SNR_MIN;
		aFile.DELTA_MAX = _DELTA_MAX;
		aFile.SIGNAL_WINDOW_END = _SIGNAL_WINDOW_END;
		aFile.SEISMO = _SEISMO;
		aFile.BUTTERWORTH = _BUTTERWORTH;
	}

	double distance_km = Math::Geo::deg2km(distance_deg);
	double windowLength = (aFile.MD_MAX - aFile.FMA - (aFile.FMZ * aFile.DEPTH_MAX)
	        - aFile.STACOR - (aFile.FMD * distance_km)) / (aFile.FMB + aFile.FMF);

	windowLength = pow(10, windowLength) + aFile.SIGNAL_WINDOW_END;
	SEISCOMP_DEBUG("%s Requesting stream of %.2fsec for current station", AMPTAG, windowLength);

	return windowLength;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*----[ END OF AMPLITUDE PROCESSOR CLASS ]----*/

/*----[ MAGNITUDE PROCESSOR CLASS ]----*/

IMPLEMENT_SC_CLASS_DERIVED(MagnitudeProcessor_Md, MagnitudeProcessor, "MagnitudeProcessor_Md");
REGISTER_MAGNITUDEPROCESSOR(MagnitudeProcessor_Md, "Md");

struct magConfig {

		double DEPTH_MAX;
		double LINEAR_CORRECTION;
		double OFFSET;
		double DELTA_MAX;
		double MD_MAX;
		double FMA;
		double FMB;
		double FMD;
		double FMF;
		double FMZ;
		double STACOR;
};
magConfig mFile;

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
MagnitudeProcessor_Md::MagnitudeProcessor_Md() :
		MagnitudeProcessor("Md") {

	_linearCorrection = mFile.LINEAR_CORRECTION;
	_constantCorrection = mFile.OFFSET;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
bool MagnitudeProcessor_Md::setup(const Settings& settings) {

	try {
		mFile.DELTA_MAX = settings.getDouble("md.deltamax");
		SEISCOMP_DEBUG("%s sets DELTA MAX to  %.2f [%s.%s]", MAGTAG, mFile.DELTA_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.DELTA_MAX = _DELTA_MAX;
		SEISCOMP_ERROR("%s can not read DELTA MAX value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.DEPTH_MAX = settings.getDouble("md.depthmax");
		SEISCOMP_DEBUG("%s sets DEPTH MAX to  %.2f [%s.%s]", MAGTAG, mFile.DEPTH_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.DEPTH_MAX = _DEPTH_MAX;
		SEISCOMP_ERROR("%s can not read DEPTH MAX value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.MD_MAX = settings.getDouble("md.mdmax");
		SEISCOMP_DEBUG("%s sets MD MAX to  %.2f [%s.%s]", MAGTAG, mFile.MD_MAX,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.MD_MAX = _MD_MAX;
		SEISCOMP_ERROR("%s can not read MD MAX value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.LINEAR_CORRECTION = settings.getDouble("md.linearcorrection");
		SEISCOMP_DEBUG("%s sets LINEAR CORRECTION to  %.2f [%s.%s]", MAGTAG,
		    mFile.LINEAR_CORRECTION, settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.LINEAR_CORRECTION = _LINEAR_CORRECTION;
		SEISCOMP_ERROR("%s can not read LINEAR CORRECTION value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.OFFSET = settings.getDouble("md.offset");
		SEISCOMP_DEBUG("%s sets OFFSET to  %.2f [%s.%s]", MAGTAG, mFile.OFFSET,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.OFFSET = _OFFSET;
		SEISCOMP_ERROR("%s can not read OFFSET value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.FMA = settings.getDouble("md.fma");
		SEISCOMP_DEBUG("%s sets FMA to  %.4f [%s.%s]", MAGTAG, mFile.FMA,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.FMA = _FMA;
		SEISCOMP_ERROR("%s can not read FMA value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.FMB = settings.getDouble("md.fmb");
		SEISCOMP_DEBUG("%s sets FMB to  %.4f [%s.%s]", MAGTAG, mFile.FMB,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.FMB = _FMB;
		SEISCOMP_ERROR("%s can not read FMB value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.FMD = settings.getDouble("md.fmd");
		SEISCOMP_DEBUG("%s sets FMD to  %.4f [%s.%s]", MAGTAG, mFile.FMD,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.FMD = _FMD;
		SEISCOMP_ERROR("%s can not read FMD value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.FMF = settings.getDouble("md.fmf");
		SEISCOMP_DEBUG("%s sets FMF to  %.4f [%s.%s]", MAGTAG, mFile.FMF,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.FMF = _FMF;
		SEISCOMP_ERROR("%s can not read FMF value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.FMZ = settings.getDouble("md.fmz");
		SEISCOMP_DEBUG("%s sets FMZ to  %.4f [%s.%s]", MAGTAG, mFile.FMZ,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.FMZ = _FMZ;
		SEISCOMP_ERROR("%s can not read FMZ value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	try {
		mFile.STACOR = settings.getDouble("md.stacor");
		SEISCOMP_DEBUG("%s sets STACOR to  %.4f [%s.%s]", MAGTAG, mFile.STACOR,
		    settings.networkCode.c_str(), settings.stationCode.c_str());
	}
	catch ( ... ) {
		mFile.STACOR = _STACOR;
		SEISCOMP_ERROR("%s can not read STACOR value from configuration file [%s.%s]",
		    MAGTAG, settings.networkCode.c_str(), settings.stationCode.c_str());
	}

	return true;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

// >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
MagnitudeProcessor::Status
MagnitudeProcessor_Md::computeMagnitude(double amplitude, double period,
                                        double delta, double depth,
                                        double& value) {

	double epdistkm;
	epdistkm = Math::Geo::deg2km(delta);

	SEISCOMP_DEBUG("%s --------------------------------", MAGTAG);
	SEISCOMP_DEBUG("%s |    PARAMETERS   |    VALUE   |", MAGTAG);
	SEISCOMP_DEBUG("%s --------------------------------", MAGTAG);
	SEISCOMP_DEBUG("%s | delta max       | %.2f ", MAGTAG, mFile.DELTA_MAX);
	SEISCOMP_DEBUG("%s | depth max       | %.2f ", MAGTAG, mFile.DEPTH_MAX);
	SEISCOMP_DEBUG("%s | md max          | %.2f ", MAGTAG, mFile.MD_MAX);
	SEISCOMP_DEBUG("%s | fma             | %.4f ", MAGTAG, mFile.FMA);
	SEISCOMP_DEBUG("%s | fmb             | %.4f ", MAGTAG, mFile.FMB);
	SEISCOMP_DEBUG("%s | fmd             | %.4f ", MAGTAG, mFile.FMD);
	SEISCOMP_DEBUG("%s | fmf             | %.4f ", MAGTAG, mFile.FMF);
	SEISCOMP_DEBUG("%s | fmz             | %.4f ", MAGTAG, mFile.FMZ);
	SEISCOMP_DEBUG("%s | stacor          | %.4f ", MAGTAG, mFile.STACOR);
	SEISCOMP_DEBUG("%s --------------------------------", MAGTAG);
	SEISCOMP_DEBUG("%s | (f-p)           | %.2f sec ", MAGTAG, period);
	SEISCOMP_DEBUG("%s | seismic depth   | %.2f km ", MAGTAG, depth);
	SEISCOMP_DEBUG("%s | epicenter dist  | %.2f km ", MAGTAG, epdistkm);
	SEISCOMP_DEBUG("%s --------------------------------", MAGTAG);

	if ( amplitude <= 0. ) {
		value = 0;
		SEISCOMP_ERROR("%s calculated amplitude is wrong, "
			"no magnitude will be calculated", MAGTAG);
		return Error;
	}

	if ( (mFile.DELTA_MAX) < epdistkm ) {
		SEISCOMP_ERROR("%s epicenter distance is out of configured range, "
			"no magnitude will be calculated", MAGTAG);
		return DistanceOutOfRange;
	}

	value = mFile.FMA + mFile.FMB * log10(period) + (mFile.FMF * period)
	        + (mFile.FMD * epdistkm) + (mFile.FMZ * depth) + mFile.STACOR;

	if ( value > mFile.MD_MAX )
		SEISCOMP_WARNING("%s Calculated magnitude is beyond max Md value [value= %.2f]",
		    MAGTAG, value);

	return OK;
}
// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<

/*----[ END OF MAGNITUDE PROCESSOR CLASS ]----*/