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>
70#ifdef ESPRESSO_ROTATION
72 std::array<std::string, 3>>{{
74 "Setting 'dip' is sufficient as it defines the scalar dipole moment."}},
76 "Setting 'quat' is sufficient as it defines the director."}},
78 "Setting 'dip' would overwrite 'quat'. Set 'quat' and 'dipm' instead."}},
80 "Setting 'dip' would overwrite 'director'. Set 'director' and "
87 if (
not params.contains(
"__cpt_sentinel")) {
89 boost::format(
"Contradicting particle attributes: '%s' and '%s'. %s");
93 throw std::invalid_argument(
err_msg);
100#if defined(ESPRESSO_ROTATION) or defined(ESPRESSO_EXTERNAL_FORCES)
113#ifdef ESPRESSO_ROTATION
120 if (q.norm2() == 0.) {
121 throw std::domain_error(
error_msg(name,
"must be non-zero"));
127#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
129#ifdef ESPRESSO_PARTICLE_ANISOTROPY
141template <
typename T,
class F>
142T ParticleHandle::get_particle_property(
F const &
fun)
const {
143 auto &cell_structure = get_cell_structure()->get_cell_structure();
145 auto const ptr =
const_cast<Particle const *
>(
147 std::optional<T>
ret;
148 if (ptr ==
nullptr) {
157T ParticleHandle::get_particle_property(T
const &(
Particle::*
getter)()
159 return get_particle_property<T>(
164void ParticleHandle::set_particle_property(F
const &fun)
const {
166 auto const &comm = context()->get_comm();
168 if (ptr !=
nullptr) {
175void ParticleHandle::set_particle_property(T &(
Particle::*setter)(),
177 set_particle_property(
181ParticleHandle::ParticleHandle() {
188 {
"id", AutoParameter::read_only, [
this]() {
return m_pid; }},
192 auto const new_type = get_value<int>(value);
194 throw std::domain_error(
195 error_msg(
"type",
"must be an integer >= 0"));
197 get_system()->
nonbonded_ias->make_particle_type_exist(new_type);
204 auto const pos = get_value<Utils::Vector3d>(value);
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);
227 if (get_value<double>(value) <= 0.) {
228 throw std::domain_error(
error_msg(
"mass",
"must be a float > 0"));
235 if (std::abs(get_value<double>(value) - default_mass) > 1e-10) {
236 throw std::runtime_error(
"Feature MASS not compiled in");
242#ifdef ESPRESSO_ELECTROSTATICS
248 if (get_value<double>(value) != 0.) {
249 throw std::runtime_error(
"Feature ELECTROSTATICS not compiled in");
254#ifdef ESPRESSO_DIPOLES
257 set_particle_property([&value](
Particle &p) {
258 auto const dip = get_value<Utils::Vector3d>(value);
269#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
276#ifdef ESPRESSO_THERMAL_STONER_WOHLFARTH
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"));
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()},
317#ifdef ESPRESSO_ROTATION
320 set_particle_property([&value](
Particle &p) {
321 auto const director = get_value<Utils::Vector3d>(value).normalized();
332 set_particle_property([&quat](
Particle &p) { p.
quat() = quat; });
342 set_particle_property([&value](
Particle &p) {
343 auto const rotation_flag =
351 ::detail::get_nth_bit(rotation_bits, 1),
352 ::detail::get_nth_bit(rotation_bits, 2)}};
356 set_particle_property([&value](
Particle &p) {
357 auto const omega = get_value<Utils::Vector3d>(value);
367 set_particle_property([&value](
Particle &p) {
368 auto const torque = get_value<Utils::Vector3d>(value);
377#ifdef ESPRESSO_ROTATIONAL_INERTIA
384#ifdef ESPRESSO_LB_ELECTROHYDRODYNAMICS
391#ifdef ESPRESSO_EXTERNAL_FORCES
394 set_particle_property([&value](
Particle &p) {
395 auto const fix_flag =
403 ::detail::get_nth_bit(fixed, 1),
404 ::detail::get_nth_bit(fixed, 2)}};
411#ifdef ESPRESSO_ROTATION
419#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
426#ifdef ESPRESSO_ROTATION
435 {
"pos_folded", AutoParameter::read_only,
437 auto const &box_geo = *get_system()->
box_geo;
440 {
"lees_edwards_offset",
445 {
"lees_edwards_flag", AutoParameter::read_only,
447 {
"image_box", AutoParameter::read_only,
449 auto const &box_geo = *get_system()->
box_geo;
451 return box_geo.folded_image_box(p.pos(), p.image_box());
453 {
"node", AutoParameter::read_only,
459 auto const mol_id = get_value<int>(value);
461 throw std::domain_error(
462 error_msg(
"mol_id",
"must be an integer >= 0"));
467#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
471 set_particle_property(
481 auto const array = get_value<std::vector<Variant>>(value);
482 if (array.size() != 3) {
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 =
491 "vs_relative",
"must take the form [id, distance, quaternion]"));
493 set_particle_property(
498 return std::vector<Variant>{{vs_rel.to_particle_id, vs_rel.distance,
502#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
507 auto const array = get_value<std::vector<int>>(value);
508 if (array.size() != 1) {
513 throw std::invalid_argument(
514 error_msg(
"vs_com",
"must take the form [id]"));
516 set_particle_property([&vs_com](
Particle &p) { p.
vs_com() = vs_com; });
520 return std::vector<Variant>{{vs_com.to_molecule_id}};
525 auto const propagation = get_value<int>(value);
528 "propagation",
"propagation combination not accepted: " +
534#ifdef ESPRESSO_ENGINE
537 set_particle_property([&value](
Particle &p) {
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"));
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;
555 {
"f_swim", swim.f_swim},
556 {
"is_engine_force_on_fluid", swim.is_engine_force_on_fluid},
563Variant ParticleHandle::do_call_method(std::string
const &name,
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.");
570 auto const &value =
params.at(
"value");
571 context()->parallel_try_catch(
572 [&]() { do_set_parameter(param_name, value); });
575 if (name ==
"update_params") {
577 context()->parallel_try_catch([&]() {
578#ifdef ESPRESSO_ROTATION
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));
589 if (
params.contains(
"bonds_ids")) {
591 set_particle_property([&](
Particle &p) { p.
bonds().clear(); });
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);
604#ifdef ESPRESSO_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"));
611 exclusion_list = get_value<std::vector<int>>(
params,
"exclusions");
613 context()->parallel_try_catch([&]() {
615 for (
auto const pid : exclusion_list) {
617 context()->get_comm());
620 set_particle_property([
this, &exclusion_list](
Particle &p) {
625 for (
auto const pid : exclusion_list) {
634 if (name ==
"get_bond_by_id") {
635 if (not context()->is_head_node()) {
638 return get_bonded_ias()->call_method(
"get_bond",
params);
640 if (name ==
"get_bonds_view") {
641 if (not context()->is_head_node()) {
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);
652 bonds_flat.emplace_back(std::move(bond_flat));
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);
663 }
else if (name ==
"del_bond") {
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()) {
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;
681 if (name ==
"remove_particle") {
682 context()->parallel_try_catch([&]() {
688 }
else if (name ==
"is_virtual") {
689 if (not context()->is_head_node()) {
693#ifdef ESPRESSO_VIRTUAL_SITES_RELATIVE
694 }
else if (name ==
"vs_auto_relate_to") {
695 if (not context()->is_head_node()) {
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");
705 throw std::domain_error(
"Invalid particle id: " +
706 std::to_string(other_pid));
708 auto const system = get_system();
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>{
725 set_parameter(
"propagation",
729#ifdef ESPRESSO_VIRTUAL_SITES_CENTER_OF_MASS
730 }
else if (name ==
"vs_com_relate_to") {
731 if (not context()->is_head_node()) {
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));
739 set_parameter(
"vs_com",
Variant{std::vector<Variant>{{other_molid}}});
744#ifdef ESPRESSO_EXCLUSIONS
745 }
else if (name ==
"has_exclusion") {
746 auto const other_pid = get_value<int>(
params,
"pid");
751 return p->has_exclusion(other_pid);
754 if (name ==
"add_exclusion") {
755 auto const other_pid = get_value<int>(
params,
"pid");
757 context()->parallel_try_catch([&]() {
759 context()->get_comm());
763 }
else if (name ==
"del_exclusion") {
764 auto const other_pid = get_value<int>(
params,
"pid");
766 context()->parallel_try_catch([&]() {
768 context()->get_comm());
772 }
else if (name ==
"set_exclusions") {
774 std::vector<int> exclusion_list;
776 auto const pid = get_value<int>(
params,
"p_ids");
777 exclusion_list.push_back(pid);
779 exclusion_list = get_value<std::vector<int>>(
params,
"p_ids");
781 context()->parallel_try_catch([&]() {
782 for (
auto const pid : exclusion_list) {
784 context()->get_comm());
787 set_particle_property([
this, &exclusion_list](
Particle &p) {
792 for (
auto const pid : exclusion_list) {
798 }
else if (name ==
"get_exclusions") {
799 if (not context()->is_head_node()) {
803 return Variant{std::vector<int>{excl_list.begin(), excl_list.end()}};
805#ifdef ESPRESSO_ROTATION
807 if (name ==
"rotate_particle") {
809 auto const axis = get_value<Utils::Vector3d>(
params,
"axis");
810 auto const angle = get_value<double>(
params,
"angle");
814 if (name ==
"convert_vector_body_to_space") {
815 return get_particle_property<std::vector<double>>(
817 auto const vec = get_value<Utils::Vector3d>(
params,
"vec");
821 if (name ==
"convert_vector_space_to_body") {
822 return get_particle_property<std::vector<double>>(
824 auto const vec = get_value<Utils::Vector3d>(
params,
"vec");
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;
841 if (
params.contains(
"__bonded_ias")) {
842 m_bonded_ias = get_value<std::shared_ptr<Interactions::BondedInteractions>>(
850 auto const n_extra_args = setup_hidden_args(
params);
851 m_pid = (
params.contains(
"id")) ? get_value<int>(
params,
"id")
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");
863 if (n_extra_args == 0) {
867 auto const pos = get_value<Utils::Vector3d>(
params,
"pos");
868 context()->parallel_try_catch([&]() {
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) +
878#ifdef ESPRESSO_ROTATION
886 context()->parallel_try_catch([&]() {
889 std::set<std::string_view>
const skip = {
890 "pos_folded",
"pos",
"id",
"exclusions",
"node",
"image_box",
"bonds",
891 "lees_edwards_flag",
"__cpt_sentinel",
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));
899 if (not
params.contains(
"type")) {
900 do_set_parameter(
"type", 0);
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.
Immutable view on a bond.
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
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.
@ TRANS_VS_CENTER_OF_MASS
static uint8_t bitfield_from_flag(Utils::Vector3i const &flag)
static void sanity_checks_rotation(VariantMap const ¶ms)
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
auto make_vector_of_variants(std::vector< T > const &v)
make_recursive_variant< ObjectRef > Variant
Possible types for parameters.
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)
std::pair< Utils::Quaternion< double >, double > convert_dip_to_quat(const Utils::Vector3d &dip)
convert a dipole moment to quaternions and dipolar strength
Utils::Vector3d convert_vector_space_to_body(const Particle &p, const Utils::Vector3d &v)
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.
static SteepestDescentParameters params
Currently active steepest descent instance.
Properties of a self-propelled particle.
bool swimming
Is the particle a swimmer.
Relate this particle to a molecule center of mass.
int to_molecule_id
Store molecule id tracked by virtual site.
The following properties define, with respect to which real particle a virtual site is placed and at ...
Struct holding all information for one particle.
auto const & dip_fld() const
bool has_exclusion(int pid) const
auto const & swimming() const
auto const & lees_edwards_offset() const
auto const & propagation() const
auto const & magnetic_anisotropy_energy() const
auto const & rinertia() const
auto const & stoner_wohlfarth_phi_0() const
auto const & mass() const
auto const & magnetic_anisotropy_field_inv() const
Utils::compact_vector< int > & exclusions()
auto const & quat() const
auto const & vs_com() const
auto const & rotation() const
auto const & vs_relative() const
auto const & stoner_wohlfarth_tau0_inv() const
auto const & gamma() const
auto const & gamma_rot() const
auto const & saturation_magnetization() const
auto const & stoner_wohlfarth_dt_incr() const
auto const & lees_edwards_flag() const
auto const & torque() const
auto const & fixed() const
auto const & ext_force() const
auto const & omega() const
auto const & stoner_wohlfarth_is_enabled() const
auto const & type() const
auto const & ext_torque() const
auto const & bonds() const
auto const & dipm() const
auto const & mol_id() const
auto const & mu_E() const
auto const & force() const
Recursive variant implementation.
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.