faunus
smart_montecarlo.h
1 #pragma once
2 
3 #include "regions.h"
4 
6 
7 template <class T>
8 concept GroupOrParticle = std::is_convertible_v<T, Group> || std::is_convertible_v<T, Particle>;
9 
16 template <GroupOrParticle T> class Selection
17 {
18  public:
19  T* item = nullptr;
20  int n_total = 0;
21  int n_inside = 0;
22  bool is_inside = false;
23  Selection(T& item, int number_total, int number_inside, bool item_is_inside);
24 };
25 
33 template <GroupOrParticle T>
34 Selection<T>::Selection(T& item, int number_total, int number_inside, bool item_is_inside)
35  : item(&item)
36  , n_total(number_total)
37  , n_inside(number_inside)
38  , is_inside(item_is_inside)
39 {
40 }
41 
42 enum class BiasDirection
43 {
44  ENTER_REGION,
45  EXIT_REGION,
46  NO_CROSSING
47 };
48 
49 double bias(double outside_acceptance, int n_total, int n_inside,
50  BiasDirection direction);
51 
61 {
62  private:
63  const double outside_acceptance =
64  1.0;
65  static BiasDirection getDirection(bool inside_before, bool inside_after);
66  template <std::ranges::range Range> double getNumberInside(Range& range) const;
67 
68  protected:
69  const std::unique_ptr<Region::RegionBase> region;
70 
71  public:
72  RegionSampler(double outside_acceptance, std::unique_ptr<Region::RegionBase> region);
73  virtual ~RegionSampler() = default;
74  void to_json(json& j) const;
75 
76  template <GroupOrParticle T, std::ranges::range Range>
77  std::optional<Selection<T>> select(Range& range, Random& random);
78 
79  template <GroupOrParticle T> double bias(const Selection<T>& selection);
80  std::optional<double> fixed_number_inside;
81 };
82 
89 template <std::ranges::range Range> double RegionSampler::getNumberInside(Range& range) const
90 {
91  if (fixed_number_inside) {
92  return fixed_number_inside.value();
93  }
94  return static_cast<double>(std::ranges::count_if(
95  range, [&](const auto& i) { return region->inside(i); })); // expensive
96 }
97 
110 template <GroupOrParticle T, std::ranges::range Range>
111 std::optional<Selection<T>> RegionSampler::select(Range& range, Random& random)
112 {
113  const auto n_total = std::ranges::distance(range.begin(), range.end());
114  int max_selection_attempts = 10 * n_total;
115  do {
116  auto it = random.sample(range.begin(), range.end()); // random particle or group
117  if (it == range.end()) {
118  return std::nullopt;
119  }
120  const auto inside = region->inside(*it); // is element inside or outside region?
121  if (not inside && outside_acceptance < random()) {
122  continue;
123  }
124  return Selection<T>(*it, n_total, getNumberInside(range), inside);
125  } while (max_selection_attempts-- > 0);
126  faunus_logger->warn("Max selection attempts reached. Increase outside acceptance (p) ?");
127  return std::nullopt;
128 }
129 
138 template <GroupOrParticle T> double RegionSampler::bias(const Selection<T>& selection)
139 {
140  const auto is_inside_after = region->inside(*(selection.item)); // may have changed due to move
141  const auto direction = getDirection(selection.is_inside, is_inside_after);
142  return SmarterMonteCarlo::bias(outside_acceptance, selection.n_total, selection.n_inside,
143  direction);
144 }
145 
149 template <GroupOrParticle T> class MoveSupport
150 {
151  private:
152  using OptionalElement =
153  std::optional<std::reference_wrapper<T>>;
154  std::optional<SmarterMonteCarlo::Selection<T>>
155  selection;
156  SmarterMonteCarlo::RegionSampler region_sampler;
157  Average<double> mean_bias_energy;
158  Average<double> mean_selected_inside;
159  AverageStdev<double> mean_number_inside;
160  AverageStdev<double> blocks_inside;
161  void analyseSelection(int count_inside);
162 
163  public:
164  MoveSupport(const Space& spc, const json& j);
165  double bias();
166  void to_json(json& j) const;
167  template <std::ranges::range Range> OptionalElement select(Range& mollist, Random& random);
168 };
169 
170 template <GroupOrParticle T>
171 MoveSupport<T>::MoveSupport(const Space& spc, const json& j)
172  : region_sampler(j.at("p").get<double>(), Region::createRegion(spc, j))
173 {
174 }
175 
183 template <GroupOrParticle T> void MoveSupport<T>::analyseSelection(int count_inside)
184 {
185  assert(selection);
186  mean_selected_inside += static_cast<double>(selection->is_inside);
187 
188  if (region_sampler.fixed_number_inside) {
189  return; // skip the rest as we have already fixed n_inside
190  }
191 
192  mean_number_inside += static_cast<double>(count_inside);
193  if (mean_number_inside.size() % 100 == 0) { // build up a block average
194  blocks_inside += mean_number_inside.avg(); // add a new block
195  if (blocks_inside.size() % 2 == 0 &&
196  blocks_inside.rsd() < 0.04) { // check if block has converged
197  region_sampler.fixed_number_inside =
198  blocks_inside.avg(); // disable all further sampling!
199  faunus_logger->info(
200  "Regional <N_inside> = {:.1f} converged and settled after {} iterations",
201  region_sampler.fixed_number_inside.value(), mean_number_inside.size());
202  }
203  }
204 }
205 
214 template <GroupOrParticle T> double MoveSupport<T>::bias()
215 {
216  auto bias_energy(0.0);
217  if (selection) {
218  analyseSelection(selection->n_inside);
219  bias_energy = region_sampler.bias(*selection);
220  }
221  mean_bias_energy += bias_energy;
222  return bias_energy;
223 }
224 
225 template <GroupOrParticle T> void MoveSupport<T>::to_json(json& j) const
226 {
227  region_sampler.to_json(j);
228  if (mean_number_inside) {
229  j["mean number inside"] = mean_number_inside.avg();
230  }
231  if (mean_bias_energy) {
232  j["mean bias energy (kT)"] = mean_bias_energy.avg();
233  }
234  if (mean_selected_inside) {
235  j["selected inside fraction"] = mean_selected_inside.avg();
236  }
237 }
238 
245 template <GroupOrParticle T>
246 template <std::ranges::range Range>
247 typename MoveSupport<T>::OptionalElement MoveSupport<T>::select(Range& mollist, Random& random)
248 {
249  selection = region_sampler.select<T>(mollist, random);
250  if (selection) {
251  return *(selection->item);
252  }
253  return std::nullopt;
254 }
255 
256 } // namespace Faunus::SmarterMonteCarlo
nlohmann::json json
JSON object.
Definition: json_support.h:10
Particle or groups selection for smart Monte Carlo (SMC) moves.
Definition: smart_montecarlo.h:16
Random random
global instance of Random
Definition: random.cpp:62
Helper class for smart MC moves that preferentially samples within Regions.
Definition: smart_montecarlo.h:60
Random number generator.
Definition: random.h:34
double T
floating point size
Definition: units.h:73
double bias(const Selection< T > &selection)
Definition: smart_montecarlo.h:138
std::optional< double > fixed_number_inside
Optionally give a fixed number inside.
Definition: smart_montecarlo.h:80
Iterator sample(Iterator begin, Iterator end)
Pick random element in iterable range.
Definition: random.h:61
std::unique_ptr< RegionBase > createRegion(const Space &spc, const json &j)
Expects an object where KEY is an arbitrary, user-defined name and the VALUE is another object defini...
Definition: regions.cpp:47
Definition: smart_montecarlo.cpp:4
T * item
Selected group, particle etc.
Definition: smart_montecarlo.h:19
const std::unique_ptr< Region::RegionBase > region
This defines the smart MC region.
Definition: smart_montecarlo.h:69
Placeholder for atoms and molecules.
Definition: space.h:92
std::optional< Selection< T > > select(Range &range, Random &random)
Select a random element from range and perform region check.
Definition: smart_montecarlo.h:111
double bias()
Returns the bias energy.
Definition: smart_montecarlo.h:214
OptionalElement select(Range &mollist, Random &random)
Definition: smart_montecarlo.h:247
Selection(T &item, int number_total, int number_inside, bool item_is_inside)
Definition: smart_montecarlo.h:34
Helper class for constructing smart monte carlo moves.
Definition: smart_montecarlo.h:149