// $Id: IndividualNearestNeighbours.h 1365 2024-06-28 20:00:02Z ge $
/// \file IndividualNearestNeighbours.h
/// \brief contains the Gibbs Free Energy function class
///
/// $Revision: 1365 $
/// \author Gerald Weber <gweberbh@gmail.com>
#ifndef GBC_INDIVIDUALNEARESTNEIGHBOURS_H
#define GBC_INDIVIDUALNEARESTNEIGHBOURS_H "$Id: IndividualNearestNeighbours.h 1365 2024-06-28 20:00:02Z ge $"
#include "SequenceInfo.h"
#include "NeighbourSequence.h"
#include "Duplex.h"
#include <valarray>
#include <fstream>
#include "ParameterMap.h"
#include <boost/regex.hpp>
#include "valmatrix.h"

using namespace gbc;
namespace gbc
  {
  /// \brief Calculate Gibbs Free Energies and Melting Temperatures
  ///
  /// This implements DNA melting temperature calculations based on the independent nearest neighbour model (INN)
  /// See santalucia96, santalucia98, gray97, gray97b
  class IndividualNearestNeighbours
    {
    public:
      typedef double                           value_type;
      typedef ParameterMap<value_type>         parameter_map_type;
      typedef std::set<std::string>            nearest_neighbours_map_type;
      typedef std::map<std::string,value_type> enthalpy_map_type; //Despite its name this typedef is also used for entropy
      typedef std::valarray<value_type> vector_type;
      typedef SequenceInfo<false,value_type>   sequenceinfo_type;//false refers to periodicity
      typedef typename sequenceinfo_type::base_pair_type     
                                               base_pair_type;
      typedef typename sequenceinfo_type::nneighbour_type     
                                               nneighbour_type;
      typedef typename sequenceinfo_type::neighboursequence_type
                                               neighboursequence_type;
      typedef typename sequenceinfo_type::neighbours_ref_type
                                               neighbours_ref_type;
      typedef sequenceinfo_type::neighbours_ref_type base_model_type;
      typedef std::deque<std::string>          string_deque_type;
      typedef typename sequenceinfo_type::salt_map_type
                                               salt_map_type;
      typedef valmatrix<value_type>            matrix_type;                                               
      typedef std::deque<std::string>          svd_deque_type;

      constexpr static const value_type R=1.987;   ///< Gas constant, cal/(K mol)

      sequenceinfo_type*  Sequence_info;
      bool                Debug;
      value_type          Sequence_enthalpy;      ///< Contains the calculated total sequence ethalphy
      value_type          Sequence_entropy;       ///< Contains the calculated total sequence entropy
      value_type          Sequence_terminal_enthalpy;      ///< Contains the calculated terminal sequence ethalphy
      value_type          Sequence_terminal_entropy;       ///< Contains the calculated terminal sequence entropy
      value_type          Sequence_initiation_enthalpy;    ///< Contains the calculated initiation sequence ethalphy
      value_type          Sequence_initiation_entropy;     ///< Contains the calculated initiation sequence entropy
      value_type          Sequence_symmetry_enthalpy;    ///< Contains the calculated symmetry sequence ethalphy
      value_type          Sequence_symmetry_entropy;     ///< Contains the calculated symmetry sequence entropy
      value_type          Initiation_CG_entropy;  ///< For sequences that contain at least one CG base-pair
      value_type          Initiation_CG_enthalpy; ///< For sequences that contain at least one CG base-pair
      value_type          Initiation_AT_entropy;  ///< For sequences that are AT only
      value_type          Initiation_AT_enthalpy; ///< For sequences that are AT only
      value_type          Initiation_entropy;     ///< Entropy for initiation
      value_type          Initiation_enthalpy;    ///< Enthalpy for initiation
      value_type          Symmetry_correction_entropy;    ///< Symmetry correction of self-complementary sequences
      value_type          Symmetry_correction_enthalpy;    ///< Symmetry correction of self-complementary sequences
      value_type          Terminal_5TA3_enthalpy; ///< AT 5' terminal correction
      value_type          Terminal_AU_enthalpy;   ///< Contribution for AU or UA at ends
      value_type          Terminal_AU_entropy;    ///< Contribution for AU or UA at ends
      value_type          Terminal_AT_enthalpy;   ///< Contribution for AT or TA at ends
      value_type          Terminal_AT_entropy;    ///< Contribution for AT or TA at ends
      value_type          Terminal_CG_enthalpy;   ///< Contribution for AT or TA at ends
      value_type          Terminal_CG_entropy;    ///< Contribution for AT or TA at ends
      value_type          Terminal_dCrG_dGrC_enthalpy; ///< Contains *at least* one dCrG or dGrC at terminal
      value_type          Terminal_dCrG_dGrC_entropy;
      value_type          Terminal_dArU_dTrA_only_enthalpy; ///< Contains *only*  dArU or dTrA
      value_type          Terminal_dArU_dTrA_only_entropy;
      salt_map_type       Parameter_salt_concentration; ///< the salt concentration for which the parameters are valid
      parameter_map_type  parameter_map;          ///< Contains all parameters
      enthalpy_map_type   Enthalpies, Entropies;  ///< Contains all nearest-neighbour enthalpies and entropies
      bool                NN_parameters_only;

    IndividualNearestNeighbours(void) 
        : Debug(false), 
          Sequence_enthalpy(), Sequence_entropy(), Sequence_terminal_enthalpy(), Sequence_terminal_entropy(),
          Sequence_initiation_enthalpy(), Sequence_initiation_entropy(), Sequence_symmetry_enthalpy(), Sequence_symmetry_entropy(),
          Initiation_CG_entropy(), Initiation_CG_enthalpy(), Initiation_AT_entropy(), Initiation_AT_enthalpy(),
          Symmetry_correction_entropy(), Symmetry_correction_enthalpy(),
          Terminal_5TA3_enthalpy(),
          Terminal_AU_enthalpy(), Terminal_AU_entropy(), Terminal_CG_enthalpy(), Terminal_CG_entropy(),
          Terminal_dCrG_dGrC_enthalpy(), Terminal_dCrG_dGrC_entropy(), 
          Terminal_dArU_dTrA_only_enthalpy(), Terminal_dArU_dTrA_only_entropy(),
          NN_parameters_only(false) {}
          
    inline void reset_calculation(void)
      {
      Sequence_enthalpy=value_type();
      Sequence_entropy=value_type();
      }

    inline std::set<std::string> get_nn(void) {return Sequence_info->Reduced_neighbours_set.string_set();}
      
    /// \brief dummy function, needed in ParameterSearch, has no function
    inline void temperature(value_type T) {}

    /// \brief dummy function, needed in ParameterSearch, has no function
    inline value_type temperature(void) {return value_type();}

    inline void print_calculation(std::ostream &out)
      {
      parameter_map.print_parameters_and_stats(out);
      }

    inline bool from_file(const std::string Filename)
      {
      parameter_map.read_from(Filename);
       set_TM_parameters();
      return true;
      }
      
    /// \brief Reads a deque of parameter files and assigns to all maps
    /// All parameters are places into parameter_map.
    inline bool get_parameters_from_list_of_files(string_deque_type &par_file_deque)
      {
      parameter_map.read_from_list_of_files(par_file_deque);
      set_TM_parameters();
      return true;
      }
      
    
    template<class _Map>
    void populate_enthalpy_tables(std::string match)
      {
      _Map map;
      boost::regex expr(match);
      //Note that this loads all parameters in the files even when not strictly required
      parameter_map.get_all_id(map); // Here we get all combinations of NN from the parameter file
      for(auto it: map)
        {
        if (boost::regex_match(it, expr))
          {
          std::string parameter_name=it + ":" + "enthalpy";
          Enthalpies[it]=parameter_map.get_parameter_or_generic(parameter_name);
          
          parameter_name=it + ":" + "entropy";
          Entropies[it] =parameter_map.get_parameter_or_generic(parameter_name);
          }
        }
      if ((Enthalpies.size() == size_t()) || (Entropies.size() == size_t())) 
        CERR_WARN(WNSEEV) << " no specific Enthalpy or Entropy values, falling back to generic values" << std::endl;
      }

    void store_enthalpy_parameters(void)
      {
      for(auto nn: Sequence_info->Reduced_neighbours_set)
        {
        std::string parameter_name=(std::string)nn.first + ":" + "enthalpy";
        parameter_map.set_parameters(parameter_name,Enthalpies[nn.first]);
          
        parameter_name=(std::string)nn.first + ":" + "entropy";
        parameter_map.set_parameters(parameter_name,Entropies[nn.first]);
        }
      }
      
    void set_initiation_parameters(void)
      {
      Initiation_entropy=parameter_map.get_if_exists("initiation_entropy");
        
      Initiation_enthalpy=parameter_map.get_if_exists("initiation_enthalpy");

      Initiation_CG_entropy=parameter_map.get_if_exists("initiation_CG_entropy");
      
      Initiation_CG_enthalpy=parameter_map.get_if_exists("initiation_CG_enthalpy");

      Initiation_AT_entropy=parameter_map.get_if_exists("initiation_AT_entropy");
	
      Initiation_AT_enthalpy=parameter_map.get_if_exists("initiation_AT_enthalpy");
      }
      
    void set_symmetry_parameters(void)
      {
      Symmetry_correction_entropy=parameter_map.get_if_exists("symmetry_correction_entropy");
      
      Symmetry_correction_enthalpy=parameter_map.get_if_exists("symmetry_correction_enthalpy");
      }

    void set_terminal_parameters(void)
      {
      //Terminal parameters that are applied for each terminal occurence should start lowercase in the .par file
      Terminal_5TA3_enthalpy=parameter_map.get_if_exists("terminal_5TA3_enthalpy");
      
      Terminal_AU_enthalpy=parameter_map.get_if_exists("terminal_AU_enthalpy");

      Terminal_AU_entropy=parameter_map.get_if_exists("terminal_AU_entropy");

      Terminal_AT_enthalpy=parameter_map.get_if_exists("terminal_AT_enthalpy");

      Terminal_AT_entropy=parameter_map.get_if_exists("terminal_AT_entropy");

      Terminal_CG_enthalpy=parameter_map.get_if_exists("terminal_CG_enthalpy");

      Terminal_CG_entropy=parameter_map.get_if_exists("terminal_CG_entropy");

      //Terminal parameters that are applied just once should start uppercase in the .par file
      Terminal_dArU_dTrA_only_enthalpy=parameter_map.get_if_exists("Terminal_dArU_dTrA_only_enthalpy");
      Terminal_dArU_dTrA_only_entropy =parameter_map.get_if_exists("Terminal_dArU_dTrA_only_entropy");
      
      Terminal_dCrG_dGrC_enthalpy=parameter_map.get_if_exists("Terminal_dCrG_dGrC_enthalpy");
      Terminal_dCrG_dGrC_entropy =parameter_map.get_if_exists("Terminal_dCrG_dGrC_entropy");
      }
    
    void set_salt_parameters(void)
      {
      typename parameter_map_type::map_type salt_map=parameter_map.get_regex_id_par_map("^.*:concentration");
      
      for(auto &slt: salt_map)
        {
        value_type concentration=slt.second;
        std::string ion=reg_match_string(slt.first,"^(.*):concentration");
        if (Parameter_salt_concentration[ion] != concentration)
          {
          Parameter_salt_concentration[ion]=concentration;
          COUT_INFO(INFOSPSCT) <<  " setting " << ion << " parameter salt concentration to " << Parameter_salt_concentration[ion] << std::endl;
          }
        }

      }
      

    virtual void  set_TM_parameters(void) 
      {
      //This block is crucial for the parameter minimization, each time this function is called we reset the parameters to the ones used in the parameter file
      //Note that this loads all parameters in the files even when not strictly required
        
      populate_enthalpy_tables<nearest_neighbours_map_type>(".+_.+");
 
      set_initiation_parameters();

      set_symmetry_parameters();
      
      set_terminal_parameters();

      set_salt_parameters();
      
      }
      
      
    inline value_type get_terminal_parameter (base_pair_type &bp, std::string parameter_name)
      {
      std::string formatted_parameter_name = std::string(bp) + parameter_name;
      
      value_type parameter = parameter_map.get_if_exists(formatted_parameter_name);

      if (parameter != value_type()) 
        CERR_DDEBUG(DINN_FPN) << formatted_parameter_name  << " = " << parameter << std::endl;
      else
        CERR_DDEBUG(DINN_FPN) "No parameter " << formatted_parameter_name  << " found " << std::endl;
        
      return parameter;
      }
      
    inline value_type get_terminal_parameter (nneighbour_type &nn, std::string parameter_name)
      {
      std::string formatted_parameter_name = std::string(nn) + parameter_name;
      
      value_type parameter = parameter_map.get_if_exists(formatted_parameter_name);

      if (parameter != value_type()) 
        CERR_DDEBUG(DINN_FPN) << formatted_parameter_name  << " = " << parameter << std::endl;
      else
        CERR_DDEBUG(DINN_FPN) "No parameter " << formatted_parameter_name  << " found " << std::endl;
        
      return parameter;
      }

    inline bool empty_enthalpy_tables(void)
      {
      return (Sequence_info->ppEntropy == NULL) or (Sequence_info->ppEnthalpy == NULL);
      }
      
    template<class _Set>
    inline void alloc_enthalpy_tables(const _Set &set)
      {
      CERR_DDEBUG(DINN_AEET) "Allocating enthalpy/entropy tables" << std::endl;
      Sequence_info->clear_pointers_for_different_sequence(); //as a precaution we clear previous pointers
      //Here we create a int vector that can be used later more efficiently
      Sequence_info->pReduced_nn_number = new int[set.size()];
      //We also allocate space for ppEnthalpy and ppEntropy
      Sequence_info->ppEnthalpy = new value_type*[set.size()];
      Sequence_info->ppEntropy  = new value_type*[set.size()];

      int n=0;
      for(auto rns: set)
        {
        Sequence_info->pReduced_nn_number[n]=rns.second;
        Sequence_info->ppEnthalpy[n]=Sequence_info->ppEntropy[n]=NULL;
        n++;
        }
      }
      
    template<class _Set>
    inline void attribute_sequence_enthalpy_tables(_Set &set)
      {
      int n=0;
      if (!Sequence_info->ppEnthalpy[0] or !Sequence_info->ppEntropy[0])
        {
        CERR_DDEBUG(DINN_HSG) << Sequence_info->sequence.first << "/" << Sequence_info->sequence.second << std::endl;
        for(auto it: set)
          {
          typename enthalpy_map_type::iterator enth=Enthalpies.find(it.first);
          if (enth == Enthalpies.end())
            {
            std::string parameter_name=std::string(it.first) + ":" + "enthalpy";
            Enthalpies[it.first]=parameter_map.get_parameter_or_generic(parameter_name,false);
            enth=Enthalpies.find(it.first);
            }
          Sequence_info->ppEnthalpy[n] = &(enth->second);
          Sequence_enthalpy += it.second*enth->second;
        
          typename enthalpy_map_type::iterator entr=Entropies.find(it.first);
          if (entr == Entropies.end())
            {
            std::string parameter_name=std::string(it.first) + ":" + "entropy";
            Entropies[it.first]=parameter_map.get_parameter_or_generic(parameter_name,false);
            entr=Entropies.find(it.first);
            }
          Sequence_info->ppEntropy[n] = &(entr->second);
          Sequence_entropy += it.second*entr->second;
          
          CERR_DDEBUG(DINN_HSG) << " " << it.second << " X " << it.first
                                << " DeltaH = " <<  Enthalpies[it.first]
                                << " DeltaS = " <<  Entropies[it.first]
                                << " DeltaG = " << gibbs_free_energy(Enthalpies[it.first],Entropies[it.first])  << std::endl;
          n++;
          }
        parameter_map.list_missing_parameters_and_exit("parameters not found ");
        }
      else
        {
        for(n=0; n < set.size(); n++)
          {
          Sequence_enthalpy += Sequence_info->pReduced_nn_number[n] * *(Sequence_info->ppEnthalpy[n]);
          Sequence_entropy  += Sequence_info->pReduced_nn_number[n] * *(Sequence_info->ppEntropy[n] );
          }
        }
      }

      
    inline void apply_symmetry_factors(void)
      {
      Sequence_symmetry_enthalpy=Sequence_symmetry_entropy=value_type();
      if (Sequence_info->Self_complementary) 
        {
        Sequence_symmetry_entropy  += Symmetry_correction_entropy;
        Sequence_symmetry_enthalpy += Symmetry_correction_enthalpy;
      
        CERR_DDEBUG(DINN_SYCORRENTR) << "Symmetry correction entropy = " << Symmetry_correction_entropy << std::endl;
        CERR_DDEBUG(DINN_SYCORRENTH) << "Symmetry correction enthalpy = " << Symmetry_correction_enthalpy << std::endl;
        }
      Sequence_entropy  += Sequence_symmetry_entropy;
      Sequence_enthalpy += Sequence_symmetry_enthalpy;
      }

    inline void apply_terminal_factors(void)
      {
      Sequence_terminal_enthalpy += get_terminal_parameter(Sequence_info->Terminal3_bp_reduced,std::string(":terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(Sequence_info->Terminal3_bp_reduced,std::string(":terminal_entropy"));

      Sequence_terminal_enthalpy += get_terminal_parameter(Sequence_info->Terminal5_bp_reduced,std::string(":terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(Sequence_info->Terminal5_bp_reduced,std::string(":terminal_entropy"));
      
      base_pair_type bp=Sequence_info->Terminal3_bp;
      bp.swap();
              
      Sequence_terminal_enthalpy += get_terminal_parameter(bp,std::string(":53_terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(bp,std::string(":53_terminal_entropy"));

      Sequence_terminal_enthalpy += get_terminal_parameter(Sequence_info->Terminal5_bp,std::string(":53_terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(Sequence_info->Terminal5_bp,std::string(":53_terminal_entropy"));
      
      Sequence_terminal_enthalpy += get_terminal_parameter(Sequence_info->Terminal3_NN_reduced,std::string(":terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(Sequence_info->Terminal3_NN_reduced,std::string(":terminal_entropy"));

      Sequence_terminal_enthalpy += get_terminal_parameter(Sequence_info->Terminal5_NN_reduced,std::string(":terminal_enthalpy"));
      Sequence_terminal_entropy  += get_terminal_parameter(Sequence_info->Terminal5_NN_reduced,std::string(":terminal_entropy"));

      if (Sequence_info->Terminal_5TA3)
        {
        Sequence_terminal_enthalpy += Sequence_info->Terminal_5TA3*Terminal_5TA3_enthalpy;
        if (Terminal_5TA3_enthalpy != 0) 
          CERR_DDEBUG(DINN_T5AT3ENTH) << "Terminal_5TA3_enthalpy = " << Sequence_info->Terminal_5TA3 << " X " << Terminal_5TA3_enthalpy << " = " << Sequence_info->Terminal_5TA3*Terminal_5TA3_enthalpy << std::endl;
        }
      if (Sequence_info->Terminal_AU)
        {
        Sequence_terminal_enthalpy += Sequence_info->Terminal_AU*Terminal_AU_enthalpy;
        Sequence_terminal_entropy  += Sequence_info->Terminal_AU*Terminal_AU_entropy;

        if (Terminal_AU_enthalpy != 0) 
          CERR_DDEBUG(DINN_TAUENTH) << "Terminal_AU_enthalpy = " << Sequence_info->Terminal_AU << " X " << Terminal_AU_enthalpy << " = " << Sequence_info->Terminal_AU*Terminal_AU_enthalpy << std::endl;
        
        if (Terminal_AU_entropy != 0)
          CERR_DDEBUG(DINN_TAUENTR) << "Terminal_AU_entropy = " << Sequence_info->Terminal_AU << " X " << Terminal_AU_entropy << " = "  << Sequence_info->Terminal_AU*Terminal_AU_entropy << std::endl;

        }
       if (Sequence_info->Terminal_AT)
        {
        value_type enthalpy =  Sequence_info->Terminal_AT*Terminal_AT_enthalpy;
        value_type entropy  = Sequence_info->Terminal_AT*Terminal_AT_entropy;
        Sequence_terminal_enthalpy += enthalpy;
        Sequence_terminal_entropy  += entropy;
       
        if (Terminal_AT_enthalpy != 0)
          CERR_DDEBUG(DINN_TMATENTH) << "Terminal_AT_enthalpy = " << Sequence_info->Terminal_AT << " X " << Terminal_AT_enthalpy << " = " << Sequence_info->Terminal_AT*Terminal_AT_enthalpy << std::endl;
       
        if (Terminal_AT_entropy != 0)
          CERR_DDEBUG(DINN_TMATENTR) << "Terminal_AT_entropy = " << Sequence_info->Terminal_AT << " X " << Terminal_AT_entropy << " = "  << Sequence_info->Terminal_AT*Terminal_AT_entropy << std::endl;
        
        if ( (Terminal_AT_enthalpy != 0) or (Terminal_AT_entropy != 0) )
          CERR_DDEBUG(DINN_TMATDLTG) << "Terminal_AT_DeltaG = " << gibbs_free_energy(enthalpy,entropy) << std::endl;
        }
        
        //Terminal_CG_enthalpy and Terminal_CG_entropy are added for each terminal
        if (Sequence_info->Terminal_CG)
        {
        value_type enthalpy = Sequence_info->Terminal_CG*Terminal_CG_enthalpy;
        value_type entropy  = Sequence_info->Terminal_CG*Terminal_CG_entropy;
        Sequence_terminal_enthalpy += enthalpy;
        Sequence_terminal_entropy  += entropy;

        if (Terminal_CG_enthalpy != 0)
          CERR_DDEBUG(DINN_TMCGENTH) << "Terminal_CG_enthalpy = " << Sequence_info->Terminal_CG << " X " << Terminal_CG_enthalpy << " = " << enthalpy << std::endl;
        
        if (Terminal_CG_entropy != 0)
          CERR_DDEBUG(DINN_TMCGENTR) << "Terminal_CG_entropy = " << Sequence_info->Terminal_CG << " X " << Terminal_CG_entropy << " = "  << entropy << std::endl;
        
        if ( (Terminal_CG_enthalpy != 0) or (Terminal_CG_entropy != 0) )
          CERR_DDEBUG(DINN_TMCGDLTG) << "Terminal_CG_DeltaG = " << gibbs_free_energy(enthalpy,entropy) << std::endl;
        }
        
      //Single contribution if there is either dArU or dTrA, introduced in banerjee20 doi:10.1093/nar/gkaa572
      if ((Sequence_info->Terminal_dArU+Sequence_info->Terminal_dTrA) == 2) //both ends need to have it
        {
        value_type enthalpy = Terminal_dArU_dTrA_only_enthalpy;
        value_type entropy  = Terminal_dArU_dTrA_only_entropy;
        Sequence_terminal_enthalpy += enthalpy;
        Sequence_terminal_entropy  += entropy;

        if (Terminal_dArU_dTrA_only_enthalpy != 0)
          CERR_DEBUG(DINN_TDARUDTRAONLYENTH)  << "Terminal_dArU_dTrA_only_enthalpy = "
                                              << Terminal_dArU_dTrA_only_enthalpy << " = " << enthalpy << std::endl;
        if (Terminal_dArU_dTrA_only_entropy != 0)
          CERR_DEBUG(DINN_TDARUDTRAONLYENTR) << "Terminal_dArU_dTrA_only_entropy = " 
                    << Terminal_dArU_dTrA_only_entropy << " = "  << entropy << std::endl;
        
        if ( (Terminal_dArU_dTrA_only_enthalpy != 0) or (Terminal_dArU_dTrA_only_entropy != 0) )
          CERR_DEBUG(DINN_TDARUDTRAONLYDLTG) << "Terminal_dArU_dTrA_only_DeltaG = " << gibbs_free_energy(enthalpy,entropy)  << std::endl;
        
        }

      //Single contribution if there is at least dCrG or dGrC, introduced in banerjee20 doi:10.1093/nar/gkaa572
      if ( (Sequence_info->Terminal_dCrG > 0) or (Sequence_info->Terminal_dGrC > 0) ) //at least one
        {
        value_type enthalpy = Terminal_dCrG_dGrC_enthalpy;
        value_type entropy  = Terminal_dCrG_dGrC_entropy;
        Sequence_terminal_enthalpy += enthalpy;
        Sequence_terminal_entropy  += entropy;

        if (Terminal_dCrG_dGrC_enthalpy != 0) 
          CERR_DEBUG(DINN_TDCRGDGRCENTH) << "Terminal_dCrG_dGrC_enthalpy = " 
                                       << Terminal_dCrG_dGrC_enthalpy << " = " << enthalpy << std::endl;
        if (Terminal_dCrG_dGrC_entropy != 0)
          CERR_DEBUG(DINN_TDCRGDGRCENTR) << "Terminal_dCrG_dGrC_entropy = "  
                                       << Terminal_dCrG_dGrC_entropy  << " = " << entropy << std::endl;
        if ( (Terminal_dCrG_dGrC_enthalpy != 0) or (Terminal_dCrG_dGrC_entropy != 0) )
          CERR_DEBUG(DINN_TDCRGDGRCDLTG) << "Terminal_dCrG_dGrC_DeltaG = " << gibbs_free_energy(enthalpy,entropy) << std::endl;

        }

      //End of sequence terminal analysis
      Sequence_enthalpy += Sequence_terminal_enthalpy;
      Sequence_entropy  += Sequence_terminal_entropy;
      }
    
    inline void apply_initiation_factors(void)
      {
      Sequence_initiation_enthalpy=Sequence_initiation_entropy=value_type();
      if (Sequence_info->Has_CG)    
        {
        Sequence_initiation_enthalpy += Initiation_CG_enthalpy; 
        Sequence_initiation_entropy  += Initiation_CG_entropy; 

        if (Initiation_CG_enthalpy != 0) CERR_DEBUG(DINN_INCGENTH) << " Initiation CG enthalpy= " << Initiation_CG_enthalpy << std::endl;
        if (Initiation_CG_entropy != 0)  CERR_DDEBUG(DINN_INCGENTR) << " Initiation CG entropy= " << Initiation_CG_entropy << std::endl;
        if ( (Initiation_CG_enthalpy != 0) or (Initiation_CG_entropy != 0) ) CERR_DDEBUG(DINN_INCGDELG) << " Initiation CG DeltaG= " << gibbs_free_energy(Initiation_CG_enthalpy,Initiation_CG_entropy) << std::endl;

        }  
      if (Sequence_info->Is_AT_only)   
        {
        Sequence_initiation_enthalpy += Initiation_AT_enthalpy;
        Sequence_initiation_entropy += Initiation_AT_entropy;

        if (Initiation_AT_enthalpy != 0) CERR_DDEBUG(DINN_INATENTH) << " Initiation AT enthalpy = " << Initiation_AT_enthalpy << std::endl;
          
        if (Initiation_AT_entropy != 0)  CERR_DDEBUG(DINN_INATENTR) << " Initiation AT entropy = " << Initiation_AT_entropy << std::endl;
        }
        

      if (Initiation_enthalpy != 0)
        {
        Sequence_enthalpy += Initiation_enthalpy;
        CERR_DDEBUG(DINN_INENTH) << "Initiation_enthalpy = " << Initiation_enthalpy << std::endl;
        }
      if (Initiation_entropy != 0)
        {
        Sequence_initiation_entropy += Initiation_entropy;
        CERR_DDEBUG(DINN_INENTR) << "Initiation_entropy = " << Initiation_entropy << std::endl;
        }
        
      if ( (Initiation_enthalpy != 0) or (Initiation_entropy != 0) )
        {
        CERR_DDEBUG(DINN_INDLTG) << "Initiation_DeltaG = " << gibbs_free_energy(Initiation_enthalpy,Initiation_entropy) << std::endl;
        }
        
      Sequence_enthalpy += Sequence_initiation_enthalpy;
      Sequence_entropy  += Sequence_initiation_entropy;
      }

    /// \returns Enthalpy in Kcal/mol
    /// \returns Entropy in cal/(mol.K)
    virtual inline void calculate_total_enthalpy_and_entropy(void)
      {
      Sequence_enthalpy = value_type();
      Sequence_entropy  = value_type();
      Sequence_terminal_enthalpy = value_type();
      Sequence_terminal_entropy  = value_type();
      Sequence_initiation_enthalpy = Sequence_initiation_entropy = value_type();
      Sequence_symmetry_enthalpy   = Sequence_symmetry_entropy   = value_type();
      
      CERR_DDEBUG(DINN_SIED) "Sequence " << Sequence_info->Exact_duplex << std::endl;
      
      if (empty_enthalpy_tables()) alloc_enthalpy_tables(Sequence_info->Reduced_neighbours_set);
            
      attribute_sequence_enthalpy_tables(Sequence_info->Reduced_neighbours_set);
      
      CERR_DDEBUG(DINN_PTHSG) << " Partial total (before initiation) DeltaH = " << Sequence_enthalpy << " DeltaS = " << Sequence_entropy <<
          " Delta G(37C) = " << gibbs_free_energy(Sequence_enthalpy,Sequence_entropy) << std::endl;
        
      if (NN_parameters_only) //skip all further initialization contributions
        {
        Sequence_info->enthalpy.predicted=Sequence_enthalpy;
        Sequence_info->entropy.predicted=Sequence_entropy;
        return;
        }
        
      apply_initiation_factors();        
      
      apply_terminal_factors();

      apply_symmetry_factors();
      
      Sequence_info->enthalpy.predicted=Sequence_enthalpy;
      Sequence_info->entropy.predicted=Sequence_entropy;
        
      CERR_DDEBUG(DINN_TDHDSDG) << "Total DH = " << Sequence_enthalpy << " Total DS = " << Sequence_entropy 
        << " Total DG = " << gibbs_free_energy(Sequence_enthalpy,Sequence_entropy) << std::endl;
      }

    /// \returns Enthalpy in Kcal/mol
    inline value_type total_enthalpy(void)
      {
      if (Sequence_enthalpy == value_type()) calculate_total_enthalpy_and_entropy();
      return Sequence_enthalpy;
      }

    /// \returns Entropy in cal/(mol.K)
    inline value_type total_entropy(void)
      {
      if (Sequence_entropy == value_type()) calculate_total_enthalpy_and_entropy();
      return Sequence_entropy;
      }

    inline value_type melting_temperature(void)
      {
      value_type alpha=4.0;
      if (Sequence_info->Self_complementary) alpha=1.0;
      if (Sequence_info->species_concentration == value_type())
        CERR_WARN(WINNSCIZ) << "species concentration is zero (Ct=0), temperature calculation will fail due to ln(0) calculation " << std::endl;
      
      //ATTENTION The following equation is what usually is called the "melting temperature" although strictly speaking it is the reverse processes, for a discussion
      //see Footnote 7 of Schreiber-Gosche, Sherrie, and Robert A. Edwards. "Thermodynamics of oligonucleotide duplex melting." Journal of chemical education 86.5 (2009): 644. (26/06/2020) 
      Sequence_info->temperature.predicted=total_enthalpy()*1000.0/(total_entropy()+R*log(Sequence_info->species_concentration*sequenceinfo_type::Concentration_factor/alpha))-273.15;

      if (Sequence_info->Prediction_with_salt_correction) Sequence_info->apply_salt_correction(Parameter_salt_concentration["Na+"],&parameter_map); //If Prediction_with_salt_correction==true and scheme != nocorrection
      return Sequence_info->temperature.predicted;
      }

    inline value_type RlnCt(void)
      {
      value_type alpha=4.0;
      if (Sequence_info->Self_complementary) alpha=1.0;
      if (Sequence_info->species_concentration == value_type())
        CERR_WARN(WINNSCIZ) << "species concentration is zero (Ct=0), temperature calculation will fail due to ln(0) calculation " << std::endl;
      return R*log(Sequence_info->species_concentration*sequenceinfo_type::Concentration_factor/alpha);   
      }
      
    ///< \brief Calculates the Gibbs Free Energy (in kcal/mol)
    ///
    /// If the temperature is not given we assume 37 C.
    /// If entropy and ethalpy are not given, we calculate them.
    inline value_type gibbs_free_energy(value_type temp=37.0)
      {
      value_type enth=total_enthalpy();
      value_type entr=total_entropy();
      return (enth*1000.0-(273.15+temp)*entr)/1000.0;
      }

    inline value_type gibbs_free_energy(value_type enth, value_type entr, value_type temp=37)
      {
      return (enth*1000.0-(273.15+temp)*entr)/1000.0;
      }
      
    inline void get_svd_line(const svd_deque_type &svd, matrix_type &M, value_type &b)
      {
      calculate_total_enthalpy_and_entropy();
      int N=0;
      for(auto &par : svd) if (reg_match(par,":(enthalpy|entropy)$")) N++;
      M.resize(1,N);
      int i=0;
      value_type Tm=Sequence_info->temperature.measured + 273.15;
      b = Tm*RlnCt() -(Sequence_terminal_enthalpy + Sequence_initiation_enthalpy + Sequence_symmetry_enthalpy)*1000.0 
                  +Tm*(Sequence_terminal_entropy  + Sequence_initiation_entropy  + Sequence_symmetry_entropy) ;
                  
      neighbours_ref_type RN=Sequence_info->Reduced_neighbours_set;
      for(auto &par : svd)
        {
        M(0,i)=0;
        std::string nn=reg_match_string(par,"^(.+):");
        std::string partype=reg_match_string(par,"^.+:(.+)$");
        int n=0;
         if (Sequence_info->Reduced_neighbours_set.find(nn) != Sequence_info->Reduced_neighbours_set.end())
          {
          n=Sequence_info->Reduced_neighbours_set[nn];
          RN.erase(nn);
          }
        if (partype == "enthalpy" ) M(0,i)=n;
        else 
          {
          if (partype == "entropy") M(0,i)=-n*Tm;
          else
            {
            CERR_ERROR(ERRNOVAR) << "no such parameter=" << partype << std::endl;
            CERR_TERM
            }
          }
        i++;
        }
      if (RN.size() > 0)
        {
        for(auto &rn : RN) b += -rn.second*Enthalpies[rn.first]*1000 + Tm*rn.second*Entropies[rn.first];
        }
      }
      
    };

  };

#endif
