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
26
27#include "core/BoxGeometry.hpp"
31#include "core/exclusions.hpp"
34#include "core/propagation.hpp"
35#include "core/rotation.hpp"
38
39#include <utils/Vector.hpp>
41
42#include <boost/format.hpp>
43#include <boost/mpi/collectives/all_reduce.hpp>
44#include <boost/mpi/collectives/broadcast.hpp>
45#include <boost/mpi/communicator.hpp>
46
47#include <algorithm>
48#include <cassert>
49#include <cmath>
50#include <cstddef>
51#include <memory>
52#include <optional>
53#include <set>
54#include <sstream>
55#include <stdexcept>
56#include <string>
57#include <tuple>
58#include <type_traits>
59#include <vector>
60
61namespace ScriptInterface {
62namespace Particles {
63
64static void particle_checks(int p_id, Utils::Vector3d const &pos) {
65 if (p_id < 0) {
66 throw std::domain_error("Invalid particle id: " + std::to_string(p_id));
67 }
68#ifndef __FAST_MATH__
69 if (std::isnan(pos[0]) or std::isnan(pos[1]) or std::isnan(pos[2]) or
70 std::isinf(pos[0]) or std::isinf(pos[1]) or std::isinf(pos[2])) {
71 throw std::domain_error("Particle position must be finite");
72 }
73#endif // __FAST_MATH__
74}
75
76static uint8_t bitfield_from_flag(Utils::Vector3i const &flag) {
77 auto bitfield = static_cast<uint8_t>(0u);
78 if (flag[0])
79 bitfield |= static_cast<uint8_t>(1u);
80 if (flag[1])
81 bitfield |= static_cast<uint8_t>(2u);
82 if (flag[2])
83 bitfield |= static_cast<uint8_t>(4u);
84 return bitfield;
85}
86
87static auto error_msg(std::string const &name, std::string const &reason) {
88 std::stringstream msg;
89 msg << "attribute '" << name << "' of 'ParticleHandle' " << reason;
90 return msg.str();
91}
92
94 return Utils::Vector4d{{q[0], q[1], q[2], q[3]}};
95}
96
97static auto get_quaternion_safe(std::string const &name, Variant const &value) {
98 auto const q = get_value<Utils::Vector4d>(value);
99 if (q.norm2() == 0.) {
100 throw std::domain_error(error_msg(name, "must be non-zero"));
101 }
102 return Utils::Quaternion<double>{{q[0], q[1], q[2], q[3]}};
103}
104
105#ifdef THERMOSTAT_PER_PARTICLE
106static auto get_gamma_safe(Variant const &value) {
107#ifdef PARTICLE_ANISOTROPY
108 try {
109 return Utils::Vector3d::broadcast(get_value<double>(value));
110 } catch (...) {
111 return get_value<Utils::Vector3d>(value);
112 }
113#else // PARTICLE_ANISOTROPY
114 return get_value<double>(value);
115#endif // PARTICLE_ANISOTROPY
116}
117#endif // THERMOSTAT_PER_PARTICLE
118
119static auto get_real_particle(boost::mpi::communicator const &comm, int p_id) {
120 if (p_id < 0) {
121 throw std::domain_error("Invalid particle id: " + std::to_string(p_id));
122 }
123 auto const &system = ::System::get_system();
124 auto &cell_structure = *system.cell_structure;
125 auto ptr = cell_structure.get_local_particle(p_id);
126 if (ptr != nullptr and ptr->is_ghost()) {
127 ptr = nullptr;
128 }
129 auto const n_found = boost::mpi::all_reduce(
130 comm, static_cast<int>(ptr != nullptr), std::plus<>());
131 if (n_found == 0) {
132 throw std::runtime_error("Particle with id " + std::to_string(p_id) +
133 " not found");
134 }
135 return ptr;
136}
137
138template <typename T, class F>
139T ParticleHandle::get_particle_property(F const &fun) const {
140 auto const &comm = context()->get_comm();
141 auto const ptr = const_cast<Particle const *>(get_real_particle(comm, m_pid));
142 std::optional<T> ret;
143 if (ptr == nullptr) {
144 ret = {};
145 } else {
146 ret = {fun(*ptr)};
147 }
148 return Utils::Mpi::reduce_optional(comm, ret);
149}
150
151template <typename T>
152T ParticleHandle::get_particle_property(T const &(Particle::*getter)()
153 const) const {
154 return get_particle_property<T>(
155 [getter](Particle const &p) { return (p.*getter)(); });
156}
157
158template <class F>
159void ParticleHandle::set_particle_property(F const &fun) const {
160 auto const &comm = context()->get_comm();
161 auto const ptr = get_real_particle(comm, m_pid);
162 if (ptr != nullptr) {
163 fun(*ptr);
164 }
166}
167
168template <typename T>
169void ParticleHandle::set_particle_property(T &(Particle::*setter)(),
170 Variant const &value) const {
171 set_particle_property(
172 [&value, setter](Particle &p) { (p.*setter)() = get_value<T>(value); });
173}
174
175ParticleHandle::ParticleHandle() {
176 add_parameters({
177 {"id", AutoParameter::read_only, [this]() { return m_pid; }},
178 {"type",
179 [this](Variant const &value) {
180 auto const old_type = get_particle_property(&Particle::type);
181 auto const new_type = get_value<int>(value);
182 if (new_type < 0) {
183 throw std::domain_error(
184 error_msg("type", "must be an integer >= 0"));
185 }
186 System::get_system().nonbonded_ias->make_particle_type_exist(new_type);
187 on_particle_type_change(m_pid, old_type, new_type);
188 set_particle_property(&Particle::type, value);
189 },
190 [this]() { return get_particle_data(m_pid).type(); }},
191 {"pos",
192 [this](Variant const &value) {
193 auto const pos = get_value<Utils::Vector3d>(value);
194 particle_checks(m_pid, pos);
195 set_particle_pos(m_pid, pos);
196 },
197 [this]() {
198 auto const p = get_particle_data(m_pid);
199 auto const pos = p.pos();
200 auto const image_box = p.image_box();
201 return System::get_system().box_geo->unfolded_position(pos, image_box);
202 }},
203 {"v",
204 [this](Variant const &value) {
205 set_particle_property(&Particle::v, value);
206 },
207 [this]() { return get_particle_data(m_pid).v(); }},
208 {"f",
209 [this](Variant const &value) {
210 set_particle_property(&Particle::force, value);
211 },
212 [this]() { return get_particle_data(m_pid).force(); }},
213 {"mass",
214#ifdef MASS
215 [this](Variant const &value) {
216 if (get_value<double>(value) <= 0.) {
217 throw std::domain_error(error_msg("mass", "must be a float > 0"));
218 }
219 set_particle_property(&Particle::mass, value);
220 },
221#else // MASS
222 [](Variant const &value) {
223 auto const default_mass = Particle().mass();
224 if (std::abs(get_value<double>(value) - default_mass) > 1e-10) {
225 throw std::runtime_error("Feature MASS not compiled in");
226 }
227 },
228#endif // MASS
229 [this]() { return get_particle_data(m_pid).mass(); }},
230 {"q",
231#ifdef ELECTROSTATICS
232 [this](Variant const &value) {
233 set_particle_property(&Particle::q, value);
234 },
235#else // ELECTROSTATICS
236 [](Variant const &value) {
237 if (get_value<double>(value) != 0.) {
238 throw std::runtime_error("Feature ELECTROSTATICS not compiled in");
239 }
240 },
241#endif // ELECTROSTATICS
242 [this]() { return get_particle_data(m_pid).q(); }},
243#ifdef ROTATION
244 {"director",
245 [this](Variant const &value) {
246 set_particle_property([&value](Particle &p) {
247 auto const director = get_value<Utils::Vector3d>(value).normalized();
249 });
250 },
251 [this]() {
252 auto const quat = get_particle_data(m_pid).quat();
254 }},
255 {"quat",
256 [this](Variant const &value) {
257 auto const quat = get_quaternion_safe("quat", value);
258 set_particle_property([&quat](Particle &p) { p.quat() = quat; });
259 },
260 [this]() { return quat2vector(get_particle_data(m_pid).quat()); }},
261 {"omega_body",
262 [this](Variant const &value) {
263 set_particle_property(&Particle::omega, value);
264 },
265 [this]() { return get_particle_data(m_pid).omega(); }},
266 {"rotation",
267 [this](Variant const &value) {
268 set_particle_property([&value](Particle &p) {
269 auto const rotation_flag =
270 Utils::Vector3i{(get_value<Utils::Vector3b>(value))};
271 p.rotation() = bitfield_from_flag(rotation_flag);
272 });
273 },
274 [this]() {
275 auto const rotation_bits = get_particle_data(m_pid).rotation();
276 return Utils::Vector3b{{::detail::get_nth_bit(rotation_bits, 0),
277 ::detail::get_nth_bit(rotation_bits, 1),
278 ::detail::get_nth_bit(rotation_bits, 2)}};
279 }},
280 {"omega_lab",
281 [this](Variant const &value) {
282 set_particle_property([&value](Particle &p) {
283 auto const omega = get_value<Utils::Vector3d>(value);
284 p.omega() = convert_vector_space_to_body(p, omega);
285 });
286 },
287 [this]() {
288 auto &p = get_particle_data(m_pid);
289 return convert_vector_body_to_space(p, p.omega());
290 }},
291 {"torque_lab",
292 [this](Variant const &value) {
293 set_particle_property([&value](Particle &p) {
294 auto const torque = get_value<Utils::Vector3d>(value);
296 });
297 },
298 [this]() {
299 auto &p = get_particle_data(m_pid);
300 return convert_vector_body_to_space(p, p.torque());
301 }},
302#endif // ROTATION
303#ifdef DIPOLES
304 {"dip",
305 [this](Variant const &value) {
306 set_particle_property([&value](Particle &p) {
307 auto const dip = get_value<Utils::Vector3d>(value);
308 std::tie(p.quat(), p.dipm()) = convert_dip_to_quat(dip);
309 });
310 },
311 [this]() { return get_particle_data(m_pid).calc_dip(); }},
312 {"dipm",
313 [this](Variant const &value) {
314 set_particle_property(&Particle::dipm, value);
315 },
316 [this]() { return get_particle_data(m_pid).dipm(); }},
317#endif // DIPOLES
318#ifdef DIPOLE_FIELD_TRACKING
319 {"dip_fld",
320 [this](Variant const &value) {
321 set_particle_property(&Particle::dip_fld, value);
322 },
323 [this]() { return get_particle_data(m_pid).dip_fld(); }},
324#endif
325#ifdef ROTATIONAL_INERTIA
326 {"rinertia",
327 [this](Variant const &value) {
328 set_particle_property(&Particle::rinertia, value);
329 },
330 [this]() { return get_particle_data(m_pid).rinertia(); }},
331#endif // ROTATIONAL_INERTIA
332#ifdef LB_ELECTROHYDRODYNAMICS
333 {"mu_E",
334 [this](Variant const &value) {
335 set_particle_property(&Particle::mu_E, value);
336 },
337 [this]() { return get_particle_data(m_pid).mu_E(); }},
338#endif // LB_ELECTROHYDRODYNAMICS
339#ifdef EXTERNAL_FORCES
340 {"fix",
341 [this](Variant const &value) {
342 set_particle_property([&value](Particle &p) {
343 auto const fix_flag =
344 Utils::Vector3i{(get_value<Utils::Vector3b>(value))};
345 p.fixed() = bitfield_from_flag(fix_flag);
346 });
347 },
348 [this]() {
349 auto const fixed = get_particle_data(m_pid).fixed();
350 return Utils::Vector3b{{::detail::get_nth_bit(fixed, 0),
351 ::detail::get_nth_bit(fixed, 1),
352 ::detail::get_nth_bit(fixed, 2)}};
353 }},
354 {"ext_force",
355 [this](Variant const &value) {
356 set_particle_property(&Particle::ext_force, value);
357 },
358 [this]() { return get_particle_data(m_pid).ext_force(); }},
359#ifdef ROTATION
360 {"ext_torque",
361 [this](Variant const &value) {
362 set_particle_property(&Particle::ext_torque, value);
363 },
364 [this]() { return get_particle_data(m_pid).ext_torque(); }},
365#endif // ROTATION
366#endif // EXTERNAL_FORCES
367 {"propagation",
368 [this](Variant const &value) {
369 auto const propagation = get_value<int>(value);
370 if (!is_valid_propagation_combination(propagation)) {
371 throw std::domain_error(error_msg(
372 "propagation", "propagation combination not accepted: " +
373 propagation_bitmask_to_string(propagation)));
374 }
375 set_particle_property(&Particle::propagation, value);
376 },
377 [this]() { return get_particle_data(m_pid).propagation(); }},
378#ifdef THERMOSTAT_PER_PARTICLE
379 {"gamma",
380 [this](Variant const &value) {
381 set_particle_property(&Particle::gamma,
382 Variant{get_gamma_safe(value)});
383 },
384 [this]() { return get_particle_data(m_pid).gamma(); }},
385#ifdef ROTATION
386 {"gamma_rot",
387 [this](Variant const &value) {
388 set_particle_property(&Particle::gamma_rot,
389 Variant{get_gamma_safe(value)});
390 },
391 [this]() { return get_particle_data(m_pid).gamma_rot(); }},
392#endif // ROTATION
393#endif // THERMOSTAT_PER_PARTICLE
394 {"pos_folded", AutoParameter::read_only,
395 [this]() {
396 auto const &box_geo = *System::get_system().box_geo;
397 return box_geo.folded_position(get_particle_data(m_pid).pos());
398 }},
399 {"lees_edwards_offset",
400 [this](Variant const &value) {
401 set_particle_property(&Particle::lees_edwards_offset, value);
402 },
403 [this]() { return get_particle_data(m_pid).lees_edwards_offset(); }},
404 {"lees_edwards_flag", AutoParameter::read_only,
405 [this]() { return get_particle_data(m_pid).lees_edwards_flag(); }},
406 {"image_box", AutoParameter::read_only,
407 [this]() { return get_particle_data(m_pid).image_box(); }},
408 {"node", AutoParameter::read_only,
409 [this]() {
410 return (context()->is_head_node()) ? get_particle_node(m_pid) : -1;
411 }},
412 {"mol_id",
413 [this](Variant const &value) {
414 auto const mol_id = get_value<int>(value);
415 if (mol_id < 0) {
416 throw std::domain_error(
417 error_msg("mol_id", "must be an integer >= 0"));
418 }
419 set_particle_property(&Particle::mol_id, Variant{mol_id});
420 },
421 [this]() { return get_particle_data(m_pid).mol_id(); }},
422#ifdef VIRTUAL_SITES_RELATIVE
423 {"vs_quat",
424 [this](Variant const &value) {
425 auto const quat = get_quaternion_safe("vs_quat", value);
426 set_particle_property(
427 [&quat](Particle &p) { p.vs_relative().quat = quat; });
428 },
429 [this]() {
430 return quat2vector(get_particle_data(m_pid).vs_relative().quat);
431 }},
432 {"vs_relative",
433 [this](Variant const &value) {
435 try {
436 auto const array = get_value<std::vector<Variant>>(value);
437 if (array.size() != 3) {
438 throw 0;
439 }
440 vs_relative.distance = get_value<double>(array[1]);
441 vs_relative.to_particle_id = get_value<int>(array[0]);
442 vs_relative.rel_orientation =
443 get_quaternion_safe("vs_relative", array[2]);
444 } catch (...) {
445 throw std::invalid_argument(error_msg(
446 "vs_relative", "must take the form [id, distance, quaternion]"));
447 }
448 set_particle_property([&vs_relative](Particle &p) {
449 p.vs_relative() = vs_relative;
450 if (vs_relative.to_particle_id != -1) {
451 p.propagation() = PropagationMode::TRANS_VS_RELATIVE |
452 PropagationMode::ROT_VS_RELATIVE;
453 }
455 });
456 },
457 [this]() {
458 auto const vs_rel = get_particle_data(m_pid).vs_relative();
459 return std::vector<Variant>{{vs_rel.to_particle_id, vs_rel.distance,
460 quat2vector(vs_rel.rel_orientation)}};
461 }},
462#endif // VIRTUAL_SITES_RELATIVE
463#ifdef ENGINE
464 {"swimming",
465 [this](Variant const &value) {
466 set_particle_property([&value](Particle &p) {
468 swim.swimming = true;
469 auto const dict = get_value<VariantMap>(value);
470 if (dict.count("f_swim") != 0) {
471 swim.f_swim = get_value<double>(dict.at("f_swim"));
472 }
473 if (dict.count("is_engine_force_on_fluid") != 0) {
474 auto const is_engine_force_on_fluid =
475 get_value<bool>(dict.at("is_engine_force_on_fluid"));
476 swim.is_engine_force_on_fluid = is_engine_force_on_fluid;
477 }
478 p.swimming() = swim;
479 });
480 },
481 [this]() {
482 auto const swim = get_particle_data(m_pid).swimming();
483 return VariantMap{
484 {"f_swim", swim.f_swim},
485 {"is_engine_force_on_fluid", swim.is_engine_force_on_fluid},
486 };
487 }},
488#endif // ENGINE
489 });
490}
491
492#ifdef EXCLUSIONS
493/**
494 * @brief Locally add an exclusion to a particle.
495 * @param pid1 the identity of the first exclusion partner
496 * @param pid2 the identity of the second exclusion partner
497 */
498static void local_add_exclusion(int pid1, int pid2) {
499 auto const &system = ::System::get_system();
500 auto &cell_structure = *system.cell_structure;
501 if (auto p1 = cell_structure.get_local_particle(pid1)) {
502 add_exclusion(*p1, pid2);
503 }
504 if (auto p2 = cell_structure.get_local_particle(pid2)) {
505 add_exclusion(*p2, pid1);
506 }
507}
508
509/**
510 * @brief Locally remove an exclusion to a particle.
511 * @param pid1 the identity of the first exclusion partner
512 * @param pid2 the identity of the second exclusion partner
513 */
514static void local_remove_exclusion(int pid1, int pid2) {
515 auto const &system = ::System::get_system();
516 auto &cell_structure = *system.cell_structure;
517 if (auto p1 = cell_structure.get_local_particle(pid1)) {
518 delete_exclusion(*p1, pid2);
519 }
520 if (auto p2 = cell_structure.get_local_particle(pid2)) {
521 delete_exclusion(*p2, pid1);
522 }
523}
524
525void ParticleHandle::particle_exclusion_sanity_checks(int pid1,
526 int pid2) const {
527 if (pid1 == pid2) {
528 throw std::runtime_error("Particles cannot exclude themselves (id " +
529 std::to_string(pid1) + ")");
530 }
531 std::ignore = get_real_particle(context()->get_comm(), pid1);
532 std::ignore = get_real_particle(context()->get_comm(), pid2);
533}
534#endif // EXCLUSIONS
535
536Variant ParticleHandle::do_call_method(std::string const &name,
537 VariantMap const &params) {
538 if (name == "set_param_parallel") {
539 auto const param_name = get_value<std::string>(params, "name");
540 if (params.count("value") == 0) {
541 throw Exception("Parameter '" + param_name + "' is missing.");
542 }
543 auto const &value = params.at("value");
544 context()->parallel_try_catch(
545 [&]() { do_set_parameter(param_name, value); });
546 return {};
547 }
548 if (name == "get_bonds_view") {
549 if (not context()->is_head_node()) {
550 return {};
551 }
552 auto const bond_list = get_particle_data(m_pid).bonds();
553 std::vector<std::vector<int>> bonds_flat;
554 for (auto const &&bond_view : bond_list) {
555 std::vector<int> bond_flat;
556 bond_flat.emplace_back(bond_view.bond_id());
557 for (auto const pid : bond_view.partner_ids()) {
558 bond_flat.emplace_back(pid);
559 }
560 bonds_flat.emplace_back(std::move(bond_flat));
561 }
562 return make_vector_of_variants(bonds_flat);
563 }
564 if (name == "add_bond") {
565 set_particle_property([&params](Particle &p) {
566 auto const bond_id = get_value<int>(params, "bond_id");
567 auto const part_id = get_value<std::vector<int>>(params, "part_id");
568 auto const bond_view =
569 BondView(bond_id, {part_id.data(), part_id.size()});
570 p.bonds().insert(bond_view);
571 });
572 } else if (name == "del_bond") {
573 set_particle_property([&params](Particle &p) {
574 auto const bond_id = get_value<int>(params, "bond_id");
575 auto const part_id = get_value<std::vector<int>>(params, "part_id");
576 auto const bond_view =
577 BondView(bond_id, {part_id.data(), part_id.size()});
578 auto &bond_list = p.bonds();
579 auto it = std::find(bond_list.begin(), bond_list.end(), bond_view);
580 if (it != bond_list.end()) {
581 bond_list.erase(it);
582 }
583 });
584 } else if (name == "delete_all_bonds") {
585 set_particle_property([&](Particle &p) { p.bonds().clear(); });
586 } else if (name == "is_valid_bond_id") {
587 auto const bond_id = get_value<int>(params, "bond_id");
588 return ::bonded_ia_params.get_zero_based_type(bond_id) != 0;
589 }
590 if (name == "remove_particle") {
591 context()->parallel_try_catch([&]() {
592 std::ignore = get_real_particle(context()->get_comm(), m_pid);
593 remove_particle(m_pid);
594 });
595 } else if (name == "is_virtual") {
596 if (not context()->is_head_node()) {
597 return {};
598 }
599 return get_particle_data(m_pid).is_virtual();
600#ifdef VIRTUAL_SITES_RELATIVE
601 } else if (name == "vs_relate_to") {
602 if (not context()->is_head_node()) {
603 return {};
604 }
605 auto const other_pid = get_value<int>(params, "pid");
606 auto const override_cutoff_check =
607 get_value<bool>(params, "override_cutoff_check");
608 if (m_pid == other_pid) {
609 throw std::invalid_argument("A virtual site cannot relate to itself");
610 }
611 if (other_pid < 0) {
612 throw std::domain_error("Invalid particle id: " +
613 std::to_string(other_pid));
614 }
615 auto const &system = ::System::get_system();
616 /* note: this code can be rewritten as parallel code, but only with a call
617 * to `cells_update_ghosts(DATA_PART_POSITION | DATA_PART_PROPERTIES)`, as
618 * there is no guarantee the virtual site has visibility of the relative
619 * particle through the ghost layer during particle creation. However,
620 * ghost updates can scramble the particle ordering in the local cells,
621 * which is an issue for checkpointing: the H5MD writer will use the
622 * scrambled ordering before writing to a checkpoint file and the
623 * non-scrambled ordering after reloading from a checkpoint file.
624 */
625 auto const &p_current = get_particle_data(m_pid);
626 auto const &p_relate_to = get_particle_data(other_pid);
627 auto const [quat, dist] = calculate_vs_relate_to_params(
628 p_current, p_relate_to, *system.box_geo, system.get_min_global_cut(),
629 override_cutoff_check);
630 set_parameter("vs_relative", Variant{std::vector<Variant>{
631 {other_pid, dist, quat2vector(quat)}}});
632#endif // VIRTUAL_SITES_RELATIVE
633#ifdef EXCLUSIONS
634 } else if (name == "has_exclusion") {
635 auto const other_pid = get_value<int>(params, "pid");
636 auto const p = get_real_particle(context()->get_comm(), m_pid);
637 if (p != nullptr) {
638 return p->has_exclusion(other_pid);
639 }
640 }
641 if (name == "add_exclusion") {
642 auto const other_pid = get_value<int>(params, "pid");
643 context()->parallel_try_catch(
644 [&]() { particle_exclusion_sanity_checks(m_pid, other_pid); });
645 local_add_exclusion(m_pid, other_pid);
647 } else if (name == "del_exclusion") {
648 auto const other_pid = get_value<int>(params, "pid");
649 context()->parallel_try_catch(
650 [&]() { particle_exclusion_sanity_checks(m_pid, other_pid); });
651 local_remove_exclusion(m_pid, other_pid);
653 } else if (name == "set_exclusions") {
654 std::vector<int> exclusion_list;
655 try {
656 auto const pid = get_value<int>(params, "p_ids");
657 exclusion_list.push_back(pid);
658 } catch (...) {
659 exclusion_list = get_value<std::vector<int>>(params, "p_ids");
660 }
661 context()->parallel_try_catch([&]() {
662 for (auto const pid : exclusion_list) {
663 particle_exclusion_sanity_checks(m_pid, pid);
664 }
665 });
666 set_particle_property([this, &exclusion_list](Particle &p) {
667 for (auto const pid : p.exclusions()) {
668 local_remove_exclusion(m_pid, pid);
669 }
670 for (auto const pid : exclusion_list) {
671 if (!p.has_exclusion(pid)) {
672 local_add_exclusion(m_pid, pid);
673 }
674 }
675 });
676 } else if (name == "get_exclusions") {
677 if (not context()->is_head_node()) {
678 return {};
679 }
680 auto const excl_list = get_particle_data(m_pid).exclusions();
681 return Variant{std::vector<int>{excl_list.begin(), excl_list.end()}};
682#endif // EXCLUSIONS
683#ifdef ROTATION
684 }
685 if (name == "rotate_particle") {
686 set_particle_property([&params](Particle &p) {
687 auto const axis = get_value<Utils::Vector3d>(params, "axis");
688 auto const angle = get_value<double>(params, "angle");
689 local_rotate_particle(p, axis, angle);
690 });
691 }
692 if (name == "convert_vector_body_to_space") {
693 return get_particle_property<std::vector<double>>(
694 [&params](Particle const &p) {
695 auto const vec = get_value<Utils::Vector3d>(params, "vec");
697 });
698 }
699 if (name == "convert_vector_space_to_body") {
700 return get_particle_property<std::vector<double>>(
701 [&params](Particle const &p) {
702 auto const vec = get_value<Utils::Vector3d>(params, "vec");
704 });
705#endif // ROTATION
706 }
707 return {};
708}
709
710#ifdef ROTATION
711static auto const contradicting_arguments_quat = std::vector<
712 std::array<std::string, 3>>{{
713 {{"dip", "dipm",
714 "Setting 'dip' is sufficient as it defines the scalar dipole moment."}},
715 {{"quat", "director",
716 "Setting 'quat' is sufficient as it defines the director."}},
717 {{"dip", "quat",
718 "Setting 'dip' would overwrite 'quat'. Set 'quat' and 'dipm' instead."}},
719 {{"dip", "director",
720 "Setting 'dip' would overwrite 'director'. Set 'director' and "
721 "'dipm' instead."}},
722}};
723#endif // ROTATION
724
725void ParticleHandle::do_construct(VariantMap const &params) {
726 auto const n_extra_args = params.size() - params.count("id");
727 auto const has_param = [&params](std::string const key) {
728 return params.count(key) == 1;
729 };
730 m_pid = (has_param("id")) ? get_value<int>(params, "id")
732
733#ifndef NDEBUG
734 if (!has_param("id")) {
735 auto head_node_reference = m_pid;
736 boost::mpi::broadcast(context()->get_comm(), head_node_reference, 0);
737 assert(m_pid == head_node_reference && "global max_seen_pid has diverged");
738 }
739#endif
740
741 // create a new particle if extra arguments were passed
742 if (n_extra_args == 0) {
743 return;
744 }
745
746 auto const pos = get_value<Utils::Vector3d>(params, "pos");
747 context()->parallel_try_catch([&]() {
748 particle_checks(m_pid, pos);
749 auto const &system = ::System::get_system();
750 auto const &cell_structure = *system.cell_structure;
751 auto ptr = cell_structure.get_local_particle(m_pid);
752 if (ptr != nullptr) {
753 throw std::invalid_argument("Particle " + std::to_string(m_pid) +
754 " already exists");
755 }
756 });
757
758#ifdef ROTATION
759 context()->parallel_try_catch([&]() {
760 // if we are not constructing a particle from a checkpoint file,
761 // check the quaternion is not accidentally set twice by the user
762 if (not has_param("__cpt_sentinel")) {
763 auto formatter =
764 boost::format("Contradicting particle attributes: '%s' and '%s'. %s");
765 for (auto const &[prop1, prop2, reason] : contradicting_arguments_quat) {
766 if (has_param(prop1) and has_param(prop2)) {
767 auto const err_msg = boost::str(formatter % prop1 % prop2 % reason);
768 throw std::invalid_argument(err_msg);
769 }
770 }
771 }
772 });
773#endif // ROTATION
774
775 // create a default-constructed particle
776 make_new_particle(m_pid, pos);
777
778 try {
779 context()->parallel_try_catch([&]() {
780 /* clang-format off */
781 // set particle properties (filter out read-only and deferred properties)
782 std::set<std::string> const skip = {
783 "pos_folded", "pos", "quat", "director", "id",
784 "exclusions", "dip", "node", "image_box", "bonds",
785 "lees_edwards_flag", "__cpt_sentinel",
786 };
787 /* clang-format on */
788#ifdef ROTATION
789 // multiple parameters can potentially set the quaternion, but only one
790 // can be allowed to; these conditionals are required to handle a reload
791 // from a checkpoint file, where all properties exist (avoids accidentally
792 // overwriting the quaternion by the default-constructed dipole moment)
793 for (std::string name : {"quat", "director", "dip"}) {
794 if (has_param(name)) {
795 do_set_parameter(name, params.at(name));
796 break;
797 }
798 }
799#endif // ROTATION
800 for (auto const &name : get_parameter_insertion_order()) {
801 if (params.count(name) != 0ul and skip.count(name) == 0ul) {
802 do_set_parameter(name, params.at(name));
803 }
804 }
805 if (not has_param("type")) {
806 do_set_parameter("type", 0);
807 }
808 });
809 } catch (...) {
810 remove_particle(m_pid);
811 throw;
812 }
813}
814
815} // namespace Particles
816} // namespace ScriptInterface
Vector implementation and trait types for boost qvm interoperability.
__global__ float float * torque
__shared__ int pos[MAXDEPTH *THREADS5/WARPSIZE]
float u[3]
Data structures for bonded interactions.
Immutable view on a bond.
Definition BondList.hpp:45
virtual boost::mpi::communicator const & get_comm() const =0
Context * context() const
Responsible context.
void on_particle_change()
Called every time a particle property changes.
std::shared_ptr< BoxGeometry > box_geo
std::shared_ptr< InteractionsNonBonded > nonbonded_ias
static Vector< T, N > broadcast(T const &s)
Create a vector that has all entries set to one value.
Definition Vector.hpp:110
std::vector< T > as_vector() const
Definition Vector.hpp:117
This file contains the defaults for ESPResSo.
void delete_exclusion(Particle &p, int p_id)
Remove exclusion from particle if possible.
void add_exclusion(Particle &p, int p_id)
Insert an exclusion if not already set.
static uint8_t bitfield_from_flag(Utils::Vector3i const &flag)
static void particle_checks(int p_id, Utils::Vector3d const &pos)
static auto get_quaternion_safe(std::string const &name, Variant const &value)
static auto const contradicting_arguments_quat
static void local_add_exclusion(int pid1, int pid2)
Locally add an exclusion to a particle.
static auto get_gamma_safe(Variant const &value)
static auto quat2vector(Utils::Quaternion< double > const &q)
static void local_remove_exclusion(int pid1, int pid2)
Locally remove an exclusion to a particle.
static auto get_real_particle(boost::mpi::communicator const &comm, int p_id)
static auto error_msg(std::string const &name, std::string const &reason)
std::unordered_map< std::string, Variant > VariantMap
Definition Variant.hpp:82
auto make_vector_of_variants(std::vector< T > const &v)
Definition Variant.hpp:101
boost::make_recursive_variant< None, bool, int, std::size_t, double, std::string, ObjectRef, Utils::Vector3b, Utils::Vector3i, Utils::Vector2d, Utils::Vector3d, Utils::Vector4d, std::vector< int >, std::vector< double >, std::vector< boost::recursive_variant_ >, std::unordered_map< int, boost::recursive_variant_ >, std::unordered_map< std::string, boost::recursive_variant_ > >::type Variant
Possible types for parameters.
Definition Variant.hpp:80
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.
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:57
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:108
Utils::Vector3d convert_vector_space_to_body(const Particle &p, const Utils::Vector3d &v)
Definition rotation.hpp:61
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:133
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
The following properties define, with respect to which real particle a virtual site is placed and at ...
Definition Particle.hpp:152
Struct holding all information for one particle.
Definition Particle.hpp:393
auto const & dip_fld() const
Definition Particle.hpp:496
bool has_exclusion(int pid) const
Definition Particle.hpp:574
auto const & swimming() const
Definition Particle.hpp:559
auto const & lees_edwards_offset() const
Definition Particle.hpp:444
auto const & propagation() const
Definition Particle.hpp:419
auto const & rinertia() const
Definition Particle.hpp:500
auto is_virtual() const
Definition Particle.hpp:516
auto const & mass() const
Definition Particle.hpp:450
Utils::compact_vector< int > & exclusions()
Definition Particle.hpp:572
auto const & quat() const
Definition Particle.hpp:475
auto const & rotation() const
Definition Particle.hpp:456
auto const & vs_relative() const
Definition Particle.hpp:525
auto const & q() const
Definition Particle.hpp:506
auto const & gamma() const
Definition Particle.hpp:529
auto const & gamma_rot() const
Definition Particle.hpp:532
auto const & lees_edwards_flag() const
Definition Particle.hpp:446
auto calc_dip() const
Definition Particle.hpp:493
auto const & v() const
Definition Particle.hpp:431
auto const & torque() const
Definition Particle.hpp:477
auto const & fixed() const
Definition Particle.hpp:537
auto const & ext_force() const
Definition Particle.hpp:552
auto const & omega() const
Definition Particle.hpp:479
auto const & image_box() const
Definition Particle.hpp:442
auto const & type() const
Definition Particle.hpp:416
auto const & ext_torque() const
Definition Particle.hpp:482
auto const & bonds() const
Definition Particle.hpp:426
auto const & dipm() const
Definition Particle.hpp:491
auto const & mol_id() const
Definition Particle.hpp:414
auto const & mu_E() const
Definition Particle.hpp:512
auto const & force() const
Definition Particle.hpp:433
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.