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