faunus
energy.h
1 #pragma once
2 
3 #include "bonds.h"
4 #include "externalpotential.h" // Energybase implemented here
5 #include "potentials_base.h"
6 #include "sasa.h"
7 #include "space.h"
8 #include "aux/eigensupport.h"
9 #include "aux/pairmatrix.h"
10 #include "smart_montecarlo.h"
11 #include <range/v3/range/conversion.hpp>
12 #include <spdlog/spdlog.h>
13 #include <numeric>
14 #include <algorithm>
15 #include <concepts>
16 
17 struct freesasa_parameters_fwd; // workaround for freesasa unnamed struct that cannot be forward
18  // declared
19 
20 #if defined(__cpp_lib_parallel_algorithm) && __has_include(<tbb/tbb.h>)
21 #include <execution>
22 #endif
23 
24 #if defined(__cpp_lib_parallel_algorithm) && \
25  __has_include(<tbb/tbb.h>) && ((defined(__clang__) && __clang_major__ >= 10) || (defined(__GNUC__) && __GNUC__ >= 10))
26 #define HAS_PARALLEL_TRANSFORM_REDUCE
27 #endif
28 
29 namespace Faunus {
30 
31 namespace ReactionCoordinate {
32 class ReactionCoordinateBase;
33 }
34 
35 namespace pairpotential {
36 class PairPotential;
37 }
38 
59 namespace Energy {
60 
61 class Hamiltonian;
62 
70 {
71  private:
72  const Space& spc;
73  bool groupIsOutsideContainer(const Change::GroupChange& group_change) const;
74  double energyOfAllGroups() const;
75 
76  public:
77  explicit ContainerOverlap(const Space& spc);
78  double energy(const Change& change) override;
79 };
80 
92 struct EwaldData
93 {
94  using Tcomplex = std::complex<double>;
95  Eigen::Matrix3Xd k_vectors;
96  Eigen::VectorXd Aks;
97  Eigen::VectorXcd Q_ion, Q_dipole;
98  double r_cutoff = 0;
99  double n_cutoff = 0;
100  double surface_dielectric_constant = 0;
101  double bjerrum_length = 0;
102  double kappa = 0;
103  double kappa_squared = 0;
104  double alpha = 0;
105  double const_inf = 0;
106  double check_k2_zero = 0;
107  bool use_spherical_sum = true;
108  int num_kvectors = 0;
109  Point box_length = {0.0, 0.0, 0.0};
110 
111  enum Policies
112  {
113  PBC,
114  PBCEigen,
115  IPBC,
116  IPBCEigen,
117  INVALID
118  };
119 
120  Policies policy = PBC;
121  explicit EwaldData(const json& j);
122 };
123 
124 NLOHMANN_JSON_SERIALIZE_ENUM(EwaldData::Policies, {
125  {EwaldData::INVALID, nullptr},
126  {EwaldData::PBC, "PBC"},
127  {EwaldData::PBCEigen, "PBCEigen"},
128  {EwaldData::IPBC, "IPBC"},
129  {EwaldData::IPBCEigen, "IPBCEigen"},
130  })
131 
132 void to_json(json& j, const EwaldData& d);
133 
138 {
139  public:
140  std::string cite;
141  virtual ~EwaldPolicyBase() = default;
142  virtual void
143  updateBox(EwaldData&,
144  const Point&) const = 0;
145  virtual void
146  updateComplex(EwaldData& d,
147  const Space::GroupVector& groups) const = 0;
148  virtual void updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups,
149  const Space::GroupVector& oldgroups)
150  const = 0;
151  virtual double
152  selfEnergy(const EwaldData& d, Change& change,
153  Space::GroupVector& groups) = 0;
154  virtual double surfaceEnergy(
155  const EwaldData& d, const Change& change,
156  const Space::GroupVector& groups) = 0;
157  virtual double reciprocalEnergy(const EwaldData& d) = 0;
158 
167  static auto mapGroupsToEigen(Space::GroupVector& groups)
168  {
169  auto is_partially_inactive = [](const Group& group) {
170  return group.size() != group.capacity();
171  };
172  if (std::ranges::any_of(groups, is_partially_inactive)) {
173  throw std::runtime_error("Eigen optimized Ewald not available with inactive groups");
174  }
175  auto first_particle = groups.front().begin();
176  auto last_particle = groups.back().end();
177  auto pos = asEigenMatrix(first_particle, last_particle,
178  &Particle::pos); // N x 3
179  auto charge = asEigenVector(first_particle, last_particle,
180  &Particle::charge); // N x 1
181  return std::make_tuple(pos, charge);
182  }
183 
184  static auto mapGroupsToEigen(const Space::GroupVector& groups)
185  {
186  auto is_partially_inactive = [](const Group& group) {
187  return group.size() != group.capacity();
188  };
189  if (std::ranges::any_of(groups, is_partially_inactive)) {
190  throw std::runtime_error("Eigen optimized Ewald not available with inactive groups");
191  }
192  auto first_particle = groups.front().begin();
193  auto last_particle = groups.back().end();
194  auto pos = asEigenMatrix(first_particle, last_particle,
195  &Particle::pos); // N x 3
196  auto charge = asEigenVector(first_particle, last_particle,
197  &Particle::charge); // N x 1
198  return std::make_tuple(pos, charge);
199  }
200 
201  static std::unique_ptr<EwaldPolicyBase> makePolicy(EwaldData::Policies);
202 };
203 
208 {
209  PolicyIonIon();
210  void updateBox(EwaldData& d, const Point& box) const override;
211  void updateComplex(EwaldData& data, const Space::GroupVector& groups) const override;
212  void updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups,
213  const Space::GroupVector& oldgroups) const override;
214  double selfEnergy(const EwaldData& d, Change& change, Space::GroupVector& groups) override;
215  double surfaceEnergy(const EwaldData& data, const Change& change,
216  const Space::GroupVector& groups) override;
217  double reciprocalEnergy(const EwaldData& d) override;
218 };
219 
232 {
233  using PolicyIonIon::updateComplex;
234  void updateComplex(EwaldData&, const Space::GroupVector&) const override;
235  double reciprocalEnergy(const EwaldData&) override;
236 };
237 
242 {
243  using PolicyIonIon::updateComplex;
245  void updateBox(EwaldData&, const Point&) const override;
246  void updateComplex(EwaldData&, const Space::GroupVector&) const override;
247  void updateComplex(EwaldData&, const Change&, const Space::GroupVector&,
248  const Space::GroupVector&) const override;
249 };
250 
256 {
257  using PolicyIonIonIPBC::updateComplex;
258  void updateComplex(EwaldData&, const Space::GroupVector&) const override;
259 };
260 
266 class Ewald : public EnergyTerm
267 {
268  private:
269  const Space& spc;
270  EwaldData data;
271  std::shared_ptr<EwaldPolicyBase> policy;
272  const Space::GroupVector* old_groups = nullptr;
273 
274  public:
275  Ewald(const Space& spc, const EwaldData& data);
276  Ewald(const json& j, const Space& spc);
277  void init() override;
278  void setOldGroups(const Space::GroupVector&
279  old_groups);
280  void updateState(const Change& change) override;
281  double energy(const Change& change) override;
282  void sync(EnergyTerm* energybase, const Change& change) override;
283  void to_json(json& j) const override;
284  void force(std::vector<Point>& forces) override; // update forces on all particles
285 };
286 
290 class Isobaric : public EnergyTerm
291 {
292  private:
293  const Space& spc;
294  double pressure = 0.0;
295  static const std::map<std::string, double>
296  pressure_units;
297  public:
298  Isobaric(const json& j, const Space& spc);
299  double energy(const Change& change) override;
300  void to_json(json& j) const override;
301 };
302 
308 class Constrain : public EnergyTerm
309 {
310  private:
311  std::string type;
312  std::unique_ptr<ReactionCoordinate::ReactionCoordinateBase> coordinate;
313  std::optional<Faunus::pairpotential::HarmonicBond> harmonic;
314 
315  public:
316  Constrain(const json& j, Space& space);
317  double energy(const Change& change) override;
318  void to_json(json& j) const override;
319 };
320 
329 class Bonded : public EnergyTerm
330 {
331  private:
333  const Space& spc;
334  BondVector external_bonds;
335  std::map<int, BondVector> internal_bonds;
336  void updateGroupBonds(const Space::GroupType& group);
337  double sumBondEnergy(const BondVector& bonds) const;
338  double internalGroupEnergy(const Change::GroupChange& changed);
339  double sumEnergy(const BondVector& bonds,
340  const std::ranges::range auto& particle_indices) const;
341  void updateInternalBonds();
342 
343  public:
344  Bonded(const Space& spc, BondVector external_bonds);
345  Bonded(const json& j, const Space& spc);
346  void to_json(json& j) const override;
347  double energy(const Change& change) override;
348  void force(std::vector<Point>& forces) override;
349 };
350 
360 double Bonded::sumEnergy(const Bonded::BondVector& bonds,
361  const std::ranges::range auto& particle_indices) const
362 {
363  assert(std::is_sorted(particle_indices.begin(), particle_indices.end()));
364 
365  auto index_is_included = [&](auto index) {
366  return std::binary_search(particle_indices.begin(), particle_indices.end(), index);
367  };
368  auto affected_bonds = bonds | std::views::filter([&](const auto& bond) {
369  return std::ranges::any_of(bond->indices, index_is_included);
370  });
371  auto bond_energy = [dist = spc.geometry.getDistanceFunc()](const auto& bond) {
372  return bond->energyFunc(dist);
373  };
374  return std::transform_reduce(affected_bonds.begin(), affected_bonds.end(), 0.0, std::plus<>(),
375  bond_energy);
376 }
377 
386 auto indexComplement(std::integral auto size, const std::ranges::range auto& range)
387 {
388  using namespace std::views;
389  return iota(0, static_cast<int>(size)) |
390  filter([&](auto i) { return !std::binary_search(range.begin(), range.end(), i); });
391 }
392 
400 template <pairpotential::RequirePairPotential TPairPotential,
401  bool allow_anisotropic_pair_potential = true>
403 {
404  const Space::GeometryType& geometry;
405  TPairPotential pair_potential;
406  Space& spc;
408  potentials;
409  public:
415  : geometry(spc.geometry)
416  , spc(spc)
417  , potentials(potentials)
418  {
419  }
420 
428  template <typename T> inline double potential(const T& a, const T& b) const
429  {
430  assert(&a != &b); // a and b cannot be the same particle
431  if constexpr (allow_anisotropic_pair_potential) {
432  const Point r = geometry.vdist(a.pos, b.pos);
433  return pair_potential(a, b, r.squaredNorm(), r);
434  }
435  else {
436  return pair_potential(a, b, geometry.sqdist(a.pos, b.pos), {0, 0, 0});
437  }
438  }
439 
440  // just a temporary placement until PairForce class template will be implemented
441  template <typename ParticleType>
442  inline Point force(const ParticleType& a, const ParticleType& b) const
443  {
444  assert(&a != &b); // a and b cannot be the same particle
445  const Point b_towards_a = geometry.vdist(a.pos, b.pos); // vector b -> a = a - b
446  return pair_potential.force(a, b, b_towards_a.squaredNorm(), b_towards_a);
447  }
448 
453  template <typename... Args> inline auto operator()(Args&&... args)
454  {
455  return potential(std::forward<Args>(args)...);
456  }
457 
463  {
464  if (pair_potential.selfEnergy) { // only add if self energy is defined
465  faunus_logger->debug("Adding self-energy from {} to hamiltonian", pair_potential.name);
466  potentials.emplace_back<Energy::ParticleSelfEnergy>(spc, pair_potential.selfEnergy);
467  }
468  }
469 
470  void from_json(const json& j)
471  {
472  pairpotential::from_json(j, pair_potential);
473  if (!pair_potential.isotropic && !allow_anisotropic_pair_potential) {
474  throw std::logic_error("Only isotropic pair potentials are allowed.");
475  }
476  addPairPotentialSelfEnergy();
477  }
478 
479  void to_json(json& j) const { pair_potential.to_json(j); }
480 };
481 
482 template <typename T>
483 concept RequirePairEnergy = requires(T instance) {
484  instance.potential(Particle(), Particle());
485  instance.force(Particle(), Particle());
486  instance.addPairPotentialSelfEnergy();
487 };
488 
502 {
503  protected:
504  double value = 0.0;
505  using ParticleRef = const std::reference_wrapper<const Particle>;
506  using ParticlePair = std::pair<ParticleRef, ParticleRef>;
507 
508  public:
509  enum class Scheme
510  {
511  SERIAL,
512  OPENMP,
513  PARALLEL,
514  INVALID
515  };
516  Scheme scheme = Scheme::SERIAL;
517 
518  EnergyAccumulatorBase(double value);
519  virtual ~EnergyAccumulatorBase() = default;
520  virtual void reserve(size_t number_of_particles);
521  virtual void clear();
522  virtual void from_json(const json& j);
523  virtual void to_json(json& j) const;
524 
525  virtual explicit operator double();
526  virtual EnergyAccumulatorBase& operator=(double new_value) = 0;
527  virtual EnergyAccumulatorBase& operator+=(double new_value) = 0;
528  virtual EnergyAccumulatorBase& operator+=(ParticlePair&& pair) = 0;
529 
530  template <typename TOtherAccumulator>
531  inline EnergyAccumulatorBase& operator+=(TOtherAccumulator& acc)
532  {
533  value += static_cast<double>(acc);
534  return *this;
535  }
536 };
537 
538 NLOHMANN_JSON_SERIALIZE_ENUM(EnergyAccumulatorBase::Scheme,
539  {{EnergyAccumulatorBase::Scheme::INVALID, nullptr},
540  {EnergyAccumulatorBase::Scheme::SERIAL, "serial"},
541  {EnergyAccumulatorBase::Scheme::OPENMP, "openmp"},
542  {EnergyAccumulatorBase::Scheme::PARALLEL, "parallel"}})
543 
544 template <class T>
545 concept RequireEnergyAccumulator = std::is_base_of_v<EnergyAccumulatorBase, T>;
546 
557 template <RequirePairEnergy PairEnergy>
559 {
560  private:
561  const PairEnergy&
562  pair_energy;
563 
564  public:
565  InstantEnergyAccumulator(const PairEnergy& pair_energy, const double value = 0.0)
566  : EnergyAccumulatorBase(value)
567  , pair_energy(pair_energy)
568  {
569  }
570 
571  inline InstantEnergyAccumulator& operator=(const double new_value) override
572  {
573  value = new_value;
574  return *this;
575  }
576 
577  inline InstantEnergyAccumulator& operator+=(const double new_value) override
578  {
579  value += new_value;
580  return *this;
581  }
582 
583  inline InstantEnergyAccumulator& operator+=(ParticlePair&& pair) override
584  {
585  // keep this short to get inlined
586  value += pair_energy.potential(pair.first.get(), pair.second.get());
587  return *this;
588  }
589 
590  void from_json(const json& j) override
591  {
592  EnergyAccumulatorBase::from_json(j);
593  if (scheme != Scheme::SERIAL) {
594  faunus_logger->warn("unsupported summation scheme; falling back to 'serial'");
595  }
596  }
597 };
598 
604 template <RequirePairEnergy PairEnergy>
606 {
607  private:
608  std::vector<ParticlePair> particle_pairs;
609  const PairEnergy&
610  pair_energy;
611  const size_t max_particles_in_buffer =
612  10000;
613 
614  public:
615  explicit DelayedEnergyAccumulator(const PairEnergy& pair_energy, const double value = 0.0)
616  : EnergyAccumulatorBase(value)
617  , pair_energy(pair_energy)
618  {
619  }
620 
622  void reserve(size_t number_of_particles) override
623  {
624  try {
625  number_of_particles = std::min(number_of_particles, max_particles_in_buffer);
626  const auto number_of_pairs = (number_of_particles - 1U) * number_of_particles / 2U;
627  faunus_logger->debug(
628  std::format("reserving memory for {} energy pairs ({} MB)", number_of_pairs,
629  number_of_pairs * sizeof(ParticlePair) / (1024U * 1024U)));
630  particle_pairs.reserve(number_of_pairs);
631  }
632  catch (std::exception& e) {
633  throw std::runtime_error(std::format(
634  "cannot allocate memory for energy pairs: {}. Use another summation policy.",
635  e.what()));
636  }
637  }
638 
639  void clear() override
640  {
641  value = 0.0;
642  particle_pairs.clear();
643  }
644 
645  DelayedEnergyAccumulator& operator=(const double new_value) override
646  {
647  clear();
648  value = new_value;
649  return *this;
650  }
651 
652  inline DelayedEnergyAccumulator& operator+=(const double new_value) override
653  {
654  value += new_value;
655  return *this;
656  }
657 
658  inline DelayedEnergyAccumulator& operator+=(ParticlePair&& pair) override
659  {
660  assert(particle_pairs.capacity() > 0);
661  if (particle_pairs.size() == particle_pairs.capacity()) {
662  operator double(); // sum stored pairs and reset buffer
663  }
664  particle_pairs.emplace_back(pair);
665  return *this;
666  }
667 
668  explicit operator double() override
669  {
670  switch (scheme) {
671  case Scheme::OPENMP:
672  value += accumulateOpenMP();
673  break;
674  case Scheme::PARALLEL:
675  value += accumulateParallel();
676  break;
677  default:
678  value += accumulateSerial();
679  }
680  particle_pairs.clear();
681  return value;
682  }
683 
684  private:
685  double accumulateSerial() const
686  {
687  double sum = 0.0;
688  for (const auto& [particle1, particle2] : particle_pairs) {
689  sum += pair_energy.potential(particle1.get(), particle2.get());
690  }
691  return sum;
692  }
693 
694  double accumulateParallel() const
695  {
696 #if defined(HAS_PARALLEL_TRANSFORM_REDUCE)
697  return std::transform_reduce(
698  std::execution::par, particle_pairs.cbegin(), particle_pairs.cend(), 0.0, std::plus<>(),
699  [&](const auto& pair) {
700  return pair_energy.potential(pair.first.get(), pair.second.get());
701  });
702 #else
703  return accumulateSerial(); // fallback
704 #endif
705  }
706 
707  double accumulateOpenMP() const
708  {
709  double sum = 0.0;
710 #pragma omp parallel for reduction(+ : sum)
711  for (const auto& pair : particle_pairs) {
712  sum += pair_energy.potential(pair.first.get(), pair.second.get());
713  }
714  return sum;
715  }
716 };
717 
718 template <RequirePairEnergy TPairEnergy>
719 std::unique_ptr<EnergyAccumulatorBase>
720 createEnergyAccumulator(const json& j, const TPairEnergy& pair_energy, double initial_value)
721 {
722  std::unique_ptr<EnergyAccumulatorBase> accumulator;
723  if (j.value("summation_policy", EnergyAccumulatorBase::Scheme::SERIAL) !=
724  EnergyAccumulatorBase::Scheme::SERIAL) {
725  accumulator =
726  std::make_unique<DelayedEnergyAccumulator<TPairEnergy>>(pair_energy, initial_value);
727  faunus_logger->debug("activated delayed energy summation");
728  }
729  else {
730  accumulator =
731  std::make_unique<InstantEnergyAccumulator<TPairEnergy>>(pair_energy, initial_value);
732  faunus_logger->debug("activated instant energy summation");
733  }
734  accumulator->from_json(j);
735  return accumulator;
736 }
737 
747 {
748  double default_cutoff_squared = pc::max_value;
750  cutoff_squared;
751  Space::GeometryType& geometry;
752  friend void from_json(const json&, GroupCutoff&);
753  friend void to_json(json&, const GroupCutoff&);
754  void setSingleCutoff(double cutoff);
755 
756  public:
761  inline bool cut(const Group& group1, const Group& group2)
762  {
763  if (group1.isAtomic() || group2.isAtomic()) {
764  return false; // atomic groups have ill-defined mass centers
765  }
766  return geometry.sqdist(group1.mass_center, group2.mass_center) >=
767  cutoff_squared(group1.id, group2.id);
768  }
769 
770  double getCutoff(size_t id1, size_t id2) const;
771 
776  template <typename... Args> inline auto operator()(Args&&... args)
777  {
778  return cut(std::forward<Args>(args)...);
779  }
780 
785  explicit GroupCutoff(Space::GeometryType& geometry);
786 };
787 
788 void from_json(const json&, GroupCutoff&);
789 void to_json(json&, const GroupCutoff&);
790 
804 template <typename TCutoff> class GroupPairingPolicy
805 {
806  protected:
807  const Space& spc;
808  TCutoff cut;
809 
810  public:
814  explicit GroupPairingPolicy(Space& spc)
815  : spc(spc)
816  , cut(spc.geometry)
817  {
818  }
819 
820  void from_json(const json& j) { Energy::from_json(j, cut); }
821 
822  void to_json(json& j) const { Energy::to_json(j, cut); }
823 
837  template <RequireEnergyAccumulator TAccumulator, typename T>
838  inline void particle2particle(TAccumulator& pair_accumulator, const T& a, const T& b) const
839  {
840  pair_accumulator += {std::cref(a), std::cref(b)};
841  }
842 
855  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
856  void groupInternal(TAccumulator& pair_accumulator, const TGroup& group)
857  {
858  const auto& moldata = group.traits();
859  if (!moldata.rigid) {
860  const int group_size = group.size();
861  for (int i = 0; i < group_size - 1; ++i) {
862  for (int j = i + 1; j < group_size; ++j) {
863  // This compound condition is faster than an outer atomic condition;
864  // tested on bulk example in GCC 9.2.
865  if (group.isAtomic() || !moldata.isPairExcluded(i, j)) {
866  particle2particle(pair_accumulator, group[i], group[j]);
867  }
868  }
869  }
870  }
871  }
872 
885  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
886  void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const std::size_t index)
887  {
888  const auto& moldata = group.traits();
889  if (!moldata.rigid) {
890  if (group.isAtomic()) {
891  // speed optimization: non-bonded interaction exclusions do not need to be checked
892  // for atomic groups
893  for (int i = 0; i < index; ++i) {
894  particle2particle(pair_accumulator, group[index], group[i]);
895  }
896  for (int i = index + 1; i < group.size(); ++i) {
897  particle2particle(pair_accumulator, group[index], group[i]);
898  }
899  }
900  else {
901  // molecular group
902  for (int i = 0; i < index; ++i) {
903  if (!moldata.isPairExcluded(index, i)) {
904  particle2particle(pair_accumulator, group[index], group[i]);
905  }
906  }
907  for (int i = index + 1; i < group.size(); ++i) {
908  if (!moldata.isPairExcluded(index, i)) {
909  particle2particle(pair_accumulator, group[index], group[i]);
910  }
911  }
912  }
913  }
914  }
915 
931  template <RequireEnergyAccumulator TAccumulator, typename TGroup, typename TIndex>
932  void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const TIndex& index)
933  {
934  auto& moldata = group.traits();
935  if (!moldata.rigid) {
936  if (index.size() == 1) {
937  groupInternal(pair_accumulator, group, index[0]);
938  }
939  else {
940  // TODO investigate overhead of `index_complement` filtering;
941  // TODO perhaps allow different strategies based on the index-size/group-size ratio
942  auto index_complement = indexComplement(group.size(), index);
943  // moved <-> static
944  for (int i : index) {
945  for (int j : index_complement) {
946  if (!moldata.isPairExcluded(i, j)) {
947  particle2particle(pair_accumulator, group[i], group[j]);
948  }
949  }
950  }
951  // moved <-> moved
952  for (auto i_it = index.begin(); i_it < index.end(); ++i_it) {
953  for (auto j_it = std::next(i_it); j_it < index.end(); ++j_it) {
954  if (!moldata.isPairExcluded(*i_it, *j_it)) {
955  particle2particle(pair_accumulator, group[*i_it], group[*j_it]);
956  }
957  }
958  }
959  }
960  }
961  }
962 
979  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
980  void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2)
981  {
982  if (!cut(group1, group2)) {
983  for (auto& particle1 : group1) {
984  for (auto& particle2 : group2) {
985  particle2particle(pair_accumulator, particle1, particle2);
986  }
987  }
988  }
989  }
990 
1015  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
1016  void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2,
1017  const std::vector<std::size_t>& index1)
1018  {
1019  if (!cut(group1, group2)) {
1020  for (auto particle1_ndx : index1) {
1021  for (auto& particle2 : group2) {
1022  particle2particle(pair_accumulator, *(group1.begin() + particle1_ndx),
1023  particle2);
1024  }
1025  }
1026  }
1027  }
1028 
1052  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
1053  void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2,
1054  const std::vector<std::size_t>& index1, const std::vector<std::size_t>& index2)
1055  {
1056  if (!cut(group1, group2)) {
1057  if (!index2.empty()) {
1058  // (∁⊕group1 × ⊕group2) + (⊕group1 × ⊕group2) = group1 × ⊕group2
1059  group2group(pair_accumulator, group2, group1, index2);
1060  // + (⊕group1 × ∁⊕group2)
1061  auto index2_complement = indexComplement(group2.size(), index2);
1062  for (auto particle1_ndx : index1) {
1063  for (auto particle2_ndx : index2_complement) {
1064  particle2particle(pair_accumulator, group2[particle2_ndx],
1065  group1[particle1_ndx]);
1066  }
1067  }
1068  }
1069  else if (!index1.empty()) {
1070  // (⊕group1 × ∁⊕group2) + (⊕group1 × ⊕group2) = ⊕group1 × group2
1071  group2group(pair_accumulator, group1, group2, index1);
1072  // + (∁⊕group1 × ⊕group2) = Ø as ⊕group2 is empty
1073  }
1074  else {
1075  // both indices empty hence nothing to do
1076  }
1077  }
1078  }
1079 
1097  template <RequireEnergyAccumulator TAccumulator, typename TGroup, typename TGroups>
1098  void group2groups(TAccumulator& pair_accumulator, const TGroup& group, const TGroups& groups)
1099  {
1100  for (auto& other_group : groups) {
1101  if (&other_group != &group) {
1102  group2group(pair_accumulator, group, other_group);
1103  }
1104  }
1105  }
1106 
1128  template <RequireEnergyAccumulator TAccumulator, typename TGroup, typename TGroups>
1129  void group2groups(TAccumulator& pair_accumulator, const TGroup& group,
1130  const TGroups& group_index, const std::vector<std::size_t>& index)
1131  {
1132  for (auto other_group_ndx : group_index) {
1133  const auto& other_group = spc.groups[other_group_ndx];
1134  if (&other_group != &group) {
1135  group2group(pair_accumulator, group, other_group, index);
1136  }
1137  }
1138  }
1139 
1155  template <RequireEnergyAccumulator TAccumulator, typename Tgroup>
1156  void group2all(TAccumulator& pair_accumulator, const Tgroup& group)
1157  {
1158  for (auto& other_group : spc.groups) {
1159  if (&other_group != &group) {
1160  group2group(pair_accumulator, group, other_group);
1161  }
1162  }
1163  }
1164 
1182  template <RequireEnergyAccumulator TAccumulator, typename TGroup>
1183  void group2all(TAccumulator& pair_accumulator, const TGroup& group, const int index)
1184  {
1185  const auto& particle = group[index];
1186  for (auto& other_group : spc.groups) {
1187  if (&other_group != &group) { // avoid self-interaction
1188  if (!cut(other_group, group)) { // check g2g cut-off
1189  for (auto& other_particle : other_group) { // loop over particles in other group
1190  particle2particle(pair_accumulator, particle, other_particle);
1191  }
1192  }
1193  }
1194  }
1195  }
1196 
1212  template <RequireEnergyAccumulator TAccumulator, typename Tgroup>
1213  void group2all(TAccumulator& pair_accumulator, const Tgroup& group,
1214  const std::vector<std::size_t>& index)
1215  {
1216  if (index.size() == 1) {
1217  group2all(pair_accumulator, group, index[0]);
1218  }
1219  else {
1220  for (auto& other_group : spc.groups) {
1221  if (&other_group != &group) {
1222  group2group(pair_accumulator, group, other_group, index);
1223  }
1224  }
1225  }
1226  }
1227 
1241  template <RequireEnergyAccumulator TAccumulator, typename T>
1242  void groups2self(TAccumulator& pair_accumulator, const T& group_index)
1243  {
1244  for (auto group1_ndx_it = group_index.begin(); group1_ndx_it < group_index.end();
1245  ++group1_ndx_it) {
1246  // no such move exists that the internal energy has to be recalculated
1247  // groupInternal(pair_accumulator, spc.groups[*group1_ndx_it]);
1248  for (auto group2_ndx_it = std::next(group1_ndx_it); group2_ndx_it < group_index.end();
1249  group2_ndx_it++) {
1250  group2group(pair_accumulator, spc.groups[*group1_ndx_it],
1251  spc.groups[*group2_ndx_it]);
1252  }
1253  }
1254  }
1255 
1268  template <RequireEnergyAccumulator TAccumulator, typename T>
1269  void groups2all(TAccumulator& pair_accumulator, const T& group_index)
1270  {
1271  groups2self(pair_accumulator, group_index);
1272  auto index_complement = indexComplement(spc.groups.size(), group_index);
1273  for (auto group1_ndx : group_index) {
1274  for (auto group2_ndx : index_complement) {
1275  group2group(pair_accumulator, spc.groups[group1_ndx], spc.groups[group2_ndx]);
1276  }
1277  }
1278  }
1279 
1290  template <RequireEnergyAccumulator TAccumulator> void all(TAccumulator& pair_accumulator)
1291  {
1292  for (auto group_it = spc.groups.begin(); group_it < spc.groups.end(); ++group_it) {
1293  groupInternal(pair_accumulator, *group_it);
1294  for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end();
1295  other_group_it++) {
1296  group2group(pair_accumulator, *group_it, *other_group_it);
1297  }
1298  }
1299  }
1300 
1313  template <RequireEnergyAccumulator TAccumulator, typename TCondition>
1314  void all(TAccumulator& pair_accumulator, TCondition condition)
1315  {
1316  for (auto group_it = spc.groups.begin(); group_it < spc.groups.end(); ++group_it) {
1317  if (condition(*group_it)) {
1318  groupInternal(pair_accumulator, *group_it);
1319  }
1320  for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end();
1321  other_group_it++) {
1322  group2group(pair_accumulator, *group_it, *other_group_it);
1323  }
1324  }
1325  }
1326 };
1327 
1334 template <typename TPolicy> class GroupPairing
1335 {
1336  const Space& spc;
1337  TPolicy pairing;
1338 
1339  protected:
1347  template <RequireEnergyAccumulator TAccumulator>
1348  void accumulateGroup(TAccumulator& pair_accumulator, const Change& change)
1349  {
1350  const auto& change_data = change.groups.at(0);
1351  const auto& group = spc.groups.at(change_data.group_index);
1352  if (change_data.relative_atom_indices.size() == 1) {
1353  // faster algorithm if only a single particle moves
1354  pairing.group2all(pair_accumulator, group, change_data.relative_atom_indices[0]);
1355  if (change_data.internal) {
1356  pairing.groupInternal(pair_accumulator, group,
1357  change_data.relative_atom_indices[0]);
1358  }
1359  }
1360  else {
1361  const bool change_all =
1362  change_data.relative_atom_indices.empty(); // all particles or only their subset?
1363  if (change_all) {
1364  pairing.group2all(pair_accumulator, group);
1365  if (change_data.internal) {
1366  pairing.groupInternal(pair_accumulator, group);
1367  }
1368  }
1369  else {
1370  pairing.group2all(pair_accumulator, group, change_data.relative_atom_indices);
1371  if (change_data.internal) {
1372  pairing.groupInternal(pair_accumulator, group,
1373  change_data.relative_atom_indices);
1374  }
1375  }
1376  }
1377  }
1378 
1390  template <RequireEnergyAccumulator TAccumulator>
1391  void accumulateSpeciation(TAccumulator& pair_accumulator, const Change& change)
1392  {
1393  assert(change.matter_change);
1394  const auto& moved = change.touchedGroupIndex(); // index of moved groups
1395  const auto fixed = indexComplement(spc.groups.size(), moved) |
1396  ranges::to<std::vector>; // index of static groups
1397  auto filter_active = [](int size) {
1398  return std::views::filter([size](const auto i) { return i < size; });
1399  };
1400 
1401  // loop over all changed groups
1402  for (auto change_group1_it = change.groups.begin(); change_group1_it < change.groups.end();
1403  ++change_group1_it) {
1404  const auto& group1 = spc.groups.at(change_group1_it->group_index);
1405  // filter only active particles
1406  const auto index1 = change_group1_it->relative_atom_indices |
1407  filter_active(group1.size()) | ranges::to<std::vector>;
1408  if (!index1.empty()) {
1409  // particles added into the group: compute (changed group) <-> (static group)
1410  pairing.group2groups(pair_accumulator, group1, fixed, index1);
1411  }
1412  // loop over successor changed groups (hence avoid double counting group1×group2 and
1413  // group2×group1)
1414  for (auto change_group2_it = std::next(change_group1_it);
1415  change_group2_it < change.groups.end(); ++change_group2_it) {
1416  const auto& group2 = spc.groups.at(change_group2_it->group_index);
1417  const auto index2 = change_group2_it->relative_atom_indices |
1418  filter_active(group2.size()) | ranges::to<std::vector>;
1419  if (!index1.empty() || !index2.empty()) {
1420  // particles added into one or other group: compute (changed group) <-> (changed
1421  // group)
1422  pairing.group2group(pair_accumulator, group1, group2, index1, index2);
1423  }
1424  }
1425  if (!index1.empty() && !molecules.at(group1.id).rigid) {
1426  // compute internal energy in the changed group
1427  if (change_group1_it->all) {
1428  pairing.groupInternal(pair_accumulator, group1);
1429  }
1430  else {
1431  pairing.groupInternal(pair_accumulator, group1, index1);
1432  };
1433  }
1434  }
1435  }
1436 
1437  public:
1447  template <RequireEnergyAccumulator TAccumulator>
1448  void accumulate(TAccumulator& pair_accumulator, const Change& change)
1449  {
1450  assert(std::is_sorted(change.groups.begin(), change.groups.end()));
1451  if (change.everything) {
1452  pairing.all(pair_accumulator);
1453  }
1454  else if (change.volume_change) {
1455  // sum all interaction energies except the internal energies of incompressible molecules
1456  pairing.all(pair_accumulator, [](auto& group) {
1457  return group.isAtomic() || group.traits().compressible;
1458  });
1459  }
1460  else if (!change.matter_change) {
1461  if (change.groups.size() == 1) {
1462  // if only a single group changes use faster algorithm and optionally add the
1463  // internal energy
1464  accumulateGroup(pair_accumulator, change);
1465  }
1466  else {
1467  // if multiple groups move, no internal energies are computed
1468  const auto& moved = change.touchedGroupIndex(); // index of moved groups
1469  pairing.groups2all(pair_accumulator, moved);
1470  }
1471  }
1472  else { // change.dN
1473  accumulateSpeciation(pair_accumulator, change);
1474  }
1475  }
1476 
1477  GroupPairing(Space& spc)
1478  : spc(spc)
1479  , pairing(spc)
1480  {
1481  }
1482 
1483  void from_json(const json& j) { pairing.from_json(j); }
1484 
1485  void to_json(json& j) const { pairing.to_json(j); }
1486 
1487  // FIXME a temporal fix for non-refactorized NonbondedCached
1488  template <typename Accumulator>
1489  void group2group(Accumulator& pair_accumulator, const Space::GroupType& group1,
1490  const Space::GroupType& group2)
1491  {
1492  pairing.group2group(std::forward<Accumulator&>(pair_accumulator),
1493  std::forward<const Space::GroupType&>(group1),
1494  std::forward<const Space::GroupType&>(group2));
1495  }
1496 };
1497 
1499 {
1500  public:
1501  virtual double particleParticleEnergy(const Particle& particle1, const Particle& particle2) = 0;
1502  virtual double groupGroupEnergy(const Group& group1, const Group& group2) = 0;
1503 };
1504 
1512 template <RequirePairEnergy TPairEnergy, typename TPairingPolicy>
1513 class Nonbonded : public NonbondedBase
1514 {
1515  protected:
1516  const Space& spc;
1517  TPairEnergy pair_energy;
1518  TPairingPolicy
1521  std::shared_ptr<EnergyAccumulatorBase>
1523 
1524  public:
1525  Nonbonded(const json& j, Space& spc, BasePointerVector<EnergyTerm>& pot)
1526  : spc(spc)
1527  , pair_energy(spc, pot)
1528  , pairing(spc)
1529  {
1530  name = "nonbonded";
1531  from_json(j);
1532  energy_accumulator = createEnergyAccumulator(j, pair_energy, 0.0);
1533  energy_accumulator->reserve(spc.numParticles()); // attempt to reduce memory fragmentation
1534  }
1535 
1536  double particleParticleEnergy(const Particle& particle1, const Particle& particle2) override
1537  {
1538  return pair_energy(particle1, particle2);
1539  }
1540 
1541  double groupGroupEnergy(const Group& group1, const Group& group2) override
1542  {
1543  InstantEnergyAccumulator<TPairEnergy> accumulator(pair_energy);
1544  pairing.group2group(accumulator, group1, group2);
1545  return static_cast<double>(accumulator);
1546  }
1547 
1548  void from_json(const json& j)
1549  {
1550  pair_energy.from_json(j);
1551  pairing.from_json(j);
1552  }
1553 
1554  void to_json(json& j) const override
1555  {
1556  pair_energy.to_json(j);
1557  pairing.to_json(j);
1558  energy_accumulator->to_json(j);
1559  }
1560 
1561  double energy(const Change& change) override
1562  {
1563  energy_accumulator->clear();
1564  // down-cast to avoid slow, virtual function calls:
1565  if (auto ptr = std::dynamic_pointer_cast<InstantEnergyAccumulator<TPairEnergy>>(
1566  energy_accumulator)) {
1567  pairing.accumulate(*ptr, change);
1568  }
1569  else if (auto ptr = std::dynamic_pointer_cast<DelayedEnergyAccumulator<TPairEnergy>>(
1570  energy_accumulator)) {
1571  pairing.accumulate(*ptr, change);
1572  }
1573  else {
1574  pairing.accumulate(*energy_accumulator, change);
1575  }
1576  return static_cast<double>(*energy_accumulator);
1577  }
1578 
1584  void force(std::vector<Point>& forces) override
1585  {
1586  // just a temporary hack; perhaps better to allow PairForce instead of the PairEnergy
1587  // template
1588  assert(forces.size() == spc.particles.size() &&
1589  "the forces size must match the particle size");
1590  for (size_t i = 0; i < spc.particles.size() - 1; ++i) {
1591  for (size_t j = i + 1; j < spc.particles.size(); ++j) {
1592  const Point f = pair_energy.force(spc.particles[i], spc.particles[j]);
1593  forces[i] += f;
1594  forces[j] -= f;
1595  }
1596  }
1597  }
1598 };
1599 
1613 template <RequirePairEnergy TPairEnergy, typename TPairingPolicy>
1614 class NonbondedCached : public Nonbonded<TPairEnergy, TPairingPolicy>
1615 {
1618  Eigen::MatrixXf energy_cache;
1619  using Base::spc;
1620 
1621  template <typename TGroup> double g2g(const TGroup& g1, const TGroup& g2)
1622  {
1623  int i = &g1 - spc.groups.data();
1624  int j = &g2 - spc.groups.data();
1625  if (j < i) {
1626  std::swap(i, j);
1627  }
1628  if (EnergyTerm::state ==
1629  EnergyTerm::MonteCarloState::TRIAL) { // if this is from the trial system
1630  TAccumulator energy_accumulator(Base::pair_energy);
1631  Base::pairing.group2group(energy_accumulator, g1, g2);
1632  energy_cache(i, j) = static_cast<double>(energy_accumulator); // update the cache
1633  }
1634  return energy_cache(i, j); // return (cached) value
1635  }
1636 
1637  template <typename TGroup>
1638  double g2g(const TGroup& g1, const TGroup& g2,
1639  [[maybe_unused]] const std::vector<std::size_t>& index)
1640  {
1641  // index not implemented
1642  return g2g(g1, g2);
1643  }
1644 
1645  public:
1647  : Base(j, spc, pot)
1648  {
1649  Base::name += "EM";
1650  init();
1651  }
1652 
1656  void init() override
1657  {
1658  const auto groups_size = spc.groups.size();
1659  energy_cache.resize(groups_size, groups_size);
1660  energy_cache.setZero();
1661  TAccumulator u(Base::pair_energy);
1662  for (auto i = 0; i < groups_size - 1; ++i) {
1663  for (auto j = i + 1; j < groups_size; ++j) {
1664  u = 0.0;
1665  Base::pairing.group2group(u, spc.groups.at(i), spc.groups.at(j));
1666  energy_cache(i, j) = static_cast<double>(u);
1667  }
1668  }
1669  }
1670 
1671  double energy(const Change& change) override
1672  {
1673  if (!change) {
1674  return 0.0;
1675  }
1676  // Only g2g may be called there to compute (and cache) energy!
1677  double energy_sum = 0.0;
1678  if (change.everything || change.volume_change) {
1679  for (auto i = spc.groups.begin(); i < spc.groups.end(); ++i) {
1680  for (auto j = std::next(i); j < Base::spc.groups.end(); ++j) {
1681  energy_sum += g2g(*i, *j);
1682  }
1683  }
1684  return energy_sum;
1685  };
1686 
1687  if (change.groups.size() == 1) { // if exactly ONE molecule is changed
1688  const auto& d = change.groups[0];
1689  const auto& g1 = spc.groups.at(d.group_index);
1690  for (auto g2_it = spc.groups.begin(); g2_it < spc.groups.end(); ++g2_it) {
1691  if (&g1 != &(*g2_it)) {
1692  energy_sum += g2g(g1, *g2_it, d.relative_atom_indices);
1693  }
1694  }
1695  return energy_sum;
1696  }
1697 
1698  // many molecules are changed:
1699 
1700  auto moved = change.touchedGroupIndex(); // index of moved groups
1701  // moved<->moved
1702  if (change.moved_to_moved_interactions) {
1703  for (auto i = moved.begin(); i < moved.end(); ++i) {
1704  for (auto j = std::next(i); j < moved.end(); ++j) {
1705  energy_sum += g2g(spc.groups[*i], spc.groups[*j]);
1706  }
1707  }
1708  }
1709  // moved<->static
1710 #if true
1711  // classic version
1712  const auto fixed =
1713  indexComplement(spc.groups.size(), moved) | ranges::to_vector; // static groups
1714  for (auto i : moved) {
1715  for (auto j : fixed) {
1716  energy_sum += g2g(spc.groups[i], spc.groups[j]);
1717  }
1718  }
1719 #else
1720  // OMP-ready version
1721  auto fixed = indexComplement(spc.groups.size(), moved) |
1722  ranges::to<std::vector>; // index of static groups
1723  const size_t moved_size = moved.size();
1724  const size_t fixed_size = fixed.size();
1725  for (auto i = 0; i < moved_size; ++i) {
1726  for (auto j = 0; j < fixed_size; ++j) {
1727  energy_sum += g2g(spc.groups[moved[i]], spc.groups[fixed[j]]);
1728  }
1729  }
1730 #endif
1731  return energy_sum;
1732  }
1733 
1739  void sync(EnergyTerm* base_ptr, const Change& change) override
1740  {
1741  auto other = dynamic_cast<decltype(this)>(base_ptr);
1742  assert(other);
1743  if (change.everything || change.volume_change) {
1744  energy_cache.triangularView<Eigen::StrictlyUpper>() =
1745  (other->energy_cache).template triangularView<Eigen::StrictlyUpper>();
1746  }
1747  else {
1748  for (const auto& d : change.groups) {
1749  for (int i = 0; i < d.group_index; i++) {
1750  energy_cache(i, d.group_index) = other->energy_cache(i, d.group_index);
1751  }
1752  for (size_t i = d.group_index + 1; i < spc.groups.size(); i++) {
1753  energy_cache(d.group_index, i) = other->energy_cache(d.group_index, i);
1754  }
1755  }
1756  }
1757  }
1758 };
1759 
1760 #ifdef ENABLE_FREESASA
1761 
1768 class FreeSASAEnergy : public EnergyTerm
1769 {
1770  private:
1771  std::vector<double> positions;
1772  std::vector<double> radii;
1773  std::vector<double> sasa;
1774 
1775  const Space& spc;
1776  double cosolute_molarity = 0.;
1777  std::unique_ptr<freesasa_parameters_fwd> parameters;
1778  Average<double> mean_surface_area;
1779 
1780  void to_json(json& j) const override;
1781  void sync(EnergyTerm* energybase_ptr, const Change& change) override;
1782  void updateSASA(const Change& change);
1783  void init() override;
1784 
1791  template <typename Tfirst, typename Tend>
1792  void updateRadii(Tfirst begin, Tend end, [[maybe_unused]] const Change& change)
1793  {
1794  const auto number_of_particles = std::distance(begin, end);
1795  radii.clear();
1796  radii.reserve(number_of_particles);
1797  std::transform(begin, end, std::back_inserter(radii),
1798  [](const Particle& particle) { return particle.traits().sigma * 0.5; });
1799  }
1800 
1807  template <typename Tfirst, typename Tend>
1808  void updatePositions(Tfirst begin, Tend end, [[maybe_unused]] const Change& change)
1809  {
1810  const auto number_of_particles = std::distance(begin, end);
1811  positions.clear();
1812  positions.reserve(3 * number_of_particles);
1813  for (const auto& particle : spc.activeParticles()) {
1814  const auto* xyz = particle.pos.data();
1815  positions.insert(positions.end(), xyz, xyz + 3);
1816  }
1817  }
1818 
1819  public:
1825  FreeSASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius);
1826  FreeSASAEnergy(const json& j, const Space& spc);
1827  double energy(const Change& change) override;
1828 
1829  const std::vector<double>& getAreas() const { return sasa; }
1830 };
1831 #endif
1832 
1841 {
1842  private:
1843  void to_json(json& j) const override;
1844  void sync(EnergyTerm* energybase_ptr, const Change& change) override;
1845 
1846  protected:
1847  void init() override;
1848  using index_type = size_t;
1849  const Space& spc;
1850  std::vector<double> areas;
1851  double cosolute_molarity = 0.0;
1852  std::unique_ptr<SASA::SASABase>
1854 
1856  inline auto indexOf(const Particle& particle) const
1857  {
1858  return static_cast<index_type>(std::addressof(particle) -
1859  std::addressof(spc.particles.at(0)));
1860  }
1861 
1862  public:
1863  SASAEnergyReference(const Space& spc, double cosolute_molarity, double probe_radius,
1864  int slices_per_atom = 25, bool dense_container = true);
1865  SASAEnergyReference(const json& j, const Space& spc);
1866  const std::vector<double>& getAreas() const;
1867  double energy(const Change& change) override;
1868 };
1869 
1875 {
1876  private:
1877  std::vector<std::vector<index_type>>
1878  current_neighbours;
1879  std::vector<index_type>
1880  changed_indices;
1881 
1882  void sync(EnergyTerm* energybase_ptr, const Change& change) override;
1883  void init() override;
1884 
1885  void updateChangedIndices(const Change& change);
1886  void insertChangedNeighboursOf(index_type index, std::set<index_type>& target_indices) const;
1887 
1888  public:
1889  SASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius,
1890  int slices_per_atom = 25, bool dense_container = true);
1891  SASAEnergy(const json& j, const Space& spc);
1892  double energy(const Change& change) override;
1893 };
1894 
1902 class Example2D : public EnergyTerm
1903 {
1904  private:
1905  bool use_2d = true; // Set to false to apply energy only along x (as by the book)
1906  double scale_energy = 1.0; // effective temperature
1907  const Point& particle; // reference to 1st particle in the system
1908  void to_json(json& j) const override;
1909 
1910  public:
1911  Example2D(const json& j, Space& spc);
1912  double energy(const Change& change) override;
1913 };
1914 
1925 {
1926  private:
1927  const Space& spc;
1928  MoleculeData::index_type molid1;
1929  MoleculeData::index_type molid2;
1930  std::map<MoleculeData::index_type, Average<double>> mean_charges;
1931  std::unique_ptr<ExprFunction<double>> expr;
1932  json json_input_backup; // initial json input
1933 
1934  struct Properties
1935  { // storage for particle properties
1936  double mean_charge1 = 0;
1937  double mean_charge2 = 0;
1938  double mass_center_separation = 0;
1939  };
1940 
1941  Properties properties;
1942 
1943  void to_json(json& j) const override;
1944  void setParameters(const Group& group1, const Group& group2);
1945 
1946  public:
1947  CustomGroupGroup(const json& j, const Space& spc);
1948  double energy(const Change& change) override;
1949 };
1950 
1954 class Hamiltonian : public EnergyTerm, public BasePointerVector<EnergyTerm>
1955 {
1956  private:
1957  double maximum_allowed_energy = pc::infty;
1958  std::vector<double>
1959  latest_energies;
1960  decltype(vec)& energy_terms;
1961  void
1962  addEwald(const json& j,
1963  Space& spc);
1964  void checkBondedMolecules() const;
1965  void to_json(json& j) const override;
1966  void force(PointVector& forces) override;
1967  std::unique_ptr<EnergyTerm> createEnergy(Space& spc, const std::string& name, const json& j);
1968 
1969  public:
1970  Hamiltonian(Space& spc, const json& j);
1971  void init() override;
1972  void updateState(const Change& change) override;
1973  void sync(EnergyTerm* other_hamiltonian, const Change& change) override;
1974  double energy(const Change& change) override;
1975  const std::vector<double>&
1976  latestEnergies() const;
1977 };
1978 } // namespace Energy
1979 } // namespace Faunus
auto operator()(Args &&... args)
A functor alias for cut().
Definition: energy.h:776
nlohmann::json json
JSON object.
Definition: json_support.h:10
void sync(EnergyTerm *base_ptr, const Change &change) override
Copy energy matrix from other.
Definition: energy.h:1739
Eigen::Vector3d Point
3D vector used for positions, velocities, forces etc.
Definition: coordinates.h:7
auto asEigenMatrix(iter begin, iter end, memberptr m)
Eigen::Map facade to data members in STL container.
Definition: eigensupport.h:46
void all(TAccumulator &pair_accumulator, TCondition condition)
Cross pairing between all particles in the space.
Definition: energy.h:1314
double potential(const T &a, const T &b) const
Computes pair potential energy.
Definition: energy.h:428
Point pos
Particle position vector.
Definition: particle.h:227
int id
Molecule id.
Definition: group.h:183
const Space & spc
space to operate on
Definition: energy.h:1516
GroupPairingPolicy(Space &spc)
Definition: energy.h:814
Ion-Ion Ewald with isotropic periodic boundary conditions (IPBC) using Eigen operations.
Definition: energy.h:255
std::vector< Point > PointVector
Vector of 3D vectors.
Definition: core.h:24
Geometry class for spheres, cylinders, cuboids, hexagonal prism, truncated octahedron, slits.
Definition: geometry.h:342
void group2group(TAccumulator &pair_accumulator, const TGroup &group1, const TGroup &group2, const std::vector< std::size_t > &index1, const std::vector< std::size_t > &index2)
Cross pairing of particles in two groups.
Definition: energy.h:1053
Computes pair quantity difference for a systen perturbation.
Definition: energy.h:1334
bool moved_to_moved_interactions
If several groups are moved, should they interact with each other?
Definition: space.h:35
void group2all(TAccumulator &pair_accumulator, const Tgroup &group, const std::vector< std::size_t > &index)
Complete cartesian pairing between selected particles in a group and particles in other groups in spa...
Definition: energy.h:1213
The keys of the intra map are group index and the values is a vector of BondData. ...
Definition: energy.h:329
std::unique_ptr< SASA::SASABase > sasa
performs neighbour searching and subsequent sasa calculation
Definition: energy.h:1853
double T
floating point size
Definition: units.h:73
std::vector< GroupChange > groups
Touched groups by index in group vector.
Definition: space.h:56
void groupInternal(TAccumulator &pair_accumulator, const TGroup &group, const TIndex &index)
Pairing in the group involving only the particles present in the index.
Definition: energy.h:932
auto touchedGroupIndex() const
List of moved groups (index)
Definition: space.h:61
Provides a fast inlineable interface for non-bonded pair potential energy computation.
Definition: energy.h:402
void group2group(TAccumulator &pair_accumulator, const TGroup &group1, const TGroup &group2)
Complete cartesian pairing of particles in two groups.
Definition: energy.h:980
const Space & spc
Space to operate on.
Definition: energy.h:1849
Pressure term for NPT ensemble.
Definition: energy.h:290
Ion-Ion Ewald with periodic boundary conditions (PBC) using Eigen operations.
Definition: energy.h:231
void group2all(TAccumulator &pair_accumulator, const Tgroup &group)
Complete cartesian pairing between particles in a group and particles in other groups in space...
Definition: energy.h:1156
Used to add self energies to atoms from i.e.
Definition: externalpotential.h:194
A basic accumulator which immediately computes and adds energy of a pair of particles upon addition u...
Definition: energy.h:558
auto indexComplement(std::integral auto size, const std::ranges::range auto &range)
Provides a complementary set of ints with respect to the iota set of a given size.
Definition: energy.h:386
Interface for energy accumulators.
Definition: energy.h:501
bool everything
Everything has changed (particles, groups, volume)
Definition: space.h:32
std::string cite
Optional reference, preferably DOI, to further information.
Definition: energy.h:140
Ewald summation reciprocal energy.
Definition: energy.h:266
TCutoff cut
a cutoff functor that determines if energy between two groups can be ignored
Definition: energy.h:808
class for calculating SASA energies calculating SASA of each particle every step
Definition: energy.h:1840
std::pair< ParticleRef, ParticleRef > ParticlePair
References to two particles.
Definition: energy.h:506
const std::reference_wrapper< const Particle > ParticleRef
Particle reference.
Definition: energy.h:505
double sqdist(const Point &, const Point &) const
(Minimum) squared distance between two points
Definition: geometry.h:460
std::shared_ptr< EnergyAccumulatorBase > energy_accumulator
energy accumulator used for storing and summing pair-wise energies
Definition: energy.h:1522
Computes change in the non-bonded energy, assuming pairwise additive energy terms.
Definition: energy.h:1513
Data class for Ewald k-space calculations.
Definition: energy.h:92
Ion-Ion Ewald with isotropic periodic boundary conditions (IPBC)
Definition: energy.h:241
void to_json(json &j) const override
json output
Definition: energy.h:1554
Custom energy between pair of molecules.
Definition: energy.h:1924
void accumulateGroup(TAccumulator &pair_accumulator, const Change &change)
Computes pair quantity difference if only a single group has changed.
Definition: energy.h:1348
auto operator()(Args &&... args)
A functor alias for potential().
Definition: energy.h:453
double energy(const Change &change) override
energy due to change
Definition: energy.h:1561
bool isAtomic() const
Is it an atomic group?
Definition: group.h:187
auto activeParticles()
Range with all active particles.
Definition: space.h:302
double energy(const Change &change) override
energy due to change
Definition: energy.h:1671
Determines if two groups are separated beyond the cutoff distance.
Definition: energy.h:746
PairEnergy(Space &spc, BasePointerVector< EnergyTerm > &potentials)
Definition: energy.h:414
Particle pairing to calculate pairẃise interaction using particles&#39; groups internally.
Definition: energy.h:804
void group2groups(TAccumulator &pair_accumulator, const TGroup &group, const TGroups &groups)
Complete cartesian pairing between particles in a group and a union of groups.
Definition: energy.h:1098
void addPairPotentialSelfEnergy()
Registers the potential self-energy to hamiltonian if needed.
Definition: energy.h:462
void groupInternal(TAccumulator &pair_accumulator, const TGroup &group, const std::size_t index)
Pairings of a single particle within the group.
Definition: energy.h:886
static auto mapGroupsToEigen(Space::GroupVector &groups)
Represent charges and positions using an Eigen facade (Map)
Definition: energy.h:167
Properties of changed groups.
Definition: space.h:40
Particle class for storing positions, id, and other properties.
Definition: particle.h:220
const AtomData & traits() const
get properties from AtomData
Definition: particle.cpp:183
Computes non-bonded energy contribution from changed particles.
Definition: energy.h:1614
void group2group(TAccumulator &pair_accumulator, const TGroup &group1, const TGroup &group2, const std::vector< std::size_t > &index1)
Cross pairing of particles in two groups.
Definition: energy.h:1016
concept RequirePairPotential
Concept matching a particle pair potential derived from Potential::PairPotentialBase ...
Definition: potentials_base.h:180
Stores a vector of particle pairs and postpones the energy evaluation until operator double() is call...
Definition: energy.h:605
Cell list class templates.
Definition: actions.cpp:11
class for calculating SASA energies calculating SASA of particles based on change object every step ...
Definition: energy.h:1874
bool volume_change
The volume has changed.
Definition: space.h:33
Check if particles are outside the simulation container.
Definition: energy.h:69
auto indexOf(const Particle &particle) const
absolute index of particle in space particle vector
Definition: energy.h:1856
End of Group class.
Definition: group.h:177
Aggregate and sum energy terms.
Definition: energy.h:1954
Eigen::VectorXd Aks
1xK for update optimization (see Eq.24, DOI:10.1063/1.481216)
Definition: energy.h:96
void all(TAccumulator &pair_accumulator)
Cross pairing between all particles in the space.
Definition: energy.h:1290
All energies inherit from this class.
Definition: externalpotential.h:21
std::vector< double > areas
Target buffer for calculated surface areas.
Definition: energy.h:1850
double charge
Particle charge.
Definition: particle.h:226
void accumulate(TAccumulator &pair_accumulator, const Change &change)
Computes pair quantity difference from changed particles.
Definition: energy.h:1448
ParticleVector particles
All particles are stored here!
Definition: space.h:120
Constrain system using reaction coordinates.
Definition: energy.h:308
NLOHMANN_JSON_SERIALIZE_ENUM(SpheroCylinderData::PatchType, {{SpheroCylinderData::PatchType::Invalid, nullptr}, {SpheroCylinderData::PatchType::Full, "full"}, {SpheroCylinderData::PatchType::Capped, "capped"}, {SpheroCylinderData::PatchType::None, "none"}}) class AtomData
General properties for atoms.
Definition: atomdata.h:61
TPairEnergy pair_energy
a functor to compute non-bonded energy between two particles, see PairEnergy
Definition: energy.h:1517
void groupInternal(TAccumulator &pair_accumulator, const TGroup &group)
All pairings within a group.
Definition: energy.h:856
std::vector< MoleculeData > molecules
List of molecule types.
Definition: molecule.cpp:22
Specify changes made to a system.
Definition: space.h:29
void force(std::vector< Point > &forces) override
Calculates the force on all particles.
Definition: energy.h:1584
void emplace_back(Args &... args)
Create an (derived) instance and append a pointer to it to the vector.
Definition: auxiliary.h:268
void accumulateSpeciation(TAccumulator &pair_accumulator, const Change &change)
Computes pair quantity difference if the number of particles has changed.
Definition: energy.h:1391
bool matter_change
The number of atomic or molecular species has changed.
Definition: space.h:34
void groups2all(TAccumulator &pair_accumulator, const T &group_index)
Cross pairing of particles between a union of groups and its complement in space. ...
Definition: energy.h:1269
bool cut(const Group &group1, const Group &group2)
Determines if two groups are separated beyond the cutoff distance.
Definition: energy.h:761
Eigen::VectorXcd Q_dipole
Complex 1xK vectors.
Definition: energy.h:97
const Space & spc
a space to operate on
Definition: energy.h:807
Placeholder for atoms and molecules.
Definition: space.h:92
Definition: energy.h:1498
Point vdist(const Point &, const Point &) const override
Minimum distance vector b->a.
Definition: geometry.h:429
Base class for Ewald k-space updates policies.
Definition: energy.h:137
Eigen::Matrix3Xd k_vectors
k-vectors, 3xK
Definition: energy.h:95
GroupVector groups
All groups are stored here (i.e. molecules)
Definition: space.h:121
Oscillating energy on a single particle.
Definition: energy.h:1902
Point mass_center
Mass center.
Definition: group.h:185
void groups2self(TAccumulator &pair_accumulator, const T &group_index)
Cross pairing of particles among a union of groups.
Definition: energy.h:1242
void reserve(size_t number_of_particles) override
Reserve memory for (N-1)*N/2 interaction pairs.
Definition: energy.h:622
size_t numParticles(Selection selection=Selection::ACTIVE) const
Number of (active) particles.
Definition: space.cpp:367
void particle2particle(TAccumulator &pair_accumulator, const T &a, const T &b) const
Add two interacting particles to the accumulator.
Definition: energy.h:838
Ion-Ion Ewald using periodic boundary conditions (PBC)
Definition: energy.h:207
void group2all(TAccumulator &pair_accumulator, const TGroup &group, const int index)
Complete cartesian pairing between a single particle in a group and particles in other groups in spac...
Definition: energy.h:1183
void group2groups(TAccumulator &pair_accumulator, const TGroup &group, const TGroups &group_index, const std::vector< std::size_t > &index)
Cross pairing of particles in a group and a union of groups.
Definition: energy.h:1129
TPairingPolicy pairing
pairing policy to effectively sum up the pairwise additive non-bonded energy
Definition: energy.h:1520
void init() override
Cache pair interactions in matrix.
Definition: energy.h:1656