// -*- c++ -*-

// This file is part of the Collective Variables module (Colvars).
// The original version of Colvars and its updates are located at:
// https://github.com/Colvars/colvars
// Please update all Colvars source files before making any changes.
// If you wish to distribute your changes, please submit them to the
// Colvars repository at GitHub.

#ifndef COLVARCOMP_H
#define COLVARCOMP_H

// Declaration of colvar::cvc base class and derived ones.
//
// Future cvc's could be declared on additional header files.
// After the declaration of a new derived class, its metric
// functions must be reimplemented as well.
// If the new cvc has no symmetry or periodicity,
// this can be done straightforwardly by using the macro:
// simple_scalar_dist_functions (derived_class)

#include <memory>

#include "colvarmodule.h"
#include "colvaratoms.h"
#include "colvar.h"
#include "colvar_geometricpath.h"
#include "colvaratoms.h"
#include "colvarproxy.h"
#include "colvar_gpu_calc.h"

/// \brief Colvar component (base class for collective variables)
///
/// A \link colvar::cvc \endlink object (or an object of a
/// cvc-derived class) implements the calculation of a collective
/// variable, its gradients and any other related physical quantities
/// that depend on microscopic degrees of freedom.
///
/// No restriction is set to what kind of calculation a \link colvar::cvc \endlink
/// object performs (usually an analytical function of atomic coordinates).
/// The only constraints are that: \par
///
/// - The value is calculated by the \link calc_value() \endlink
///   method, and is an object of \link colvarvalue \endlink class.  This
///   provides a transparent way to treat scalar and non-scalar variables
///   alike, and allows an automatic selection of the applicable algorithms.
///
/// - The object provides an implementation \link apply_force() \endlink to
///   apply forces to atoms.  Typically, one or more \link colvarmodule::atom_group or \link colvarmodule::atom_group
///   \endlink objects are used, but this is not a requirement for as long as
///   the \link colvar::cvc \endlink object communicates with the simulation program.
///
/// <b> If you wish to implement a new collective variable component, you
/// should write your own class by inheriting directly from \link
/// colvar::cvc \endlink, or one of its derived classes (for instance,
/// \link colvar::distance \endlink is frequently used, because it provides
/// useful data and function members for any colvar based on two
/// atom groups).</b>
///
/// The steps are: \par
/// 1. Declare the new class as a derivative of \link colvar::cvc \endlink
///    in the file \link colvarcomp.h \endlink
/// 2. Implement the new class in a file named colvarcomp_<something>.cpp
/// 3. Declare the name of the new class inside the \link colvar \endlink class
///    in \link colvar.h \endlink (see "list of available components")
/// 4. Add a call for the new class in colvar::init_components()
////   (file: colvar.cpp)
///

class colvar::cvc
  : public colvardeps
{
public:

  /// \brief The name of the object (helps to identify this
  /// cvc instance when debugging)
  std::string name;

  /// String identifier for the type of collective variable
  std::string function_type() const;

  /// Keyword used in the input to denote this CVC
  std::string config_key;

  /// \brief Coefficient in the polynomial combination (default: 1.0)
  cvm::real sup_coeff = 1.0;

  /// \brief Exponent in the polynomial combination (default: 1)
  int       sup_np = 1;

  /// \brief Period of the values of this CVC (default: 0.0, non periodic)
  cvm::real period = 0.0;

  /// \brief If the component is periodic, wrap around this value (default: 0.0)
  cvm::real wrap_center = 0.0;

  /// Constructor
  cvc();

  cvc(colvarmodule *cvmodule_in);

  /// Destructor
  virtual ~cvc();

  /// An init function should be defined for every class inheriting from cvc
  /// \param conf Contents of the configuration file pertaining to this \link
  /// cvc \endlink
  virtual int init(std::string const &conf);

  /// \brief Initialize dependency tree
  int init_dependencies() override;

  /// \brief After construction, set data related to dependency handling
  int setup();

  /// \brief Implementation of the feature list accessor for colvar
  virtual const std::vector<feature *> &features() const override
  {
    return cvc_features;
  }

  virtual std::vector<feature *> &modify_features() override
  {
    return cvc_features;
  }

  static void delete_features() {
    for (size_t i=0; i < cvc_features.size(); i++) {
      delete cvc_features[i];
    }
    cvc_features.clear();
  }

  /// \brief Get vector of vectors of atom IDs for all atom groups
  virtual std::vector<std::vector<int> > get_atom_lists();

  /// \brief Obtain data needed for the calculation for the backend
  virtual void read_data();

  /// \brief Calculate the variable
  virtual void calc_value() = 0;

  /// \brief Calculate the atomic gradients, to be reused later in
  /// order to apply forces
  virtual void calc_gradients() {}

  /// \brief Calculate the atomic fit gradients
  void calc_fit_gradients();

  /// \brief Calculate finite-difference gradients alongside the analytical ones, for each Cartesian component
  virtual void debug_gradients();

  /// \brief Calculate atomic gradients and add them to the corresponding item in gradient vector
  /// May be overridden by CVCs that do not store their gradients in the classic way, see dihedPC
  virtual void collect_gradients(std::vector<int> const &atom_ids, std::vector<cvm::rvector> &atomic_gradients);

  /// \brief Calculate the total force from the system using the
  /// inverse atomic gradients
  virtual void calc_force_invgrads();

  /// \brief Calculate the divergence of the inverse atomic gradients
  virtual void calc_Jacobian_derivative();

  // TODO: Maybe this should be a feature in colvarsdep but I am still constantly confused by colvarsdep
  /// \brief Check the GPU availability
  virtual bool has_gpu_implementation() const { return false; }

#if defined (COLVARS_CUDA) || defined (COLVARS_HIP)
  /// \brief Calculate the variable on GPU
  virtual int add_calc_value_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map)
  { return COLVARS_NOT_IMPLEMENTED; }

  /// \brief CPU-side calculation after the graph in add_calc_value_node is done on GPU
  virtual int calc_value_after_gpu() { return COLVARS_OK; }

  /// \brief Calculate the atomic gradients, to be reused later in
  /// order to apply forces on GPU
  virtual int add_calc_gradients_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map)
  { return COLVARS_NOT_IMPLEMENTED; }

  /// \brief Calculate the total force from the system using the
  /// inverse atomic gradients on GPU
  virtual int add_calc_force_invgrads_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map)
  { return COLVARS_NOT_IMPLEMENTED; }

  /// \brief CPU-side calculation after the graph in add_calc_force_invgrads_node is done on GPU
  virtual int calc_force_invgrads_after_gpu() { return COLVARS_OK; }

  /// \brief Calculate the divergence of the inverse atomic gradients on GPU
  virtual int add_calc_Jacobian_derivative_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map)
  { return COLVARS_NOT_IMPLEMENTED; }

  /// \brief CPU-side calculation after the graph in add_calc_Jacobian_derivative_node is done on GPU
  virtual int calc_Jacobian_derivative_after_gpu() { return COLVARS_OK; }

  /// \brief Calculate finite-difference gradients alongside the analytical ones, for each Cartesian component on GPU
  virtual int debug_gradients_gpu(
    colvars_gpu::colvarmodule_gpu_calc::compute_gpu_graph_t& calc_value_graph,
    colvars_gpu::colvarmodule_gpu_calc::compute_gpu_graph_t& calc_gradients_graph);
#endif // defined (COLVARS_CUDA) || defined (COLVARS_HIP)

  /// \brief Return the previously calculated value
  colvarvalue const & value() const;

  /// \brief Return the previously calculated total force
  colvarvalue const & total_force() const;

  /// \brief Return the previously calculated divergence of the
  /// inverse atomic gradients
  colvarvalue const & Jacobian_derivative() const;

  /// \brief Apply the collective variable force, by communicating the
  /// atomic forces to the simulation program (\b Note: the \link ft
  /// \endlink member is not altered by this function)
  ///
  /// Note: multiple calls to this function within the same simulation
  /// step will add the forces altogether \param cvforce The
  /// collective variable force, usually coming from the biases and
  /// eventually manipulated by the parent \link colvar \endlink
  /// object
  virtual void apply_force(colvarvalue const &cvforce);

  /// Square distance between x1 and x2 (can be redefined to transparently
  /// implement metrics in multi-dimensional spaces with or without
  /// constraints, symmetries and periodicities).  The default implementation
  /// assumes scalar numbers and no symmetries or periodicities.
  ///
  /// If symmetries or periodicities are present, the
  /// colvar::cvc::dist2() should be redefined to return the
  /// "closest distance" value and colvar::cvc::dist2_lgrad(),
  /// colvar::cvc::dist2_rgrad() to return its gradients.
  ///
  /// If constraints are present (and not already implemented by any
  /// of the \link colvarvalue \endlink types), the
  /// colvar::cvc::dist2_lgrad() and
  /// colvar::cvc::dist2_rgrad() functions should be redefined
  /// to provide a gradient which is compatible with the constraint,
  /// i.e. already deprived of its component normal to the constraint
  /// hypersurface.
  ///
  virtual cvm::real dist2(colvarvalue const &x1,
                          colvarvalue const &x2) const;

  /// \brief Gradient(with respect to x1) of the square distance (can
  /// be redefined to transparently implement constraints, symmetries
  /// and periodicities)
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;

  /// \brief Gradient(with respect to x2) of the square distance (can
  /// be redefined to transparently implement constraints, symmetries
  /// and periodicities)
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;

  /// \brief Wrap value (for periodic/symmetric cvcs)
  virtual void wrap(colvarvalue &x_unwrapped) const;

  /// \brief Pointers to all atom groups, to let colvars collect info
  /// e.g. atomic gradients
  std::vector<cvm::atom_group *> atom_groups;

  /// \brief Store a pointer to new atom group, and list as child for dependencies
  void register_atom_group(cvm::atom_group *ag);

  /// Pointer to the gradient of parameter param_name
  virtual colvarvalue const *get_param_grad(std::string const &param_name);

  /// Set the named parameter to the given value
  virtual int set_param(std::string const &param_name, void const *new_value) override;

  /// \brief Whether or not this CVC will be computed in parallel whenever possible
  bool b_try_scalable = true;

  /// Forcibly set value of CVC - useful for driving an external coordinate,
  /// eg. lambda dynamics
  inline void set_value(colvarvalue const &new_value, bool now=false) {
    x = new_value;
    // Cache value to be communicated to back-end between time steps
    cvmodule->proxy->set_alch_lambda(x.real_value);
    if (now) {
      // If requested (e.g. upon restarting), sync to back-end
      cvmodule->proxy->send_alch_lambda();
    }
  }

protected:

  /// Set the value of \link function_type \endlink and its dependencies
  int set_function_type(std::string const &type);

  /// Update the description string based on name and type
  int update_description();

  /// Parse a group definition
  cvm::atom_group *parse_group(std::string const &conf, char const *group_key,
                               bool optional = false);

  /// \brief Parse options pertaining to total force calculation
  virtual int init_total_force_params(std::string const &conf);

  /// \brief Implementation of the feature list for colvar
  static std::vector<feature *> cvc_features;

  /// Record the type of this class as well as those it is derived from
  std::vector<std::string> function_types;

  /// \brief Cached value
  colvarvalue x;

  /// \brief Value at the previous step
  colvarvalue x_old;

  /// \brief Calculated total force (\b Note: this is calculated from
  /// the total atomic forces read from the program, subtracting fromt
  /// the "internal" forces of the system the "external" forces from
  /// the colvar biases)
  colvarvalue ft;

  /// \brief Calculated Jacobian derivative (divergence of the inverse
  /// gradients): serves to calculate the phase space correction
  colvarvalue jd;

  /// \brief Set data types for a scalar distance (convenience function)
  void init_as_distance();

  /// \brief Set data types for a bounded angle (0° to 180°)
  void init_as_angle();

  /// \brief Set data types for a periodic angle (-180° to 180°)
  void init_as_periodic_angle();

  /// \brief Set two scalar boundaries (convenience function)
  void init_scalar_boundaries(cvm::real lb, cvm::real ub);

  /// \brief Location of the lower boundary (not defined by user choice)
  colvarvalue lower_boundary;

  /// \brief Location of the upper boundary (not defined by user choice)
  colvarvalue upper_boundary;

  /// \brief CVC-specific default colvar width (default: not provided)
  cvm::real width = 0.0;
};


inline colvarvalue const & colvar::cvc::value() const
{
  return x;
}


inline colvarvalue const & colvar::cvc::total_force() const
{
  return ft;
}


inline colvarvalue const & colvar::cvc::Jacobian_derivative() const
{
  return jd;
}



/// \brief Colvar component: distance between the centers of mass of
/// two groups (colvarvalue::type_scalar type, range [0:*))

class colvar::distance
  : public colvar::cvc
{
protected:
  /// First atom group
  cvm::atom_group  *group1 = nullptr;
  /// Second atom group
  cvm::atom_group  *group2 = nullptr;
  /// Vector distance, cached to be recycled
  cvm::rvector     dist_v;
public:
  distance();
  virtual ~distance() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};



// \brief Colvar component: distance vector between centers of mass
// of two groups (\link colvarvalue::type_3vector \endlink type,
// range (-*:*)x(-*:*)x(-*:*))
class colvar::distance_vec
  : public colvar::distance
{
public:
  distance_vec();
  virtual ~distance_vec() {}
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to deal with multiple dimensions
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


/// \brief Colvar component: distance unit vector (direction) between
/// centers of mass of two groups (colvarvalue::type_unit3vector type,
/// range [-1:1]x[-1:1]x[-1:1])
class colvar::distance_dir
  : public colvar::distance
{
public:
  distance_dir();
  virtual ~distance_dir() {}
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to deal with multiple dimensions
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};



/// \brief Colvar component: projection of the distance vector along
/// an axis(colvarvalue::type_scalar type, range (-*:*))
class colvar::distance_z
  : public colvar::cvc
{
protected:
  /// Main atom group
  cvm::atom_group  *main = nullptr;
  /// Reference atom group
  cvm::atom_group  *ref1 = nullptr;
  /// Optional, second ref atom group
  cvm::atom_group  *ref2 = nullptr;
  /// Vector on which the distance vector is projected
  cvm::rvector axis;
  /// Norm of the axis
  cvm::real axis_norm;
  /// Vector distance, cached to be recycled
  cvm::rvector     dist_v;
  /// Flag: using a fixed axis vector?
  bool fixed_axis = true;
public:
  distance_z();
  virtual ~distance_z() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};



/// \brief Colvar component: projection of the distance vector on a
/// plane (colvarvalue::type_scalar type, range [0:*))
class colvar::distance_xy
  : public colvar::distance_z
{
protected:
  /// Components of the distance vector orthogonal to the axis
  cvm::rvector dist_v_ortho;
  /// Vector distances
  cvm::rvector v12, v13;
public:
  distance_xy();
  virtual ~distance_xy() {}
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};


/// \brief Colvar component: polar coordinate phi of a group
/// (colvarvalue::type_scalar type, range [-180:180])
class colvar::polar_phi
  : public colvar::cvc
{
protected:
  cvm::atom_group *atoms = nullptr;
  cvm::real r, theta, phi;

public:
  polar_phi();
  virtual ~polar_phi() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};


/// \brief Colvar component: polar coordinate theta of a group
/// (colvarvalue::type_scalar type, range [0:180])
class colvar::polar_theta
  : public colvar::cvc
{
public:
  polar_theta();
  virtual ~polar_theta() {}
  virtual int init(std::string const &conf);
protected:
  cvm::atom_group *atoms = nullptr;
  cvm::real r, theta, phi;
public:
  virtual void calc_value();
  virtual void calc_gradients();
};


/// \brief Colvar component: average distance between two groups of atoms, weighted as the sixth power,
/// as in NMR refinements(colvarvalue::type_scalar type, range (0:*))
class colvar::distance_inv
  : public colvar::cvc
{
protected:
  /// First atom group
  cvm::atom_group  *group1 = nullptr;
  /// Second atom group
  cvm::atom_group  *group2 = nullptr;
  /// Components of the distance vector orthogonal to the axis
  int exponent = 6;
public:
  distance_inv();
  virtual ~distance_inv() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: N1xN2 vector of pairwise distances
/// (colvarvalue::type_vector type, range (0:*) for each component)
class colvar::distance_pairs
  : public colvar::cvc
{
protected:
  /// First atom group
  cvm::atom_group  *group1 = nullptr;
  /// Second atom group
  cvm::atom_group  *group2 = nullptr;
public:
  distance_pairs();
  virtual ~distance_pairs() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to deal with multiple dimensions
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


/// \brief Colvar component:  dipole magnitude of a molecule
class colvar::dipole_magnitude
  : public colvar::cvc
{
protected:
  /// Dipole atom group
  cvm::atom_group *atoms = nullptr;
  cvm::atom_pos dipoleV;
public:
  dipole_magnitude();
  virtual ~dipole_magnitude() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: Radius of gyration of an atom group
/// (colvarvalue::type_scalar type, range [0:*))
class colvar::gyration
  : public colvar::cvc
{
protected:
  /// Atoms involved
  cvm::atom_group *atoms = nullptr;
public:
  gyration();
  virtual ~gyration() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};



/// \brief Colvar component: moment of inertia of an atom group
/// (colvarvalue::type_scalar type, range [0:*))
class colvar::inertia
  : public colvar::gyration
{
public:
  inertia();
  virtual ~inertia() {}
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: moment of inertia of an atom group
/// around a user-defined axis (colvarvalue::type_scalar type, range [0:*))
class colvar::inertia_z
  : public colvar::inertia
{
protected:
  /// Vector on which the inertia tensor is projected
  cvm::rvector axis;
public:
  inertia_z();
  virtual ~inertia_z() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: projection of 3N coordinates onto an
/// eigenvector(colvarvalue::type_scalar type, range (-*:*))
class colvar::eigenvector
  : public colvar::cvc
{
protected:

  /// Atom group
  cvm::atom_group *atoms = nullptr;

  /// Reference coordinates
  std::vector<cvm::atom_pos>  ref_pos;

  /// Eigenvector (of a normal or essential mode): will always have zero center
  std::vector<cvm::rvector>   eigenvec;

  /// Inverse square norm of the eigenvector
  cvm::real                   eigenvec_invnorm2;

public:

  eigenvector();
  virtual ~eigenvector() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};



/// \brief Colvar component: angle between the centers of mass of
/// three groups (colvarvalue::type_scalar type, range [0:PI])
class colvar::angle
  : public colvar::cvc
{
protected:
  /// Atom group
  cvm::atom_group  *group1 = nullptr;
  /// Atom group
  cvm::atom_group  *group2 = nullptr;
  /// Atom group
  cvm::atom_group  *group3 = nullptr;
  /// Inter site vectors
  cvm::rvector r21, r23;
  /// Inter site vector norms
  cvm::real r21l, r23l;
  /// Derivatives wrt group centers of mass
  cvm::rvector dxdr1, dxdr3;

  /// Compute total force on first site only to avoid unwanted
  /// coupling to other colvars (see e.g. Ciccotti et al., 2005)
  /// (or to allow dummy atoms)
  bool b_1site_force = false;
public:

  angle();
  /// \brief Initialize the three groups after three atoms
  angle(cvm::atom_group::simple_atom const &a1,
        cvm::atom_group::simple_atom const &a2,
        cvm::atom_group::simple_atom const &a3);
  virtual ~angle() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};



/// \brief Colvar component: angle between the dipole of a molecule and an axis
/// formed by two groups of atoms(colvarvalue::type_scalar type, range [0:PI])
class colvar::dipole_angle
  : public colvar::cvc
{
protected:

  /// Atom group
  cvm::atom_group  *group1 = nullptr;
  /// Atom group
  cvm::atom_group  *group2 = nullptr;
  /// Atom group
  cvm::atom_group  *group3 = nullptr;
  /// Inter site vectors
  cvm::rvector r21, r23;
  /// Inter site vector norms
  cvm::real r21l, r23l;
  /// Derivatives wrt group centers of mass
  cvm::rvector dxdr1, dxdr3;

  /// Compute total force on first site only to avoid unwanted
  /// coupling to other colvars (see e.g. Ciccotti et al., 2005)
  /// (or to allow dummy atoms)
  bool b_1site_force = false;
public:

  dipole_angle();
  virtual ~dipole_angle() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: dihedral between the centers of mass of
/// four groups (colvarvalue::type_scalar type, range [-PI:PI])
class colvar::dihedral
  : public colvar::cvc
{
protected:

  /// Atom group
  cvm::atom_group  *group1 = nullptr;
  /// Atom group
  cvm::atom_group  *group2 = nullptr;
  /// Atom group
  cvm::atom_group  *group3 = nullptr;
  /// Atom group
  cvm::atom_group  *group4 = nullptr;
  /// Inter site vectors
  cvm::rvector r12, r23, r34;

  /// \brief Compute total force on first site only to avoid unwanted
  /// coupling to other colvars (see e.g. Ciccotti et al., 2005)
  bool b_1site_force = false;

public:

  /// \brief Initialize the four groups after four atoms
  dihedral(cvm::atom_group::simple_atom const &a1,
           cvm::atom_group::simple_atom const &a2,
           cvm::atom_group::simple_atom const &a3,
           cvm::atom_group::simple_atom const &a4);
  dihedral();
  virtual ~dihedral() {}
  virtual int init(std::string  const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
};


/// \brief Colvar component: alpha helix content of a contiguous
/// segment of 5 or more residues, implemented as a sum of Ca-Ca-Ca
/// angles and hydrogen bonds (colvarvalue::type_scalar type, range
/// [0:1])
class colvar::alpha_angles
  : public colvar::cvc
{
protected:

  /// Reference Calpha-Calpha angle (default: 88 degrees)
  cvm::real theta_ref = 88.0;

  /// Tolerance on the Calpha-Calpha angle
  cvm::real theta_tol = 15.0;

  /// List of Calpha-Calpha angles
  std::vector<angle *> theta;

  /// List of hydrogen bonds
  std::vector<h_bond *>   hb;

  /// Contribution of the HB terms
  cvm::real hb_coeff = 0.5;

  /// Cutoff for HB
  cvm::real r0;

  /// Integer exponent of the HB numerator
  int en = 6;

  /// Integer exponent of the HB denominator
  int ed = 8;

public:

  alpha_angles();
  virtual ~alpha_angles();
  virtual int init(std::string const &conf);
  void calc_value();
  void calc_gradients();
  /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs
  void collect_gradients(std::vector<int> const &atom_ids, std::vector<cvm::rvector> &atomic_gradients);
  void apply_force(colvarvalue const &force);
};



/// \brief Colvar component: dihedPC
/// Projection of the config onto a dihedral principal component
/// See e.g. Altis et al., J. Chem. Phys 126, 244111 (2007)
/// Based on a set of 'dihedral' cvcs
class colvar::dihedPC
  : public colvar::cvc
{
protected:

  std::vector<dihedral *> theta;
  std::vector<cvm::real> coeffs;

public:

  dihedPC();
  virtual ~dihedPC();
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  /// Re-implementation of cvc::collect_gradients() to carry over atomic gradients of sub-cvcs
  virtual void collect_gradients(std::vector<int> const &atom_ids, std::vector<cvm::rvector> &atomic_gradients);
  virtual void apply_force(colvarvalue const &force);
};



/// \brief Colvar component: orientation in space of an atom group,
/// with respect to a set of reference coordinates
/// (colvarvalue::type_quaternion type, range
/// [-1:1]x[-1:1]x[-1:1]x[-1:1])
class colvar::orientation
  : public colvar::cvc
{
protected:
  /// Atom group
  cvm::atom_group  *          atoms = nullptr;
  /// Center of geometry of the group
  cvm::atom_pos              atoms_cog;

  /// Reference coordinates
  cvm::ag_vector_real_t ref_pos_soa;
  size_t num_ref_pos;

  /// Shifted atomic positions
  cvm::ag_vector_real_t shifted_pos_soa;
  size_t num_shifted_pos;

  /// Rotation object
  cvm::rotation              rot;

  /// \brief This is used to remove jumps in the sign of the
  /// quaternion, which may be annoying in the colvars trajectory
  cvm::quaternion            ref_quat;

  /// Rotation derivative
  struct rotation_derivative_impl_;
  std::unique_ptr<rotation_derivative_impl_> rot_deriv_impl;

public:

  orientation();
  virtual ~orientation();
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to use quaternion metrics
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use quaternion metrics
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use quaternion metrics
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use quaternion metrics
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


/// Colvar component: angle of rotation with respect to a set of reference coordinates
/// (colvarvalue::type_scalar type, range [0:PI))
/// This is also used to derive all other sub-rotation variables that return a scalar value
class colvar::orientation_angle
  : public colvar::orientation
{
public:

  orientation_angle();
  virtual ~orientation_angle() {}
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to use scalar metrics
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use scalar metrics
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use scalar metrics
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use scalar metrics
  virtual void wrap(colvarvalue &x_unwrapped) const;
};



/// \brief Colvar component: cosine of the angle of rotation with respect to a set
/// of reference coordinates (colvarvalue::type_scalar type, range
/// [-1:1])
class colvar::orientation_proj
  : public colvar::orientation_angle
{
public:

  orientation_proj();
  virtual ~orientation_proj() {}
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: projection of the orientation vector onto
/// a predefined axis (colvarvalue::type_scalar type, range [-1:1])
class colvar::tilt
  : public colvar::orientation_proj
{
protected:

  cvm::rvector axis;

public:

  tilt();
  virtual ~tilt() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
};



/// \brief Colvar component: angle of rotation around a predefined
/// axis (colvarvalue::type_scalar type, range [-PI:PI])
class colvar::spin_angle
  : public colvar::tilt
{
public:

  spin_angle();
  virtual ~spin_angle() {}
  virtual void calc_value();
  virtual void calc_gradients();
};


class colvar::euler_phi
  : public colvar::orientation_angle
{
public:
  euler_phi();
  virtual ~euler_phi() {}
  virtual void calc_value();
  virtual void calc_gradients();
};


class colvar::euler_psi
  : public colvar::orientation_angle
{
public:
  euler_psi();
  virtual ~euler_psi() {}
  virtual void calc_value();
  virtual void calc_gradients();
};


class colvar::euler_theta
  : public colvar::orientation_angle
{
public:
  euler_theta();
  virtual ~euler_theta() {}
  virtual void calc_value();
  virtual void calc_gradients();
};


/// \brief Colvar component: root mean square deviation (RMSD) of a
/// group with respect to a set of reference coordinates; uses \link
/// colvar::orientation \endlink to calculate the rotation matrix
/// (colvarvalue::type_scalar type, range [0:*))
class colvar::rmsd
  : public colvar::cvc
{
protected:

  // TODO: transfrom ref_pos to soa
  /// Atom group
  cvm::atom_group  *          atoms = nullptr;

  /// Reference coordinates (for RMSD calculation only)
  /// Includes sets with symmetry permutations (n_permutations * n_atoms)
  size_t num_ref_pos = 0;
  cvm::ag_vector_real_t  ref_pos_soa;

#if defined (COLVARS_CUDA) || defined (COLVARS_HIP)
  cvm::real* d_ref_pos_soa;
  cvm::real* d_permutation_msds;
  unsigned int* d_tbcounts;
  cvm::real* h_rmsd;
  size_t* h_best_perm_index;
  cvm::real* d_ft;
  cvm::real* h_ft;
  cvm::real* d_jd;
  cvm::real* h_jd;
  unsigned int* d_tbcount_ft;
  unsigned int* d_tbcount_jd;
#endif // defined (COLVARS_CUDA) || defined (COLVARS_HIP)

  /// Number of permutations of symmetry-related atoms
  size_t n_permutations = 1;
  cvm::ag_vector_real_t permutation_msds;

  /// Index of the permutation yielding the smallest RMSD (0 for identity)
  size_t best_perm_index = 0;

  /// Permutation RMSD input parsing
  int init_permutation(std::vector<cvm::atom_pos>& ref_pos, std::string const &conf);

public:
  rmsd();
  bool has_gpu_implementation() const override;
#if defined (COLVARS_CUDA) || defined (COLVARS_HIP)
  int add_calc_value_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map) override;
  int calc_value_after_gpu() override;
  int add_calc_gradients_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map) override;
  int add_calc_force_invgrads_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map) override;
  int calc_force_invgrads_after_gpu() override;
  int add_calc_Jacobian_derivative_node(
    cudaGraph_t& graph,
    std::unordered_map<std::string, cudaGraphNode_t>& nodes_map) override;
  int calc_Jacobian_derivative_after_gpu() override;
#endif // defined (COLVARS_CUDA) || defined (COLVARS_HIP)
  virtual ~rmsd();
  virtual int init(std::string const &conf) override;
  virtual void calc_value() override;
  virtual void calc_gradients() override;
  virtual void calc_force_invgrads() override;
  virtual void calc_Jacobian_derivative() override;
};



// \brief Colvar component: flat vector of Cartesian coordinates
// Mostly useful to compute scripted colvar values
class colvar::cartesian
  : public colvar::cvc
{
protected:
  /// Atom group
  cvm::atom_group  *          atoms = nullptr;
  /// Which Cartesian coordinates to include
  std::vector<size_t> axes;
public:
  cartesian();
  virtual ~cartesian() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
  /// Redefined to deal with multiple dimensions
  virtual cvm::real dist2(colvarvalue const &x1,
                          colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to deal with multiple dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


// \brief Colvar component: alch_lambda
// To communicate value with back-end in lambda-dynamics
class colvar::alch_lambda
  : public colvar::cvc
{
protected:
  // No atom groups needed
public:
  alch_lambda();
  int init_alchemy(int time_step_factor);
  virtual ~alch_lambda() {}
  virtual void calc_value();
  virtual void calc_force_invgrads();
  virtual void calc_Jacobian_derivative();
  virtual void apply_force(colvarvalue const &force);
};


// \brief Colvar component: alch_Flambda
// To communicate force on lambda with back-end in lambda-dynamics
class colvar::alch_Flambda
  : public colvar::cvc
{
protected:
  // No atom groups needed
public:
  alch_Flambda();
  virtual ~alch_Flambda() {}
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);
};


class colvar::CartesianBasedPath
  : public colvar::cvc
{
protected:
    virtual void computeDistanceBetweenReferenceFrames(std::vector<cvm::real>& result);
    virtual void computeDistanceToReferenceFrames(std::vector<cvm::real>& result);
    /// Selected atoms
    cvm::atom_group *atoms = nullptr;
    /// Fitting options
    bool has_user_defined_fitting = false;
    /// Reference frames
    std::vector<std::vector<cvm::atom_pos>> reference_frames;
    std::vector<std::vector<cvm::atom_pos>> reference_fitting_frames;
    /// Atom groups for RMSD calculation together with reference frames
    std::vector<cvm::atom_group*> comp_atoms;
    /// Total number of reference frames
    size_t total_reference_frames = 0;
public:
    CartesianBasedPath();
    virtual ~CartesianBasedPath();
    virtual int init(std::string const &conf);
    virtual void calc_value() = 0;
    /// Redefined to raise error because this is an abstract type
    virtual void apply_force(colvarvalue const &force);
};

/// \brief Colvar component: alternative path collective variable using geometry, variable s
/// For more information see https://plumed.github.io/doc-v2.5/user-doc/html/_p_a_t_h.html
/// Diaz Leines, G.; Ensing, B. Path Finding on High-Dimensional Free Energy Landscapes. Phys. Rev. Lett. 2012, 109 (2), 020601. https://doi.org/10.1103/PhysRevLett.109.020601.
class colvar::gspath
  : public colvar::CartesianBasedPath, public GeometricPathCV::GeometricPathBase<cvm::atom_pos, cvm::real, GeometricPathCV::path_sz::S>
{
private:
    // Optimal rotation for compute v3
    cvm::rotation rot_v3;
protected:
    virtual void prepareVectors();
    virtual void updateDistanceToReferenceFrames();
public:
    gspath();
    virtual ~gspath() {}
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};



/// \brief Colvar component: alternative path collective variable using geometry, variable z
/// This should be merged with gspath in the same class by class inheritance or something else
class colvar::gzpath
  : public colvar::CartesianBasedPath, public GeometricPathCV::GeometricPathBase<cvm::atom_pos, cvm::real, GeometricPathCV::path_sz::Z>
{
private:
    // Optimal rotation for compute v3, v4
    cvm::rotation rot_v3;
    cvm::rotation rot_v4;
protected:
    virtual void prepareVectors();
    virtual void updateDistanceToReferenceFrames();
public:
    gzpath();
    virtual ~gzpath() {}
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};

/// Current only linear combination of sub-CVCs is available
class colvar::linearCombination
  : public colvar::cvc
{
protected:
    /// Sub-colvar components
    std::vector<colvar::cvc*> cv;
    /// If all sub-cvs use explicit gradients then we also use it
    bool use_explicit_gradients;
protected:
    cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const;
public:
    linearCombination();
    virtual ~linearCombination();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
  /// Redefined to allow arbitrary dimensions
  virtual cvm::real dist2(colvarvalue const &x1,
                          colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


/// custom expression of colvars
class colvar::customColvar
  : public colvar::linearCombination
{
protected:
    bool use_custom_function = false;
#ifdef LEPTON
    /// Vector of evaluators for custom functions using Lepton
    std::vector<Lepton::CompiledExpression *> value_evaluators;
    /// Vector of evaluators for gradients of custom functions
    std::vector<Lepton::CompiledExpression *> gradient_evaluators;
    /// Vector of references to cvc values to be passed to Lepton evaluators
    std::vector<double *> value_eval_var_refs;
    std::vector<double *> grad_eval_var_refs;
    /// Unused value that is written to when a variable simplifies out of a Lepton expression
    double dev_null = 0.0;
#endif
public:
    customColvar();
    virtual ~customColvar();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};


class colvar::CVBasedPath
  : public colvar::cvc
{
protected:
    /// Sub-colvar components
    std::vector<colvar::cvc*> cv;
    /// Reference colvar values from path
    std::vector<std::vector<colvarvalue>> ref_cv;
    /// If all sub-cvs use explicit gradients then we also use it
    bool use_explicit_gradients;
    /// Total number of reference frames
    size_t total_reference_frames = 0;
protected:
    virtual void computeDistanceToReferenceFrames(std::vector<cvm::real>& result);
    /// Helper function to determine the distance between reference frames
    virtual void computeDistanceBetweenReferenceFrames(std::vector<cvm::real>& result) const;
    cvm::real getPolynomialFactorOfCVGradient(size_t i_cv) const;
public:
    CVBasedPath();
    virtual ~CVBasedPath();
    virtual int init(std::string const &conf);
    virtual void calc_value() = 0;
    /// Redefined to raise error because this is an abstract type
    virtual void apply_force(colvarvalue const &force);
  /// Redefined to use the metric of the returned colvarvalue (defined at runtime)
  virtual cvm::real dist2(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use the metric of the returned colvarvalue (defined at runtime)
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use the metric of the returned colvarvalue (defined at runtime)
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1, colvarvalue const &x2) const;
  /// Redefined to use the metric of the returned colvarvalue (defined at runtime)
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


/// \brief Colvar component: alternative path collective variable using geometry, variable s
/// Allow any combination of existing (scalar) CVs
/// For more information see https://plumed.github.io/doc-v2.5/user-doc/html/_p_a_t_h.html
/// Diaz Leines, G.; Ensing, B. Path Finding on High-Dimensional Free Energy Landscapes. Phys. Rev. Lett. 2012, 109 (2), 020601. https://doi.org/10.1103/PhysRevLett.109.020601.
class colvar::gspathCV
  : public colvar::CVBasedPath, public GeometricPathCV::GeometricPathBase<colvarvalue, cvm::real, GeometricPathCV::path_sz::S>
{
protected:
    virtual void updateDistanceToReferenceFrames();
    virtual void prepareVectors();
public:
    gspathCV();
    virtual ~gspathCV();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};



class colvar::gzpathCV
  : public colvar::CVBasedPath, public GeometricPathCV::GeometricPathBase<colvarvalue, cvm::real, GeometricPathCV::path_sz::Z>
{
protected:
    virtual void updateDistanceToReferenceFrames();
    virtual void prepareVectors();
public:
    gzpathCV();
    virtual ~gzpathCV();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};

struct ArithmeticPathImpl;

class colvar::aspath
  : public colvar::CartesianBasedPath
{
private:
    std::unique_ptr<ArithmeticPathImpl> impl_;
    friend struct ArithmeticPathImpl;
public:
    aspath();
    virtual ~aspath();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};

class colvar::azpath
  : public colvar::CartesianBasedPath
{
private:
    std::unique_ptr<ArithmeticPathImpl> impl_;
    friend struct ArithmeticPathImpl;
public:
    azpath();
    virtual ~azpath();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};

class colvar::aspathCV
  : public colvar::CVBasedPath
{
private:
    std::unique_ptr<ArithmeticPathImpl> impl_;
    friend struct ArithmeticPathImpl;
public:
    aspathCV();
    virtual ~aspathCV();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};


class colvar::azpathCV
  : public colvar::CVBasedPath
{
private:
    std::unique_ptr<ArithmeticPathImpl> impl_;
    friend struct ArithmeticPathImpl;
public:
    azpathCV();
    virtual ~azpathCV();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
};

// forward declaration
namespace neuralnetworkCV {
    class neuralNetworkCompute;
}


class colvar::neuralNetwork
  : public linearCombination
{
protected:
    /// actual computation happens in neuralnetworkCV::neuralNetworkCompute
    std::unique_ptr<neuralnetworkCV::neuralNetworkCompute> nn;
    /// the index of nn output components
    size_t m_output_index = 0;
public:
    neuralNetwork();
    virtual ~neuralNetwork();
    virtual int init(std::string const &conf);
    virtual void calc_value();
    virtual void calc_gradients();
    virtual void apply_force(colvarvalue const &force);
  /// Redefined to allow arbitrary dimensions
  virtual cvm::real dist2(colvarvalue const &x1,
                          colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual colvarvalue dist2_lgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual colvarvalue dist2_rgrad(colvarvalue const &x1,
                                  colvarvalue const &x2) const;
  /// Redefined to allow arbitrary dimensions
  virtual void wrap(colvarvalue &x_unwrapped) const;
};


// \brief Colvar component: total value of a scalar map
// (usually implemented as a grid by the simulation engine)
class colvar::map_total
  : public colvar::cvc
{
public:

  map_total();
  virtual ~map_total() {}
  virtual int init(std::string const &conf);
  virtual void calc_value();
  virtual void calc_gradients();
  virtual void apply_force(colvarvalue const &force);

protected:

  /// String identifier of the map object (as used by the simulation engine)
  std::string volmap_name;

  /// Numeric identifier of the map object (as used by the simulation engine)
  int volmap_id = -1;

  /// Index of the map objet in the proxy arrays
  int volmap_index = -1;

  /// Group of atoms selected internally (optional)
  cvm::atom_group *atoms = nullptr;

  /// Weights assigned to each atom (default: uniform weights)
  std::vector<cvm::real> atom_weights;
};



#endif
