Loading [MathJax]/extensions/TeX/AMSmath.js
ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages Concepts
statistics.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2022 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21/** \file
22 * Statistical tools to analyze simulations.
23 *
24 * The corresponding header file is statistics.hpp.
25 */
26
28
29#include "BoxGeometry.hpp"
30#include "Particle.hpp"
32#include "communication.hpp"
33#include "errorhandling.hpp"
34#include "npt.hpp"
35#include "system/System.hpp"
36
37#include <utils/Vector.hpp>
38#include <utils/contains.hpp>
39#include <utils/math/sqr.hpp>
41
42#include <boost/mpi/collectives/broadcast.hpp>
43#include <boost/mpi/collectives/reduce.hpp>
44
45#include <cassert>
46#include <cmath>
47#include <cstdlib>
48#include <functional>
49#include <limits>
50#include <numbers>
51#include <stdexcept>
52#include <tuple>
53#include <utility>
54#include <vector>
55
57/** @brief Serialize @c std::tuple. */
58template <typename Archive, typename... T>
59void serialize(Archive &ar, std::tuple<T...> &pack, unsigned int const) {
60 std::apply([&](auto &...item) { ((ar & item), ...); }, pack);
61}
62} // namespace boost::serialization
63
64/** @brief Decay a tuple of only 1 type to that type. */
65template <typename... F> struct DecayTupleResult {
66 using type = std::tuple<std::invoke_result_t<F, Particle const &>...>;
67};
68
69template <typename F> struct DecayTupleResult<F> {
70 using type = std::invoke_result_t<F, Particle const &>;
71};
72
73/**
74 * @brief Gather particle traits to MPI rank 0.
75 * When only one trait is requested, return a vector of type T.
76 * When multiple traits are requested, return a vector of tuples.
77 */
78template <class... Trait>
79static auto gather_traits_for_types(System::System const &system,
80 std::vector<int> const &p_types,
81 Trait &&...trait) {
82 std::vector<typename DecayTupleResult<Trait...>::type> buffer{};
83
84 for (auto const &p : system.cell_structure->local_particles()) {
85 if (Utils::contains(p_types, p.type())) {
86 buffer.emplace_back(trait(p)...);
87 }
88 }
89
91 if (::comm_cart.rank() != 0) {
92 buffer.clear();
93 }
94 return buffer;
95}
96
97double mindist(System::System const &system, std::vector<int> const &set1,
98 std::vector<int> const &set2) {
99 using Utils::contains;
100
101 std::vector<int> buf_type{};
102 std::vector<Utils::Vector3d> buf_pos{};
103
104 auto const &box_geo = *system.box_geo;
105 auto const accept_all = set1.empty() or set2.empty();
106 for (auto const &p : system.cell_structure->local_particles()) {
107 if (accept_all or contains(set1, p.type()) or contains(set2, p.type())) {
108 buf_type.emplace_back(p.type());
109 buf_pos.emplace_back(box_geo.unfolded_position(p.pos(), p.image_box()));
110 }
111 }
112
115 if (::comm_cart.rank() != 0) {
116 buf_type.clear();
117 buf_pos.clear();
118 }
119
120 auto mindist_sq = std::numeric_limits<double>::infinity();
121 for (std::size_t j = 0ul; j < buf_type.size(); ++j) {
122 /* check which sets particle j belongs to (bit 0: set1, bit1: set2) */
123 auto in_set = 0;
124 if (set1.empty() || contains(set1, buf_type[j]))
125 in_set = 1;
126 if (set2.empty() || contains(set2, buf_type[j]))
127 in_set |= 2;
128 assert(in_set != 0);
129
130 for (auto i = j + 1ul; i != buf_type.size(); ++i) {
131 /* accept a pair if particle j is in set1 and particle i in set2 or vice
132 * versa. */
133 if (((in_set & 1) and (set2.empty() or contains(set2, buf_type[i]))) or
134 ((in_set & 2) and (set1.empty() or contains(set1, buf_type[i])))) {
135 mindist_sq = std::min(
136 mindist_sq, box_geo.get_mi_vector(buf_pos[j], buf_pos[i]).norm2());
137 }
138 }
139 }
140
141 return std::sqrt(mindist_sq);
142}
143
145 bool include_particles,
146 bool include_lbfluid) {
147 Utils::Vector3d momentum{};
148 if (include_particles) {
149 auto const particles = system.cell_structure->local_particles();
150 momentum =
151 std::accumulate(particles.begin(), particles.end(), Utils::Vector3d{},
152 [](Utils::Vector3d const &m, Particle const &p) {
153 return m + p.mass() * p.v();
154 });
155 }
156 if (include_lbfluid and system.lb.is_solver_set()) {
157 momentum += system.lb.get_momentum() * system.lb.get_lattice_speed();
158 }
159 return momentum;
160}
161
163 auto const &box_geo = *system.box_geo;
164 auto const &cell_structure = *system.cell_structure;
165 Utils::Vector3d local_com{};
166 double local_mass = 0.;
167
168 for (auto const &p : cell_structure.local_particles()) {
169 if ((p.type() == p_type or p_type == -1) and not p.is_virtual()) {
170 local_com += box_geo.unfolded_position(p.pos(), p.image_box()) * p.mass();
171 local_mass += p.mass();
172 }
173 }
174 Utils::Vector3d com{};
175 double mass = 1.; // placeholder value to avoid division by zero
176 boost::mpi::reduce(::comm_cart, local_com, com, std::plus<>(), 0);
177 boost::mpi::reduce(::comm_cart, local_mass, mass, std::plus<>(), 0);
178 return com / mass;
179}
180
182 auto const &box_geo = *system.box_geo;
183 auto const &cell_structure = *system.cell_structure;
184 Utils::Vector3d am{};
185
186 for (auto const &p : cell_structure.local_particles()) {
187 if ((p.type() == p_type or p_type == -1) and not p.is_virtual()) {
188 auto const pos = box_geo.unfolded_position(p.pos(), p.image_box());
189 am += p.mass() * vector_product(pos, p.v());
190 }
191 }
192 return am;
193}
194
196 std::vector<int> const &p_types) {
197 auto const &box_geo = *system.box_geo;
198 auto const trait_pos = [&box_geo](Particle const &p) {
199 return box_geo.unfolded_position(p.pos(), p.image_box());
200 };
201 auto const buf_pos = gather_traits_for_types(system, p_types, trait_pos);
202
203 Utils::Vector9d mat{};
204 if (::comm_cart.rank() == 0) {
205 auto const center =
206 std::accumulate(buf_pos.begin(), buf_pos.end(), Utils::Vector3d{}) /
207 static_cast<double>(buf_pos.size());
208 // compute covariance matrix
209 for (unsigned int i = 0u; i < 3u; ++i) {
210 for (unsigned int j = 0u; j < 3u; ++j) {
211 if (i > j) {
212 mat[i * 3u + j] = mat[j * 3u + i];
213 } else {
214 mat[i * 3u + j] = std::accumulate(
215 buf_pos.begin(), buf_pos.end(), 0.,
216 [i, j, &center](double acc, Utils::Vector3d const &pos) {
217 return acc + (pos[i] - center[i]) * (pos[j] - center[j]);
218 });
219 }
220 }
221 }
222 mat /= static_cast<double>(buf_pos.size());
223 }
224 return mat;
225}
226
228 int p_type) {
229 auto const &box_geo = *system.box_geo;
230 auto const &cell_structure = *system.cell_structure;
231 Utils::Vector9d mat{};
232 auto com = center_of_mass(system, p_type);
233 boost::mpi::broadcast(::comm_cart, com, 0);
234
235 for (auto const &p : cell_structure.local_particles()) {
236 if (p.type() == p_type and not p.is_virtual()) {
237 auto const pos = box_geo.unfolded_position(p.pos(), p.image_box()) - com;
238 auto const mass = p.mass();
239 mat[0] += mass * (pos[1] * pos[1] + pos[2] * pos[2]);
240 mat[4] += mass * (pos[0] * pos[0] + pos[2] * pos[2]);
241 mat[8] += mass * (pos[0] * pos[0] + pos[1] * pos[1]);
242 mat[1] -= mass * (pos[0] * pos[1]);
243 mat[2] -= mass * (pos[0] * pos[2]);
244 mat[5] -= mass * (pos[1] * pos[2]);
245 }
246 }
247 /* use symmetry */
248 mat[3] = mat[1];
249 mat[6] = mat[2];
250 mat[7] = mat[5];
251 return mat;
252}
253
254std::vector<int> nbhood(System::System const &system,
255 Utils::Vector3d const &pos, double dist) {
256 std::vector<int> buf_pid{};
257 auto const dist_sq = dist * dist;
258 auto const &box_geo = *system.box_geo;
259
260 for (auto const &p : system.cell_structure->local_particles()) {
261 auto const r_sq = box_geo.get_mi_vector(pos, p.pos()).norm2();
262 if (r_sq < dist_sq) {
263 buf_pid.push_back(p.id());
264 }
265 }
266
268 if (::comm_cart.rank() != 0) {
269 buf_pid.clear();
270 }
271
272 return buf_pid;
273}
274
275std::vector<std::vector<double>>
277 std::vector<int> const &p1_types,
278 std::vector<int> const &p2_types, double r_min,
279 double r_max, int r_bins, bool log_flag, bool int_flag) {
280
281 auto const &box_geo = *system.box_geo;
282 auto const trait_id = [](Particle const &p) { return p.id(); };
283 auto const trait_pos = [&box_geo](Particle const &p) {
284 return box_geo.unfolded_position(p.pos(), p.image_box());
285 };
286 auto const buf1 =
287 gather_traits_for_types(system, p1_types, trait_id, trait_pos);
288 auto const buf2 =
289 gather_traits_for_types(system, p2_types, trait_id, trait_pos);
290 auto const r_max2 = Utils::sqr(r_max);
291 auto const r_min2 = Utils::sqr(r_min);
292 auto const start_dist2 = Utils::sqr(r_max + 1.);
293 auto const inv_bin_width =
294 (log_flag) ? static_cast<double>(r_bins) / std::log(r_max / r_min)
295 : static_cast<double>(r_bins) / (r_max - r_min);
296
297 long cnt = 0;
298 double low = 0.0;
299 std::vector<double> distribution(r_bins);
300
301 for (auto const &[pid1, pos1] : buf1) {
302 auto min_dist2 = start_dist2;
303 /* particle loop: p2_types */
304 for (auto const &[pid2, pos2] : buf2) {
305 if (pid1 != pid2) {
306 auto const act_dist2 = box_geo.get_mi_vector(pos1, pos2).norm2();
307 if (act_dist2 < min_dist2) {
308 min_dist2 = act_dist2;
309 }
310 }
311 }
312 if (min_dist2 <= r_max2) {
313 if (min_dist2 >= r_min2) {
314 auto const min_dist = std::sqrt(min_dist2);
315 /* calculate bin index */
316 auto const ind = static_cast<int>(
317 ((log_flag) ? std::log(min_dist / r_min) : (min_dist - r_min)) *
318 inv_bin_width);
319 if (ind >= 0 and ind < r_bins) {
320 distribution[ind] += 1.0;
321 }
322 } else {
323 low += 1.0;
324 }
325 }
326 cnt++;
327 }
328
329 if (cnt != 0) {
330 // normalization
331 low /= static_cast<double>(cnt);
332 for (int i = 0; i < r_bins; i++) {
333 distribution[i] /= static_cast<double>(cnt);
334 }
335
336 // integration
337 if (int_flag) {
338 distribution[0] += low;
339 for (int i = 0; i < r_bins - 1; i++)
340 distribution[i + 1] += distribution[i];
341 }
342 }
343
344 std::vector<double> radii(r_bins);
345 if (log_flag) {
346 auto const log_fac = std::pow(r_max / r_min, 1. / r_bins);
347 radii[0] = r_min * std::sqrt(log_fac);
348 for (int i = 1; i < r_bins; ++i) {
349 radii[i] = radii[i - 1] * log_fac;
350 }
351 } else {
352 auto const bin_width = (r_max - r_min) / static_cast<double>(r_bins);
353 for (int i = 0; i < r_bins; ++i) {
354 radii[i] = r_min + bin_width / 2. + static_cast<double>(i) * bin_width;
355 }
356 }
357
358 return {radii, distribution};
359}
360
361std::vector<std::vector<double>>
362structure_factor(System::System const &system, std::vector<int> const &p_types,
363 int order) {
364 auto const &box_geo = *system.box_geo;
365 auto const trait_pos = [&box_geo](Particle const &p) {
366 return box_geo.unfolded_position(p.pos(), p.image_box());
367 };
368 auto const buf_pos = gather_traits_for_types(system, p_types, trait_pos);
369 auto const order_sq = Utils::sqr(static_cast<std::size_t>(order));
370 auto const twoPI_L = 2. * std::numbers::pi * system.box_geo->length_inv()[0];
371 std::vector<double> ff(2ul * order_sq + 1ul);
372 std::vector<double> wavevectors;
373 std::vector<double> intensities;
374
375 if (::comm_cart.rank() == 0) {
376 for (int i = 0; i <= order; i++) {
377 for (int j = -order; j <= order; j++) {
378 for (int k = -order; k <= order; k++) {
379 auto const n = i * i + j * j + k * k;
380 if ((static_cast<std::size_t>(n) <= order_sq) && (n >= 1)) {
381 double C_sum = 0.0, S_sum = 0.0;
382 for (auto const &pos : buf_pos) {
383 auto const qr = twoPI_L * (Utils::Vector3i{{i, j, k}} * pos);
384 C_sum += cos(qr);
385 S_sum += sin(qr);
386 }
387 ff[2 * n - 2] += C_sum * C_sum + S_sum * S_sum;
388 ff[2 * n - 1]++;
389 }
390 }
391 }
392 }
393
394 std::size_t length = 0;
395 for (std::size_t qi = 0; qi < order_sq; qi++) {
396 if (ff[2 * qi + 1] != 0) {
397 ff[2 * qi] /= static_cast<double>(buf_pos.size()) * ff[2 * qi + 1];
398 ++length;
399 }
400 }
401
402 wavevectors.resize(length);
403 intensities.resize(length);
404
405 int cnt = 0;
406 for (std::size_t i = 0; i < order_sq; i++) {
407 if (ff[2 * i + 1] != 0) {
408 wavevectors[cnt] = twoPI_L * std::sqrt(static_cast<double>(i + 1));
409 intensities[cnt] = ff[2 * i];
410 cnt++;
411 }
412 }
413 }
414
415 return {std::move(wavevectors), std::move(intensities)};
416}
Vector implementation and trait types for boost qvm interoperability.
Main system class.
std::shared_ptr< CellStructure > cell_structure
std::shared_ptr< BoxGeometry > box_geo
boost::mpi::communicator comm_cart
The communicator.
__device__ void vector_product(float const *a, float const *b, float *out)
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
void gather_buffer(std::vector< T, Allocator > &buffer, boost::mpi::communicator const &comm, int root=0)
Gather buffer with different size on each node.
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
bool contains(Range &&rng, T const &value)
Check whether a range contains a value.
Definition contains.hpp:36
void serialize(Archive &ar, std::tuple< T... > &pack, unsigned int const)
Serialize std::tuple.
Exports for the NpT code.
Utils::Vector3d center_of_mass(System::System const &system, int p_type)
Calculate the center of mass of particles of a certain type.
Utils::Vector3d angular_momentum(System::System const &system, int p_type)
Calculate the angular momentum of particles of a certain type.
std::vector< int > nbhood(System::System const &system, Utils::Vector3d const &pos, double dist)
Find all particles within a given radius dist around a position pos.
Utils::Vector9d moment_of_inertia_matrix(System::System const &system, int p_type)
Calculate the moment of inertia of particles of a certain type.
Utils::Vector9d gyration_tensor(System::System const &system, std::vector< int > const &p_types)
Calculate the gyration tensor of particles of certain types.
Utils::Vector3d calc_linear_momentum(System::System const &system, bool include_particles, bool include_lbfluid)
Calculate total momentum of the system (particles & LB fluid).
std::vector< std::vector< double > > structure_factor(System::System const &system, std::vector< int > const &p_types, int order)
Calculate the spherically averaged structure factor.
double mindist(System::System const &system, std::vector< int > const &set1, std::vector< int > const &set2)
Calculate the minimal distance of two particles with types in set1 and set2, respectively.
static auto gather_traits_for_types(System::System const &system, std::vector< int > const &p_types, Trait &&...trait)
Gather particle traits to MPI rank 0.
std::vector< std::vector< double > > calc_part_distribution(System::System const &system, std::vector< int > const &p1_types, std::vector< int > const &p2_types, double r_min, double r_max, int r_bins, bool log_flag, bool int_flag)
Calculate the distribution of particles around others.
Statistical tools to analyze simulations.
std::invoke_result_t< F, Particle const & > type
Decay a tuple of only 1 type to that type.
std::tuple< std::invoke_result_t< F, Particle const & >... > type
bool is_solver_set() const
Return true if a LB solver is active.
Definition lb/Solver.cpp:64
auto get_lattice_speed() const
Get the lattice speed (agrid/tau).
Utils::Vector3d get_momentum() const
Struct holding all information for one particle.
Definition Particle.hpp:395