ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
Analysis.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2013-2026 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "Analysis.hpp"
21#include "ObservableStat.hpp"
22
23#include "core/BoxGeometry.hpp"
28#include "core/cells.hpp"
30#include "core/dpd.hpp"
32#include "core/npt.hpp"
33
35
36#include <utils/Vector.hpp>
39
40#include <boost/mpi/collectives/all_reduce.hpp>
41
42#include <algorithm>
43#include <cmath>
44#include <functional>
45#include <memory>
46#include <sstream>
47#include <stdexcept>
48#include <string>
49#include <unordered_map>
50#include <vector>
51
52namespace ScriptInterface {
53namespace Analysis {
54
55/** @brief Check if a contiguous range of particle ids exists. */
56static void check_topology(CellStructure const &cell_structure, int chain_start,
57 int chain_length, int n_chains) {
58 try {
59 if (n_chains <= 0) {
60 throw std::domain_error("Chain analysis needs at least 1 chain");
61 }
62 if (chain_length <= 0) {
63 throw std::domain_error("Chain analysis needs at least 1 bead per chain");
64 }
65
66 auto n_particles_local = 0;
67 for (int i = 0; i < n_chains; ++i) {
68 for (int j = 0; j < chain_length; ++j) {
69 auto const pid = chain_start + i * chain_length + j;
70 auto ptr = cell_structure.get_local_particle(pid);
71 if (ptr != nullptr and not ptr->is_ghost()) {
73 }
74 }
75 }
76 auto const n_particles_total = boost::mpi::all_reduce(
77 ::comm_cart, n_particles_local, std::plus<int>{});
78
80 for (int i = 0; i < chain_length * n_chains; ++i) {
81 auto const pid = chain_start + i;
82 auto ptr = cell_structure.get_local_particle(pid);
83 int local_count = 0;
84 if (ptr != nullptr and not ptr->is_ghost()) {
85 local_count = 1;
86 }
87 auto const total_count =
88 boost::mpi::all_reduce(::comm_cart, local_count, std::plus<int>{});
89 if (total_count == 0) {
90 std::stringstream error_msg;
91 error_msg << "Particle with id " << pid << " does not exist; "
92 << "cannot perform analysis on the range chain_start="
93 << chain_start << ", number_of_chains=" << n_chains
94 << ", chain_length=" << chain_length << ". "
95 << "Please provide a contiguous range of particle ids.";
96 throw std::runtime_error(error_msg.str());
97 }
98 }
99 }
100 } catch (...) {
101 if (::comm_cart.rank() == 0) {
102 throw;
103 }
104 throw Exception("");
105 }
106}
107
108void Analysis::check_particle_type(int p_type) const {
109 auto const &nonbonded_ias = get_system().nonbonded_ias;
110 if (p_type < 0 or p_type > nonbonded_ias->get_max_seen_particle_type()) {
111 std::stringstream error_msg;
112 error_msg << "Particle type " << p_type << " does not exist";
113 throw std::invalid_argument(error_msg.str());
114 }
115}
116
117Variant Analysis::do_call_method(std::string const &name,
118 VariantMap const &parameters) {
119 if (name == "linear_momentum") {
120 auto const local = calc_linear_momentum(
121 get_system(), get_value_or<bool>(parameters, "include_particles", true),
122 get_value_or<bool>(parameters, "include_lbfluid", true));
123 return mpi_reduce_sum(context()->get_comm(), local).as_vector();
124 }
125 if (name == "particle_energy") {
126 auto &system = get_system();
127 auto const pid = get_value<int>(parameters, "pid");
128 auto const local = system.particle_short_range_energy_contribution(pid);
129 return mpi_reduce_sum(context()->get_comm(), local);
130 }
131 if (name == "particle_bond_energy") {
132 auto &system = get_system();
133 auto const pid = get_value<int>(parameters, "pid");
134 auto const bond_id = get_value<int>(parameters, "bond_id");
135 auto const partners = get_value<std::vector<int>>(parameters, "partners");
136 auto const local = system.particle_bond_energy(pid, bond_id, partners);
137 return Utils::Mpi::reduce_optional(context()->get_comm(), local);
138 }
139 if (name == "potential_energy") {
140 auto const &obs = get_system().calculate_energy();
141 return obs.accumulate(-(obs.kinetic_lin[0] + obs.kinetic_rot[0]));
142 }
143 if (name == "particle_neighbor_pids") {
144 auto &system = get_system();
145 system.on_observable_calc();
146 std::unordered_map<int, std::vector<int>> dict;
147 context()->parallel_try_catch([&]() {
148 auto neighbor_pids = get_neighbor_pids(system);
149 Utils::Mpi::gather_buffer(neighbor_pids, context()->get_comm());
150 std::ranges::for_each(neighbor_pids, [&dict](auto const &nbhood) {
151 dict[nbhood.pid] = nbhood.neighbor_pids;
152 });
153 });
155 }
156#ifdef ESPRESSO_DPD
157 if (name == "dpd_pressure") {
158 auto const result = dpd_pressure(get_system(), context()->get_comm());
159 return result.as_vector();
160 }
161#endif // ESPRESSO_DPD
162 if (name == "min_dist") {
163 auto const p_types1 = get_value<std::vector<int>>(parameters, "p_types1");
164 auto const p_types2 = get_value<std::vector<int>>(parameters, "p_types2");
165 for (auto const p_type : p_types1) {
166 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
167 }
168 for (auto const p_type : p_types2) {
169 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
170 }
172 }
173 if (name == "center_of_mass") {
174 auto const p_type = get_value<int>(parameters, "p_type");
175 Variant result;
176 context()->parallel_try_catch([&]() {
177 // p_type == -1 is the sentinel for all (non-virtual) particles
178 if (p_type != -1) {
179 check_particle_type(p_type);
180 }
181 auto const local = center_of_mass(get_system(), p_type);
182 result = mpi_reduce_sum(context()->get_comm(), local).as_vector();
183 });
184 return result;
185 }
186 if (name == "angular_momentum") {
187 auto const p_type = get_value<int>(parameters, "p_type");
188 // p_type == -1 is the sentinel for all (non-virtual) particles
189 if (p_type != -1) {
190 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
191 }
192 auto const local = angular_momentum(get_system(), p_type);
193 return mpi_reduce_sum(context()->get_comm(), local).as_vector();
194 }
195 if (name == "nbhood") {
196 auto const pos = get_value<Utils::Vector3d>(parameters, "pos");
197 auto const radius = get_value<double>(parameters, "r_catch");
198 auto const result = nbhood(get_system(), pos, radius);
199 return result;
200 }
201 if (name == "calc_re") {
202 auto const &system = get_system();
203 auto const chain_start = get_value<int>(parameters, "chain_start");
204 auto const chain_length = get_value<int>(parameters, "chain_length");
205 auto const n_chains = get_value<int>(parameters, "number_of_chains");
207 auto const result = calc_re(system, chain_start, chain_length, n_chains);
208 return std::vector<double>(result.begin(), result.end());
209 }
210 if (name == "calc_rg") {
211 auto const &system = get_system();
212 auto const chain_start = get_value<int>(parameters, "chain_start");
213 auto const chain_length = get_value<int>(parameters, "chain_length");
214 auto const n_chains = get_value<int>(parameters, "number_of_chains");
217 context()->parallel_try_catch([&]() {
218 auto const result = calc_rg(system, chain_start, chain_length, n_chains);
219 output = Variant{std::vector<double>(result.begin(), result.end())};
220 });
221 return output;
222 }
223 if (name == "calc_rh") {
224 auto const &system = get_system();
225 auto const chain_start = get_value<int>(parameters, "chain_start");
226 auto const chain_length = get_value<int>(parameters, "chain_length");
227 auto const n_chains = get_value<int>(parameters, "number_of_chains");
229 context()->parallel_try_catch([&]() {
230 if (chain_length < 2) {
231 throw std::domain_error(
232 "Hydrodynamic radius is undefined for chains shorter than 2 beads");
233 }
234 });
235 auto const result = calc_rh(system, chain_start, chain_length, n_chains);
236 return std::vector<double>(result.begin(), result.end());
237 }
238 if (name == "gyration_tensor") {
239 auto const p_types = get_value<std::vector<int>>(parameters, "p_types");
240 Variant result;
241 context()->parallel_try_catch([&]() {
242 for (auto const p_type : p_types) {
243 check_particle_type(p_type);
244 }
245 auto const mat = gyration_tensor(get_system(), p_types);
246 result = std::vector<double>(mat.begin(), mat.end());
247 });
248 return result;
249 }
250 if (name == "moment_of_inertia_matrix") {
251 auto const p_type = get_value<int>(parameters, "p_type");
252 Variant result;
253 context()->parallel_try_catch([&]() {
254 check_particle_type(p_type);
255 auto const local = moment_of_inertia_matrix(get_system(), p_type);
256 result = mpi_reduce_sum(context()->get_comm(), local).as_vector();
257 });
258 return result;
259 }
260 if (name == "structure_factor") {
261 auto const order = get_value<int>(parameters, "sf_order");
262 auto const p_types = get_value<std::vector<int>>(parameters, "sf_types");
264 if (order < 1)
265 throw std::domain_error("order has to be a strictly positive number");
266 });
267 for (auto const p_type : p_types) {
268 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
269 }
270 auto const result = structure_factor(get_system(), p_types, order);
271 return make_vector_of_variants(result);
272 }
273 if (name == "distribution") {
274 auto const &box_l = get_system().box_geo->length();
275 auto const r_max_limit =
276 0.5 * std::min(std::min(box_l[0], box_l[1]), box_l[2]);
277 auto const r_min = get_value_or<double>(parameters, "r_min", 0.);
278 auto const r_max = get_value_or<double>(parameters, "r_max", r_max_limit);
279 auto const r_bins = get_value_or<int>(parameters, "r_bins", 100);
280 auto const log_flag = get_value_or<bool>(parameters, "log_flag", false);
281 auto const int_flag = get_value_or<bool>(parameters, "int_flag", false);
282 context()->parallel_try_catch([=]() {
283 if (log_flag and r_min <= 0.) {
284 throw std::domain_error("Parameter 'r_min' must be > 0");
285 }
286 if (r_min < 0.) {
287 throw std::domain_error("Parameter 'r_min' must be >= 0");
288 }
289 if (r_min >= r_max) {
290 throw std::domain_error("Parameter 'r_max' must be > 'r_min'");
291 }
292 if (r_max > r_max_limit) {
293 throw std::domain_error("Parameter 'r_max' must be <= box_l / 2");
294 }
295 if (r_bins <= 0) {
296 throw std::domain_error("Parameter 'r_bins' must be >= 1");
297 }
298 });
299 auto const p_types1 =
300 get_value<std::vector<int>>(parameters, "type_list_a");
301 auto const p_types2 =
302 get_value<std::vector<int>>(parameters, "type_list_b");
303 for (auto const p_type : p_types1) {
304 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
305 }
306 for (auto const p_type : p_types2) {
307 context()->parallel_try_catch([&]() { check_particle_type(p_type); });
308 }
312 }
313 if (name == "calculate_energy") {
314 return m_obs_stat->do_call_method("calculate_energy", {});
315 }
316 if (name == "calculate_scalar_pressure") {
317 return m_obs_stat->do_call_method("calculate_scalar_pressure", {});
318 }
319 if (name == "calculate_pressure_tensor") {
320 return m_obs_stat->do_call_method("calculate_pressure_tensor", {});
321 }
322 if (name == "_observable_stat_test_fallthrough") {
323 // this is only exposed for unit testing purposes
324 return m_obs_stat->do_call_method("unknown", {});
325 }
326#ifdef ESPRESSO_NPT
327 if (name == "get_instantaneous_pressure") {
328 return get_system().npt_inst_pressure->p_inst[0];
329 }
330 if (name == "get_instantaneous_pressure_virial") {
331 return get_system().npt_inst_pressure->p_inst[1];
332 }
333#endif // ESPRESSO_NPT
334 return {};
335}
336
337} // namespace Analysis
338} // namespace ScriptInterface
Vector implementation and trait types for boost qvm interoperability.
std::vector< NeighborPIDs > get_neighbor_pids(System::System const &system)
Returns pairs of particle ids and neighbor particle id lists.
Definition cells.cpp:186
This file contains everything related to the global cell structure / cell system.
Describes a cell structure / cell system.
Particle * get_local_particle(int id)
Get a local particle by id.
Variant do_call_method(std::string const &name, VariantMap const &parameters) override
Definition Analysis.cpp:117
virtual void parallel_try_catch(std::function< void()> const &cb) const =0
Context * context() const
Responsible context.
std::string_view name() const
boost::mpi::communicator comm_cart
The communicator.
This file contains the asynchronous MPI communication.
Utils::Vector9d dpd_pressure(System::System &system, boost::mpi::communicator const &comm)
Pressure tensor contribution of the DPD interaction.
Definition dpd.cpp:154
Routines to use DPD as thermostat or pair force .
static void check_topology(CellStructure const &cell_structure, int chain_start, int chain_length, int n_chains)
Check if a contiguous range of particle ids exists.
Definition Analysis.cpp:56
T get_value(Variant const &v)
Extract value of specific type T from a Variant.
std::unordered_map< std::string, Variant > VariantMap
Definition Variant.hpp:133
auto make_unordered_map_of_variants(std::unordered_map< K, V > const &v)
Definition Variant.hpp:144
T mpi_reduce_sum(boost::mpi::communicator const &comm, T const &result)
Reduce object by sum on the head node.
auto make_vector_of_variants(std::vector< T > const &v)
Definition Variant.hpp:148
void gather_buffer(std::vector< T, Allocator > &buffer, boost::mpi::communicator const &comm, int root=0)
Gather buffer with different size on each node.
T reduce_optional(boost::mpi::communicator const &comm, std::optional< T > const &result)
Reduce an optional on the head node.
Various procedures concerning interactions between particles.
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.
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::array< double, 4 > calc_rg(System::System const &system, int chain_start, int chain_length, int n_chains)
Calculate the radius of gyration.
std::array< double, 2 > calc_rh(System::System const &system, int chain_start, int chain_length, int n_chains)
Calculate the hydrodynamic radius (ref.
std::array< double, 4 > calc_re(System::System const &system, int chain_start, int chain_length, int n_chains)
Calculate the end-to-end-distance.
This file contains the code for statistics on chains.
Recursive variant implementation.
Definition Variant.hpp:84