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