ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
ParticleHandle.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2022-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 <config/config.hpp>
21
22#include "ParticleHandle.hpp"
23
29
30#include "core/BoxGeometry.hpp"
33#include "core/bonds.hpp"
35#include "core/exclusions.hpp"
38#include "core/propagation.hpp"
39#include "core/rotation.hpp"
43
44#include <utils/Vector.hpp>
46
47#include <boost/format.hpp>
48#include <boost/mpi/collectives/all_reduce.hpp>
49#include <boost/mpi/collectives/broadcast.hpp>
50#include <boost/mpi/communicator.hpp>
51
52#include <algorithm>
53#include <cassert>
54#include <cmath>
55#include <cstddef>
56#include <memory>
57#include <optional>
58#include <ranges>
59#include <set>
60#include <sstream>
61#include <stdexcept>
62#include <string>
63#include <string_view>
64#include <tuple>
65#include <type_traits>
66#include <vector>
67
68namespace ScriptInterface {
69namespace Particles {
70
71#ifdef ESPRESSO_ROTATION
72static std::array<std::array<std::string_view, 3>,
74 {{"dip", "dipm",
75 "Setting 'dip' is sufficient as it defines the scalar dipole moment."}},
76 {{"quat", "director",
77 "Setting 'quat' is sufficient as it defines the director."}},
78 {{"dip", "quat",
79 "Setting 'dip' would overwrite 'quat'. Set 'quat' and 'dipm' instead."}},
80 {{"dip", "director",
81 "Setting 'dip' would overwrite 'director'. Set 'director' and "
82 "'dipm' instead."}},
83}};
84
85static void sanity_checks_rotation(VariantMap const &params) {
86 // if we are not constructing a particle from a checkpoint file,
87 // check the quaternion is not accidentally set twice by the user
88 if (not params.contains("__cpt_sentinel")) {
89 auto formatter =
90 boost::format("Contradicting particle attributes: '%s' and '%s'. %s");
91 for (auto const &[prop1, prop2, reason] : contradicting_arguments_quat) {
92 if (params.contains(std::string{prop1}) and
93 params.contains(std::string{prop2})) {
94 auto const err_msg = boost::str(formatter % prop1 % prop2 % reason);
95 throw std::invalid_argument(err_msg);
96 }
97 }
98 }
99}
100#endif // ESPRESSO_ROTATION
101
102#if defined(ESPRESSO_ROTATION) or defined(ESPRESSO_EXTERNAL_FORCES)
104 auto bitfield = static_cast<uint8_t>(0u);
105 if (flag[0])
106 bitfield |= static_cast<uint8_t>(1u);
107 if (flag[1])
108 bitfield |= static_cast<uint8_t>(2u);
109 if (flag[2])
110 bitfield |= static_cast<uint8_t>(4u);
111 return bitfield;
112}
113#endif
114
115#ifdef ESPRESSO_ROTATION
117 return Utils::Vector4d{{q[0], q[1], q[2], q[3]}};
118}
119
120static auto get_quaternion_safe(std::string const &name, Variant const &value) {
121 auto const q = get_value<Utils::Vector4d>(value);
122 if (q.norm2() == 0.) {
123 throw std::domain_error(error_msg(name, "must be non-zero"));
124 }
125 return Utils::Quaternion<double>{{q[0], q[1], q[2], q[3]}};
126}
127#endif // ESPRESSO_ROTATION
128
129#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
130static auto get_gamma_safe(Variant const &value) {
131#ifdef ESPRESSO_PARTICLE_ANISOTROPY
132 try {
134 } catch (...) {
135 return get_value<Utils::Vector3d>(value);
136 }
137#else // ESPRESSO_PARTICLE_ANISOTROPY
138 return get_value<double>(value);
139#endif // ESPRESSO_PARTICLE_ANISOTROPY
140}
141#endif // ESPRESSO_THERMOSTAT_PER_PARTICLE
142
143template <typename T, class F>
144T ParticleHandle::get_particle_property(F const &fun) const {
145 auto &cell_structure = get_cell_structure()->get_cell_structure();
146 auto const &comm = context()->get_comm();
147 auto const ptr = const_cast<Particle const *>(
148 get_real_particle(comm, m_pid, cell_structure));
149 std::optional<T> ret;
150 if (ptr == nullptr) {
151 ret = {};
152 } else {
153 ret = {fun(*ptr)};
154 }
155 return Utils::Mpi::reduce_optional(comm, ret);
156}
157
158template <typename T>
159T ParticleHandle::get_particle_property(T const &(Particle::*getter)()
160 const) const {
161 return get_particle_property<T>(
162 [getter](Particle const &p) { return (p.*getter)(); });
163}
164
165template <class F>
166void ParticleHandle::set_particle_property(F const &fun) const {
167 auto &cell_structure = get_cell_structure()->get_cell_structure();
168 auto const &comm = context()->get_comm();
169 auto const ptr = get_real_particle(comm, m_pid, cell_structure);
170 if (ptr != nullptr) {
171 fun(*ptr);
172 }
174}
175
176template <typename T>
177void ParticleHandle::set_particle_property(T &(Particle::*setter)(),
178 Variant const &value) const {
179 set_particle_property(
180 [&value, setter](Particle &p) { (p.*setter)() = get_value<T>(value); });
181}
182
183#ifdef ESPRESSO_EXCLUSIONS
184void ParticleHandle::set_exclusions(Variant const &value) {
185 std::vector<int> exclusion_list;
186 if (is_type<int>(value)) {
187 exclusion_list.emplace_back(get_value<int>(value));
188 } else {
189 exclusion_list = get_value<std::vector<int>>(value);
190 }
191 auto &cell_structure = get_cell_structure()->get_cell_structure();
192 context()->parallel_try_catch([&]() {
193 for (auto const pid : exclusion_list) {
194 particle_exclusion_sanity_checks(m_pid, pid, cell_structure,
195 context()->get_comm());
196 }
197 });
198 set_particle_property([&](Particle const &p) {
199 for (auto const pid : p.exclusions()) {
200 local_remove_exclusion(m_pid, pid, cell_structure);
201 }
202 for (auto const pid : exclusion_list) {
203 if (!p.has_exclusion(pid)) {
204 local_add_exclusion(m_pid, pid, cell_structure);
205 }
206 }
207 });
208}
209#endif // ESPRESSO_EXCLUSIONS
210
211ParticleHandle::ParticleHandle() {
212 /* Warning: the order of particle property setters matters! Some properties
213 * override each other, e.g. quat/director/dip or dip/dipm.
214 * This is relevant during checkpointing: all particle properties are set at
215 * once, in the order specified in the following call to `add_parameters()`.
216 */
217 add_parameters({
218 {"id", AutoParameter::read_only, [this]() { return m_pid; }},
219 {"type",
220 [this](Variant const &value) {
221 auto const new_type = get_value<int>(value);
222 if (new_type < 0) {
223 throw std::domain_error(
224 error_msg("type", "must be an integer >= 0"));
225 }
226 get_system()->nonbonded_ias->make_particle_type_exist(new_type);
227 set_particle_property(&Particle::type, value);
228 },
229 [this]() { return get_particle_data(m_pid).type(); }},
230 {"pos",
231 [this](Variant const &value) {
232 auto const pos = get_value<Utils::Vector3d>(value);
233 particle_checks(m_pid, pos);
234 set_particle_pos(m_pid, pos);
235 },
236 [this]() {
237 auto const p = get_particle_data(m_pid);
238 auto const pos = p.pos();
239 auto const image_box = p.image_box();
240 return get_system()->box_geo->unfolded_position(pos, image_box);
241 }},
242 {"v",
243 [this](Variant const &value) {
244 set_particle_property(&Particle::v, value);
245 },
246 [this]() { return get_particle_data(m_pid).v(); }},
247 {"f",
248 [this](Variant const &value) {
249 set_particle_property(&Particle::force, value);
250 },
251 [this]() { return get_particle_data(m_pid).force(); }},
252 {"mass",
253#ifdef ESPRESSO_MASS
254 [this](Variant const &value) {
255 if (get_value<double>(value) <= 0.) {
256 throw std::domain_error(error_msg("mass", "must be a float > 0"));
257 }
258 set_particle_property(&Particle::mass, value);
259 },
260#else // ESPRESSO_MASS
261 [](Variant const &value) {
262 auto const default_mass = Particle().mass();
263 if (std::abs(get_value<double>(value) - default_mass) > 1e-10) {
264 throw std::runtime_error("Feature MASS not compiled in");
265 }
266 },
267#endif // ESPRESSO_MASS
268 [this]() { return get_particle_data(m_pid).mass(); }},
269 {"q",
270#ifdef ESPRESSO_ELECTROSTATICS
271 [this](Variant const &value) {
272 set_particle_property(&Particle::q, value);
273 },
274#else // ESPRESSO_ELECTROSTATICS
275 [](Variant const &value) {
276 if (get_value<double>(value) != 0.) {
277 throw std::runtime_error("Feature ELECTROSTATICS not compiled in");
278 }
279 },
280#endif // ESPRESSO_ELECTROSTATICS
281 [this]() { return get_particle_data(m_pid).q(); }},
282#ifdef ESPRESSO_DIPOLES
283 {"dip",
284 [this](Variant const &value) {
285 set_particle_property([&value](Particle &p) {
286 auto const dip = get_value<Utils::Vector3d>(value);
287 std::tie(p.quat(), p.dipm()) = convert_dip_to_quat(dip);
288 });
289 },
290 [this]() { return get_particle_data(m_pid).calc_dip(); }},
291 {"dipm",
292 [this](Variant const &value) {
293 set_particle_property(&Particle::dipm, value);
294 },
295 [this]() { return get_particle_data(m_pid).dipm(); }},
296#endif // ESPRESSO_DIPOLES
297#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
298 {"dip_fld",
299 [this](Variant const &value) {
300 set_particle_property(&Particle::dip_fld, value);
301 },
302 [this]() { return get_particle_data(m_pid).dip_fld(); }},
303#endif
304#ifdef ESPRESSO_THERMAL_STONER_WOHLFARTH
305 {"magnetodynamics",
306 [this](Variant const &value) {
307 set_particle_property([&value](Particle &p) {
308 auto const dict = get_value<VariantMap>(value);
309 if (dict.contains("is_enabled"))
311 get_value<bool>(dict.at("is_enabled"));
312 if (dict.contains("sw_phi_0"))
314 get_value<double>(dict.at("sw_phi_0"));
315 if (dict.contains("sat_mag"))
317 get_value<double>(dict.at("sat_mag"));
318 if (dict.contains("anisotropy_field_inv"))
320 get_value<double>(dict.at("anisotropy_field_inv"));
321 if (dict.contains("anisotropy_energy"))
323 get_value<double>(dict.at("anisotropy_energy"));
324 if (dict.contains("sw_tau0_inv"))
326 get_value<double>(dict.at("sw_tau0_inv"));
327 if (dict.contains("sw_dt_incr"))
329 get_value<double>(dict.at("sw_dt_incr"));
330 });
331 },
332 [this]() {
333 auto const &p = get_particle_data(m_pid);
334 return VariantMap{
335 {"is_enabled", p.stoner_wohlfarth_is_enabled()},
336 {"sw_phi_0", p.stoner_wohlfarth_phi_0()},
337 {"sat_mag", p.saturation_magnetization()},
338 {"anisotropy_field_inv", p.magnetic_anisotropy_field_inv()},
339 {"anisotropy_energy", p.magnetic_anisotropy_energy()},
340 {"sw_tau0_inv", p.stoner_wohlfarth_tau0_inv()},
341 {"sw_dt_incr", p.stoner_wohlfarth_dt_incr()},
342 };
343 }},
344#endif // ESPRESSO_THERMAL_STONER_WOHLFARTH
345#ifdef ESPRESSO_ROTATION
346 {"director",
347 [this](Variant const &value) {
348 set_particle_property([&value](Particle &p) {
349 auto const director = get_value<Utils::Vector3d>(value).normalized();
351 });
352 },
353 [this]() {
354 auto const quat = get_particle_data(m_pid).quat();
356 }},
357 {"quat",
358 [this](Variant const &value) {
359 auto const quat = get_quaternion_safe("quat", value);
360 set_particle_property([&quat](Particle &p) { p.quat() = quat; });
361 },
362 [this]() { return quat2vector(get_particle_data(m_pid).quat()); }},
363 {"omega_body",
364 [this](Variant const &value) {
365 set_particle_property(&Particle::omega, value);
366 },
367 [this]() { return get_particle_data(m_pid).omega(); }},
368 {"rotation",
369 [this](Variant const &value) {
370 set_particle_property([&value](Particle &p) {
371 auto const rotation_flag =
372 Utils::Vector3i{get_value<Utils::Vector3b>(value)};
373 p.rotation() = bitfield_from_flag(rotation_flag);
374 });
375 },
376 [this]() {
377 auto const rotation_bits = get_particle_data(m_pid).rotation();
378 return Utils::Vector3b{{::detail::get_nth_bit(rotation_bits, 0),
379 ::detail::get_nth_bit(rotation_bits, 1),
380 ::detail::get_nth_bit(rotation_bits, 2)}};
381 }},
382 {"omega_lab",
383 [this](Variant const &value) {
384 set_particle_property([&value](Particle &p) {
385 auto const omega = get_value<Utils::Vector3d>(value);
386 p.omega() = convert_vector_space_to_body(p, omega);
387 });
388 },
389 [this]() {
390 auto &p = get_particle_data(m_pid);
391 return convert_vector_body_to_space(p, p.omega());
392 }},
393 {"torque_lab",
394 [this](Variant const &value) {
395 set_particle_property([&value](Particle &p) {
396 auto const torque = get_value<Utils::Vector3d>(value);
397 p.torque() = convert_vector_space_to_body(p, torque);
398 });
399 },
400 [this]() {
401 auto &p = get_particle_data(m_pid);
403 }},
404#endif // ESPRESSO_ROTATION
405#ifdef ESPRESSO_ROTATIONAL_INERTIA
406 {"rinertia",
407 [this](Variant const &value) {
408 set_particle_property(&Particle::rinertia, value);
409 },
410 [this]() { return get_particle_data(m_pid).rinertia(); }},
411#endif // ESPRESSO_ROTATIONAL_INERTIA
412#ifdef ESPRESSO_LB_ELECTROHYDRODYNAMICS
413 {"mu_E",
414 [this](Variant const &value) {
415 set_particle_property(&Particle::mu_E, value);
416 },
417 [this]() { return get_particle_data(m_pid).mu_E(); }},
418#endif // ESPRESSO_LB_ELECTROHYDRODYNAMICS
419#ifdef ESPRESSO_EXTERNAL_FORCES
420 {"fix",
421 [this](Variant const &value) {
422 set_particle_property([&value](Particle &p) {
423 auto const fix_flag =
424 Utils::Vector3i{(get_value<Utils::Vector3b>(value))};
425 p.fixed() = bitfield_from_flag(fix_flag);
426 });
427 },
428 [this]() {
429 auto const fixed = get_particle_data(m_pid).fixed();
430 return Utils::Vector3b{{::detail::get_nth_bit(fixed, 0),
431 ::detail::get_nth_bit(fixed, 1),
432 ::detail::get_nth_bit(fixed, 2)}};
433 }},
434 {"ext_force",
435 [this](Variant const &value) {
436 set_particle_property(&Particle::ext_force, value);
437 },
438 [this]() { return get_particle_data(m_pid).ext_force(); }},
439#ifdef ESPRESSO_ROTATION
440 {"ext_torque",
441 [this](Variant const &value) {
442 set_particle_property(&Particle::ext_torque, value);
443 },
444 [this]() { return get_particle_data(m_pid).ext_torque(); }},
445#endif // ESPRESSO_ROTATION
446#endif // ESPRESSO_EXTERNAL_FORCES
447#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
448 {"gamma",
449 [this](Variant const &value) {
450 set_particle_property(&Particle::gamma,
451 Variant{get_gamma_safe(value)});
452 },
453 [this]() { return get_particle_data(m_pid).gamma(); }},
454#ifdef ESPRESSO_ROTATION
455 {"gamma_rot",
456 [this](Variant const &value) {
457 set_particle_property(&Particle::gamma_rot,
458 Variant{get_gamma_safe(value)});
459 },
460 [this]() { return get_particle_data(m_pid).gamma_rot(); }},
461#endif // ESPRESSO_ROTATION
462#endif // ESPRESSO_THERMOSTAT_PER_PARTICLE
463 {"pos_folded", AutoParameter::read_only,
464 [this]() {
465 auto const &box_geo = *get_system()->box_geo;
466 return box_geo.folded_position(get_particle_data(m_pid).pos());
467 }},
468 {"lees_edwards_offset",
469 [this](Variant const &value) {
470 set_particle_property(&Particle::lees_edwards_offset, value);
471 },
472 [this]() { return get_particle_data(m_pid).lees_edwards_offset(); }},
473 {"lees_edwards_flag", AutoParameter::read_only,
474 [this]() { return get_particle_data(m_pid).lees_edwards_flag(); }},
475 {"image_box", AutoParameter::read_only,
476 [this]() {
477 auto const &box_geo = *get_system()->box_geo;
478 auto const p = get_particle_data(m_pid);
479 return box_geo.folded_image_box(p.pos(), p.image_box());
480 }},
481 {"node", AutoParameter::read_only,
482 [this]() {
483 return (context()->is_head_node()) ? get_particle_node(m_pid) : -1;
484 }},
485 {"mol_id",
486 [this](Variant const &value) {
487 auto const mol_id = get_value<int>(value);
488 if (mol_id < 0) {
489 throw std::domain_error(
490 error_msg("mol_id", "must be an integer >= 0"));
491 }
492 set_particle_property(&Particle::mol_id, Variant{mol_id});
493 },
494 [this]() { return get_particle_data(m_pid).mol_id(); }},
495#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
496 {"vs_quat",
497 [this](Variant const &value) {
498 auto const quat = get_quaternion_safe("vs_quat", value);
499 set_particle_property(
500 [&quat](Particle &p) { p.vs_relative().quat = quat; });
501 },
502 [this]() {
503 return quat2vector(get_particle_data(m_pid).vs_relative().quat);
504 }},
505 {"vs_relative",
506 [this](Variant const &value) {
508 try {
509 auto const array = get_value<std::vector<Variant>>(value);
510 if (array.size() != 3) {
511 throw 0;
512 }
513 vs_relative.distance = get_value<double>(array[1]);
514 vs_relative.to_particle_id = get_value<int>(array[0]);
515 vs_relative.rel_orientation =
516 get_quaternion_safe("vs_relative", array[2]);
517 } catch (...) {
518 throw std::invalid_argument(error_msg(
519 "vs_relative", "must take the form [id, distance, quaternion]"));
520 }
521 set_particle_property(
522 [&vs_relative](Particle &p) { p.vs_relative() = vs_relative; });
523 },
524 [this]() {
525 auto const vs_rel = get_particle_data(m_pid).vs_relative();
526 return std::vector<Variant>{{vs_rel.to_particle_id, vs_rel.distance,
527 quat2vector(vs_rel.rel_orientation)}};
528 }},
529#endif // ESPRESSO_VIRTUAL_SITES_RELATIVE
530 {"propagation",
531 [this](Variant const &value) {
532 auto const propagation = get_value<int>(value);
533 if (!is_valid_propagation_combination(propagation)) {
534 throw std::domain_error(error_msg(
535 "propagation", "propagation combination not accepted: " +
536 propagation_bitmask_to_string(propagation)));
537 }
538 set_particle_property(&Particle::propagation, value);
539 },
540 [this]() { return get_particle_data(m_pid).propagation(); }},
541#ifdef ESPRESSO_ENGINE
542 {"swimming",
543 [this](Variant const &value) {
544 set_particle_property([&value](Particle &p) {
546 swim.swimming = true;
547 auto const dict = get_value<VariantMap>(value);
548 if (dict.contains("f_swim")) {
549 swim.f_swim = get_value<double>(dict.at("f_swim"));
550 }
551 if (dict.contains("is_engine_force_on_fluid")) {
552 auto const is_engine_force_on_fluid =
553 get_value<bool>(dict.at("is_engine_force_on_fluid"));
554 swim.is_engine_force_on_fluid = is_engine_force_on_fluid;
555 }
556 p.swimming() = swim;
557 });
558 },
559 [this]() {
560 auto const swim = get_particle_data(m_pid).swimming();
561 return VariantMap{
562 {"f_swim", swim.f_swim},
563 {"is_engine_force_on_fluid", swim.is_engine_force_on_fluid},
564 };
565 }},
566#endif // ESPRESSO_ENGINE
567 });
568}
569
570Variant ParticleHandle::do_call_method(std::string const &name,
571 VariantMap const &params) {
572 if (name == "set_param_parallel") {
573 auto const param_name = get_value<std::string>(params, "name");
574 if (not params.contains("value")) {
575 throw Exception("Parameter '" + param_name + "' is missing.");
576 }
577 auto const &value = params.at("value");
578 context()->parallel_try_catch(
579 [&]() { do_set_parameter(param_name, value); });
580 return {};
581 }
582 if (name == "update_params") {
583 // Set new properties
584 context()->parallel_try_catch([&]() {
585#ifdef ESPRESSO_ROTATION
587#endif
588 for (auto const &name : get_parameter_insertion_order()) {
589 if (params.contains(name) and name != "bonds") {
590 do_set_parameter(name, params.at(name));
591 }
592 }
593 });
594
595 // Set bonds
596 if (params.contains("bonds_ids")) {
597 // Remove old bonds
598 set_particle_property([&](Particle &p) { p.bonds().clear(); });
599 // Add new bonds
600 auto const bonds_ids = get_value<std::vector<int>>(params, "bonds_ids");
601 auto const bonds_partner_ids =
602 get_value<std::vector<std::vector<int>>>(params, "bonds_parts");
603 for (std::size_t i = 0; i < bonds_ids.size(); i += 1) {
604 std::vector<int> particle_ids = {m_pid};
605 std::ranges::copy(bonds_partner_ids[i],
606 std::back_inserter(particle_ids));
607 ::add_bond(*get_system(), bonds_ids[i], particle_ids);
608 get_system()->on_particle_change();
609 }
610 }
611#ifdef ESPRESSO_EXCLUSIONS
612 // set exclusions
613 if (params.contains("exclusions")) {
614 set_exclusions(params.at("exclusions"));
615 }
616#endif // ESPRESSO_EXCLUSIONS
617 return {};
618 }
619 if (name == "get_bond_by_id") {
620 if (not context()->is_head_node()) {
621 return {};
622 }
623 return get_bonded_ias()->call_method("get_bond", params);
624 }
625 if (name == "get_bonds_view") {
626 if (not context()->is_head_node()) {
627 return {};
628 }
629 auto const bond_list = get_particle_data(m_pid).bonds();
630 std::vector<std::vector<Variant>> bonds_flat;
631 for (auto const &&bond_view : bond_list) {
632 std::vector<Variant> bond_flat;
633 bond_flat.emplace_back(bond_view.bond_id());
634 for (auto const pid : bond_view.partner_ids()) {
635 bond_flat.emplace_back(pid);
636 }
637 bonds_flat.emplace_back(std::move(bond_flat));
638 }
639 return make_vector_of_variants(bonds_flat);
640 }
641 if (name == "add_bond") {
642 auto const bond_id = get_value<int>(params, "bond_id");
643 auto const partner_ids = get_value<std::vector<int>>(params, "part_id");
644 std::vector<int> particle_ids = {m_pid};
645 std::ranges::copy(partner_ids, std::back_inserter(particle_ids));
646 ::add_bond(*get_system(), bond_id, particle_ids);
647 get_system()->on_particle_change();
648 } else if (name == "del_bond") {
649 set_particle_property([&params](Particle &p) {
650 auto const bond_id = get_value<int>(params, "bond_id");
651 auto const part_id = get_value<std::vector<int>>(params, "part_id");
652 auto const bond_view =
653 BondView(bond_id, {part_id.data(), part_id.size()});
654 auto &bond_list = p.bonds();
655 auto it = std::find(bond_list.begin(), bond_list.end(), bond_view);
656 if (it != bond_list.end()) {
657 bond_list.erase(it);
658 }
659 });
660 } else if (name == "delete_all_bonds") {
661 set_particle_property([&](Particle &p) { p.bonds().clear(); });
662 } else if (name == "is_valid_bond_id") {
663 auto const bond_id = get_value<int>(params, "bond_id");
664 return get_system()->bonded_ias->get_zero_based_type(bond_id) != 0;
665 }
666 if (name == "remove_particle") {
667 context()->parallel_try_catch([&]() {
668 auto &cell_structure = get_cell_structure()->get_cell_structure();
669 std::ignore =
670 get_real_particle(context()->get_comm(), m_pid, cell_structure);
671 remove_particle(m_pid);
672 });
673 } else if (name == "is_virtual") {
674 if (not context()->is_head_node()) {
675 return {};
676 }
677 return get_particle_data(m_pid).is_virtual();
678#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
679 } else if (name == "vs_auto_relate_to") {
680 if (not context()->is_head_node()) {
681 return {};
682 }
683 auto const other_pid = get_value<int>(params, "pid");
684 auto const override_cutoff_check =
685 get_value<bool>(params, "override_cutoff_check");
686 if (m_pid == other_pid) {
687 throw std::invalid_argument("A virtual site cannot relate to itself");
688 }
689 if (other_pid < 0) {
690 throw std::domain_error("Invalid particle id: " +
691 std::to_string(other_pid));
692 }
693 auto const system = get_system();
694 /* note: this code can be rewritten as parallel code, but only with a call
695 * to `cells_update_ghosts(DATA_PART_POSITION | DATA_PART_PROPERTIES)`, as
696 * there is no guarantee the virtual site has visibility of the relative
697 * particle through the ghost layer during particle creation. However,
698 * ghost updates can scramble the particle ordering in the local cells,
699 * which is an issue for checkpointing: the H5MD writer will use the
700 * scrambled ordering before writing to a checkpoint file and the
701 * non-scrambled ordering after reloading from a checkpoint file.
702 */
703 auto const &p_current = get_particle_data(m_pid);
704 auto const &p_relate_to = get_particle_data(other_pid);
705 auto const [quat, dist] = calculate_vs_relate_to_params(
706 p_current, p_relate_to, *system->box_geo, system->get_min_global_cut(),
707 override_cutoff_check);
708 set_parameter("vs_relative", Variant{std::vector<Variant>{
709 {other_pid, dist, quat2vector(quat)}}});
710 set_parameter("propagation",
713#endif // ESPRESSO_VIRTUAL_SITES_RELATIVE
714#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
715 } else if (name == "vs_com_relate_to") {
716 auto &cell_structure = get_cell_structure()->get_cell_structure();
717 auto const molid = get_value<int>(params, "molid");
718 auto const maybe_exists_vs = get_pid_for_vs_com(cell_structure, molid);
719 if (not context()->is_head_node()) {
720 return {};
721 }
722 if (molid < 0) {
723 throw std::domain_error("Invalid molecule id: " + std::to_string(molid));
724 }
725 if (maybe_exists_vs) {
726 throw std::runtime_error(
727 "Molecule id: " + std::to_string(molid) +
728 " is already tracked by virtual site with particle id: " +
729 std::to_string(*maybe_exists_vs));
730 }
731 set_parameter("mol_id", params.at("molid"));
732 set_parameter(
733 "propagation",
735#endif // ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
736#ifdef ESPRESSO_EXCLUSIONS
737 } else if (name == "has_exclusion") {
738 auto const other_pid = get_value<int>(params, "pid");
739 auto &cell_structure = get_cell_structure()->get_cell_structure();
740 auto const p =
741 get_real_particle(context()->get_comm(), m_pid, cell_structure);
742 if (p != nullptr) {
743 return p->has_exclusion(other_pid);
744 }
745 }
746 if (name == "add_exclusion") {
747 auto const other_pid = get_value<int>(params, "pid");
748 auto &cell_structure = get_cell_structure()->get_cell_structure();
749 context()->parallel_try_catch([&]() {
750 particle_exclusion_sanity_checks(m_pid, other_pid, cell_structure,
751 context()->get_comm());
752 });
753 local_add_exclusion(m_pid, other_pid, cell_structure);
754 get_system()->on_particle_change();
755 } else if (name == "del_exclusion") {
756 auto const other_pid = get_value<int>(params, "pid");
757 auto &cell_structure = get_cell_structure()->get_cell_structure();
758 context()->parallel_try_catch([&]() {
759 particle_exclusion_sanity_checks(m_pid, other_pid, cell_structure,
760 context()->get_comm());
761 });
762 local_remove_exclusion(m_pid, other_pid, cell_structure);
763 get_system()->on_particle_change();
764#ifdef ESPRESSO_EXCLUSIONS
765 } else if (name == "set_exclusions") {
766 set_exclusions(params.at("p_ids"));
767#endif // ESPRESSO_EXCLUSIONS
768 } else if (name == "get_exclusions") {
769 if (not context()->is_head_node()) {
770 return {};
771 }
772 auto const excl_list = get_particle_data(m_pid).exclusions();
773 return Variant{std::vector<int>{excl_list.begin(), excl_list.end()}};
774#endif // ESPRESSO_EXCLUSIONS
775#ifdef ESPRESSO_ROTATION
776 }
777 if (name == "rotate_particle") {
778 set_particle_property([&params](Particle &p) {
779 auto const axis = get_value<Utils::Vector3d>(params, "axis");
780 auto const angle = get_value<double>(params, "angle");
781 local_rotate_particle(p, axis, angle);
782 });
783 }
784 if (name == "convert_vector_body_to_space") {
785 return get_particle_property<std::vector<double>>(
786 [&params](Particle const &p) {
787 auto const vec = get_value<Utils::Vector3d>(params, "vec");
789 });
790 }
791 if (name == "convert_vector_space_to_body") {
792 return get_particle_property<std::vector<double>>(
793 [&params](Particle const &p) {
794 auto const vec = get_value<Utils::Vector3d>(params, "vec");
796 });
797#endif // ESPRESSO_ROTATION
798 }
799 return {};
800}
801
802std::size_t ParticleHandle::setup_hidden_args(VariantMap const &params) {
803 auto n_extra_args = params.size() - params.count("id");
804 if (params.contains("__cell_structure")) {
805 auto so = get_value<std::shared_ptr<CellSystem::CellSystem>>(
806 params, "__cell_structure");
807 so->configure(*this);
808 m_cell_structure = so;
809 --n_extra_args;
810 }
811 if (params.contains("__bonded_ias")) {
812 m_bonded_ias = get_value<std::shared_ptr<Interactions::BondedInteractions>>(
813 params, "__bonded_ias");
814 --n_extra_args;
815 }
816 return n_extra_args;
817}
818
819void ParticleHandle::do_construct(VariantMap const &params) {
820 auto const n_extra_args = setup_hidden_args(params);
821 m_pid = (params.contains("id")) ? get_value<int>(params, "id")
823
824#ifndef NDEBUG
825 if (not params.contains("id")) {
826 auto head_node_reference = m_pid;
827 boost::mpi::broadcast(context()->get_comm(), head_node_reference, 0);
828 assert(m_pid == head_node_reference && "global max_seen_pid has diverged");
829 }
830#endif
831
832 // create a new particle if extra arguments were passed
833 if (n_extra_args == 0) {
834 return;
835 }
836
837 auto const pos = get_value<Utils::Vector3d>(params, "pos");
838 context()->parallel_try_catch([&]() {
839 particle_checks(m_pid, pos);
840 auto &cell_structure = get_cell_structure()->get_cell_structure();
841 auto ptr = cell_structure.get_local_particle(m_pid);
842 if (ptr != nullptr) {
843 throw std::invalid_argument("Particle " + std::to_string(m_pid) +
844 " already exists");
845 }
846 });
847
848#ifdef ESPRESSO_ROTATION
849 context()->parallel_try_catch([&]() { sanity_checks_rotation(params); });
850#endif // ESPRESSO_ROTATION
851
852 // create a default-constructed particle
853 make_new_particle(m_pid, pos);
854
855 try {
856 context()->parallel_try_catch([&]() {
857 /* clang-format off */
858 // set particle properties (filter out read-only and deferred properties)
859 std::set<std::string_view> const skip = {
860 "pos_folded", "pos", "id", "node", "image_box", "bonds",
861#ifdef ESPRESSO_EXCLUSIONS
862 "exclusions",
863#endif // ESPRESSO_EXCLUSIONS
864 "lees_edwards_flag", "__cpt_sentinel",
865 };
866 /* clang-format on */
867 for (auto const &name : get_parameter_insertion_order()) {
868 if (params.contains(name) and not skip.contains(name)) {
869 do_set_parameter(name, params.at(name));
870 }
871 }
872 for (auto const &name : params | std::views::keys) {
873 if (not skip.contains(name) and not name.starts_with('_') and
874 not has_parameter(name)) {
875 auto error_msg = "Unknown parameter '" + name + "' for particle.";
876 std::string hint = "Hint: a feature is probably not compiled in.";
877 throw std::invalid_argument(error_msg + " " + hint);
878 }
879 }
880 if (not params.contains("type")) {
881 do_set_parameter("type", 0);
882 }
883#ifdef ESPRESSO_EXCLUSIONS
884 if (params.contains("exclusions")) {
885 do_call_method("set_exclusions", {{"p_ids", params.at("exclusions")}});
886 }
887#endif // ESPRESSO_EXCLUSIONS
888 });
889 } catch (...) {
890 remove_particle(m_pid);
891 throw;
892 }
893}
894
895} // namespace Particles
896} // namespace ScriptInterface
static auto get_real_particle(boost::mpi::communicator const &comm, int p_id)
Vector implementation and trait types for boost qvm interoperability.
Data structures for bonded interactions.
bool add_bond(System::System &system, int bond_id, std::vector< int > const &particle_ids)
Add a bond to a particle.
Definition bonds.cpp:25
Immutable view on a bond.
Definition BondList.hpp:44
virtual boost::mpi::communicator const & get_comm() const =0
Context * context() const
Responsible context.
std::shared_ptr< BondedInteractionsMap > bonded_ias
void on_particle_change()
Called every time a particle property changes.
std::shared_ptr< BoxGeometry > box_geo
std::shared_ptr< InteractionsNonBonded > nonbonded_ias
std::vector< T > as_vector() const
Definition Vector.hpp:142
static DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:134
std::optional< int > get_pid_for_vs_com(CellStructure &cell_structure, int mol_id)
Definition com.cpp:97
static uint8_t bitfield_from_flag(Utils::Vector3i const &flag)
static void sanity_checks_rotation(VariantMap const &params)
static auto get_quaternion_safe(std::string const &name, Variant const &value)
auto get_real_particle(boost::mpi::communicator const &comm, int p_id, ::CellStructure &cell_structure)
void particle_exclusion_sanity_checks(int pid1, int pid2, ::CellStructure &cell_structure, auto const &comm)
static auto get_gamma_safe(Variant const &value)
static auto quat2vector(Utils::Quaternion< double > const &q)
void local_remove_exclusion(int pid1, int pid2, ::CellStructure &cell_structure)
Locally remove an exclusion to a particle.
void particle_checks(int p_id, Utils::Vector3d const &pos)
auto error_msg(std::string const &name, std::string const &reason)
static std::array< std::array< std::string_view, 3 >, 4 > constexpr contradicting_arguments_quat
void local_add_exclusion(int pid1, int pid2, ::CellStructure &cell_structure)
Locally add an exclusion to a particle.
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_vector_of_variants(std::vector< T > const &v)
Definition Variant.hpp:148
make_recursive_variant< ObjectRef > Variant
Possible types for parameters.
Definition Variant.hpp:131
System & get_system()
T reduce_optional(boost::mpi::communicator const &comm, std::optional< T > const &result)
Reduce an optional on the head node.
constexpr Vector< T, 3 > convert_quaternion_to_director(Quaternion< T > const &quat)
Convert quaternion to director.
Quaternion< T > convert_director_to_quaternion(Vector< T, 3 > const &d)
Convert director to quaternion.
Various procedures concerning interactions between particles.
void make_new_particle(int p_id, Utils::Vector3d const &pos)
Create a new particle and attach it to a cell.
const Particle & get_particle_data(int p_id)
Get particle data.
int get_particle_node(int p_id)
Get the MPI rank which owns the a specific particle.
void set_particle_pos(int p_id, Utils::Vector3d const &pos)
Move particle to a new position.
void remove_particle(int p_id)
Remove particle with a given identity.
int get_maximal_particle_id()
Get maximal particle id.
static auto & get_cell_structure()
Particles creation and deletion.
std::string propagation_bitmask_to_string(int propagation)
Convert a propagation modes bitmask to a string.
bool is_valid_propagation_combination(int propagation)
Note for developers: when enabling new propagation mode combinations, make sure every single line of ...
This file contains all subroutines required to process rotational motion.
Utils::Vector3d convert_vector_body_to_space(const Particle &p, const Utils::Vector3d &vec)
Definition rotation.hpp:58
std::pair< Utils::Quaternion< double >, double > convert_dip_to_quat(const Utils::Vector3d &dip)
convert a dipole moment to quaternions and dipolar strength
Definition rotation.hpp:109
Utils::Vector3d convert_vector_space_to_body(const Particle &p, const Utils::Vector3d &v)
Definition rotation.hpp:62
void local_rotate_particle(Particle &p, const Utils::Vector3d &axis_space_frame, const double phi)
Rotate the particle p around the NORMALIZED axis aSpaceFrame by amount phi.
Definition rotation.hpp:134
Properties of a self-propelled particle.
Definition Particle.hpp:51
bool swimming
Is the particle a swimmer.
Definition Particle.hpp:55
The following properties define, with respect to which real particle a virtual site is placed and at ...
Definition Particle.hpp:190
Struct holding all information for one particle.
Definition Particle.hpp:436
constexpr auto const & dip_fld() const
Definition Particle.hpp:590
bool has_exclusion(int pid) const
Definition Particle.hpp:668
constexpr auto const & bonds() const
Definition Particle.hpp:473
constexpr auto const & magnetic_anisotropy_field_inv() const
Definition Particle.hpp:564
constexpr auto const & stoner_wohlfarth_is_enabled() const
Definition Particle.hpp:548
constexpr auto const & quat() const
Definition Particle.hpp:527
constexpr auto calc_dip() const
Definition Particle.hpp:545
constexpr auto const & pos() const
Definition Particle.hpp:476
constexpr auto const & swimming() const
Definition Particle.hpp:653
constexpr auto const & rinertia() const
Definition Particle.hpp:593
constexpr auto const & mass() const
Definition Particle.hpp:500
constexpr auto const & dipm() const
Definition Particle.hpp:543
Utils::compact_vector< int > & exclusions()
Definition Particle.hpp:666
constexpr auto const & type() const
Definition Particle.hpp:459
constexpr auto const & omega() const
Definition Particle.hpp:531
constexpr auto const & saturation_magnetization() const
Definition Particle.hpp:558
constexpr auto const & stoner_wohlfarth_dt_incr() const
Definition Particle.hpp:582
constexpr auto const & magnetic_anisotropy_energy() const
Definition Particle.hpp:570
constexpr auto const & ext_force() const
Definition Particle.hpp:646
constexpr auto const & propagation() const
Definition Particle.hpp:462
constexpr auto const & ext_torque() const
Definition Particle.hpp:534
constexpr auto const & rotation() const
Definition Particle.hpp:505
constexpr auto const & force() const
Definition Particle.hpp:480
constexpr auto is_virtual() const
Definition Particle.hpp:606
constexpr auto const & vs_relative() const
Definition Particle.hpp:617
constexpr auto const & fixed() const
Definition Particle.hpp:629
constexpr auto const & gamma() const
Definition Particle.hpp:621
constexpr auto const & gamma_rot() const
Definition Particle.hpp:624
constexpr auto const & image_box() const
Definition Particle.hpp:489
constexpr auto const & mu_E() const
Definition Particle.hpp:602
constexpr auto const & stoner_wohlfarth_tau0_inv() const
Definition Particle.hpp:576
constexpr auto const & mol_id() const
Definition Particle.hpp:457
constexpr auto const & q() const
Definition Particle.hpp:597
constexpr auto const & stoner_wohlfarth_phi_0() const
Definition Particle.hpp:554
constexpr auto const & v() const
Definition Particle.hpp:478
constexpr auto const & torque() const
Definition Particle.hpp:529
constexpr auto const & lees_edwards_flag() const
Definition Particle.hpp:495
constexpr auto const & lees_edwards_offset() const
Definition Particle.hpp:491
Recursive variant implementation.
Definition Variant.hpp:84
Quaternion representation.
std::tuple< Utils::Quaternion< double >, double > calculate_vs_relate_to_params(Particle const &p_vs, Particle const &p_relate_to, BoxGeometry const &box_geo, double min_global_cut, bool override_cutoff_check)
Calculate the rotation quaternion and distance between two particles.