From 62b6a50d47f6687d8a2e39d562e04c7b3fab0411 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Mon, 17 Jun 2024 13:26:05 +0200 Subject: [PATCH 01/12] Apply clang format (#449) Update and apply clang-format to more airy style --- .clang-format | 24 +- src/actions.cpp | 96 ++- src/actions.h | 29 +- src/analysis.cpp | 1306 ++++++++++++++++++++++----------- src/analysis.h | 436 +++++++---- src/atomdata.cpp | 97 ++- src/atomdata.h | 96 +-- src/aux/arange.h | 30 +- src/aux/eigen_cerealisation.h | 18 +- src/aux/eigensupport.h | 18 +- src/aux/equidistant_table.h | 118 ++- src/aux/error_function.h | 11 +- src/aux/exp_function.h | 26 +- src/aux/invsqrt_function.h | 10 +- src/aux/iteratorsupport.h | 10 +- src/aux/legendre.h | 17 +- src/aux/matrixmarket.h | 6 +- src/aux/multimatrix.h | 44 +- src/aux/pairmatrix.h | 40 +- src/aux/pow_function.h | 6 +- src/aux/sparsehistogram.h | 23 +- src/aux/table_1d.h | 70 +- src/aux/table_2d.h | 118 +-- src/aux/timers.h | 22 +- src/aux/typeerasure.h | 97 ++- src/auxiliary.cpp | 24 +- src/auxiliary.h | 143 ++-- src/average.cpp | 23 +- src/average.h | 199 +++-- src/bonds.cpp | 693 +++++++++++------ src/bonds.h | 79 +- src/celllist.h | 72 +- src/celllistimpl.cpp | 76 +- src/celllistimpl.h | 426 +++++++---- src/chainmove.cpp | 160 ++-- src/chainmove.h | 85 ++- src/clustermove.cpp | 196 +++-- src/clustermove.h | 121 +-- src/core.cpp | 266 +++++-- src/core.h | 101 ++- src/energy.cpp | 907 +++++++++++++++-------- src/energy.h | 1007 +++++++++++++++---------- src/externalpotential.cpp | 179 +++-- src/externalpotential.h | 95 ++- src/faunus.cpp | 135 ++-- src/forcemove.cpp | 186 +++-- src/forcemove.h | 132 ++-- src/functionparser.cpp | 27 +- src/functionparser.h | 10 +- src/geometry.cpp | 644 ++++++++++------ src/geometry.h | 529 ++++++++----- src/group.cpp | 107 ++- src/group.h | 229 ++++-- src/io.cpp | 560 +++++++++----- src/io.h | 260 ++++--- src/molecule.cpp | 490 +++++++++---- src/molecule.h | 242 +++--- src/montecarlo.cpp | 143 ++-- src/montecarlo.h | 46 +- src/move.cpp | 904 +++++++++++++++-------- src/move.h | 231 +++--- src/mpicontroller.cpp | 125 +++- src/mpicontroller.h | 81 +- src/multipole.h | 127 ++-- src/particle.cpp | 173 +++-- src/particle.h | 147 ++-- src/penalty.cpp | 82 ++- src/penalty.h | 30 +- src/potentials.cpp | 898 +++++++++++++++-------- src/potentials.h | 281 ++++--- src/potentials_base.h | 136 ++-- src/pyfaunus.cpp | 38 +- src/random.cpp | 48 +- src/random.h | 49 +- src/reactioncoordinate.cpp | 284 ++++--- src/reactioncoordinate.h | 49 +- src/regions.cpp | 126 +++- src/regions.h | 58 +- src/rotate.cpp | 34 +- src/rotate.h | 17 +- src/sasa.cpp | 198 +++-- src/sasa.h | 74 +- src/scatter.cpp | 25 +- src/scatter.h | 270 ++++--- src/smart_montecarlo.cpp | 22 +- src/smart_montecarlo.h | 90 ++- src/space.cpp | 384 ++++++---- src/space.h | 229 ++++-- src/speciation.cpp | 284 ++++--- src/speciation.h | 47 +- src/spherocylinder.cpp | 206 ++++-- src/spherocylinder.h | 124 ++-- src/tabulate.h | 82 ++- src/tensor.cpp | 29 +- src/tensor.h | 18 +- src/units.cpp | 11 +- src/units.h | 193 ++++- src/voronota.cpp | 24 +- 98 files changed, 11569 insertions(+), 6019 deletions(-) diff --git a/.clang-format b/.clang-format index 38204bae6..10741aa4f 100644 --- a/.clang-format +++ b/.clang-format @@ -11,7 +11,7 @@ AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false -AllowShortFunctionsOnASingleLine: All +AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None @@ -21,23 +21,24 @@ AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: true BinPackParameters: true BraceWrapping: - AfterClass: false + AfterClass: true AfterControlStatement: false - AfterEnum: false - AfterFunction: false + AfterEnum: true + AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false - AfterStruct: false - AfterUnion: false + AfterStruct: true + AfterCaseLabel: true + AfterUnion: true AfterExternBlock: false - BeforeCatch: false - BeforeElse: false + BeforeCatch: true + BeforeElse: true IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None -BreakBeforeBraces: Attach +BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakInheritanceList: BeforeColon BreakBeforeTernaryOperators: true @@ -45,7 +46,7 @@ BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true -ColumnLimit: 120 +ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false @@ -94,8 +95,9 @@ PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 60 PointerAlignment: Left ReflowComments: true +SeparateDefinitionBlocks: Always SortIncludes: false -SortUsingDeclarations: true +SortUsingDeclarations: false SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true diff --git a/src/actions.cpp b/src/actions.cpp index fde3262b0..45df8d707 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -8,7 +8,8 @@ namespace Faunus { -AngularScan::AngularScan(const json& j, const Space& spc) { +AngularScan::AngularScan(const json& j, const Space& spc) +{ const auto angular_resolution = j.at("angular_resolution").get(); angles = Geometry::TwobodyAnglesState(angular_resolution); max_energy = j.value("max_energy", pc::infty) * 1.0_kJmol; @@ -21,58 +22,71 @@ AngularScan::AngularScan(const json& j, const Space& spc) { if (j.contains("traj")) { trajectory = std::make_unique(j["traj"]); if (angles.size() > 1e5) { - faunus_logger->warn("{}: large trajectory with {} frames will be generated", name, angles.size()); + faunus_logger->warn("{}: large trajectory with {} frames will be generated", name, + angles.size()); } } stream = IO::openCompressedOutputStream(j.value("file", "poses.gz"s), true); - *stream << fmt::format("# Δ⍺ = {:.1f}Β° -> {} x {} x {} = {} poses πŸ’ƒπŸ½πŸ•ΊπŸΌ\n", angular_resolution / 1.0_deg, - angles.quaternions_1.size(), angles.quaternions_2.size(), angles.dihedrals.size(), - angles.size()) - << fmt::format("# Mass center separation range along z: [{}, {}) with dz = {} Γ…\n", zmin, zmax, dz) + *stream << fmt::format("# Δ⍺ = {:.1f}Β° -> {} x {} x {} = {} poses πŸ’ƒπŸ½πŸ•ΊπŸΌ\n", + angular_resolution / 1.0_deg, angles.quaternions_1.size(), + angles.quaternions_2.size(), angles.dihedrals.size(), angles.size()) + << fmt::format("# Mass center separation range along z: [{}, {}) with dz = {} Γ…\n", + zmin, zmax, dz) << fmt::format("# Max energy threshold: {} kJ/mol\n", max_energy / 1.0_kJmol) << "# Column 0-3: Quaternion for molecule 1 (x y z w)\n" << "# Column 4-7: Quaternion for molecule 2 (x y z w)\n" << "# Column 8: Mass center z displacement of molecule 2\n" << "# Column 9: Energy (kJ/mol)\n"; - faunus_logger->info("{}: COM distance range = [{:.1f}, {:.1f}) max energy = {:.1f} kJ/mol", name, zmin, zmax, - max_energy / 1.0_kJmol); + faunus_logger->info("{}: COM distance range = [{:.1f}, {:.1f}) max energy = {:.1f} kJ/mol", + name, zmin, zmax, max_energy / 1.0_kJmol); } /** - * @brief Set molecular index; back up original positions, centered on the origin; save reference structure to disk + * @brief Set molecular index; back up original positions, centered on the origin; save reference + * structure to disk */ -void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int molecule_index) { +void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int molecule_index) +{ namespace rv = ranges::cpp20::views; index = molecule_index; const auto& group = groups.at(index); if (group.isAtomic()) { throw ConfigurationError("{}: group {} is not molecular", name, index); } - auto as_centered_position = [&](auto& particle) -> Point { return particle.pos - group.mass_center; }; + auto as_centered_position = [&](auto& particle) -> Point { + return particle.pos - group.mass_center; + }; ref_positions = group | rv::transform(as_centered_position) | ranges::to_vector; - XYZWriter().save(fmt::format("molecule{}_reference.xyz", index), group.begin(), group.end(), Point::Zero()); + XYZWriter().save(fmt::format("molecule{}_reference.xyz", index), group.begin(), group.end(), + Point::Zero()); } ParticleVector AngularScan::Molecule::getRotatedReference(const Space::GroupVector& groups, - const Eigen::Quaterniond& q) { + const Eigen::Quaterniond& q) +{ namespace rv = ranges::cpp20::views; const auto& group = groups.at(index); auto particles = ParticleVector(group.begin(), group.end()); // copy particles from Space - auto positions = ref_positions | rv::transform([&](const auto& pos) -> Point { return q * pos; }); - std::copy(positions.begin(), positions.end(), (particles | rv::transform(&Particle::pos)).begin()); + auto positions = + ref_positions | rv::transform([&](const auto& pos) -> Point { return q * pos; }); + std::copy(positions.begin(), positions.end(), + (particles | rv::transform(&Particle::pos)).begin()); return particles; } void AngularScan::report(const Group& group1, const Group& group2, const Eigen::Quaterniond& q1, - const Eigen::Quaterniond& q2, Energy::NonbondedBase& nonbonded) { + const Eigen::Quaterniond& q2, Energy::NonbondedBase& nonbonded) +{ const auto energy = nonbonded.groupGroupEnergy(group1, group2); if (energy >= max_energy) { return; } - auto format = [](const auto& q) { return fmt::format("{:8.4f}{:8.4f}{:8.4f}{:8.4f}", q.x(), q.y(), q.z(), q.w()); }; + auto format = [](const auto& q) { + return fmt::format("{:8.4f}{:8.4f}{:8.4f}{:8.4f}", q.x(), q.y(), q.z(), q.w()); + }; #pragma omp critical { @@ -80,13 +94,15 @@ void AngularScan::report(const Group& group1, const Group& group2, const Eigen:: *stream << format(q1) << format(q2) << fmt::format("{:8.4f} {:>10.3E}\n", group2.mass_center.z(), energy / 1.0_kJmol); if (trajectory) { - auto positions = ranges::views::concat(group1, group2) | ranges::cpp20::views::transform(&Particle::pos); + auto positions = ranges::views::concat(group1, group2) | + ranges::cpp20::views::transform(&Particle::pos); trajectory->writeNext({500, 500, 500}, positions.begin(), positions.end()); } } } -void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) { +void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) +{ auto nonbonded = hamiltonian.findFirstOf(); if (!nonbonded) { throw ConfigurationError("{}: at least one nonbonded energy term required", name); @@ -95,8 +111,8 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) { for (const auto z_pos : arange(zmin, zmax, dz)) { faunus_logger->info("{}: separation = {}", name, z_pos); auto translate = [=](auto& particle) { particle.pos.z() += z_pos; }; - auto progress_tracker = - std::make_unique(angles.quaternions_1.size() * angles.quaternions_2.size()); + auto progress_tracker = std::make_unique( + angles.quaternions_1.size() * angles.quaternions_2.size()); assert(progress_tracker); energy_analysis.clear(); @@ -108,7 +124,8 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) { for (const auto& q_body2 : angles.quaternions_2) { for (const auto& q_dihedral : angles.dihedrals) { - const auto q2 = q_dihedral * q_body2; // simulataneous rotations (noncummutative) + const auto q2 = + q_dihedral * q_body2; // simulataneous rotations (noncummutative) auto particles2 = molecules.second.getRotatedReference(spc.groups, q2); ranges::cpp20::for_each(particles2, translate); auto group2 = Group(0, particles2.begin(), particles2.end()); @@ -125,18 +142,21 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) { } // separation loop } -std::unique_ptr createAction(std::string_view name, const json& j, Space& spc) { +std::unique_ptr createAction(std::string_view name, const json& j, Space& spc) +{ try { if (name == "angular_scan") { return std::make_unique(j, spc); } throw ConfigurationError("'{}' unknown", name); - } catch (std::exception& e) { + } + catch (std::exception& e) { throw std::runtime_error("error creating action -> "s + e.what()); } } -std::vector> createActionList(const json& input, Space& spc) { +std::vector> createActionList(const json& input, Space& spc) +{ if (!input.is_array()) { throw ConfigurationError("json array expected"); } @@ -147,33 +167,43 @@ std::vector> createActionList(const json& input, S try { const auto& [name, params] = jsonSingleItem(j); actions.emplace_back(createAction(name, params, spc)); - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("actions: {}", e.what()).attachJson(j); } } return actions; } -void AngularScan::EnergyAnalysis::clear() { +void AngularScan::EnergyAnalysis::clear() +{ mean_exp_energy.clear(); partition_sum = 0; energy_sum = 0; } -void AngularScan::EnergyAnalysis::add(const double energy) { +void AngularScan::EnergyAnalysis::add(const double energy) +{ const auto exp_energy = std::exp(-energy); mean_exp_energy += exp_energy; partition_sum += exp_energy; energy_sum += energy * exp_energy; } -double AngularScan::EnergyAnalysis::getFreeEnergy() const { return -std::log(mean_exp_energy.avg()); } +double AngularScan::EnergyAnalysis::getFreeEnergy() const +{ + return -std::log(mean_exp_energy.avg()); +} -double AngularScan::EnergyAnalysis::getMeanEnergy() const { return energy_sum / partition_sum; } +double AngularScan::EnergyAnalysis::getMeanEnergy() const +{ + return energy_sum / partition_sum; +} -void AngularScan::EnergyAnalysis::printLog() const { - faunus_logger->info("{}: free energy = {:.3f} mean energy = {:.3f}", name, getFreeEnergy(), - getMeanEnergy()); +void AngularScan::EnergyAnalysis::printLog() const +{ + faunus_logger->info("{}: free energy = {:.3f} mean energy = {:.3f}", name, + getFreeEnergy(), getMeanEnergy()); } } // namespace Faunus diff --git a/src/actions.h b/src/actions.h index 5c750f60f..2b50c86f2 100644 --- a/src/actions.h +++ b/src/actions.h @@ -14,7 +14,8 @@ class XTCWriter; /** * @brief Performs a task or "action" on the system, typically before or after a simulation */ -struct SystemAction { +struct SystemAction +{ virtual void operator()(Space& space, Energy::Hamiltonian& hamiltonian) = 0; virtual ~SystemAction() = default; }; @@ -26,11 +27,13 @@ struct SystemAction { * along with two quaternions that describe rotations from the initial * orientations. A trajectory with the poses can be optionally saved. */ -class AngularScan : public SystemAction { +class AngularScan : public SystemAction +{ inline static const std::string name = "angular scan"; //!< Name used for logging /// @brief Helper class to analyse (free) energies - class EnergyAnalysis { + class EnergyAnalysis + { double partition_sum = 0; //!< Partition function (per COM separation) double energy_sum = 0; //!< Thermal energy sum for each COM separation run Average mean_exp_energy; //!< Free energy @@ -43,11 +46,14 @@ class AngularScan : public SystemAction { }; /// @brief Helper class for store information about each of the two rigid bodies - struct Molecule { - Space::GroupVector::size_type index; //!< Group index in `Space::groups` - std::vector ref_positions; //!< Original reference positions of particles - void initialize(const Space::GroupVector& groups, int index); //!< Set molecule index and reset - ParticleVector getRotatedReference(const Space::GroupVector& groups, const Eigen::Quaterniond& q); + struct Molecule + { + Space::GroupVector::size_type index; //!< Group index in `Space::groups` + std::vector ref_positions; //!< Original reference positions of particles + void initialize(const Space::GroupVector& groups, + int index); //!< Set molecule index and reset + ParticleVector getRotatedReference(const Space::GroupVector& groups, + const Eigen::Quaterniond& q); }; double zmin = 0; //!< Minimum mass center separation (along z) @@ -62,8 +68,8 @@ class AngularScan : public SystemAction { Geometry::TwobodyAngles angles; //!< Helper class handling angular space /// Calculate energy and report to stream and trajectory - void report(const Group& group1, const Group& group2, const Eigen::Quaterniond& q1, const Eigen::Quaterniond& q2, - Energy::NonbondedBase& nonbonded); + void report(const Group& group1, const Group& group2, const Eigen::Quaterniond& q1, + const Eigen::Quaterniond& q2, Energy::NonbondedBase& nonbonded); public: AngularScan(const json& input, const Space& spc); @@ -71,7 +77,8 @@ class AngularScan : public SystemAction { }; /// @brief Create single action from JSON input -std::unique_ptr createAction(std::string_view name, const json& properties, Space& spc); +std::unique_ptr createAction(std::string_view name, const json& properties, + Space& spc); /// @brief Create vector of actions from JSON list input std::vector> createActionList(const json& input, Space& spc); diff --git a/src/analysis.cpp b/src/analysis.cpp index 8ca593033..bbe2b9044 100644 --- a/src/analysis.cpp +++ b/src/analysis.cpp @@ -30,7 +30,10 @@ namespace Faunus::analysis { -void to_json(json& j, const Analysis& base) { base.to_json(j); } +void to_json(json& j, const Analysis& base) +{ + base.to_json(j); +} /** * This is virtual and can be overridden in derived classes @@ -40,7 +43,10 @@ void Analysis::_to_disk() {} /** * Wrapper function used for non-intrusive future padding around _to_disk(). */ -void Analysis::to_disk() { _to_disk(); } +void Analysis::to_disk() +{ + _to_disk(); +} /** * For each call: @@ -51,7 +57,8 @@ void Analysis::to_disk() { _to_disk(); } * * The call to the sampling function is timed. */ -void Analysis::sample() { +void Analysis::sample() +{ try { number_of_steps++; if (sample_interval > 0 && number_of_steps > number_of_skipped_steps) { @@ -62,22 +69,26 @@ void Analysis::sample() { timer.stop(); } } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw std::runtime_error(name + ": " + e.what()); } } -void Analysis::from_json(const json& j) { +void Analysis::from_json(const json& j) +{ try { number_of_skipped_steps = j.value("nskip", 0); sample_interval = j.value("nstep", 0); _from_json(j); - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("{}: {}", name, e.what()); } } -void Analysis::to_json(json& json_output) const { +void Analysis::to_json(json& json_output) const +{ try { auto& j = json_output[name]; _to_json(j); // fill in info from derived classes @@ -95,7 +106,8 @@ void Analysis::to_json(json& json_output) const { if (!cite.empty()) { j["reference"] = cite; } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw std::runtime_error(name + ": " + e.what()); } } @@ -104,19 +116,26 @@ void Analysis::_to_json(json&) const {} void Analysis::_from_json(const json&) {} -int Analysis::getNumberOfSteps() const { return number_of_steps; } +int Analysis::getNumberOfSteps() const +{ + return number_of_steps; +} Analysis::Analysis(const Space& spc, std::string_view name) : spc(spc) - , name(name) { + , name(name) +{ assert(!name.empty()); } -Analysis::Analysis(const Space& spc, std::string_view name, int sample_interval, int number_of_skipped_steps) +Analysis::Analysis(const Space& spc, std::string_view name, int sample_interval, + int number_of_skipped_steps) : number_of_skipped_steps(number_of_skipped_steps) , spc(spc) , sample_interval(sample_interval) - , name(name) {} + , name(name) +{ +} /** * @brief Factory function for generating analysis based on name @@ -126,102 +145,142 @@ Analysis::Analysis(const Space& spc, std::string_view name, int sample_interval, * @param pot Hamiltonian * @return shared pointer to created analysis base class */ -std::unique_ptr createAnalysis(const std::string& name, const json& j, Space& spc, Energy::Hamiltonian& pot) { +std::unique_ptr createAnalysis(const std::string& name, const json& j, Space& spc, + Energy::Hamiltonian& pot) +{ try { if (name == "atomprofile") { return std::make_unique(j, spc); - } else if (name == "displacement") { + } + else if (name == "displacement") { return std::make_unique(j, spc); - } else if (name == "displacement_com") { + } + else if (name == "displacement_com") { return std::make_unique(j, spc); - } else if (name == "atomrdf") { + } + else if (name == "atomrdf") { return std::make_unique(j, spc); - } else if (name == "atomdipdipcorr") { + } + else if (name == "atomdipdipcorr") { return std::make_unique(j, spc); - } else if (name == "density") { + } + else if (name == "density") { faunus_logger->warn("`density` is replaced by `molecule_density` and `atom_density`. " "Activating the latter..."); return std::make_unique(j, spc); - } else if (name == "molecule_density") { + } + else if (name == "molecule_density") { return std::make_unique(j, spc); - } else if (name == "atom_density") { + } + else if (name == "atom_density") { return std::make_unique(j, spc); - } else if (name == "electricpotential") { + } + else if (name == "electricpotential") { return std::make_unique(j, spc); - } else if (name == "chargefluctuations") { + } + else if (name == "chargefluctuations") { return std::make_unique(j, spc); - } else if (name == "groupmatrix") { + } + else if (name == "groupmatrix") { return std::make_unique(j, spc, pot); - } else if (name == "molrdf") { + } + else if (name == "molrdf") { return std::make_unique(j, spc); - } else if (name == "multipole") { + } + else if (name == "multipole") { return std::make_unique(j, spc); - } else if (name == "atominertia") { + } + else if (name == "atominertia") { return std::make_unique(j, spc); - } else if (name == "inertia") { + } + else if (name == "inertia") { return std::make_unique(j, spc); - } else if (name == "moleculeconformation") { + } + else if (name == "moleculeconformation") { return std::make_unique(j, spc); - } else if (name == "multipolemoments") { + } + else if (name == "multipolemoments") { return std::make_unique(j, spc); - } else if (name == "multipoledist") { + } + else if (name == "multipoledist") { return std::make_unique(j, spc); - } else if (name == "polymershape") { + } + else if (name == "polymershape") { return std::make_unique(j, spc); - } else if (name == "qrfile") { + } + else if (name == "qrfile") { return std::make_unique(j, spc); - } else if (name == "psctraj") { + } + else if (name == "psctraj") { return std::make_unique(j, spc); - } else if (name == "reactioncoordinate") { + } + else if (name == "reactioncoordinate") { return std::make_unique(j, spc); - } else if (name == "sanity") { + } + else if (name == "sanity") { return std::make_unique(j, spc); - } else if (name == "sasa") { + } + else if (name == "sasa") { return std::make_unique(j, spc); - } else if (name == "savestate") { + } + else if (name == "savestate") { return std::make_unique(j, spc); - } else if (name == "penaltyfunction") { + } + else if (name == "penaltyfunction") { return std::make_unique(j, spc, pot); - } else if (name == "scatter") { + } + else if (name == "scatter") { return std::make_unique(j, spc); - } else if (name == "sliceddensity") { + } + else if (name == "sliceddensity") { return std::make_unique(j, spc); - } else if (name == "systemenergy") { + } + else if (name == "systemenergy") { return std::make_unique(j, spc, pot); - } else if (name == "virtualvolume") { + } + else if (name == "virtualvolume") { return std::make_unique(j, spc, pot); - } else if (name == "virtualtranslate") { + } + else if (name == "virtualtranslate") { return std::make_unique(j, spc, pot); - } else if (name == "voronoi") { + } + else if (name == "voronoi") { return std::make_unique(j, spc); - } else if (name == "widom") { + } + else if (name == "widom") { return std::make_unique(j, spc, pot); - } else if (name == "xtcfile") { + } + else if (name == "xtcfile") { return std::make_unique(j, spc); - } else if (name == "spacetraj") { + } + else if (name == "spacetraj") { return std::make_unique(j, spc); } // append more analysis here... throw ConfigurationError("unknown analysis"); - } catch (std::exception& e) { + } + catch (std::exception& e) { usageTip.pick(name); throw ConfigurationError("{}: {}", name, e.what()); } } -void CombinedAnalysis::sample() { +void CombinedAnalysis::sample() +{ for (const auto& analysis : this->vec) { analysis->sample(); } } -void CombinedAnalysis::to_disk() { +void CombinedAnalysis::to_disk() +{ for (const auto& analysis : this->vec) { analysis->to_disk(); } } -CombinedAnalysis::CombinedAnalysis(const json& json_array, Space& spc, Energy::Hamiltonian& pot) { +CombinedAnalysis::CombinedAnalysis(const json& json_array, Space& spc, Energy::Hamiltonian& pot) +{ if (!json_array.is_array()) { throw ConfigurationError("json array expected"); } @@ -229,13 +288,15 @@ CombinedAnalysis::CombinedAnalysis(const json& json_array, Space& spc, Energy::H try { const auto& [key, json_parameters] = jsonSingleItem(j); vec.emplace_back(createAnalysis(key, json_parameters, spc, pot)); - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("analysis: {}", e.what()).attachJson(j); } } } -[[maybe_unused]] void SystemEnergy::normalize() { +[[maybe_unused]] void SystemEnergy::normalize() +{ const auto sum = energy_histogram.sumy(); for (auto& i : energy_histogram.getMap()) { i.second = i.second / sum; @@ -250,19 +311,22 @@ CombinedAnalysis::CombinedAnalysis(const json& json_array, Space& spc, Energy::H * * @return True if a new minimum energy was encountered */ -bool SystemEnergy::updateMinimumEnergy(const double current_energy) { +bool SystemEnergy::updateMinimumEnergy(const double current_energy) +{ if (!dump_minimum_energy_configuration || current_energy >= minimum_energy) { return false; } minimum_energy = current_energy; auto filename = MPI::prefix + "minimum_energy.pqr"; - faunus_logger->debug("{}: saving {} ({:.2f} kT) at step {}", name, filename, minimum_energy, getNumberOfSteps()); + faunus_logger->debug("{}: saving {} ({:.2f} kT) at step {}", name, filename, minimum_energy, + getNumberOfSteps()); PQRWriter(PQRWriter::Style::PQR).save(filename, spc.groups, spc.geometry.getLength()); filename = MPI::prefix + "minimum_energy.state"; if (std::ofstream file(filename); file) { - faunus_logger->debug("{}: saving {} ({:.2f} kT) at step {}", name, filename, minimum_energy, getNumberOfSteps()); + faunus_logger->debug("{}: saving {} ({:.2f} kT) at step {}", name, filename, minimum_energy, + getNumberOfSteps()); json j; Faunus::to_json(j, spc); file << std::setw(1) << j; @@ -270,15 +334,18 @@ bool SystemEnergy::updateMinimumEnergy(const double current_energy) { return true; } -void SystemEnergy::_sample() { +void SystemEnergy::_sample() +{ const auto energies = calculateEnergies(); // current energy from all terms in Hamiltonian const auto total_energy = ranges::accumulate(energies, 0.0); updateMinimumEnergy(total_energy); if (std::isfinite(total_energy)) { mean_energy += total_energy; mean_squared_energy += total_energy * total_energy; - } else { - faunus_logger->warn("{}: non-finite energy excluded from averages at step {}", name, getNumberOfSteps()); + } + else { + faunus_logger->warn("{}: non-finite energy excluded from averages at step {}", name, + getNumberOfSteps()); } *output_stream << fmt::format("{:10d}{}{:.6E}", getNumberOfSteps(), separator, total_energy); for (auto energy : energies) { @@ -287,7 +354,8 @@ void SystemEnergy::_sample() { *output_stream << "\n"; } -void SystemEnergy::_to_json(json& j) const { +void SystemEnergy::_to_json(json& j) const +{ j = {{"file", file_name}, {"init", initial_energy}, {"final", calculateEnergies()}, @@ -299,42 +367,50 @@ void SystemEnergy::_to_json(json& j) const { roundJSON(j, 5); } -void SystemEnergy::_from_json(const json& j) { +void SystemEnergy::_from_json(const json& j) +{ file_name = MPI::prefix + j.at("file").get(); dump_minimum_energy_configuration = j.value("save_min_conf", false); } -void SystemEnergy::createOutputStream() { +void SystemEnergy::createOutputStream() +{ output_stream = IO::openCompressedOutputStream(file_name, true); const bool is_csv_file = file_name.substr(file_name.find_last_of('.') + 1) == "csv"; if (is_csv_file) { separator = ","; - } else { + } + else { separator = " "; *output_stream << "#"; } *output_stream << fmt::format("{:>9}{}{:12}", "step", separator, "total"); - ranges::for_each(hamiltonian, - [&](auto& energy) { *output_stream << fmt::format("{}{:12}", separator, energy->name); }); + ranges::for_each(hamiltonian, [&](auto& energy) { + *output_stream << fmt::format("{}{:12}", separator, energy->name); + }); *output_stream << "\n"; } SystemEnergy::SystemEnergy(const json& j, const Space& spc, Energy::Hamiltonian& hamiltonian) : Analysis(spc, "systemenergy") - , hamiltonian(hamiltonian) { + , hamiltonian(hamiltonian) +{ from_json(j); energy_histogram.setResolution(0.25); initial_energy = ranges::accumulate(calculateEnergies(), 0.0); createOutputStream(); } -std::vector SystemEnergy::calculateEnergies() const { +std::vector SystemEnergy::calculateEnergies() const +{ Change change; change.everything = true; // trigger full energy calculation - return hamiltonian | ranges::views::transform([&](auto& i) { return i->energy(change); }) | ranges::to_vector; + return hamiltonian | ranges::views::transform([&](auto& i) { return i->energy(change); }) | + ranges::to_vector; } -void SystemEnergy::_to_disk() { +void SystemEnergy::_to_disk() +{ output_stream->flush(); // empty buffer } @@ -344,15 +420,17 @@ void SystemEnergy::_to_disk() { * Returns a lambda function that calculates a scalar property for a pair of groups. * This can be their interaction energy; mass-center distance etc. */ -std::function createGroupGroupProperty(const json& j, const Space& spc, - Energy::Hamiltonian& hamiltonian) { +std::function +createGroupGroupProperty(const json& j, const Space& spc, Energy::Hamiltonian& hamiltonian) +{ const auto name = j.at("property").get(); if (name == "energy") { // total nonbonded energy return [&](auto& group_1, auto& group_2) { double energy = 0.0; // in kT for (auto& energy_term : hamiltonian) { - if (auto nonbonded = std::dynamic_pointer_cast(energy_term)) { + if (auto nonbonded = + std::dynamic_pointer_cast(energy_term)) { energy += nonbonded->groupGroupEnergy(group_1, group_2); } } @@ -389,10 +467,13 @@ std::function createGroupGroupProperty(const * @param name the policy to use: `all`, `smaller_than`, `function`, ... * @param j json object with data matching policy * @param throw_on_error Throw if key is an unknown criterion - * @return Unary predicate to determine if a value is selected or not; `nullptr` or throw if unknown name. + * @return Unary predicate to determine if a value is selected or not; `nullptr` or throw if unknown + * name. * @throw if unknown name and `throw_on_error` is true */ -std::function createValueFilter(const std::string& name, const json& j, bool throw_on_error) { +std::function createValueFilter(const std::string& name, const json& j, + bool throw_on_error) +{ if (name == "absolute_larger_than") { return [threshold = j.get()](auto value) { return std::fabs(value) > threshold; }; } @@ -424,14 +505,16 @@ std::function createValueFilter(const std::string& name, const jso PairMatrixAnalysis::PairMatrixAnalysis(const json& j, const Space& spc) : Analysis(spc, "cluster") - , spc(spc) { + , spc(spc) +{ from_json(j); filename = j.at("file").get(); matrix_stream = IO::openCompressedOutputStream(filename, true); if (auto it = j.find("filter"); it != j.end()) { value_filter = createValueFilter("function", *it, true); - } else { + } + else { value_filter = [](auto) { return true; }; } } @@ -440,24 +523,32 @@ void PairMatrixAnalysis::_to_json([[maybe_unused]] json& j) const {} void PairMatrixAnalysis::_from_json([[maybe_unused]] const json& j) {} -void PairMatrixAnalysis::_sample() { +void PairMatrixAnalysis::_sample() +{ assert(matrix_stream); setPairMatrix(); Faunus::streamMarket(pair_matrix, *matrix_stream, true); *matrix_stream << "\n"; // separare frames w. blank line } -void PairMatrixAnalysis::_to_disk() { matrix_stream->flush(); } +void PairMatrixAnalysis::_to_disk() +{ + matrix_stream->flush(); +} -GroupMatrixAnalysis::GroupMatrixAnalysis(const json& j, const Space& spc, Energy::Hamiltonian& hamiltonian) - : PairMatrixAnalysis(j, spc) { +GroupMatrixAnalysis::GroupMatrixAnalysis(const json& j, const Space& spc, + Energy::Hamiltonian& hamiltonian) + : PairMatrixAnalysis(j, spc) +{ namespace rv = ranges::cpp20::views; // finds the molecule ids and store their indices auto molids = Faunus::parseMolecules(j.at("molecules")); - auto is_selected = [&](const auto& group) { return std::binary_search(molids.begin(), molids.end(), group.id); }; + auto is_selected = [&](const auto& group) { + return std::binary_search(molids.begin(), molids.end(), group.id); + }; auto to_index = [&](const auto& group) { return spc.getGroupIndex(group); }; - group_indices = - spc.groups | rv::filter(is_selected) | rv::transform(to_index) | ranges::to; + group_indices = spc.groups | rv::filter(is_selected) | rv::transform(to_index) | + ranges::to; property = createGroupGroupProperty(j, spc, hamiltonian); pair_matrix.resize(spc.groups.size(), spc.groups.size()); @@ -468,9 +559,11 @@ GroupMatrixAnalysis::GroupMatrixAnalysis(const json& j, const Space& spc, Energy * and fills in the sparse matrix `pair_matrix`. This also filters out * values (if a filter is given) */ -void GroupMatrixAnalysis::setPairMatrix() { +void GroupMatrixAnalysis::setPairMatrix() +{ auto is_active = [&](auto index) { return !spc.groups.at(index).empty(); }; - const auto indices = group_indices | ranges::cpp20::views::filter(is_active) | ranges::to_vector; + const auto indices = + group_indices | ranges::cpp20::views::filter(is_active) | ranges::to_vector; // zero matrix, then fill it pair_matrix.setZero(); @@ -489,21 +582,28 @@ void GroupMatrixAnalysis::setPairMatrix() { // -------------------------------- -void SaveState::_to_json(json& j) const { j["file"] = filename; } +void SaveState::_to_json(json& j) const +{ + j["file"] = filename; +} -void SaveState::_sample() { +void SaveState::_sample() +{ assert(sample_interval >= 0); if (use_numbered_files) { // tag filename with step number auto numbered_filename = filename; - numbered_filename.insert(filename.find_last_of('.'), fmt::format("_{}", getNumberOfSteps())); + numbered_filename.insert(filename.find_last_of('.'), + fmt::format("_{}", getNumberOfSteps())); writeFunc(numbered_filename); - } else { + } + else { writeFunc(filename); } } -SaveState::~SaveState() { +SaveState::~SaveState() +{ if (sample_interval == -1) { // writes data just before destruction writeFunc(filename); @@ -511,7 +611,8 @@ SaveState::~SaveState() { } SaveState::SaveState(json j, const Space& spc) - : Analysis(spc, "savestate") { + : Analysis(spc, "savestate") +{ if (j.count("nstep") == 0) { // by default, disable _sample() and j["nstep"] = -1; // store only when _to_disk() is called @@ -525,28 +626,34 @@ SaveState::SaveState(json j, const Space& spc) setWriteFunction(spc); } -void SaveState::setWriteFunction(const Space& spc) { +void SaveState::setWriteFunction(const Space& spc) +{ const auto suffix = filename.substr(filename.find_last_of('.') + 1); if (std::shared_ptr writer = createStructureFileWriter(suffix)) { writeFunc = [&, w = writer](auto& file) { if (convert_hexagonal_prism_to_cuboid) { saveAsCuboid(file, spc, *w); - } else { + } + else { w->save(file, spc.groups, spc.geometry.getLength()); } }; - } else if (suffix == "json") { + } + else if (suffix == "json") { // JSON state file writeFunc = [&](auto& file) { saveJsonStateFile(file, spc); }; - } else if (suffix == "ubj") { + } + else if (suffix == "ubj") { // Universal Binary JSON state file writeFunc = [&](auto& file) { saveBinaryJsonStateFile(file, spc); }; - } else { + } + else { throw ConfigurationError("unknown file extension for '{}'", filename); } } -void SaveState::saveBinaryJsonStateFile(const std::string& filename, const Space& spc) const { +void SaveState::saveBinaryJsonStateFile(const std::string& filename, const Space& spc) const +{ if (std::ofstream f(filename, std::ios::binary); f) { json j; Faunus::to_json(j, spc); @@ -559,7 +666,8 @@ void SaveState::saveBinaryJsonStateFile(const std::string& filename, const Space } } -void SaveState::saveJsonStateFile(const std::string& filename, const Space& spc) const { +void SaveState::saveJsonStateFile(const std::string& filename, const Space& spc) const +{ if (std::ofstream f(filename); f) { json j; Faunus::to_json(j, spc); @@ -572,28 +680,36 @@ void SaveState::saveJsonStateFile(const std::string& filename, const Space& spc) } } -void SaveState::saveAsCuboid(const std::string& filename, const Space& spc, StructureFileWriter& writer) { - auto hexagonal_prism = std::dynamic_pointer_cast(spc.geometry.asSimpleGeometry()); +void SaveState::saveAsCuboid(const std::string& filename, const Space& spc, + StructureFileWriter& writer) +{ + auto hexagonal_prism = + std::dynamic_pointer_cast(spc.geometry.asSimpleGeometry()); if (hexagonal_prism) { faunus_logger->debug("creating cuboid from hexagonal prism"); - const auto& [cuboid, particles] = Geometry::hexagonalPrismToCuboid(*hexagonal_prism, spc.activeParticles()); + const auto& [cuboid, particles] = + Geometry::hexagonalPrismToCuboid(*hexagonal_prism, spc.activeParticles()); writer.save(filename, particles.begin(), particles.end(), cuboid.getLength()); - } else { + } + else { throw std::runtime_error("hexagonal prism required for `convert_to_hexagon`"); } } PairFunction::PairFunction(const Space& spc, const json& j, const std::string_view name) - : Analysis(spc, name) { + : Analysis(spc, name) +{ from_json(j); } -void PairFunction::_to_json(json& j) const { +void PairFunction::_to_json(json& j) const +{ j = {{"dr", dr / 1.0_angstrom}, {"name1", name1}, {"name2", name2}, {"file", file}, {"dim", dimensions}, {"slicedir", slicedir}, {"thickness", thickness}}; } -void PairFunction::_from_json(const json& j) { +void PairFunction::_from_json(const json& j) +{ file = j.at("file").get(); name1 = j.at("name1").get(); name2 = j.at("name2").get(); @@ -604,26 +720,29 @@ void PairFunction::_from_json(const json& j) { histogram.setResolution(dr, 0); } -void PairFunction::_to_disk() { +void PairFunction::_to_disk() +{ if (std::ofstream f(MPI::prefix + file); f) { histogram.stream_decorator = [&](std::ostream& o, double r, double N) { const auto volume_at_r = volumeElement(r); if (volume_at_r > 0.0) { const auto total_number_of_samples = histogram.sumy(); - o << fmt::format("{:.6E} {:.6E}\n", r, N * mean_volume.avg() / (volume_at_r * total_number_of_samples)); + o << fmt::format("{:.6E} {:.6E}\n", r, + N * mean_volume.avg() / (volume_at_r * total_number_of_samples)); } }; f << histogram; } } -double PairFunction::volumeElement(double r) const { +double PairFunction::volumeElement(double r) const +{ switch (dimensions) { case 3: return 4.0 * pc::pi * r * r * dr; case 2: - if (const auto hypersphere = - std::dynamic_pointer_cast(spc.geometry.asSimpleGeometry())) { + if (const auto hypersphere = std::dynamic_pointer_cast( + spc.geometry.asSimpleGeometry())) { faunus_logger->trace("{}: hypersphere detected; radius set", name); const auto radius = hypersphere->getRadius(); return 2.0 * pc::pi * radius * std::sin(r / radius) * dr; @@ -637,10 +756,12 @@ double PairFunction::volumeElement(double r) const { } PairAngleFunction::PairAngleFunction(const Space& spc, const json& j, const std::string& name) - : PairFunction(spc, j, name) { + : PairFunction(spc, j, name) +{ from_json(j); correlation_filename = MPI::prefix + file; - average_correlation_vs_distance.stream_decorator = [&](auto& stream, double distance, Average N) { + average_correlation_vs_distance.stream_decorator = [&](auto& stream, double distance, + Average N) { if (!N.empty() && std::isfinite(N.avg())) { stream << distance << " " << N.avg() << "\n"; } @@ -650,36 +771,44 @@ PairAngleFunction::PairAngleFunction(const Space& spc, const json& j, const std: file = fmt::format("{}{}.dummy", MPI::prefix, file); } -void PairAngleFunction::_to_disk() { +void PairAngleFunction::_to_disk() +{ if (auto f = std::ofstream(correlation_filename)) { f << average_correlation_vs_distance; } } -void PairAngleFunction::_from_json(const json&) { average_correlation_vs_distance.setResolution(dr, 0); } +void PairAngleFunction::_from_json(const json&) +{ + average_correlation_vs_distance.setResolution(dr, 0); +} -void PerturbationAnalysis::_to_disk() { +void PerturbationAnalysis::_to_disk() +{ if (stream) { stream->flush(); // empty buffer } } -PerturbationAnalysis::PerturbationAnalysis(const std::string& name, Energy::EnergyTerm& pot, Space& spc, - const std::string& filename) +PerturbationAnalysis::PerturbationAnalysis(const std::string& name, Energy::EnergyTerm& pot, + Space& spc, const std::string& filename) : Analysis(spc, name) , mutable_space(spc) , pot(pot) - , filename(filename) { + , filename(filename) +{ if (!filename.empty()) { this->filename = MPI::prefix + filename; stream = IO::openCompressedOutputStream(this->filename, true); // throws if error } } -bool PerturbationAnalysis::collectWidomAverage(const double energy_change) { +bool PerturbationAnalysis::collectWidomAverage(const double energy_change) +{ if (-energy_change > pc::max_exp_argument) { - faunus_logger->warn( - "{}: skipping sample event due to too negative energy; consider decreasing the perturbation", name); + faunus_logger->warn("{}: skipping sample event due to too negative energy; consider " + "decreasing the perturbation", + name); number_of_samples--; // update_counter is incremented by sample() so we need to decrease return false; } @@ -687,18 +816,23 @@ bool PerturbationAnalysis::collectWidomAverage(const double energy_change) { return true; } -double PerturbationAnalysis::meanFreeEnergy() const { return -std::log(mean_exponentiated_energy_change.avg()); } +double PerturbationAnalysis::meanFreeEnergy() const +{ + return -std::log(mean_exponentiated_energy_change.avg()); +} -void VirtualVolumeMove::_sample() { +void VirtualVolumeMove::_sample() +{ if (std::fabs(volume_displacement) <= pc::epsilon_dbl) { return; } const auto old_volume = mutable_space.geometry.getVolume(); // store old volume const auto old_energy = pot.energy(change); // ...and energy - const auto scale = mutable_space.scaleVolume(old_volume + volume_displacement, - volume_scaling_method); // scale entire system to new volume - const auto new_energy = pot.energy(change); // energy after scaling - mutable_space.scaleVolume(old_volume, volume_scaling_method); // restore saved system + const auto scale = + mutable_space.scaleVolume(old_volume + volume_displacement, + volume_scaling_method); // scale entire system to new volume + const auto new_energy = pot.energy(change); // energy after scaling + mutable_space.scaleVolume(old_volume, volume_scaling_method); // restore saved system const auto energy_change = new_energy - old_energy; // system energy change if (collectWidomAverage(energy_change)) { @@ -712,7 +846,8 @@ void VirtualVolumeMove::_sample() { * Expensive and one would normally not perform this test and we trigger it * only when using log-level "debug" or lower */ -void VirtualVolumeMove::sanityCheck(const double old_energy) { +void VirtualVolumeMove::sanityCheck(const double old_energy) +{ if (faunus_logger->level() <= spdlog::level::debug and old_energy != 0.0) { const auto should_be_small = 1.0 - pot.energy(change) / old_energy; if (std::fabs(should_be_small) > 1e-6) { @@ -721,18 +856,22 @@ void VirtualVolumeMove::sanityCheck(const double old_energy) { } } -void VirtualVolumeMove::writeToFileStream(const Point& scale, const double energy_change) const { +void VirtualVolumeMove::writeToFileStream(const Point& scale, const double energy_change) const +{ if (stream) { const auto mean_excess_pressure = -meanFreeEnergy() / volume_displacement; // units of kT/Γ…Β³ - *stream << fmt::format("{:d} {:.3E} {:.6E} {:.6E} {:.6E}", getNumberOfSteps(), volume_displacement, - energy_change, std::exp(-energy_change), mean_excess_pressure); + *stream << fmt::format("{:d} {:.3E} {:.6E} {:.6E} {:.6E}", getNumberOfSteps(), + volume_displacement, energy_change, std::exp(-energy_change), + mean_excess_pressure); // if anisotropic scaling, add an extra column with area or length perturbation const auto box_length = spc.geometry.getLength(); if (volume_scaling_method == Geometry::VolumeMethod::XY) { - const auto area_change = box_length.x() * box_length.y() * (scale.x() * scale.y() - 1.0); + const auto area_change = + box_length.x() * box_length.y() * (scale.x() * scale.y() - 1.0); *stream << fmt::format(" {:.6E}", area_change); - } else if (volume_scaling_method == Geometry::VolumeMethod::Z) { + } + else if (volume_scaling_method == Geometry::VolumeMethod::Z) { const auto length_change = box_length.z() * (scale.z() - 1.0); *stream << fmt::format(" {:.6E}", length_change); } @@ -740,7 +879,8 @@ void VirtualVolumeMove::writeToFileStream(const Point& scale, const double energ } } -void VirtualVolumeMove::_from_json(const json& j) { +void VirtualVolumeMove::_from_json(const json& j) +{ volume_displacement = j.at("dV").get(); volume_scaling_method = j.value("scaling", Geometry::VolumeMethod::ISOTROPIC); if (volume_scaling_method == Geometry::VolumeMethod::ISOCHORIC) { @@ -748,7 +888,8 @@ void VirtualVolumeMove::_from_json(const json& j) { } } -void VirtualVolumeMove::_to_json(json& j) const { +void VirtualVolumeMove::_to_json(json& j) const +{ if (number_of_samples > 0) { const auto excess_pressure = -meanFreeEnergy() / volume_displacement; j = {{"dV", volume_displacement}, @@ -762,46 +903,60 @@ void VirtualVolumeMove::_to_json(json& j) const { } VirtualVolumeMove::VirtualVolumeMove(const json& j, Space& spc, Energy::EnergyTerm& pot) - : PerturbationAnalysis("virtualvolume", pot, spc, j.value("file", ""s)) { + : PerturbationAnalysis("virtualvolume", pot, spc, j.value("file", ""s)) +{ cite = "doi:10.1063/1.472721"; from_json(j); change.volume_change = true; change.everything = true; if (stream) { - *stream << "# steps dV/" + unicode::angstrom + unicode::cubed + " du/kT exp(-du/kT) /kT/" + - unicode::angstrom + unicode::cubed; + *stream << "# steps dV/" + unicode::angstrom + unicode::cubed + + " du/kT exp(-du/kT) /kT/" + unicode::angstrom + unicode::cubed; // if non-isotropic scaling, add another column with dA or dL if (volume_scaling_method == Geometry::VolumeMethod::XY) { *stream << " dA/" + unicode::angstrom + unicode::squared; - } else if (volume_scaling_method == Geometry::VolumeMethod::Z) { + } + else if (volume_scaling_method == Geometry::VolumeMethod::Z) { *stream << " dL/" + unicode::angstrom; } *stream << "\n"; // trailing newline } } -void MolecularConformationID::_sample() { +void MolecularConformationID::_sample() +{ auto molecules = spc.findMolecules(molid, Space::Selection::ACTIVE); for (const auto& group : molecules) { histogram[group.conformation_id]++; } } -void MolecularConformationID::_to_json(json& j) const { j["histogram"] = histogram; } +void MolecularConformationID::_to_json(json& j) const +{ + j["histogram"] = histogram; +} MolecularConformationID::MolecularConformationID(const json& j, const Space& spc) - : Analysis(spc, "moleculeconformation") { + : Analysis(spc, "moleculeconformation") +{ from_json(j); const auto molname = j.at("molecule").get(); molid = Faunus::findMoleculeByName(molname).id(); } -void QRtraj::_sample() { write_to_file(); } +void QRtraj::_sample() +{ + write_to_file(); +} -void QRtraj::_to_json(json& j) const { j = {{"file", filename}}; } +void QRtraj::_to_json(json& j) const +{ + j = {{"file", filename}}; +} QRtraj::QRtraj(const json& j, const Space& spc, const std::string& name) - : Analysis(spc, name) { + : Analysis(spc, name) +{ from_json(j); filename = MPI::prefix + j.value("file", "qrtraj.dat"s); stream = IO::openCompressedOutputStream(filename, true); @@ -812,7 +967,8 @@ QRtraj::QRtraj(const json& j, const Space& spc, const std::string& name) if (it < group.end()) { // active particles... *stream << fmt::format("{} {} ", it->charge, it->traits().sigma * 0.5); - } else { + } + else { // inactive particles... *stream << "0 0 "; // ... have zero charge and size } @@ -822,14 +978,16 @@ QRtraj::QRtraj(const json& j, const Space& spc, const std::string& name) }; } -void QRtraj::_to_disk() { +void QRtraj::_to_disk() +{ if (*stream) { stream->flush(); // empty buffer } } PatchySpheroCylinderTrajectory::PatchySpheroCylinderTrajectory(const json& j, const Space& spc) - : QRtraj(j, spc, "psctraj") { + : QRtraj(j, spc, "psctraj") +{ if (!j.contains("file")) { throw ConfigurationError("missing filename"); } @@ -842,14 +1000,16 @@ PatchySpheroCylinderTrajectory::PatchySpheroCylinderTrajectory(const json& j, co for (auto it = group.begin(); it != group.trueend(); ++it) { const auto particle_is_active = it < group.end(); const auto scale = static_cast(particle_is_active); - *stream << scale * it->pos.transpose() << " " << scale * it->getExt().scdir.transpose() << " " + *stream << scale * it->pos.transpose() << " " + << scale * it->getExt().scdir.transpose() << " " << scale * it->getExt().patchdir.transpose() << "\n"; } } }; } -void FileReactionCoordinate::_to_json(json& j) const { +void FileReactionCoordinate::_to_json(json& j) const +{ json rcjson = static_cast(*reaction_coordinate); if (rcjson.size() != 1) { throw std::runtime_error("error writing json for reaction coordinate"); @@ -866,11 +1026,13 @@ void FileReactionCoordinate::_to_json(json& j) const { } } -void FileReactionCoordinate::_sample() { +void FileReactionCoordinate::_sample() +{ const auto value = reaction_coordinate->operator()(); mean_reaction_coordinate += value; if (stream) { - (*stream) << fmt::format("{} {:.6f} {:.6f}\n", getNumberOfSteps(), value, mean_reaction_coordinate.avg()); + (*stream) << fmt::format("{} {:.6f} {:.6f}\n", getNumberOfSteps(), value, + mean_reaction_coordinate.avg()); } } @@ -879,10 +1041,12 @@ FileReactionCoordinate::FileReactionCoordinate( std::unique_ptr reaction_coordinate) : Analysis(spc, "reactioncoordinate") , filename(filename) - , reaction_coordinate(std::move(reaction_coordinate)) { + , reaction_coordinate(std::move(reaction_coordinate)) +{ if (filename.empty()) { faunus_logger->warn("{}: no filename given - only the mean coordinate will be saved", name); - } else { + } + else { stream = IO::openCompressedOutputStream(MPI::prefix + filename, true); } } @@ -890,11 +1054,13 @@ FileReactionCoordinate::FileReactionCoordinate( FileReactionCoordinate::FileReactionCoordinate(const json& j, const Space& spc) : FileReactionCoordinate( spc, j.value("file", ""s), - ReactionCoordinate::createReactionCoordinate({{j.at("type").get(), j}}, spc)) { + ReactionCoordinate::createReactionCoordinate({{j.at("type").get(), j}}, spc)) +{ from_json(j); } -void FileReactionCoordinate::_to_disk() { +void FileReactionCoordinate::_to_disk() +{ if (stream) { stream->flush(); // empty buffer } @@ -903,17 +1069,20 @@ void FileReactionCoordinate::_to_disk() { AtomicDisplacement::AtomicDisplacement(const json& j, const Space& spc, std::string_view name) : Analysis(spc, name) , displacement_histogram(j.value("histogram_resolution", 1.0)) - , reference_reset_interval(j.value("reset_interval", std::numeric_limits::max())) { + , reference_reset_interval(j.value("reset_interval", std::numeric_limits::max())) +{ from_json(j); const auto molecule_name = j.at("molecule").get(); - max_possible_displacement = j.value("max_displacement", spc.geometry.getLength().minCoeff() / 4.0); + max_possible_displacement = + j.value("max_displacement", spc.geometry.getLength().minCoeff() / 4.0); displacement_histogram_filename = j.value("histogram_file", fmt::format("displacement_histogram_{}.dat", molecule_name)); if (j.contains("file")) { - single_position_stream = IO::openCompressedOutputStream(j.at("file").get(), true); + single_position_stream = + IO::openCompressedOutputStream(j.at("file").get(), true); *single_position_stream << "# step x y z displacement\n"; } @@ -931,7 +1100,8 @@ AtomicDisplacement::AtomicDisplacement(const json& j, const Space& spc, std::str * @param position New reference position * @param index Index of reference position */ -void AtomicDisplacement::resetReferencePosition(const Point& position, const int index) { +void AtomicDisplacement::resetReferencePosition(const Point& position, const int index) +{ reference_positions.at(index) = position; } @@ -944,12 +1114,14 @@ void AtomicDisplacement::resetReferencePosition(const Point& position, const int * @param cell Stored unit cell information for the particle (will be updated) * @returns Offset used to shift currrent position to correct unit-cell */ -Point AtomicDisplacement::getOffset(const Point& diff, Eigen::Vector3i& cell) const { +Point AtomicDisplacement::getOffset(const Point& diff, Eigen::Vector3i& cell) const +{ for (int i = 0; i < 3; i++) { if (-diff[i] > max_possible_displacement) { // dx < 0 cell[i]++; - } else if (diff[i] > max_possible_displacement) { + } + else if (diff[i] > max_possible_displacement) { // dx > 0 cell[i]--; } @@ -959,7 +1131,8 @@ Point AtomicDisplacement::getOffset(const Point& diff, Eigen::Vector3i& cell) co return box.cwiseProduct(cell.cast()); } -void AtomicDisplacement::_sample() { +void AtomicDisplacement::_sample() +{ auto current_positions = getPositions(); auto zipped = ranges::views::zip(previous_positions, current_positions, cell_indices); const auto time_to_reset = getNumberOfSteps() % reference_reset_interval == 0; @@ -983,24 +1156,28 @@ void AtomicDisplacement::_sample() { * * The first position (index=0) will be streamed to `single_position_stream` if open. */ -void AtomicDisplacement::sampleDisplacementFromReference(const Point& position, const int index) { +void AtomicDisplacement::sampleDisplacementFromReference(const Point& position, const int index) +{ const Point total_displacement = position - reference_positions.at(index); mean_squared_displacement.at(index) += total_displacement.squaredNorm(); displacement_histogram.add(total_displacement.norm()); if (index == 0 && single_position_stream) { - *single_position_stream << fmt::format("{} {:.2f} {:.2f} {:.2f} {:.2f}\n", getNumberOfSteps(), position.x(), - position.y(), position.z(), total_displacement.norm()); + *single_position_stream << fmt::format("{} {:.2f} {:.2f} {:.2f} {:.2f}\n", + getNumberOfSteps(), position.x(), position.y(), + position.z(), total_displacement.norm()); } } -void AtomicDisplacement::_to_json(json& j) const { +void AtomicDisplacement::_to_json(json& j) const +{ j["max_displacement"] = max_possible_displacement; j["reset interval (steps)"] = reference_reset_interval; j["histogram_file"] = displacement_histogram_filename; j["histogram_resolution"] = displacement_histogram.getResolution(); } -void AtomicDisplacement::_to_disk() { +void AtomicDisplacement::_to_disk() +{ if (!displacement_histogram_filename.empty()) { if (auto stream = std::ofstream(displacement_histogram_filename)) { stream << "# distance count\n" << displacement_histogram; @@ -1011,25 +1188,29 @@ void AtomicDisplacement::_to_disk() { /** * @return Positions of all active particles in groups matching `molid` */ -PointVector AtomicDisplacement::getPositions() const { +PointVector AtomicDisplacement::getPositions() const +{ auto active_and_inactive = [&](const Group& group) { return ranges::make_subrange(group.begin(), group.trueend()); }; namespace rv = ranges::cpp20::views; - return spc.findMolecules(molid, Space::Selection::ALL) | rv::transform(active_and_inactive) | rv::join | - rv::transform(&Particle::pos) | ranges::to; + return spc.findMolecules(molid, Space::Selection::ALL) | rv::transform(active_and_inactive) | + rv::join | rv::transform(&Particle::pos) | ranges::to; } //---------------------------- -MassCenterDisplacement::MassCenterDisplacement(const json& j, const Space& spc, std::string_view name) - : AtomicDisplacement(j, spc, name) { +MassCenterDisplacement::MassCenterDisplacement(const json& j, const Space& spc, + std::string_view name) + : AtomicDisplacement(j, spc, name) +{ if (!Faunus::molecules.at(molid).isMolecular()) { throw ConfigurationError("molecular group required"); } } -PointVector MassCenterDisplacement::getPositions() const { +PointVector MassCenterDisplacement::getPositions() const +{ namespace rv = ranges::cpp20::views; return spc.findMolecules(molid, Space::Selection::ACTIVE) | rv::transform(&Group::mass_center) | ranges::to; @@ -1042,7 +1223,8 @@ PointVector MassCenterDisplacement::getPositions() const { * and prepares the `change` object for energy evaluation. If no * ghost molecule is found, `change` is left empty. */ -void WidomInsertion::selectGhostGroup() { +void WidomInsertion::selectGhostGroup() +{ change.clear(); auto inactive_groups = spc.findMolecules(molid, Space::Selection::INACTIVE); if (!ranges::cpp20::empty(inactive_groups)) { @@ -1057,16 +1239,21 @@ void WidomInsertion::selectGhostGroup() { } } -void WidomInsertion::_sample() { +void WidomInsertion::_sample() +{ selectGhostGroup(); // will prepare `change` if (change.empty()) { - faunus_logger->warn("{}: no inactive {} groups available", name, Faunus::molecules[molid].name); - } else { - auto& group = mutable_space.groups.at(change.groups.at(0).group_index); // inactive "ghost" group - group.resize(group.capacity()); // activate ghost - ParticleVector particles; // particles to insert + faunus_logger->warn("{}: no inactive {} groups available", name, + Faunus::molecules[molid].name); + } + else { + auto& group = + mutable_space.groups.at(change.groups.at(0).group_index); // inactive "ghost" group + group.resize(group.capacity()); // activate ghost + ParticleVector particles; // particles to insert for (int cnt = 0; cnt < number_of_insertions; ++cnt) { - particles = inserter->operator()(mutable_space.geometry, Faunus::molecules[molid], spc.particles); + particles = inserter->operator()(mutable_space.geometry, Faunus::molecules[molid], + spc.particles); // random pos&orientation updateGroup(group, particles); const auto energy_change = pot.energy(change); // in kT @@ -1076,20 +1263,23 @@ void WidomInsertion::_sample() { } } -void WidomInsertion::updateGroup(Space::GroupType& group, const ParticleVector& particles) { +void WidomInsertion::updateGroup(Space::GroupType& group, const ParticleVector& particles) +{ assert(particles.size() == group.size()); std::copy(particles.begin(), particles.end(), group.begin()); // copy to ghost group if (absolute_z_coords) { - std::for_each(group.begin(), group.end(), [](Particle& i) { i.pos.z() = std::fabs(i.pos.z()); }); + std::for_each(group.begin(), group.end(), + [](Particle& i) { i.pos.z() = std::fabs(i.pos.z()); }); } if (auto mass_center = group.massCenter()) { // update molecular mass-center for molecular groups - (*mass_center).get() = - Geometry::massCenter(group.begin(), group.end(), this->spc.geometry.getBoundaryFunc(), -group.begin()->pos); + (*mass_center).get() = Geometry::massCenter( + group.begin(), group.end(), this->spc.geometry.getBoundaryFunc(), -group.begin()->pos); } } -void WidomInsertion::_to_json(json& j) const { +void WidomInsertion::_to_json(json& j) const +{ if (!mean_exponentiated_energy_change.empty()) { const double excess_chemical_potential = meanFreeEnergy(); j = {{"molecule", Faunus::molecules[molid].name}, @@ -1100,7 +1290,8 @@ void WidomInsertion::_to_json(json& j) const { } } -void WidomInsertion::_from_json(const json& j) { +void WidomInsertion::_from_json(const json& j) +{ number_of_insertions = j.at("ninsert").get(); absolute_z_coords = j.value("absz", false); if (auto ptr = std::dynamic_pointer_cast(inserter); ptr) { @@ -1112,13 +1303,15 @@ void WidomInsertion::_from_json(const json& j) { } WidomInsertion::WidomInsertion(const json& j, Space& spc, Energy::Hamiltonian& pot) - : PerturbationAnalysis("widom", pot, spc) { + : PerturbationAnalysis("widom", pot, spc) +{ cite = "doi:10/dkv4s6"; inserter = std::make_shared(); from_json(j); } -double Density::updateVolumeStatistics() { +double Density::updateVolumeStatistics() +{ const auto volume = spc.geometry.getVolume(); mean_volume += volume; mean_cubic_root_of_volume += std::cbrt(volume); @@ -1126,7 +1319,8 @@ double Density::updateVolumeStatistics() { return volume; } -void Density::_sample() { +void Density::_sample() +{ const auto volume = updateVolumeStatistics(); for (auto [id, number] : count()) { mean_density[id] += number / volume; @@ -1134,7 +1328,8 @@ void Density::_sample() { } } -void Density::_to_json(json& j) const { +void Density::_to_json(json& j) const +{ j[""] = mean_volume.avg(); j["<βˆ›V>"] = mean_cubic_root_of_volume.avg(); j["βˆ›"] = std::cbrt(mean_volume.avg()); @@ -1152,7 +1347,8 @@ void Density::_to_json(json& j) const { /** * Write histograms to disk */ -void Density::writeTable(std::string_view name, Table& table) { +void Density::writeTable(std::string_view name, Table& table) +{ if (table.size() <= 1) { // do not save non-existent or non-fluctuating groups return; @@ -1165,19 +1361,22 @@ void Density::writeTable(std::string_view name, Table& table) { const auto filename = fmt::format("{}rho-{}.dat", MPI::prefix, name); if (std::ofstream file(filename); file) { file << "# N counts probability\n" << table; - } else { + } + else { throw std::runtime_error("could not writeKeyValuePairs "s + filename); } } -void Density::_to_disk() { +void Density::_to_disk() +{ for (auto [id, table] : probability_density) { // atomic molecules writeTable(names.at(id), table); } } -void AtomDensity::_sample() { +void AtomDensity::_sample() +{ Density::_sample(); std::set unique_reactive_atoms; for (const auto& reaction : reactions) { @@ -1194,7 +1393,8 @@ void AtomDensity::_sample() { /** * @brief Counts atoms in atomic groups */ -std::map AtomDensity::count() const { +std::map AtomDensity::count() const +{ namespace rv = ranges::cpp20::views; // All ids incl. inactive are counted; std::vector ensures constant lookup (index = id) @@ -1208,12 +1408,14 @@ std::map AtomDensity::count() const { // Copy vector --> map id_type id = 0U; std::map map; - ranges::cpp20::for_each(atom_count, [&id, &map](auto count) { map.emplace_hint(map.end(), id++, count); }); + ranges::cpp20::for_each(atom_count, + [&id, &map](auto count) { map.emplace_hint(map.end(), id++, count); }); return map; } AtomDensity::AtomDensity(const json& j, const Space& spc) - : Density(spc, Faunus::atoms, "atom_density") { + : Density(spc, Faunus::atoms, "atom_density") +{ from_json(j); for (const auto& reaction : Faunus::reactions) { // in case of reactions involving atoms (swap moves) @@ -1224,7 +1426,8 @@ AtomDensity::AtomDensity(const json& j, const Space& spc) } } -void AtomDensity::_to_disk() { +void AtomDensity::_to_disk() +{ Density::_to_disk(); for (const auto& reaction : Faunus::reactions) { const auto reactive_atomic_species = reaction.participatingAtomsAndMolecules().first; @@ -1237,7 +1440,8 @@ void AtomDensity::_to_disk() { /** * @brief Counts active, molecular groups */ -std::map MoleculeDensity::count() const { +std::map MoleculeDensity::count() const +{ using namespace ranges::cpp20; std::map molecular_group_count; @@ -1245,33 +1449,40 @@ std::map MoleculeDensity::count() const { for_each(Faunus::molecules | views::filter(&MoleculeData::isMolecular), [&](auto& moldata) { molecular_group_count[moldata.id()] = 0; }); - auto non_empty_molecular = [](const Group& group) { return group.isMolecular() && !group.empty(); }; - auto molecular_group_ids = spc.groups | views::filter(non_empty_molecular) | views::transform(&Group::id); + auto non_empty_molecular = [](const Group& group) { + return group.isMolecular() && !group.empty(); + }; + auto molecular_group_ids = + spc.groups | views::filter(non_empty_molecular) | views::transform(&Group::id); for_each(molecular_group_ids, [&](auto id) { molecular_group_count[id]++; }); return molecular_group_count; } MoleculeDensity::MoleculeDensity(const json& j, const Space& spc) - : Density(spc, Faunus::molecules, "molecule_density") { + : Density(spc, Faunus::molecules, "molecule_density") +{ from_json(j); } -void SanityCheck::_sample() { +void SanityCheck::_sample() +{ try { checkGroupsCoverParticles(); for (const auto& group : spc.groups) { checkWithinContainer(group); checkMassCenter(group); } - } catch (std::exception& e) { - PQRWriter().save(fmt::format("{}step{}-error.pqr", MPI::prefix, getNumberOfSteps()), spc.groups, - spc.geometry.getLength()); + } + catch (std::exception& e) { + PQRWriter().save(fmt::format("{}step{}-error.pqr", MPI::prefix, getNumberOfSteps()), + spc.groups, spc.geometry.getLength()); throw std::runtime_error(e.what()); } } -void SanityCheck::checkGroupsCoverParticles() { +void SanityCheck::checkGroupsCoverParticles() +{ size_t particle_index = 0; for (const auto& group : spc.groups) { for (auto it = group.begin(); it != group.trueend(); ++it) { @@ -1292,7 +1503,8 @@ void SanityCheck::checkGroupsCoverParticles() { * @param group Group to check * @throw if any particle is outside simulation cell */ -void SanityCheck::checkWithinContainer(const Space::GroupType& group) { +void SanityCheck::checkWithinContainer(const Space::GroupType& group) +{ bool outside_simulation_cell = false; auto outside_particles = group | ranges::cpp20::views::filter([&](const Particle& particle) { return spc.geometry.collision(particle.pos); @@ -1304,9 +1516,10 @@ void SanityCheck::checkWithinContainer(const Space::GroupType& group) { if (group.traits().numConformations() > 1) { group_str += fmt::format(" (conformation {})", group.conformation_id); } - faunus_logger->error("step {}: atom {}{} in molecule {}", getNumberOfSteps(), particle.traits().name, - group.getParticleIndex(particle), group_str); - faunus_logger->error(" (x,y,z) = {:.3f} {:.3f} {:.3f}", particle.pos.x(), particle.pos.y(), particle.pos.z()); + faunus_logger->error("step {}: atom {}{} in molecule {}", getNumberOfSteps(), + particle.traits().name, group.getParticleIndex(particle), group_str); + faunus_logger->error(" (x,y,z) = {:.3f} {:.3f} {:.3f}", particle.pos.x(), particle.pos.y(), + particle.pos.z()); }); if (outside_simulation_cell) { @@ -1314,47 +1527,56 @@ void SanityCheck::checkWithinContainer(const Space::GroupType& group) { } } -void SanityCheck::checkMassCenter(const Space::GroupType& group) { +void SanityCheck::checkMassCenter(const Space::GroupType& group) +{ if (group.isMolecular() && !group.empty()) { - const auto mass_center = - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); + const auto mass_center = Geometry::massCenter( + group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); const auto distance = spc.geometry.vdist(group.mass_center, mass_center).norm(); if (distance > mass_center_tolerance) { throw std::runtime_error(fmt::format( "step {}: {}{} mass center out of sync by {:.3f} Γ…. This *may* be due to a " - "molecule being longer than half the box length: consider increasing the simulation cell size.", + "molecule being longer than half the box length: consider increasing the " + "simulation cell size.", getNumberOfSteps(), group.traits().name, spc.getGroupIndex(group), distance)); } } } SanityCheck::SanityCheck(const json& j, const Space& spc) - : Analysis(spc, "sanity") { + : Analysis(spc, "sanity") +{ from_json(j); sample_interval = j.value("nstep", -1); } -void AtomRDF::sampleDistance(const Particle& particle1, const Particle& particle2) { +void AtomRDF::sampleDistance(const Particle& particle1, const Particle& particle2) +{ const auto distance = spc.geometry.vdist(particle1.pos, particle2.pos); if (slicedir.sum() > 0) { - if (distance.cwiseProduct((Point::Ones() - slicedir.cast()).cwiseAbs()).norm() < thickness) { + if (distance.cwiseProduct((Point::Ones() - slicedir.cast()).cwiseAbs()).norm() < + thickness) { histogram(distance.cwiseProduct(slicedir.cast()).norm())++; } - } else { + } + else { histogram(distance.norm())++; } } -void AtomRDF::_sample() { +void AtomRDF::_sample() +{ mean_volume += spc.geometry.getVolume(dimensions); if (id1 != id2) { sampleDifferent(); - } else { + } + else { sampleIdentical(); } } -void AtomRDF::sampleIdentical() { +void AtomRDF::sampleIdentical() +{ auto particles = spc.findAtoms(id1); // (id1 == id2) for (auto i = particles.begin(); i != particles.end(); ++i) { for (auto j = i; ++j != particles.end();) { @@ -1363,7 +1585,8 @@ void AtomRDF::sampleIdentical() { } } -void AtomRDF::sampleDifferent() { +void AtomRDF::sampleDifferent() +{ auto particles1 = spc.findAtoms(id1); auto particles2 = spc.findAtoms(id2); for (const auto& i : particles1) { @@ -1374,21 +1597,25 @@ void AtomRDF::sampleDifferent() { } AtomRDF::AtomRDF(const json& j, const Space& spc) - : PairFunction(spc, j, "atomrdf") { + : PairFunction(spc, j, "atomrdf") +{ id1 = Faunus::findAtomByName(name1).id(); id2 = Faunus::findAtomByName(name2).id(); } -void MoleculeRDF::_sample() { +void MoleculeRDF::_sample() +{ mean_volume += spc.geometry.getVolume(dimensions); if (id1 != id2) { sampleDifferent(); - } else { + } + else { sampleIdentical(); } } -void MoleculeRDF::sampleIdentical() { +void MoleculeRDF::sampleIdentical() +{ auto groups = spc.findMolecules(id1); for (auto i = groups.begin(); i != groups.end(); ++i) { for (auto j = i; ++j != groups.end();) { @@ -1397,7 +1624,8 @@ void MoleculeRDF::sampleIdentical() { } } -void MoleculeRDF::sampleDifferent() { +void MoleculeRDF::sampleDifferent() +{ auto ids1 = spc.findMolecules(id1); auto ids2 = spc.findMolecules(id2); for (const auto& i : ids1) { @@ -1407,7 +1635,8 @@ void MoleculeRDF::sampleDifferent() { } } -void MoleculeRDF::sampleDistance(const Group& group_i, const Group& group_j) { +void MoleculeRDF::sampleDistance(const Group& group_i, const Group& group_j) +{ assert(group_i.massCenter().has_value()); assert(group_j.massCenter().has_value()); const auto distance = sqrt(spc.geometry.sqdist(group_i.mass_center, group_j.mass_center)); @@ -1415,7 +1644,8 @@ void MoleculeRDF::sampleDistance(const Group& group_i, const Group& group_j) { } MoleculeRDF::MoleculeRDF(const json& j, const Space& spc) - : PairFunction(spc, j, "molrdf") { + : PairFunction(spc, j, "molrdf") +{ id1 = findMoleculeByName(name1).id(); id2 = findMoleculeByName(name2).id(); if (!Faunus::molecules.at(id1).isMolecular() || !Faunus::molecules.at(id2).isMolecular()) { @@ -1423,11 +1653,13 @@ MoleculeRDF::MoleculeRDF(const json& j, const Space& spc) } } -void AtomDipDipCorr::_sample() { +void AtomDipDipCorr::_sample() +{ mean_volume += spc.geometry.getVolume(dimensions); const auto particles = spc.activeParticles(); - auto sample_distance = [&](const auto& particle1, const auto& particle2, const Point& distance) { + auto sample_distance = [&](const auto& particle1, const auto& particle2, + const Point& distance) { if (particle1.hasExtension() && particle2.hasExtension()) { const auto cosine_angle = particle1.getExt().mu.dot(particle2.getExt().mu); const auto r = distance.norm(); @@ -1444,7 +1676,8 @@ void AtomDipDipCorr::_sample() { if (distance.cwiseProduct(slicedir.cast()).norm() < thickness) { sample_distance(*i, *j, distance); } - } else { + } + else { sample_distance(*i, *j, distance); } } @@ -1453,7 +1686,8 @@ void AtomDipDipCorr::_sample() { } AtomDipDipCorr::AtomDipDipCorr(const json& j, const Space& spc) - : PairAngleFunction(spc, j, "atomdipdipcorr") { + : PairAngleFunction(spc, j, "atomdipdipcorr") +{ id1 = findAtomByName(name1).id(); id2 = findAtomByName(name2).id(); } @@ -1465,16 +1699,20 @@ AtomDipDipCorr::AtomDipDipCorr(const json& j, const Space& spc) * @param filename Output xtc file name * @param molecule_names Save only a subset of molecules with matching names (default empty = all) */ -XTCtraj::XTCtraj(const Space& spc, const std::string& filename, const std::vector& molecule_names) - : Analysis(spc, "xtcfile") { +XTCtraj::XTCtraj(const Space& spc, const std::string& filename, + const std::vector& molecule_names) + : Analysis(spc, "xtcfile") +{ namespace rv = ranges::cpp20::views; writer = std::make_unique(filename); if (!molecule_names.empty()) { group_ids = Faunus::names2ids(Faunus::molecules, molecule_names); // molecule types to save - group_indices = group_ids | - rv::transform([&](auto id) { return spc.findMolecules(id, Space::Selection::ALL); }) | - ranges::views::cache1 | rv::join | - rv::transform([&](const Group& group) { return spc.getGroupIndex(group); }) | ranges::to_vector; + group_indices = + group_ids | + rv::transform([&](auto id) { return spc.findMolecules(id, Space::Selection::ALL); }) | + ranges::views::cache1 | rv::join | + rv::transform([&](const Group& group) { return spc.getGroupIndex(group); }) | + ranges::to_vector; if (group_indices.empty()) { throw ConfigurationError("xtc selection is empty - nothing to sample"); } @@ -1482,29 +1720,36 @@ XTCtraj::XTCtraj(const Space& spc, const std::string& filename, const std::vecto } XTCtraj::XTCtraj(const json& j, const Space& spc) - : XTCtraj(spc, MPI::prefix + j.at("file").get(), j.value("molecules", std::vector())) { + : XTCtraj(spc, MPI::prefix + j.at("file").get(), + j.value("molecules", std::vector())) +{ Analysis::from_json(j); } -void XTCtraj::_to_json(json& j) const { +void XTCtraj::_to_json(json& j) const +{ j["file"] = writer->filename; if (!group_ids.empty()) { - j["molecules"] = group_ids | - ranges::cpp20::views::transform([](auto id) { return Faunus::molecules.at(id).name; }) | - ranges::to_vector; + j["molecules"] = + group_ids | + ranges::cpp20::views::transform([](auto id) { return Faunus::molecules.at(id).name; }) | + ranges::to_vector; } } /** * Write one frame to the xtc file containing both active AND inactive particles. */ -void XTCtraj::_sample() { +void XTCtraj::_sample() +{ namespace rv = ranges::cpp20::views; if (group_ids.empty()) { auto positions = spc.particles | rv::transform(&Particle::pos); writer->writeNext(spc.geometry.getLength(), positions.begin(), positions.end()); - } else { - auto positions = group_indices | rv::transform([&](auto i) { return spc.groups.at(i).all(); }) | + } + else { + auto positions = group_indices | + rv::transform([&](auto i) { return spc.groups.at(i).all(); }) | ranges::views::cache1 | rv::join | rv::transform(&Particle::pos); writer->writeNext(spc.geometry.getLength(), positions.begin(), positions.end()); } @@ -1513,7 +1758,8 @@ void XTCtraj::_sample() { // =============== MultipoleDistribution =============== double MultipoleDistribution::groupGroupExactEnergy(const Space::GroupType& group1, - const Space::GroupType& group2) const { + const Space::GroupType& group2) const +{ double energy = 0.0; for (const auto& a : group1) { for (const auto& b : group2) { @@ -1524,21 +1770,25 @@ double MultipoleDistribution::groupGroupExactEnergy(const Space::GroupType& grou } /// Calculate multipole energies between two groups and update averages at given separation -void MultipoleDistribution::sampleGroupGroup(const Space::GroupType& group1, const Space::GroupType& group2) { +void MultipoleDistribution::sampleGroupGroup(const Space::GroupType& group1, + const Space::GroupType& group2) +{ const auto a = Faunus::toMultipole(group1, spc.geometry.getBoundaryFunc()); const auto b = Faunus::toMultipole(group2, spc.geometry.getBoundaryFunc()); const auto distance = spc.geometry.vdist(group1.mass_center, group2.mass_center); auto& data = mean_energy[to_bin(distance.norm(), dr)]; data.exact += groupGroupExactEnergy(group1, group2); data.ion_ion += a.charge * b.charge / distance.norm(); - data.ion_dipole += - q2mu(a.charge * b.getExt().mulen, b.getExt().mu, b.charge * a.getExt().mulen, a.getExt().mu, distance); - data.dipole_dipole += mu2mu(a.getExt().mu, b.getExt().mu, a.getExt().mulen * b.getExt().mulen, distance); + data.ion_dipole += q2mu(a.charge * b.getExt().mulen, b.getExt().mu, b.charge * a.getExt().mulen, + a.getExt().mu, distance); + data.dipole_dipole += + mu2mu(a.getExt().mu, b.getExt().mu, a.getExt().mulen * b.getExt().mulen, distance); data.ion_quadrupole += q2quad(a.charge, b.getExt().Q, b.charge, a.getExt().Q, distance); data.dipole_dipole_correlation += a.getExt().mu.dot(b.getExt().mu); } -void MultipoleDistribution::_sample() { +void MultipoleDistribution::_sample() +{ // loop over active molecules for (const auto& group1 : spc.findMolecules(ids.at(0))) { for (const auto& group2 : spc.findMolecules(ids.at(1))) { @@ -1552,30 +1802,36 @@ void MultipoleDistribution::_sample() { /** * @note `fmt` is currently included w. spdlog but has been accepted into c++20. */ -void MultipoleDistribution::_to_disk() { +void MultipoleDistribution::_to_disk() +{ if (number_of_samples == 0) { return; } if (std::ofstream stream(MPI::prefix + filename); stream) { stream << "# Multipolar energies (kT/lB)\n" - << fmt::format("# {:>8}{:>10}{:>10}{:>10}{:>10}{:>10}{:>10}{:>10}\n", "R", "exact", "tot", "ii", "id", - "dd", "iq", "mucorr"); + << fmt::format("# {:>8}{:>10}{:>10}{:>10}{:>10}{:>10}{:>10}{:>10}\n", "R", "exact", + "tot", "ii", "id", "dd", "iq", "mucorr"); for (auto [r_bin, energy] : mean_energy) { const auto distance = r_bin * dr; - const auto u_tot = energy.ion_ion.avg() + energy.ion_dipole.avg() + energy.dipole_dipole.avg() + - energy.ion_quadrupole.avg(); - stream << fmt::format("{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}\n", distance, - energy.exact.avg(), u_tot, energy.ion_ion.avg(), energy.ion_dipole.avg(), - energy.dipole_dipole.avg(), energy.ion_quadrupole.avg(), - energy.dipole_dipole_correlation.avg()); + const auto u_tot = energy.ion_ion.avg() + energy.ion_dipole.avg() + + energy.dipole_dipole.avg() + energy.ion_quadrupole.avg(); + stream << fmt::format( + "{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}{:10.4f}\n", distance, + energy.exact.avg(), u_tot, energy.ion_ion.avg(), energy.ion_dipole.avg(), + energy.dipole_dipole.avg(), energy.ion_quadrupole.avg(), + energy.dipole_dipole_correlation.avg()); } } } -void MultipoleDistribution::_to_json(json& j) const { j = {{"molecules", names}, {"file", filename}, {"dr", dr}}; } +void MultipoleDistribution::_to_json(json& j) const +{ + j = {{"molecules", names}, {"file", filename}, {"dr", dr}}; +} MultipoleDistribution::MultipoleDistribution(const json& j, const Space& spc) - : Analysis(spc, "Multipole Distribution") { + : Analysis(spc, "Multipole Distribution") +{ from_json(j); dr = j.at("dr").get(); filename = j.at("file").get(); @@ -1588,33 +1844,40 @@ MultipoleDistribution::MultipoleDistribution(const json& j, const Space& spc) // =============== AtomInertia =============== -void AtomInertia::_to_json(json& j) const { +void AtomInertia::_to_json(json& j) const +{ j["index"] = atom_id; // atom id } -Point AtomInertia::compute() { +Point AtomInertia::compute() +{ auto slice = spc.findAtoms(atom_id); - const auto cm = Geometry::massCenter(slice.begin(), slice.end(), spc.geometry.getBoundaryFunc()); - const auto I = Geometry::inertia(slice.begin(), slice.end(), cm, spc.geometry.getBoundaryFunc()); + const auto cm = + Geometry::massCenter(slice.begin(), slice.end(), spc.geometry.getBoundaryFunc()); + const auto I = + Geometry::inertia(slice.begin(), slice.end(), cm, spc.geometry.getBoundaryFunc()); Eigen::SelfAdjointEigenSolver esf(I); return esf.eigenvalues(); } -void AtomInertia::_sample() { +void AtomInertia::_sample() +{ if (output_stream) { output_stream << getNumberOfSteps() << " " << compute().transpose() << "\n"; } } AtomInertia::AtomInertia(const json& j, const Space& spc) - : Analysis(spc, "Atomic Inertia Eigenvalues") { + : Analysis(spc, "Atomic Inertia Eigenvalues") +{ from_json(j); filename = MPI::prefix + j.at("file").get(); output_stream.open(filename); atom_id = j.at("index").get(); } -void AtomInertia::_to_disk() { +void AtomInertia::_to_disk() +{ if (output_stream) { output_stream.flush(); // empty buffer } @@ -1622,42 +1885,52 @@ void AtomInertia::_to_disk() { // =============== InertiaTensor =============== -void InertiaTensor::_to_json(json& j) const { +void InertiaTensor::_to_json(json& j) const +{ j["indexes"] = particle_range; // range of indexes within the group j["index"] = group_index; // group index j["file"] = filename; } -std::pair InertiaTensor::compute() const { +std::pair InertiaTensor::compute() const +{ const auto& group = spc.groups.at(group_index); - auto subgroup = - ranges::make_subrange(group.begin() + particle_range.at(0), group.begin() + particle_range.at(1) + 1); + auto subgroup = ranges::make_subrange(group.begin() + particle_range.at(0), + group.begin() + particle_range.at(1) + 1); - auto I = Geometry::inertia(subgroup.begin(), subgroup.end(), group.mass_center, spc.geometry.getBoundaryFunc()); + auto I = Geometry::inertia(subgroup.begin(), subgroup.end(), group.mass_center, + spc.geometry.getBoundaryFunc()); std::ptrdiff_t index; // index of eigenvector w. minimum eigenvalue const auto esf = Eigen::SelfAdjointEigenSolver(I); Point eigen_values = esf.eigenvalues(); eigen_values.minCoeff(&index); - Point principle_axis = esf.eigenvectors().col(index).real(); // eigenvector corresponding to the smallest eigenvalue + Point principle_axis = esf.eigenvectors() + .col(index) + .real(); // eigenvector corresponding to the smallest eigenvalue return {eigen_values, principle_axis}; } -void InertiaTensor::_sample() { +void InertiaTensor::_sample() +{ const auto [eigen_values, principle_axis] = compute(); - *stream << fmt::format("{} {} {} {} {} {} {} \n", getNumberOfSteps(), eigen_values[0], eigen_values[1], - eigen_values[2], principle_axis[0], principle_axis[1], principle_axis[2]); + *stream << fmt::format("{} {} {} {} {} {} {} \n", getNumberOfSteps(), eigen_values[0], + eigen_values[1], eigen_values[2], principle_axis[0], principle_axis[1], + principle_axis[2]); } InertiaTensor::InertiaTensor(const json& j, const Space& spc) - : Analysis(spc, "Inertia Tensor") { + : Analysis(spc, "Inertia Tensor") +{ from_json(j); group_index = j.at("index").get(); const auto& group = spc.groups.at(group_index); if (!group.massCenter()) { - throw ConfigurationError("group must have a well-defined mass center (e.g. molecular groups)"); + throw ConfigurationError( + "group must have a well-defined mass center (e.g. molecular groups)"); } - particle_range = j.value("indexes", std::vector({0, group.size()})); // whole molecule by default + particle_range = + j.value("indexes", std::vector({0, group.size()})); // whole molecule by default if (particle_range.size() != 2) { throw ConfigurationError("{}: either two or no indices expected", name); } @@ -1666,27 +1939,32 @@ InertiaTensor::InertiaTensor(const json& j, const Space& spc) *stream << "# step eigen_values_xyz principal_axis_xyz\n"; } -void InertiaTensor::_to_disk() { +void InertiaTensor::_to_disk() +{ stream->flush(); // empty buffer } // =============== MultipoleMoments =============== -void MultipoleMoments::_to_json(json& j) const { +void MultipoleMoments::_to_json(json& j) const +{ const auto& group = spc.groups.at(group_index); const auto particle1 = group.begin() + particle_range[0]; const auto particle2 = group.begin() + particle_range[1]; - j["particles"] = fmt::format("{}{} {}{}", particle1->traits().name, particle_range[0], particle2->traits().name, - particle_range[1]); + j["particles"] = fmt::format("{}{} {}{}", particle1->traits().name, particle_range[0], + particle2->traits().name, particle_range[1]); j["molecule"] = group.traits().name; } -MultipoleMoments::Data MultipoleMoments::calculateMultipoleMoment() const { +MultipoleMoments::Data MultipoleMoments::calculateMultipoleMoment() const +{ const auto& group = spc.groups.at(group_index); - Space::GroupType subgroup(group.id, group.begin() + particle_range[0], group.begin() + particle_range[1] + 1); - const auto mass_center = use_molecular_mass_center ? group.mass_center - : Geometry::massCenter(subgroup.begin(), subgroup.end(), - spc.geometry.getBoundaryFunc()); + Space::GroupType subgroup(group.id, group.begin() + particle_range[0], + group.begin() + particle_range[1] + 1); + const auto mass_center = use_molecular_mass_center + ? group.mass_center + : Geometry::massCenter(subgroup.begin(), subgroup.end(), + spc.geometry.getBoundaryFunc()); MultipoleMoments::Data multipole; Tensor quadrupole; // quadrupole tensor @@ -1696,33 +1974,40 @@ MultipoleMoments::Data MultipoleMoments::calculateMultipoleMoment() const { spc.geometry.boundary(position); multipole.charge += particle.charge; multipole.dipole_moment += particle.charge * position; - quadrupole += particle.charge * (3.0 * position * position.transpose() - - Eigen::Matrix::Identity() * position.squaredNorm()); + quadrupole += + particle.charge * (3.0 * position * position.transpose() - + Eigen::Matrix::Identity() * position.squaredNorm()); } quadrupole = 0.5 * quadrupole; Eigen::SelfAdjointEigenSolver esf(quadrupole); multipole.eivals = esf.eigenvalues(); std::ptrdiff_t i_eival = 0; multipole.eivals.minCoeff(&i_eival); - multipole.eivec = esf.eigenvectors().col(i_eival).real(); // eigenvector corresponding to the smallest eigenvalue + multipole.eivec = esf.eigenvectors() + .col(i_eival) + .real(); // eigenvector corresponding to the smallest eigenvalue multipole.center = mass_center; return multipole; } -void MultipoleMoments::_sample() { +void MultipoleMoments::_sample() +{ if (output_stream) { const auto multipole = calculateMultipoleMoment(); - output_stream << getNumberOfSteps() << " " << multipole.charge << " " << multipole.dipole_moment.transpose() - << " " << multipole.center.transpose() << " " << multipole.eivals.transpose() << " " - << multipole.eivec.transpose() << "\n"; + output_stream << getNumberOfSteps() << " " << multipole.charge << " " + << multipole.dipole_moment.transpose() << " " << multipole.center.transpose() + << " " << multipole.eivals.transpose() << " " << multipole.eivec.transpose() + << "\n"; } } MultipoleMoments::MultipoleMoments(const json& j, const Space& spc) - : Analysis(spc, "Multipole Moments") { + : Analysis(spc, "Multipole Moments") +{ from_json(j); try { - use_molecular_mass_center = j.value("mol_cm", true); // use the mass center of the whole molecule + use_molecular_mass_center = + j.value("mol_cm", true); // use the mass center of the whole molecule filename = MPI::prefix + j.at("file").get(); output_stream.open(filename); // output file @@ -1735,15 +2020,17 @@ MultipoleMoments::MultipoleMoments(const json& j, const Space& spc) throw ConfigurationError("exactly two `indexes` must be given"); } if (particle_range[0] > particle_range[1] || particle_range[1] >= group.size()) { - throw ConfigurationError("`indexes [{},{}]` outside {}", particle_range[0], particle_range[1], - group.traits().name); + throw ConfigurationError("`indexes [{},{}]` outside {}", particle_range[0], + particle_range[1], group.traits().name); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("{}: {}", name, e.what()); } } -void MultipoleMoments::_to_disk() { +void MultipoleMoments::_to_disk() +{ if (output_stream) { output_stream.flush(); // empty buffer } @@ -1751,10 +2038,12 @@ void MultipoleMoments::_to_disk() { // =============== PolymerShape =============== -void PolymerShape::_to_json(json& j) const { +void PolymerShape::_to_json(json& j) const +{ if (!data.gyration_radius.empty()) { j = {{"molecule", Faunus::molecules[molid].name}, - {"⟨s²⟩-⟨s⟩²", data.gyration_radius_squared.avg() - std::pow(data.gyration_radius.avg(), 2)}, + {"⟨s²⟩-⟨s⟩²", + data.gyration_radius_squared.avg() - std::pow(data.gyration_radius.avg(), 2)}, {"⟨rΒ²βŸ©βˆ•βŸ¨s²⟩", data.end_to_end_squared.avg() / data.gyration_radius_squared.avg()}, {"Rg = √⟨s²⟩", std::sqrt(data.gyration_radius_squared.avg())}, {"Re = √⟨r²⟩", std::sqrt(data.end_to_end_squared.avg())}, @@ -1764,7 +2053,8 @@ void PolymerShape::_to_json(json& j) const { } } -void PolymerShape::_sample() { +void PolymerShape::_sample() +{ auto molecules = spc.findMolecules(molid, Space::Selection::ACTIVE); const auto num_molecules = std::distance(molecules.begin(), molecules.end()); @@ -1775,18 +2065,21 @@ void PolymerShape::_sample() { for (const auto& group : molecules) { if (group.size() >= 2) { // two or more particles required to form a polymer - const auto gyration_tensor = - Geometry::gyration(group.begin(), group.end(), group.mass_center, spc.geometry.getBoundaryFunc()); - const auto principal_moment = Eigen::SelfAdjointEigenSolver(gyration_tensor).eigenvalues(); + const auto gyration_tensor = Geometry::gyration( + group.begin(), group.end(), group.mass_center, spc.geometry.getBoundaryFunc()); + const auto principal_moment = + Eigen::SelfAdjointEigenSolver(gyration_tensor).eigenvalues(); const auto gyration_radius_squared = gyration_tensor.trace(); - const auto end_to_end_squared = spc.geometry.sqdist(group.begin()->pos, std::prev(group.end())->pos); + const auto end_to_end_squared = + spc.geometry.sqdist(group.begin()->pos, std::prev(group.end())->pos); data.end_to_end_squared += end_to_end_squared; data.gyration_radius += std::sqrt(gyration_radius_squared); data.gyration_radius_squared += gyration_radius_squared; gyration_radius_histogram(std::sqrt(gyration_radius_squared))++; - const double asphericity = 3.0 / 2.0 * principal_moment.z() - gyration_radius_squared / 2.0; + const double asphericity = + 3.0 / 2.0 * principal_moment.z() - gyration_radius_squared / 2.0; const double acylindricity = principal_moment.y() - principal_moment.x(); const double relative_shape_anisotropy = (asphericity * asphericity + 3.0 / 4.0 * acylindricity * acylindricity) / @@ -1798,16 +2091,19 @@ void PolymerShape::_sample() { if (tensor_output_stream) { const auto& t = gyration_tensor; - *tensor_output_stream << fmt::format("{} {:.2f} {:5e} {:5e} {:5e} {:5e} {:5e} {:5e}\n", - this->getNumberOfSteps(), std::sqrt(gyration_radius_squared), - t(0, 0), t(0, 1), t(0, 2), t(1, 1), t(1, 2), t(2, 2)); + *tensor_output_stream + << fmt::format("{} {:.2f} {:5e} {:5e} {:5e} {:5e} {:5e} {:5e}\n", + this->getNumberOfSteps(), std::sqrt(gyration_radius_squared), + t(0, 0), t(0, 1), t(0, 2), t(1, 1), t(1, 2), t(2, 2)); } } } } -void PolymerShape::_to_disk() { - const std::string filename = fmt::format("{}gyration_{}.dat", MPI::prefix, Faunus::molecules[molid].name); +void PolymerShape::_to_disk() +{ + const std::string filename = + fmt::format("{}gyration_{}.dat", MPI::prefix, Faunus::molecules[molid].name); if (auto stream = std::ofstream(filename); stream) { gyration_radius_histogram.stream_decorator = [](auto& stream, auto Rg, auto observations) { if (observations > 0) { @@ -1822,11 +2118,13 @@ void PolymerShape::_to_disk() { } PolymerShape::PolymerShape(const json& j, const Space& spc) - : Analysis(spc, "Polymer Shape") { + : Analysis(spc, "Polymer Shape") +{ from_json(j); cite = "doi:10/d6ff"; if (j.count("molecules") > 0) { - throw ConfigurationError("{}: 'molecules' is deprecated, use a single 'molecule' instead.", name); + throw ConfigurationError("{}: 'molecules' is deprecated, use a single 'molecule' instead.", + name); } const std::string molname = j.at("molecule"); molid = findMoleculeByName(molname).id(); @@ -1841,7 +2139,8 @@ PolymerShape::PolymerShape(const json& j, const Space& spc) } } -void SASAAnalysis::_to_disk() { +void SASAAnalysis::_to_disk() +{ if (auto stream = std::ofstream(MPI::prefix + "sasa_histogram.dat")) { stream << sasa_histogram; } @@ -1850,29 +2149,34 @@ void SASAAnalysis::_to_disk() { } } -void SASAAnalysis::_from_json(const json& input) { +void SASAAnalysis::_from_json(const json& input) +{ if (filename = input.value("file", ""s); !filename.empty()) { output_stream = IO::openCompressedOutputStream(MPI::prefix + filename); *output_stream << "# step SASA\n"; } } -SASAAnalysis::SASAAnalysis(const double probe_radius, const int slices_per_atom, const double resolution, - const Policies selected_policy, const Space& spc) +SASAAnalysis::SASAAnalysis(const double probe_radius, const int slices_per_atom, + const double resolution, const Policies selected_policy, + const Space& spc) : Analysis(spc, "sasa") , probe_radius(probe_radius) , slices_per_atom(slices_per_atom) - , sasa_histogram(resolution, 0.0) { + , sasa_histogram(resolution, 0.0) +{ using Faunus::SASA::SASACellList; const auto periodic_dimensions = spc.geometry.asSimpleGeometry()->boundary_conditions.isPeriodic().cast().sum(); switch (periodic_dimensions) { case 3: // PBC in all directions - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); + sasa = std::make_unique>( + spc, probe_radius, slices_per_atom); break; case 0: - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); + sasa = std::make_unique>(spc, probe_radius, + slices_per_atom); break; default: faunus_logger->warn("CellList neighbour search not available yet for current geometry"); @@ -1884,16 +2188,19 @@ SASAAnalysis::SASAAnalysis(const double probe_radius, const int slices_per_atom, } SASAAnalysis::SASAAnalysis(const json& j, const Space& spc) - : SASAAnalysis(j.value("radius", 1.4_angstrom), j.value("slices", 20), j.value("resolution", 50.0), - j.value("policy", Policies::INVALID), spc) { + : SASAAnalysis(j.value("radius", 1.4_angstrom), j.value("slices", 20), + j.value("resolution", 50.0), j.value("policy", Policies::INVALID), spc) +{ from_json(j); policy->from_json(j); } -void SASAAnalysis::_to_json(json& json_ouput) const { +void SASAAnalysis::_to_json(json& json_ouput) const +{ if (!average_data.area.empty()) { json_ouput = {{"⟨SASA⟩", average_data.area.avg()}, - {"⟨SASA²⟩-⟨SASA⟩²", average_data.area_squared.avg() - std::pow(average_data.area.avg(), 2)}}; + {"⟨SASA²⟩-⟨SASA⟩²", + average_data.area_squared.avg() - std::pow(average_data.area.avg(), 2)}}; } json_ouput["radius"] = (probe_radius); json_ouput["slices_per_atom"] = (slices_per_atom); @@ -1904,7 +2211,8 @@ void SASAAnalysis::_to_json(json& json_ouput) const { * * @param area */ -void SASAAnalysis::takeSample(const double area) { +void SASAAnalysis::takeSample(const double area) +{ average_data.area += area; average_data.area_squared += std::pow(area, 2); sasa_histogram(area)++; @@ -1917,7 +2225,8 @@ void SASAAnalysis::takeSample(const double area) { * * @param selected_policy selected policy */ -void SASAAnalysis::setPolicy(const Policies selected_policy) { +void SASAAnalysis::setPolicy(const Policies selected_policy) +{ switch (selected_policy) { case Policies::ATOMIC: policy = std::make_unique(); @@ -1934,7 +2243,10 @@ void SASAAnalysis::setPolicy(const Policies selected_policy) { } } -void SASAAnalysis::_sample() { policy->sample(spc, *this); } +void SASAAnalysis::_sample() +{ + policy->sample(spc, *this); +} /** @brief samples sasa of each object (either a whole group or a particle) * by sampling each sasa between first and last individually @@ -1946,7 +2258,8 @@ void SASAAnalysis::_sample() { policy->sample(spc, *this); } * @tparam TEnd * */ template -void AreaSamplingPolicy::sampleIndividualSASA(TBegin first, TEnd last, SASAAnalysis& analysis) { +void AreaSamplingPolicy::sampleIndividualSASA(TBegin first, TEnd last, SASAAnalysis& analysis) +{ analysis.sasa->init(analysis.spc); std::for_each(first, last, [&analysis](const auto& species) { auto area = analysis.sasa->calcSASAOf(analysis.spc, species); @@ -1962,7 +2275,8 @@ void AreaSamplingPolicy::sampleIndividualSASA(TBegin first, TEnd last, SASAAnaly * * */ template -void AreaSamplingPolicy::sampleTotalSASA(TBegin first, TEnd last, SASAAnalysis& analysis) { +void AreaSamplingPolicy::sampleTotalSASA(TBegin first, TEnd last, SASAAnalysis& analysis) +{ analysis.sasa->init(analysis.spc); auto area = analysis.sasa->calcSASA(analysis.spc, first, last); analysis.takeSample(area); @@ -1971,31 +2285,42 @@ void AreaSamplingPolicy::sampleTotalSASA(TBegin first, TEnd last, SASAAnalysis& } } -void AtomicPolicy::sample(const Space& spc, SASAAnalysis& analysis) { +void AtomicPolicy::sample(const Space& spc, SASAAnalysis& analysis) +{ auto atoms = spc.findAtoms(atom_id); sampleIndividualSASA(atoms.begin(), atoms.end(), analysis); } -void AtomicPolicy::to_json(json& output) const { output["atom_name"] = atom_name; } +void AtomicPolicy::to_json(json& output) const +{ + output["atom_name"] = atom_name; +} -void AtomicPolicy::from_json(const json& input) { +void AtomicPolicy::from_json(const json& input) +{ atom_name = input.at("atom").get(); atom_id = findAtomByName(atom_name).id(); } -void MolecularPolicy::sample(const Space& spc, SASAAnalysis& analysis) { +void MolecularPolicy::sample(const Space& spc, SASAAnalysis& analysis) +{ auto molecules = spc.findMolecules(molecule_id); sampleIndividualSASA(molecules.begin(), molecules.end(), analysis); } -void MolecularPolicy::to_json(json& output) const { output["molecule"] = molecule_name; } +void MolecularPolicy::to_json(json& output) const +{ + output["molecule"] = molecule_name; +} -void MolecularPolicy::from_json(const json& input) { +void MolecularPolicy::from_json(const json& input) +{ molecule_name = input.at("molecule").get(); molecule_id = findMoleculeByName(molecule_name).id(); } -void AtomsInMoleculePolicy::sample(const Space& spc, SASAAnalysis& analysis) { +void AtomsInMoleculePolicy::sample(const Space& spc, SASAAnalysis& analysis) +{ auto molecules = spc.findMolecules(molecule_id); ranges::for_each(molecules, [&](const auto& molecule) { @@ -2007,34 +2332,41 @@ void AtomsInMoleculePolicy::sample(const Space& spc, SASAAnalysis& analysis) { }); } -void AtomsInMoleculePolicy::to_json(json& output) const { +void AtomsInMoleculePolicy::to_json(json& output) const +{ output["molecule"] = molecule_name; output["atomlist"] = atom_names; } -void AtomsInMoleculePolicy::from_json(const json& input) { +void AtomsInMoleculePolicy::from_json(const json& input) +{ if (const auto& atomlist = input.at("atomlist"); !atomlist.empty()) { for (const auto& atom_specifier : atomlist) { if (atom_specifier.is_string()) { atom_names.insert(static_cast(atom_specifier)); - selected_indices.insert(findAtomByName(static_cast(atom_specifier)).id()); - } else if (atom_specifier.is_number_integer()) { + selected_indices.insert( + findAtomByName(static_cast(atom_specifier)).id()); + } + else if (atom_specifier.is_number_integer()) { selected_indices.insert(static_cast(atom_specifier)); } } - } else { + } + else { ConfigurationError("you must choose at least one atom in atomlist"); } molecule_name = input.at("molecule").get(); } -void AtomProfile::_from_json(const json& j) { +void AtomProfile::_from_json(const json& j) +{ origin = j.value("origo", Point(0, 0, 0)); dir = j.value("dir", dir); file = j.at("file").get(); - names = j.at("atoms").get(); // atom names - const auto vec_of_ids = Faunus::names2ids(Faunus::atoms, names); // names --> molids - atom_id_selection = std::set(vec_of_ids.begin(), vec_of_ids.end()); // copy vector to set + names = j.at("atoms").get(); // atom names + const auto vec_of_ids = Faunus::names2ids(Faunus::atoms, names); // names --> molids + atom_id_selection = + std::set(vec_of_ids.begin(), vec_of_ids.end()); // copy vector to set dr = j.value("dr", 0.1); table.setResolution(dr, 0); count_charge = j.value("charge", false); @@ -2045,14 +2377,17 @@ void AtomProfile::_from_json(const json& j) { } } -void AtomProfile::_to_json(json& j) const { - j = {{"origo", origin}, {"dir", dir}, {"atoms", names}, {"file", file}, {"dr", dr}, {"charge", count_charge}}; +void AtomProfile::_to_json(json& j) const +{ + j = {{"origo", origin}, {"dir", dir}, {"atoms", names}, + {"file", file}, {"dr", dr}, {"charge", count_charge}}; if (center_of_mass_atom_id >= 0) { j["atomcom"] = Faunus::atoms.at(center_of_mass_atom_id).name; } } -void AtomProfile::_sample() { +void AtomProfile::_sample() +{ if (center_of_mass_atom_id >= 0) { // calc. mass center of selected atoms auto mass_center_particles = spc.findAtoms(center_of_mass_atom_id); @@ -2060,9 +2395,10 @@ void AtomProfile::_sample() { spc.geometry.getBoundaryFunc()); } - auto selected_particles = spc.activeParticles() | ranges::cpp20::views::filter([&](const Particle& particle) { - return atom_id_selection.count(particle.id) > 0; - }); + auto selected_particles = + spc.activeParticles() | ranges::cpp20::views::filter([&](const Particle& particle) { + return atom_id_selection.count(particle.id) > 0; + }); for (const auto& particle : selected_particles) { const auto distance = distanceToOrigin(particle.pos); @@ -2070,17 +2406,20 @@ void AtomProfile::_sample() { } } -double AtomProfile::distanceToOrigin(const Point& position) const { +double AtomProfile::distanceToOrigin(const Point& position) const +{ const Point distance = spc.geometry.vdist(position, origin); return distance.cwiseProduct(dir.cast()).norm(); } AtomProfile::AtomProfile(const json& j, const Space& spc) - : Analysis(spc, "atomprofile") { + : Analysis(spc, "atomprofile") +{ from_json(j); } -void AtomProfile::_to_disk() { +void AtomProfile::_to_disk() +{ std::ofstream stream(MPI::prefix + file); if (stream) { table.stream_decorator = [&](std::ostream& o, double r, double N) { @@ -2097,7 +2436,8 @@ void AtomProfile::_to_disk() { Vr = dr; break; default: - throw std::runtime_error("bad dimension; sum of `dir` components must be one, two, or three"); + throw std::runtime_error( + "bad dimension; sum of `dir` components must be one, two, or three"); } if (Vr < dr) { // take care of the case where Vr=0 @@ -2112,7 +2452,8 @@ void AtomProfile::_to_disk() { } } -void SlicedDensity::_from_json(const json& j) { +void SlicedDensity::_from_json(const json& j) +{ file = j.at("file").get(); atom_names = j.at("atoms").get(); atom_ids = names2ids(atoms, atom_names); @@ -2124,14 +2465,16 @@ void SlicedDensity::_from_json(const json& j) { histogram.setResolution(dz); } -void SlicedDensity::_to_json(json& j) const { +void SlicedDensity::_to_json(json& j) const +{ j = {{"atoms", atom_names}, {"file", file}, {"dz", dz}}; if (center_of_mass_atom_id >= 0) { j["atomcom"] = Faunus::atoms.at(center_of_mass_atom_id).name; } } -void SlicedDensity::_sample() { +void SlicedDensity::_sample() +{ double z_offset = 0.0; if (center_of_mass_atom_id >= 0) { // calc. mass center of selected atoms @@ -2141,9 +2484,10 @@ void SlicedDensity::_sample() { .z(); } - auto filtered_particles = spc.activeParticles() | ranges::cpp20::views::filter([&](const auto& particle) { - return std::find(atom_ids.begin(), atom_ids.end(), particle.id) != atom_ids.end(); - }); + auto filtered_particles = + spc.activeParticles() | ranges::cpp20::views::filter([&](const auto& particle) { + return std::find(atom_ids.begin(), atom_ids.end(), particle.id) != atom_ids.end(); + }); for (const auto& particle : filtered_particles) { histogram(particle.pos.z() - z_offset)++; @@ -2151,11 +2495,13 @@ void SlicedDensity::_sample() { } SlicedDensity::SlicedDensity(const json& j, const Space& spc) - : Analysis(spc, "sliceddensity") { + : Analysis(spc, "sliceddensity") +{ from_json(j); } -void SlicedDensity::_to_disk() { +void SlicedDensity::_to_disk() +{ if (number_of_samples == 0) { return; } @@ -2172,7 +2518,8 @@ void SlicedDensity::_to_disk() { } } -void ChargeFluctuations::_sample() { +void ChargeFluctuations::_sample() +{ auto filtered_molecules = spc.findMolecules(mol_iter->id(), Space::Selection::ACTIVE); for (const auto& group : filtered_molecules) { size_t particle_index = 0; @@ -2184,7 +2531,8 @@ void ChargeFluctuations::_sample() { } } -void ChargeFluctuations::_to_json(json& j) const { +void ChargeFluctuations::_to_json(json& j) const +{ j["molecule"] = mol_iter->name; if (not filename.empty()) { j["pqrfile"] = filename; @@ -2196,27 +2544,35 @@ void ChargeFluctuations::_to_json(json& j) const { } } -std::vector ChargeFluctuations::getPredominantParticleNames() const { +std::vector ChargeFluctuations::getPredominantParticleNames() const +{ auto most_frequent_name = [](const AtomHistogram& histogram) { const auto [atomid, count] = - *std::max_element(histogram.begin(), histogram.end(), [](auto& a, auto& b) { return a.second < b.second; }); + *std::max_element(histogram.begin(), histogram.end(), + [](auto& a, auto& b) { return a.second < b.second; }); return Faunus::atoms[atomid].name; }; // in a histogram, find the atom name with most counts auto atom_names = atom_histograms | ranges::cpp20::views::transform(most_frequent_name); - return std::vector(ranges::cpp20::begin(atom_names), ranges::cpp20::end(atom_names)); + return std::vector(ranges::cpp20::begin(atom_names), + ranges::cpp20::end(atom_names)); } -std::vector ChargeFluctuations::getChargeStandardDeviation() const { - auto stdev = atom_mean_charges | ranges::cpp20::views::transform([](auto& i) { return i.stdev(); }); +std::vector ChargeFluctuations::getChargeStandardDeviation() const +{ + auto stdev = + atom_mean_charges | ranges::cpp20::views::transform([](auto& i) { return i.stdev(); }); return std::vector(ranges::cpp20::begin(stdev), ranges::cpp20::end(stdev)); } -std::vector ChargeFluctuations::getMeanCharges() const { - return std::vector(ranges::cpp20::begin(atom_mean_charges), ranges::cpp20::end(atom_mean_charges)); +std::vector ChargeFluctuations::getMeanCharges() const +{ + return std::vector(ranges::cpp20::begin(atom_mean_charges), + ranges::cpp20::end(atom_mean_charges)); } -void ChargeFluctuations::_to_disk() { +void ChargeFluctuations::_to_disk() +{ if (not filename.empty()) { auto molecules = spc.findMolecules(mol_iter->id(), Space::Selection::ALL); if (not ranges::cpp20::empty(molecules)) { @@ -2227,7 +2583,8 @@ void ChargeFluctuations::_to_disk() { } } -ParticleVector ChargeFluctuations::averageChargeParticles(const Space::GroupType& group) { +ParticleVector ChargeFluctuations::averageChargeParticles(const Space::GroupType& group) +{ ParticleVector particles; // temporary particle vector particles.reserve(group.capacity()); // allocate required memory already now size_t particle_index = 0; @@ -2250,7 +2607,8 @@ ParticleVector ChargeFluctuations::averageChargeParticles(const Space::GroupType * @todo replace `mol_iter` with simple molid integer */ ChargeFluctuations::ChargeFluctuations(const json& j, const Space& spc) - : Analysis(spc, "chargefluctuations") { + : Analysis(spc, "chargefluctuations") +{ from_json(j); filename = j.value("pqrfile", ""s); verbose = j.value("verbose", true); @@ -2266,7 +2624,8 @@ ChargeFluctuations::ChargeFluctuations(const json& j, const Space& spc) atom_mean_charges.resize(molecule.atoms.size()); } -void Multipole::_sample() { +void Multipole::_sample() +{ for (const auto& group : spc.groups) { // loop over all groups molecules if (group.isMolecular() and !group.empty()) { @@ -2281,32 +2640,38 @@ void Multipole::_sample() { } } -void Multipole::_to_json(json& j) const { +void Multipole::_to_json(json& j) const +{ auto& molecules_json = j["molecules"]; for (const auto& [molid, average] : average_moments) { const auto& molecule_name = Faunus::molecules[molid].name; - molecules_json[molecule_name] = {{"Z", average.charge.avg()}, - {"Z2", average.charge_squared.avg()}, - {"C", average.charge_squared.avg() - std::pow(average.charge.avg(), 2)}, - {unicode::mu, average.dipole_moment.avg()}, - {unicode::mu + unicode::squared, average.dipole_moment_squared.avg()}}; + molecules_json[molecule_name] = { + {"Z", average.charge.avg()}, + {"Z2", average.charge_squared.avg()}, + {"C", average.charge_squared.avg() - std::pow(average.charge.avg(), 2)}, + {unicode::mu, average.dipole_moment.avg()}, + {unicode::mu + unicode::squared, average.dipole_moment_squared.avg()}}; } } Multipole::Multipole(const json& j, const Space& spc) - : Analysis(spc, "multipole") { + : Analysis(spc, "multipole") +{ from_json(j); } -void ScatteringFunction::_sample() { +void ScatteringFunction::_sample() +{ namespace rv = ranges::cpp20::views; scatter_positions.clear(); - auto groups = molecule_ids | rv::transform([&](auto id) { return spc.findMolecules(id); }) | rv::join; + auto groups = + molecule_ids | rv::transform([&](auto id) { return spc.findMolecules(id); }) | rv::join; ranges::cpp20::for_each(groups, [&](auto& group) { if (mass_center_scattering && group.isMolecular()) { scatter_positions.push_back(group.mass_center); - } else { + } + else { auto positions = group.positions(); std::copy(positions.begin(), positions.end(), std::back_inserter(scatter_positions)); } @@ -2337,7 +2702,8 @@ void ScatteringFunction::_sample() { } } -void ScatteringFunction::_to_json(json& j) const { +void ScatteringFunction::_to_json(json& j) const +{ j = {{"molecules", molecule_names}, {"com", mass_center_scattering}}; switch (scheme) { case Schemes::DEBYE: @@ -2357,8 +2723,9 @@ void ScatteringFunction::_to_json(json& j) const { } } -ScatteringFunction::ScatteringFunction(const json& j, const Space& spc) try - : Analysis(spc, "scatter") { +ScatteringFunction::ScatteringFunction(const json& j, const Space& spc) + +try : Analysis(spc, "scatter") { from_json(j); mass_center_scattering = j.value("com", true); @@ -2367,7 +2734,8 @@ ScatteringFunction::ScatteringFunction(const json& j, const Space& spc) try molecule_names = j.at("molecules").get(); molecule_ids = Faunus::names2ids(molecules, molecule_names); - const auto cuboid = std::dynamic_pointer_cast(spc.geometry.asSimpleGeometry()); + const auto cuboid = + std::dynamic_pointer_cast(spc.geometry.asSimpleGeometry()); if (const auto scheme_str = j.value("scheme", "explicit"s); scheme_str == "debye") { scheme = Schemes::DEBYE; @@ -2375,7 +2743,8 @@ ScatteringFunction::ScatteringFunction(const json& j, const Space& spc) try if (cuboid) { faunus_logger->warn("cuboidal cell detected: consider using the `explicit` scheme"); } - } else if (scheme_str == "explicit") { + } + else if (scheme_str == "explicit") { if (!cuboid) { throw ConfigurationError("{} only valid for cuboidal cells", scheme_str); } @@ -2384,18 +2753,24 @@ ScatteringFunction::ScatteringFunction(const json& j, const Space& spc) try if (ipbc) { scheme = Schemes::EXPLICIT_IPBC; explicit_average_ipbc = std::make_unique>(pmax); - } else { + } + else { scheme = Schemes::EXPLICIT_PBC; explicit_average_pbc = std::make_unique>(pmax); } - } else { + } + else { throw ConfigurationError("unknown scheme"); } -} catch (std::exception& e) { +} + +catch (std::exception& e) { + throw ConfigurationError("scatter: "s + e.what()); } -void ScatteringFunction::_to_disk() { +void ScatteringFunction::_to_disk() +{ switch (scheme) { case Schemes::DEBYE: IO::writeKeyValuePairs(filename, debye->getIntensity()); @@ -2409,7 +2784,8 @@ void ScatteringFunction::_to_disk() { } } -void VirtualTranslate::_from_json(const json& j) { +void VirtualTranslate::_from_json(const json& j) +{ const auto molname = j.at("molecule").get(); molid = Faunus::findMoleculeByName(molname).id(); // throws if not found if (Faunus::molecules[molid].atomic) { @@ -2427,11 +2803,13 @@ void VirtualTranslate::_from_json(const json& j) { } } -void VirtualTranslate::_sample() { +void VirtualTranslate::_sample() +{ if (std::fabs(perturbation_distance) < pc::epsilon_dbl) { return; } - if (auto mollist = mutable_space.findMolecules(molid, Space::Selection::ACTIVE); !ranges::cpp20::empty(mollist)) { + if (auto mollist = mutable_space.findMolecules(molid, Space::Selection::ACTIVE); + !ranges::cpp20::empty(mollist)) { if (ranges::distance(mollist.begin(), mollist.end()) > 1) { throw std::runtime_error("exactly ONE active molecule expected"); } @@ -2445,12 +2823,13 @@ void VirtualTranslate::_sample() { } } -void VirtualTranslate::writeToFileStream(const double energy_change) const { +void VirtualTranslate::writeToFileStream(const double energy_change) const +{ if (stream) { // file to disk? const double mean_force = -meanFreeEnergy() / perturbation_distance; - *stream << fmt::format("{:d} {:.3E} {:.6E} {:.6E}\n", getNumberOfSteps(), perturbation_distance, energy_change, - mean_force); + *stream << fmt::format("{:d} {:.3E} {:.6E} {:.6E}\n", getNumberOfSteps(), + perturbation_distance, energy_change, mean_force); } } @@ -2461,17 +2840,21 @@ void VirtualTranslate::writeToFileStream(const double energy_change) const { * Calculates the energy change of displacing a group and * then restore it to it's original position, leaving Space untouched. */ -double VirtualTranslate::momentarilyPerturb(Space::GroupType& group) { +double VirtualTranslate::momentarilyPerturb(Space::GroupType& group) +{ change.groups.at(0).group_index = spc.getGroupIndex(group); const auto old_energy = pot.energy(change); const Point displacement_vector = perturbation_distance * perturbation_direction; - group.translate(displacement_vector, spc.geometry.getBoundaryFunc()); // temporarily translate group + group.translate(displacement_vector, + spc.geometry.getBoundaryFunc()); // temporarily translate group const auto new_energy = pot.energy(change); - group.translate(-displacement_vector, spc.geometry.getBoundaryFunc()); // restore original position + group.translate(-displacement_vector, + spc.geometry.getBoundaryFunc()); // restore original position return new_energy - old_energy; } -void VirtualTranslate::_to_json(json& j) const { +void VirtualTranslate::_to_json(json& j) const +{ if (number_of_samples > 0 && std::fabs(perturbation_distance) > 0.0) { j = {{"dL", perturbation_distance}, {"force", std::log(mean_exponentiated_energy_change.avg()) / perturbation_distance}, @@ -2480,7 +2863,8 @@ void VirtualTranslate::_to_json(json& j) const { } VirtualTranslate::VirtualTranslate(const json& j, Space& spc, Energy::EnergyTerm& pot) - : PerturbationAnalysis("virtualtranslate", pot, spc, j.value("file", ""s)) { + : PerturbationAnalysis("virtualtranslate", pot, spc, j.value("file", ""s)) +{ change.groups.resize(1); change.groups.front().internal = false; from_json(j); @@ -2488,13 +2872,15 @@ VirtualTranslate::VirtualTranslate(const json& j, Space& spc, Energy::EnergyTerm SpaceTrajectory::SpaceTrajectory(const json& j, const Space& spc) : Analysis(spc, "space trajectory") - , groups(spc.groups) { + , groups(spc.groups) +{ from_json(j); filename = j.at("file").get(); if (useCompression()) { stream = std::make_unique(MPI::prefix + filename, std::ios::binary); - } else { + } + else { stream = std::make_unique(MPI::prefix + filename, std::ios::binary); } @@ -2507,7 +2893,8 @@ SpaceTrajectory::SpaceTrajectory(const json& j, const Space& spc) } } -bool SpaceTrajectory::useCompression() const { +bool SpaceTrajectory::useCompression() const +{ assert(!filename.empty()); const auto suffix = filename.substr(filename.find_last_of('.') + 1); if (suffix == "ztraj") { @@ -2519,20 +2906,28 @@ bool SpaceTrajectory::useCompression() const { throw ConfigurationError("Trajectory file suffix must be `.traj` or `.ztraj`"); } -void SpaceTrajectory::_sample() { +void SpaceTrajectory::_sample() +{ assert(archive); for (auto& group : groups) { (*archive)(group); } } -void SpaceTrajectory::_to_json(json& j) const { j = {{"file", filename}}; } +void SpaceTrajectory::_to_json(json& j) const +{ + j = {{"file", filename}}; +} -void SpaceTrajectory::_to_disk() { stream->flush(); } +void SpaceTrajectory::_to_disk() +{ + stream->flush(); +} ElectricPotential::ElectricPotential(const json& j, const Space& spc) : Analysis(spc, "electricpotential") - , potential_correlation_histogram(histogram_resolution) { + , potential_correlation_histogram(histogram_resolution) +{ from_json(j); coulomb = std::make_unique(); pairpotential::from_json(j, *coulomb); @@ -2542,7 +2937,8 @@ ElectricPotential::ElectricPotential(const json& j, const Space& spc) file_prefix = j.value("file", "potential"s); } -void ElectricPotential::setPolicy(const json& j) { +void ElectricPotential::setPolicy(const json& j) +{ output_information.clear(); policy = j.value("policy", Policies::FIXED); auto stride = 0.0; @@ -2572,7 +2968,8 @@ void ElectricPotential::setPolicy(const json& j) { spc.geometry.randompos(origin->position, random); cnt++; if (cnt > max_overlap_trials) { - faunus_logger->warn("{}: Max number of overlaps reached. Using overlapping point", name); + faunus_logger->warn( + "{}: Max number of overlaps reached. Using overlapping point", name); break; } } while (overlapWithParticles(origin->position)); @@ -2583,7 +2980,8 @@ void ElectricPotential::setPolicy(const json& j) { target.position = origin->position + stride * randomUnitVector(random); cnt++; if (cnt > max_overlap_trials) { - faunus_logger->warn("{}: Max number of overlaps reached. Using overlapping point", name); + faunus_logger->warn( + "{}: Max number of overlaps reached. Using overlapping point", name); break; } } while (overlapWithParticles(target.position)); @@ -2596,32 +2994,39 @@ void ElectricPotential::setPolicy(const json& j) { } } -void ElectricPotential::getTargets(const json& j) { +void ElectricPotential::getTargets(const json& j) +{ if (const auto& structure = j.find("structure"); structure == j.end()) { throw ConfigurationError("missing structure"); - } else { + } + else { PointVector positions; if (structure->is_string()) { // load positions from chemical structure file auto particles = loadStructure(structure->get(), false); - positions = particles | ranges::cpp20::views::transform(&Particle::pos) | ranges::to_vector; - } else if (structure->is_array()) { + positions = + particles | ranges::cpp20::views::transform(&Particle::pos) | ranges::to_vector; + } + else if (structure->is_array()) { // list of positions positions = structure->get(); } - std::transform(positions.begin(), positions.end(), std::back_inserter(targets), [&](auto& position) { - Target target; - target.position = position; - target.potential_histogram = std::make_unique>(histogram_resolution); - return target; - }); + std::transform(positions.begin(), positions.end(), std::back_inserter(targets), + [&](auto& position) { + Target target; + target.position = position; + target.potential_histogram = + std::make_unique>(histogram_resolution); + return target; + }); if (targets.empty()) { throw ConfigurationError("no targets defined"); } } } -void ElectricPotential::_sample() { +void ElectricPotential::_sample() +{ for (unsigned int i = 0; i < calculations_per_sample_event; i++) { applyPolicy(); auto potential_correlation = 1.0; // phi1 * phi2 * ... @@ -2637,16 +3042,20 @@ void ElectricPotential::_sample() { } } -double ElectricPotential::calcPotentialOnTarget(const ElectricPotential::Target& target) { +double ElectricPotential::calcPotentialOnTarget(const ElectricPotential::Target& target) +{ auto potential_from_particle = [&](const Particle& particle) { - const auto distance_to_target = std::sqrt(spc.geometry.sqdist(particle.pos, target.position)); + const auto distance_to_target = + std::sqrt(spc.geometry.sqdist(particle.pos, target.position)); return coulomb->getCoulombGalore().ion_potential(particle.charge, distance_to_target); }; - auto potentials = spc.activeParticles() | ranges::cpp20::views::transform(potential_from_particle); + auto potentials = + spc.activeParticles() | ranges::cpp20::views::transform(potential_from_particle); return std::accumulate(potentials.begin(), potentials.end(), 0.0); } -void ElectricPotential::_to_json(json& j) const { +void ElectricPotential::_to_json(json& j) const +{ j = output_information; coulomb->to_json(j["coulomb"]); j["policy"] = policy; @@ -2661,7 +3070,8 @@ void ElectricPotential::_to_json(json& j) const { } } -void ElectricPotential::_to_disk() { +void ElectricPotential::_to_disk() +{ auto filename = fmt::format("{}{}_correlation_histogram.dat", MPI::prefix, file_prefix); if (auto stream = std::ofstream(filename)) { stream << potential_correlation_histogram; @@ -2681,7 +3091,8 @@ void ElectricPotential::_to_disk() { * * @return True if overlap with any particle */ -bool ElectricPotential::overlapWithParticles(const Point& position) const { +bool ElectricPotential::overlapWithParticles(const Point& position) const +{ auto overlap = [&position, &geometry = spc.geometry](const Particle& particle) { const auto radius = 0.5 * particle.traits().sigma; return geometry.sqdist(particle.pos, position) < radius * radius; @@ -2690,17 +3101,20 @@ bool ElectricPotential::overlapWithParticles(const Point& position) const { return std::any_of(particles.begin(), particles.end(), overlap); } -SavePenaltyEnergy::SavePenaltyEnergy(const json& j, const Space& spc, const Energy::Hamiltonian& pot) +SavePenaltyEnergy::SavePenaltyEnergy(const json& j, const Space& spc, + const Energy::Hamiltonian& pot) : Analysis(spc, "penaltyfunction") , filename(j.at("file").get()) - , penalty_energy(pot.findFirstOf()) { + , penalty_energy(pot.findFirstOf()) +{ Analysis::from_json(j); if (!penalty_energy) { faunus_logger->warn("{}: analysis disabled as no penalty function found", name); } } -void SavePenaltyEnergy::_sample() { +void SavePenaltyEnergy::_sample() +{ if (penalty_energy) { auto name = fmt::format("{}{:06d}.{}", MPI::prefix, filenumber++, filename); if (const auto stream = IO::openCompressedOutputStream(name, true); stream) { diff --git a/src/analysis.h b/src/analysis.h index 6c2cf0e50..d658aeb32 100644 --- a/src/analysis.h +++ b/src/analysis.h @@ -36,7 +36,6 @@ namespace Faunus::SASA { class SASABase; } - /** * Adding a new analysis requires the following steps: * @@ -56,14 +55,15 @@ namespace Faunus::analysis { * functionality such as timing, number of steps, json IO * and enforce a common interface. */ -class Analysis { +class Analysis +{ private: - virtual void _to_json(json&) const; //!< provide json information - virtual void _from_json(const json&); //!< setup from json - virtual void _sample() = 0; //!< perform sample event - virtual void _to_disk(); //!< save sampled data to disk - int number_of_steps = 0; //!< counter for total number of steps - int number_of_skipped_steps = 0; //!< steps to skip before sampling (do not modify) + virtual void _to_json(json&) const; //!< provide json information + virtual void _from_json(const json&); //!< setup from json + virtual void _sample() = 0; //!< perform sample event + virtual void _to_disk(); //!< save sampled data to disk + int number_of_steps = 0; //!< counter for total number of steps + int number_of_skipped_steps = 0; //!< steps to skip before sampling (do not modify) TimeRelativeOfTotal timer; //!< timer to benchmark `_sample()` protected: @@ -72,15 +72,16 @@ class Analysis { int number_of_samples = 0; //!< counter for number of samples public: - const std::string name; //!< descriptive name - std::string cite; //!< url, doi etc. describing the analysis - void to_json(json& j) const; //!< JSON report w. statistics, output etc. - void from_json(const json& j); //!< configure from json object - void to_disk(); //!< Save data to disk (if defined) - void sample(); //!< Increase step count and sample - [[nodiscard]] int getNumberOfSteps() const; //!< Number of steps + const std::string name; //!< descriptive name + std::string cite; //!< url, doi etc. describing the analysis + void to_json(json& j) const; //!< JSON report w. statistics, output etc. + void from_json(const json& j); //!< configure from json object + void to_disk(); //!< Save data to disk (if defined) + void sample(); //!< Increase step count and sample + [[nodiscard]] int getNumberOfSteps() const; //!< Number of steps Analysis(const Space& spc, std::string_view name); - Analysis(const Space& spc, std::string_view name, int sample_interval, int number_of_skipped_steps); + Analysis(const Space& spc, std::string_view name, int sample_interval, + int number_of_skipped_steps); virtual ~Analysis() = default; }; @@ -97,7 +98,8 @@ void to_json(json& j, const Analysis& base); * After writing a new analysis, it must be added to this function in * order to be controlled from the main faunus input. */ -std::unique_ptr createAnalysis(const std::string& name, const json& j, Space& spc, Energy::Hamiltonian& pot); +std::unique_ptr createAnalysis(const std::string& name, const json& j, Space& spc, + Energy::Hamiltonian& pot); /** * @brief Aggregator class for storing and selecting multiple analysis instances @@ -106,7 +108,8 @@ std::unique_ptr createAnalysis(const std::string& name, const json& j, * random, based on user input. This is typically called from the * main simulation loop. */ -class CombinedAnalysis : public BasePointerVector { +class CombinedAnalysis : public BasePointerVector +{ public: CombinedAnalysis(const json& json_array, Space& spc, Energy::Hamiltonian& pot); void sample(); @@ -116,12 +119,14 @@ class CombinedAnalysis : public BasePointerVector { /** * @brief Base class for perturbation analysis * - * This class provides basic data and functions to support Widom Particle Insertion, Virtual Volume Move - * etc. If a non-empty `filename` is given, a (compressed) output file used for streaming will be opened. + * This class provides basic data and functions to support Widom Particle Insertion, Virtual Volume + * Move etc. If a non-empty `filename` is given, a (compressed) output file used for streaming will + * be opened. * * @note Constructor throws if non-empty filename cannot to opened for writing */ -class PerturbationAnalysis : public Analysis { +class PerturbationAnalysis : public Analysis +{ private: void _to_disk() override; @@ -135,13 +140,15 @@ class PerturbationAnalysis : public Analysis { bool collectWidomAverage(double energy_change); //!< add to exp(-du/kT) incl. safety checks PerturbationAnalysis(const std::string& name, Energy::EnergyTerm& pot, Space& spc, const std::string& filename = ""s); - [[nodiscard]] double meanFreeEnergy() const; //!< Average perturbation free energy, `-ln()` + [[nodiscard]] double + meanFreeEnergy() const; //!< Average perturbation free energy, `-ln()` }; /** * @brief Sample and save reaction coordinates to a file */ -class FileReactionCoordinate : public Analysis { +class FileReactionCoordinate : public Analysis +{ private: Average mean_reaction_coordinate; const std::string filename; @@ -151,8 +158,9 @@ class FileReactionCoordinate : public Analysis { void _to_json(json& j) const override; void _sample() override; void _to_disk() override; - FileReactionCoordinate(const Space& spc, const std::string& filename, - std::unique_ptr reaction_coordinate); + FileReactionCoordinate( + const Space& spc, const std::string& filename, + std::unique_ptr reaction_coordinate); public: FileReactionCoordinate(const json& j, const Space& spc); @@ -168,25 +176,32 @@ class FileReactionCoordinate : public Analysis { * * @warning Will not work if `molid` is grand canonical */ -class AtomicDisplacement : public Analysis { +class AtomicDisplacement : public Analysis +{ protected: MoleculeData::index_type molid; private: using average_type = Average; - std::unique_ptr single_position_stream; //!< Stream x, y, z, displacement for first position only - std::vector reference_positions; //!< Positions used as references - std::vector previous_positions; //!< Positions from previous analysis event - std::vector cell_indices; //!< Tracks in which unit cell the particles are in - std::vector mean_squared_displacement; //!< Mean squared displacement for each position - double max_possible_displacement; //!< If any displacement is larger than this, assume unit cell jump - - SparseHistogram displacement_histogram; //!< P(r) where r is distance from reference - std::string displacement_histogram_filename; //!< Name of P(r) histogram file - int reference_reset_interval = std::numeric_limits::max(); //!< Renew reference at given interval - - [[nodiscard]] virtual PointVector getPositions() const; //!< Extract current positions from `molid` - void resetReferencePosition(const Point& position, int index); //!< Store current positions as reference + std::unique_ptr + single_position_stream; //!< Stream x, y, z, displacement for first position only + std::vector reference_positions; //!< Positions used as references + std::vector previous_positions; //!< Positions from previous analysis event + std::vector cell_indices; //!< Tracks in which unit cell the particles are in + std::vector + mean_squared_displacement; //!< Mean squared displacement for each position + double max_possible_displacement; //!< If any displacement is larger than this, assume unit cell + //!< jump + + SparseHistogram displacement_histogram; //!< P(r) where r is distance from reference + std::string displacement_histogram_filename; //!< Name of P(r) histogram file + int reference_reset_interval = + std::numeric_limits::max(); //!< Renew reference at given interval + + [[nodiscard]] virtual PointVector + getPositions() const; //!< Extract current positions from `molid` + void resetReferencePosition(const Point& position, + int index); //!< Store current positions as reference Point getOffset(const Point& diff, Eigen::Vector3i& cell) const; //!< Offset to other cells void sampleDisplacementFromReference(const Point& position, int index); void _sample() override; @@ -202,11 +217,14 @@ class AtomicDisplacement : public Analysis { * * Based on `AtomicDisplacement`. */ -class MassCenterDisplacement : public AtomicDisplacement { +class MassCenterDisplacement : public AtomicDisplacement +{ private: - [[nodiscard]] PointVector getPositions() const override; //!< Extracts mass centers from all active `molid` groups + [[nodiscard]] PointVector + getPositions() const override; //!< Extracts mass centers from all active `molid` groups public: - MassCenterDisplacement(const json& j, const Space& spc, std::string_view name = "displacement_com"); + MassCenterDisplacement(const json& j, const Space& spc, + std::string_view name = "displacement_com"); }; /** @@ -214,7 +232,8 @@ class MassCenterDisplacement : public AtomicDisplacement { * * @todo Migrate `absolute_z_coords` into new `MoleculeInserter` policy */ -class WidomInsertion : public PerturbationAnalysis { +class WidomInsertion : public PerturbationAnalysis +{ std::shared_ptr inserter; //!< Insertion method int number_of_insertions; //!< Number of insertions per sample event MoleculeData::index_type molid; //!< Molecule id @@ -233,13 +252,15 @@ class WidomInsertion : public PerturbationAnalysis { /** * @brief Samples the electric potential correlation as a function of distance, <Φ₁Φ₂>(r) */ -class [[maybe_unused]] PotentialCorrelation : public Analysis { +class [[maybe_unused]] PotentialCorrelation : public Analysis +{ private: - std::unique_ptr coulomb; //!< Class for calculating the potential + std::unique_ptr + coulomb; //!< Class for calculating the potential Equidistant2DTable> mean_correlation; //!< <Φ₁Φ₂>(r) - std::string filename; //!< Filename of output <Φ₁Φ₂>(r) table - std::pair range; //!< Distance range - double dr = 0; //!< Distance resolution + std::string filename; //!< Filename of output <Φ₁Φ₂>(r) table + std::pair range; //!< Distance range + double dr = 0; //!< Distance resolution unsigned int calculations_per_sample_event = 1; void _to_json(json& j) const override; void _sample() override; @@ -252,31 +273,47 @@ class [[maybe_unused]] PotentialCorrelation : public Analysis { /** * @brief Samples the electric potential at arbitrary positions in the simulation box */ -class ElectricPotential : public Analysis { +class ElectricPotential : public Analysis +{ public: - enum class Policies { FIXED, RANDOM_WALK, RANDOM_WALK_NO_OVERLAP, INVALID }; + enum class Policies + { + FIXED, + RANDOM_WALK, + RANDOM_WALK_NO_OVERLAP, + INVALID + }; private: unsigned int max_overlap_trials = 100; //!< Maximum number of overlap checks before bailing out double histogram_resolution = 0.01; //!< Potential resolution unsigned int calculations_per_sample_event = 1; std::string file_prefix; //!< Output filename prefix for potential histogram and correlation - struct Target { - Point position; //!< Target position - Average mean_potential; //!< mean potential at position - std::unique_ptr> potential_histogram; //!< Histogram of observed potentials + + struct Target + { + Point position; //!< Target position + Average mean_potential; //!< mean potential at position + std::unique_ptr> + potential_histogram; //!< Histogram of observed potentials }; - std::vector targets; //!< List of target points where to sample the potential - Policies policy; //!< Policy to apply to targets before each sample event - std::unique_ptr coulomb; //!< Class for calculating the potential - Average mean_potential_correlation; //!< Correlation between targets, - SparseHistogram potential_correlation_histogram; //!< Distribution of correlations, P() - void getTargets(const json& j); //!< Get user defined target positions - void setPolicy(const json& j); //!< Set user defined position setting policy - double calcPotentialOnTarget(const Target& target); //!< Evaluate net potential of target position - [[nodiscard]] bool overlapWithParticles(const Point& position) const; //!< Check if position is within the radius of any particle - std::function applyPolicy; //!< Lambda for position setting policy - json output_information; //!< json output generated during construction + + std::vector targets; //!< List of target points where to sample the potential + Policies policy; //!< Policy to apply to targets before each sample event + std::unique_ptr + coulomb; //!< Class for calculating the potential + Average + mean_potential_correlation; //!< Correlation between targets, + SparseHistogram + potential_correlation_histogram; //!< Distribution of correlations, P() + void getTargets(const json& j); //!< Get user defined target positions + void setPolicy(const json& j); //!< Set user defined position setting policy + double + calcPotentialOnTarget(const Target& target); //!< Evaluate net potential of target position + [[nodiscard]] bool overlapWithParticles( + const Point& position) const; //!< Check if position is within the radius of any particle + std::function applyPolicy; //!< Lambda for position setting policy + json output_information; //!< json output generated during construction void _to_json(json& j) const override; void _sample() override; void _to_disk() override; @@ -289,7 +326,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM(ElectricPotential::Policies, { {ElectricPotential::Policies::INVALID, nullptr}, {ElectricPotential::Policies::FIXED, "fixed"}, - {ElectricPotential::Policies::RANDOM_WALK_NO_OVERLAP, "no_overlap"}, + {ElectricPotential::Policies::RANDOM_WALK_NO_OVERLAP, + "no_overlap"}, {ElectricPotential::Policies::RANDOM_WALK, "random_walk"}, }) @@ -299,7 +337,8 @@ NLOHMANN_JSON_SERIALIZE_ENUM(ElectricPotential::Policies, * Calculates the summed density of `atoms` in spherical, cylindrical or planar shells around * `origo` which by default is the center of the simulation box */ -class AtomProfile : public Analysis { +class AtomProfile : public Analysis +{ Equidistant2DTable table; //!< x = distance; y = count or charge double dr; //!< resolution for `table` std::vector names; //!< atom names to analyse @@ -323,7 +362,8 @@ class AtomProfile : public Analysis { /** * @brief Measures the density of atoms along z axis */ -class SlicedDensity : public Analysis { +class SlicedDensity : public Analysis +{ double dz = 0.0; //!< Resolution of `histogram` along z Table2D histogram; // N(z) std::vector atom_names; @@ -343,7 +383,8 @@ class SlicedDensity : public Analysis { /** * Abstract base class for analysing atomic and molecular densities */ -class Density : public Analysis { +class Density : public Analysis +{ protected: using Table = Equidistant2DTable; using id_type = size_t; @@ -365,7 +406,8 @@ class Density : public Analysis { public: template Density(const Space& spc, const Range& atoms_or_molecules, const std::string_view name) - : Analysis(spc, name) { + : Analysis(spc, name) + { for (const auto& data : atoms_or_molecules) { names[data.id()] = data.name; probability_density[data.id()].setResolution(1, 0); @@ -376,7 +418,8 @@ class Density : public Analysis { /** * @brief Analysis of molecular group densities */ -class MoleculeDensity final : public Density { +class MoleculeDensity final : public Density +{ private: [[nodiscard]] std::map count() const override; @@ -387,7 +430,8 @@ class MoleculeDensity final : public Density { /** * @brief Analysis of single atom densities */ -class AtomDensity final : public Density { +class AtomDensity final : public Density +{ private: std::map atomswap_probability_density; void _sample() override; @@ -404,10 +448,11 @@ class AtomDensity final : public Density { * This looks for the `property` key in the json object. Supported names are * `energy`, `com_distance` */ -std::function createGroupGroupProperty(const json& j, const Space& spc, - Energy::Hamiltonian& hamiltonian); +std::function +createGroupGroupProperty(const json& j, const Space& spc, Energy::Hamiltonian& hamiltonian); -std::function createValueFilter(const std::string& name, const json& j, bool throw_on_error); +std::function createValueFilter(const std::string& name, const json& j, + bool throw_on_error); /** * Base class for streaming pair properties to a sparse matrix. To further @@ -418,7 +463,8 @@ std::function createValueFilter(const std::string& name, const jso * - `file` = output stream file name * - `filter` = ExprTk expression for filter */ -class PairMatrixAnalysis : public Analysis { +class PairMatrixAnalysis : public Analysis +{ private: std::unique_ptr matrix_stream; //!< Pair matrix output stream std::string filename; //!< cluster fraction filename @@ -443,11 +489,13 @@ class PairMatrixAnalysis : public Analysis { * This calculates a user-defined property between a set of * selected groups and outputs a matrix as a function of steps. */ -class GroupMatrixAnalysis : public PairMatrixAnalysis { +class GroupMatrixAnalysis : public PairMatrixAnalysis +{ private: - std::function property; //!< The group-group property to calculate - std::vector group_indices; //!< Selected groups (active and inactive) - void setPairMatrix() override; //!< Fills in `pair_matrix` with sampled values + std::function + property; //!< The group-group property to calculate + std::vector group_indices; //!< Selected groups (active and inactive) + void setPairMatrix() override; //!< Fills in `pair_matrix` with sampled values public: GroupMatrixAnalysis(const json& j, const Space& spc, Energy::Hamiltonian& pot); }; @@ -455,14 +503,15 @@ class GroupMatrixAnalysis : public PairMatrixAnalysis { /** * @todo `atom_mean_charges` could be calculated from `atom_histograms` */ -class ChargeFluctuations : public Analysis { +class ChargeFluctuations : public Analysis +{ private: typename decltype(Faunus::molecules)::const_iterator mol_iter; //!< selected molecule type - using AtomHistogram = std::map; //!< key = atom id; value = counts - std::vector atom_histograms; //!< one element for each atom in molecule - std::vector> atom_mean_charges; //!< average charges of atomic indexes - std::string filename; //!< name of PQR file with average charges - bool verbose = true; //!< set to true for more output + using AtomHistogram = std::map; //!< key = atom id; value = counts + std::vector atom_histograms; //!< one element for each atom in molecule + std::vector> atom_mean_charges; //!< average charges of atomic indexes + std::string filename; //!< name of PQR file with average charges + bool verbose = true; //!< set to true for more output ParticleVector averageChargeParticles(const Space::GroupType& group); [[nodiscard]] std::vector getMeanCharges() const; @@ -480,13 +529,16 @@ class ChargeFluctuations : public Analysis { /** * @brief Molecular multipole moments and their fluctuations */ -class Multipole : public Analysis { - struct Data { +class Multipole : public Analysis +{ + struct Data + { Average charge; Average charge_squared; Average dipole_moment; Average dipole_moment_squared; }; // Average sample moment for a molecule + std::map average_moments; //!< Molecular moments and their fluctuations. Key = molid. void _sample() override; @@ -499,7 +551,8 @@ class Multipole : public Analysis { /** * @brief Save system energy to disk */ -class SystemEnergy : public Analysis { +class SystemEnergy : public Analysis +{ private: Energy::Hamiltonian& hamiltonian; std::string file_name; @@ -511,7 +564,7 @@ class SystemEnergy : public Analysis { Table2D energy_histogram; // Density histograms double initial_energy = 0.0; double minimum_energy = std::numeric_limits::infinity(); //!< Tracks minimum energy - bool dump_minimum_energy_configuration = false; //!< Dump minimum energy config to disk + bool dump_minimum_energy_configuration = false; //!< Dump minimum energy config to disk bool updateMinimumEnergy(double current_energy); void createOutputStream(); @@ -528,13 +581,16 @@ class SystemEnergy : public Analysis { /** * @brief Checks if system is sane. If not, abort program. */ -class SanityCheck : public Analysis { +class SanityCheck : public Analysis +{ private: const double mass_center_tolerance = 1.0e-6; void _sample() override; - void checkGroupsCoverParticles(); //!< Groups must exactly contain all particles in `p` - void checkMassCenter(const Space::GroupType& group); //!< check if molecular mass centers are correct - void checkWithinContainer(const Space::GroupType& group); //!< check if particles are inside container + void checkGroupsCoverParticles(); //!< Groups must exactly contain all particles in `p` + void + checkMassCenter(const Space::GroupType& group); //!< check if molecular mass centers are correct + void checkWithinContainer( + const Space::GroupType& group); //!< check if particles are inside container public: SanityCheck(const json& j, const Space& spc); @@ -552,7 +608,8 @@ class SanityCheck : public Analysis { * - if sample interval >= 0, analysis is performed as every nstep. * - if `use_numbered_files` = true (default) files are labelled with the step count */ -class SaveState : public Analysis { +class SaveState : public Analysis +{ private: std::function writeFunc = nullptr; bool save_random_number_generator_state = false; @@ -562,7 +619,8 @@ class SaveState : public Analysis { void _to_json(json& j) const override; void _sample() override; - static void saveAsCuboid(const std::string& filename, const Space& spc, StructureFileWriter& writer); + static void saveAsCuboid(const std::string& filename, const Space& spc, + StructureFileWriter& writer); void saveJsonStateFile(const std::string& filename, const Space& spc) const; void saveBinaryJsonStateFile(const std::string& filename, const Space& spc) const; @@ -575,7 +633,8 @@ class SaveState : public Analysis { /** * @brief Base class for distribution functions etc. */ -class PairFunction : public Analysis { +class PairFunction : public Analysis +{ protected: using index_type = size_t; int dimensions = 3; //!< dimensions to use when normalizing @@ -603,7 +662,8 @@ class PairFunction : public Analysis { /** * @brief Atomic radial distribution function, g(r) */ -class AtomRDF : public PairFunction { +class AtomRDF : public PairFunction +{ private: void _sample() override; void sampleDistance(const Particle& particle1, const Particle& particle2); @@ -617,7 +677,8 @@ class AtomRDF : public PairFunction { /** * @brief Same as `AtomRDF` but for molecules. Identical input */ -class MoleculeRDF : public PairFunction { +class MoleculeRDF : public PairFunction +{ private: void _sample() override; void sampleDistance(const Group& group_i, const Group& group_j); @@ -631,7 +692,8 @@ class MoleculeRDF : public PairFunction { /** * @todo Is this class justified? Messy file handling */ -class PairAngleFunction : public PairFunction { +class PairAngleFunction : public PairFunction +{ private: std::string correlation_filename; @@ -647,7 +709,8 @@ class PairAngleFunction : public PairFunction { }; /** @brief Dipole-dipole correlation function, <\boldsymbol{\mu}(0)\cdot\boldsymbol{\mu}(r)> */ -class AtomDipDipCorr : public PairAngleFunction { +class AtomDipDipCorr : public PairAngleFunction +{ void _sample() override; public: @@ -661,7 +724,8 @@ class AtomDipDipCorr : public PairAngleFunction { * same number of particles on all frames. See `QRtraj` for a solution how to disable * inactive groups in e.g. VMD. */ -class XTCtraj : public Analysis { +class XTCtraj : public Analysis +{ private: using index_type = MoleculeData::index_type; std::vector group_ids; //!< group ids to save to disk (empty = all) @@ -669,7 +733,8 @@ class XTCtraj : public Analysis { std::unique_ptr writer; void _to_json(json& j) const override; void _sample() override; - XTCtraj(const Space& spc, const std::string& filename, const std::vector& molecule_names); + XTCtraj(const Space& spc, const std::string& filename, + const std::vector& molecule_names); public: XTCtraj(const json& j, const Space& spc); @@ -678,7 +743,8 @@ class XTCtraj : public Analysis { /** * @brief Excess pressure using virtual volume move */ -class VirtualVolumeMove : public PerturbationAnalysis { +class VirtualVolumeMove : public PerturbationAnalysis +{ Geometry::VolumeMethod volume_scaling_method = Geometry::VolumeMethod::ISOTROPIC; double volume_displacement = 0.0; void _sample() override; @@ -694,7 +760,8 @@ class VirtualVolumeMove : public PerturbationAnalysis { /** * @brief Create histogram of molecule conformation id */ -class MolecularConformationID : public Analysis { +class MolecularConformationID : public Analysis +{ MoleculeData::index_type molid; //!< molecule id to sample std::map histogram; //!< key = conformation id; value = count void _sample() override; @@ -713,7 +780,8 @@ class MolecularConformationID : public Analysis { * * @todo Does this work with Ewald summation? k-vectors must be refreshed. */ -class VirtualTranslate : public PerturbationAnalysis { +class VirtualTranslate : public PerturbationAnalysis +{ MoleculeData::index_type molid; //!< molid to operate on Point perturbation_direction = {0.0, 0.0, 1.0}; double perturbation_distance = 0.0; @@ -733,8 +801,10 @@ class VirtualTranslate : public PerturbationAnalysis { * @date Malmo 2014 * @todo Add option to use charge center instead of mass center */ -class MultipoleDistribution : public Analysis { - struct Data { +class MultipoleDistribution : public Analysis +{ + struct Data + { using average_type = Average; average_type exact; //!< Exact electrostatic energy average_type ion_ion; @@ -743,6 +813,7 @@ class MultipoleDistribution : public Analysis { average_type dipole_dipole; average_type dipole_dipole_correlation; }; + std::map mean_energy; //!< Energy distributions std::vector names; //!< Molecule names (len=2) std::vector ids; //!< Molecule ids (len=2) @@ -753,8 +824,9 @@ class MultipoleDistribution : public Analysis { void _to_json(json& j) const override; void _to_disk() override; void sampleGroupGroup(const Space::GroupType& group1, const Space::GroupType& group2); - [[nodiscard]] double groupGroupExactEnergy(const Space::GroupType& group1, - const Space::GroupType& group2) const; // scatter_positions; //!< vector of scattering points + bool mass_center_scattering; //!< scatter from mass center, only? + bool save_after_sample = false; //!< if true, save average S(q) after each sample point + std::string filename; //!< output file name + std::vector scatter_positions; //!< vector of scattering points std::vector molecule_ids; //!< Molecule ids - std::vector molecule_names; //!< Molecule names corresponding to `molecule_ids` + std::vector molecule_names; //!< Molecule names corresponding to `molecule_ids` using Tformfactor = Scatter::FormFactorUnity; std::unique_ptr> debye; @@ -789,7 +867,8 @@ class ScatteringFunction : public Analysis { /* * @brief Sample and save gyration eigenvalues of all particles having the same id */ -class AtomInertia : public Analysis { +class AtomInertia : public Analysis +{ private: std::string filename; AtomData::index_type atom_id; @@ -805,15 +884,18 @@ class AtomInertia : public Analysis { }; /** - * @brief Sample and save the eigenvalues of the inertia tensor for a range of indexes within a molecule + * @brief Sample and save the eigenvalues of the inertia tensor for a range of indexes within a + * molecule */ -class InertiaTensor : public Analysis { +class InertiaTensor : public Analysis +{ private: - std::string filename; //!< file to stream to - std::unique_ptr stream; //!< file output stream - MoleculeData::index_type group_index; //!< Group to analyse - std::vector particle_range; //!< range of indexes within the group - [[nodiscard]] std::pair compute() const; //!< Compute eigen values and principal axis + std::string filename; //!< file to stream to + std::unique_ptr stream; //!< file output stream + MoleculeData::index_type group_index; //!< Group to analyse + std::vector particle_range; //!< range of indexes within the group + [[nodiscard]] std::pair + compute() const; //!< Compute eigen values and principal axis void _to_json(json& j) const override; void _sample() override; void _to_disk() override; @@ -823,17 +905,21 @@ class InertiaTensor : public Analysis { }; /* - * @brief Sample and save charge, dipole, and quadrupole moments for a range of indexes within a molecule + * @brief Sample and save charge, dipole, and quadrupole moments for a range of indexes within a + * molecule */ -class MultipoleMoments : public Analysis { +class MultipoleMoments : public Analysis +{ private: std::string filename; std::vector particle_range; //!< range of indexes within the group MoleculeData::index_type group_index; - bool use_molecular_mass_center = true; //!< Moments w.r.t. the COM of the whole molecule (instead of the subgroup) + bool use_molecular_mass_center = + true; //!< Moments w.r.t. the COM of the whole molecule (instead of the subgroup) std::ofstream output_stream; - struct Data { + struct Data + { double charge = 0.0; // total charge Point dipole_moment{0.0, 0.0, 0.0}; // dipole vector Point eivals, eivec, center; // quadrupole eigenvalues and major axis @@ -858,8 +944,10 @@ class MultipoleMoments : public Analysis { * - end-to-end distance * - shape anisotropy */ -class PolymerShape : public Analysis { - struct AverageData { +class PolymerShape : public Analysis +{ + struct AverageData + { using average_type = Average; average_type gyration_radius_squared; average_type gyration_radius; @@ -890,7 +978,8 @@ class PolymerShape : public Analysis { * particles have zero charge and radius. If the `filename` ends with `.gz` a GZip compressed * file is created. */ -class QRtraj : public Analysis { +class QRtraj : public Analysis +{ protected: std::function write_to_file; //!< Write a single frame to stream std::unique_ptr stream = nullptr; //!< Output stream @@ -915,7 +1004,8 @@ class QRtraj : public Analysis { * This can be used to generate VMD visualisation using a conversion * script (see scripts/ folder) */ -class PatchySpheroCylinderTrajectory : public QRtraj { +class PatchySpheroCylinderTrajectory : public QRtraj +{ public: PatchySpheroCylinderTrajectory(const json& j, const Space& spc); }; @@ -933,7 +1023,8 @@ class PatchySpheroCylinderTrajectory : public QRtraj { * * @todo Geometry information; update z-compression detection */ -class SpaceTrajectory : public Analysis { +class SpaceTrajectory : public Analysis +{ private: const Space::GroupVector& groups; // reference to all groups std::string filename; @@ -949,26 +1040,36 @@ class SpaceTrajectory : public Analysis { }; class AreaSamplingPolicy; + /** * @brief Analysis class for sasa calculations * * Holds a policy which defines type of sampling to be used and * is chosen by the user input */ -class SASAAnalysis : public Analysis { +class SASAAnalysis : public Analysis +{ using index_type = Faunus::AtomData::index_type; using count_type = size_t; using table_type = Equidistant2DTable; public: - enum class Policies { ATOMIC, MOLECULAR, ATOMS_IN_MOLECULE, INVALID }; + enum class Policies + { + ATOMIC, + MOLECULAR, + ATOMS_IN_MOLECULE, + INVALID + }; private: - struct Averages { + struct Averages + { using average_type = Average; average_type area; average_type area_squared; - }; //!< Placeholder class for average properties + }; //!< Placeholder class for average properties + Averages average_data; //!< Stores all averages for the selected molecule std::unique_ptr output_stream; //!< output stream @@ -979,7 +1080,7 @@ class SASAAnalysis : public Analysis { std::string filename; //!< output file name table_type sasa_histogram; //!< histogram of sasa values std::unique_ptr sasa; //!< sasa object for calculating solute areas - std::unique_ptr policy; //!< policy specyfing how sampling will be performed + std::unique_ptr policy; //!< policy specyfing how sampling will be performed void _to_json(json& j) const override; void _from_json(const json& input) override; @@ -993,20 +1094,24 @@ class SASAAnalysis : public Analysis { public: SASAAnalysis(const json& j, const Space& spc); - SASAAnalysis(double probe_radius, int slices_per_atom, double resolution, Policies policy, const Space& spc); + SASAAnalysis(double probe_radius, int slices_per_atom, double resolution, Policies policy, + const Space& spc); }; -NLOHMANN_JSON_SERIALIZE_ENUM(SASAAnalysis::Policies, {{SASAAnalysis::Policies::ATOMIC, "atomic"}, - {SASAAnalysis::Policies::MOLECULAR, "molecular"}, - {SASAAnalysis::Policies::ATOMS_IN_MOLECULE, "atoms_in_molecule"}, - {SASAAnalysis::Policies::INVALID, nullptr}}) +NLOHMANN_JSON_SERIALIZE_ENUM(SASAAnalysis::Policies, + {{SASAAnalysis::Policies::ATOMIC, "atomic"}, + {SASAAnalysis::Policies::MOLECULAR, "molecular"}, + {SASAAnalysis::Policies::ATOMS_IN_MOLECULE, "atoms_in_molecule"}, + {SASAAnalysis::Policies::INVALID, nullptr}}) /** @brief abstract base class for different SASA sampling policies*/ -class AreaSamplingPolicy { +class AreaSamplingPolicy +{ protected: template void sampleIndividualSASA(TBegin first, TEnd last, SASAAnalysis& analysis); - template void sampleTotalSASA(TBegin first, TEnd last, SASAAnalysis& analysis); + template + void sampleTotalSASA(TBegin first, TEnd last, SASAAnalysis& analysis); public: virtual void sample(const Space& spc, SASAAnalysis& analysis) = 0; @@ -1018,7 +1123,8 @@ class AreaSamplingPolicy { }; /** @brief SASA sampling policy which samples individually atoms selected by atom type name*/ -class AtomicPolicy final : public AreaSamplingPolicy { +class AtomicPolicy final : public AreaSamplingPolicy +{ AtomData::index_type atom_id; //!< id of atom type to be sampled std::string atom_name; //!< name of the atom type to be sampled @@ -1032,7 +1138,8 @@ class AtomicPolicy final : public AreaSamplingPolicy { }; /** @brief SASA sampling policy which samples individually molecules selected by molecules name*/ -class MolecularPolicy final : public AreaSamplingPolicy { +class MolecularPolicy final : public AreaSamplingPolicy +{ MoleculeData::index_type molecule_id; //!< id of molecule to be sampled std::string molecule_name; //!< name of the molecule to be sampled @@ -1046,11 +1153,12 @@ class MolecularPolicy final : public AreaSamplingPolicy { }; /** @brief SASA sampling policy which samples as a whole atom in a selected molecule - * if multiple atoms are selected (either by atom names or by indices in a selected molecule) - * it samples sum of their SASAs in a given molecule - * if single atom name is selected, it samples just the selected atom SASA in a selected molecule + * if multiple atoms are selected (either by atom names or by indices in a selected + * molecule) it samples sum of their SASAs in a given molecule if single atom name is selected, it + * samples just the selected atom SASA in a selected molecule * */ -class AtomsInMoleculePolicy final : public AreaSamplingPolicy { +class AtomsInMoleculePolicy final : public AreaSamplingPolicy +{ MoleculeData::index_type molecule_id; //!< id of molecule to be sampled std::set selected_indices; //!< selected indices of atoms in the chosen molecule @@ -1066,16 +1174,21 @@ class AtomsInMoleculePolicy final : public AreaSamplingPolicy { }; /** @brief Example analysis */ -template struct [[maybe_unused]] _analyse { +template struct [[maybe_unused]] _analyse +{ void sample(T&) { std::cout << "not a dipole!" << std::endl; } //!< Sample -}; // primary template +}; // primary template /** @brief Example analysis */ -template struct [[maybe_unused]] _analyse::value>::type> { +template +struct [[maybe_unused]] _analyse::value>::type> +{ void sample(T&) { std::cout << "dipole!" << std::endl; } //!< Sample -}; // specialized template +}; // specialized template -class SavePenaltyEnergy : public Analysis { +class SavePenaltyEnergy : public Analysis +{ private: const std::string filename; //!< Base file name int filenumber = 0; //!< Counter for each saved file @@ -1093,12 +1206,15 @@ class SavePenaltyEnergy : public Analysis { * * https://doi.org/10/mq8k */ -class Voronota : public Analysis { +class Voronota : public Analysis +{ private: - struct Averages { + struct Averages + { Average area; Average area_squared; }; //!< Placeholder class for average properties + Averages average_data; //!< Stores all averages for the selected molecule std::unique_ptr output_stream; //!< output stream @@ -1117,5 +1233,3 @@ class Voronota : public Analysis { }; } // namespace Faunus::analysis - - diff --git a/src/atomdata.cpp b/src/atomdata.cpp index 49ae33787..c7c01cb9e 100644 --- a/src/atomdata.cpp +++ b/src/atomdata.cpp @@ -9,40 +9,47 @@ namespace Faunus { -bool InteractionData::contains(const key_type& name) const { +bool InteractionData::contains(const key_type& name) const +{ if (auto it = data.find(name); it != data.end()) { return !std::isnan(it->second); } return false; } -double InteractionData::at(const key_type& name) const { +double InteractionData::at(const key_type& name) const +{ try { return data.at(name); - } catch (const std::out_of_range& e) { + } + catch (const std::out_of_range& e) { // cannot throw until non-used atomic pairs are eliminated from the potential matrices faunus_logger->error("Unknown/unset atom property {} required.", name); return std::numeric_limits::signaling_NaN(); } } -double& InteractionData::at(const key_type& name) { +double& InteractionData::at(const key_type& name) +{ if (data.find(name) == data.end()) { insert_or_assign(name, std::numeric_limits::signaling_NaN()); } return data.at(name); } -void InteractionData::insert_or_assign(const key_type& name, const double value) { +void InteractionData::insert_or_assign(const key_type& name, const double value) +{ auto it = data.find(name); if (it != data.end()) { it->second = value; - } else { + } + else { data.insert({name, value}); } } -void from_json(const json& j, InteractionData& a) { +void from_json(const json& j, InteractionData& a) +{ for (const auto& [key, value] : j.items()) { if (value.is_number()) { a.insert_or_assign(key, value); @@ -50,7 +57,8 @@ void from_json(const json& j, InteractionData& a) { } } -void from_single_use_json(SingleUseJSON& j, InteractionData& a) { +void from_single_use_json(SingleUseJSON& j, InteractionData& a) +{ auto j_copy = j; for (const auto& [key, value] : j_copy.items()) { if (value.is_number()) { @@ -60,17 +68,25 @@ void from_single_use_json(SingleUseJSON& j, InteractionData& a) { } } -void to_json(json& j, const InteractionData& a) { +void to_json(json& j, const InteractionData& a) +{ for (const auto& [key, value] : a.data) { j[key] = value; } } -AtomData::index_type& AtomData::id() { return _id; } +AtomData::index_type& AtomData::id() +{ + return _id; +} -const AtomData::index_type& AtomData::id() const { return _id; } +const AtomData::index_type& AtomData::id() const +{ + return _id; +} -void to_json(json& j, const AtomData& a) { +void to_json(json& j, const AtomData& a) +{ auto& _j = j[a.name]; _j = {{"activity", a.activity / 1.0_molar}, {"pactivity", -std::log10(a.activity / 1.0_molar)}, @@ -94,7 +110,8 @@ void to_json(json& j, const AtomData& a) { } } -void from_json(const json& j, AtomData& a) { +void from_json(const json& j, AtomData& a) +{ if (!j.is_object() || j.size() != 1) { throw std::runtime_error("Invalid JSON data for AtomData"); } @@ -128,13 +145,15 @@ void from_json(const json& j, AtomData& a) { } if (val.contains("pactivity")) { if (val.contains("activity")) { - throw std::runtime_error("Specify either activity or pactivity for atom '"s + a.name + "'!"); + throw std::runtime_error("Specify either activity or pactivity for atom '"s + + a.name + "'!"); } a.activity = std::pow(10, -val.at("pactivity").get()) * 1.0_molar; } if (val.contains("r")) { - faunus_logger->warn("Atom property `r` is obsolete; use `sigma = 2*r` instead on atom {}", a.name); + faunus_logger->warn( + "Atom property `r` is obsolete; use `sigma = 2*r` instead on atom {}", a.name); } a.interaction.insert_or_assign("sigma", val.value("sigma", 0.0) * 1.0_angstrom); if (fabs(a.interaction.at("sigma")) < 1e-20) { @@ -147,7 +166,8 @@ void from_json(const json& j, AtomData& a) { from_single_use_json(val, a.interaction); if (!val.empty()) { usageTip.pick("atomlist"); - throw ConfigurationError("unused key(s) for atom '{}':\n{}", a.name, val.items().begin().key()); + throw ConfigurationError("unused key(s) for atom '{}':\n{}", a.name, + val.items().begin().key()); } if (std::isnan(a.interaction.at("sigma"))) { @@ -156,9 +176,10 @@ void from_json(const json& j, AtomData& a) { } } -void from_json(const json& j, std::vector& atom_vector) { - // List of atoms can be provided as an array or wrapped in an object containing an array – {"atomlist": […], …}. - // Here we attempt to unwrap the object envelope. +void from_json(const json& j, std::vector& atom_vector) +{ + // List of atoms can be provided as an array or wrapped in an object containing an array – + // {"atomlist": […], …}. Here we attempt to unwrap the object envelope. auto j_atomlist = j.find("atomlist"); const auto& new_atoms = (j_atomlist == j.end()) ? j : *j_atomlist; if (!new_atoms.is_array()) { @@ -170,17 +191,21 @@ void from_json(const json& j, std::vector& atom_vector) { for (const auto& element : new_atoms) { // loop over elements in json array if (element.is_object()) { const auto atomdata = Faunus::AtomData(element); - if (auto it = Faunus::findName(atom_vector, atomdata.name); it != atom_vector.end()) { + if (auto it = Faunus::findName(atom_vector, atomdata.name); + it != atom_vector.end()) { faunus_logger->warn("overwriting existing properties of {}", it->name); *it = atomdata; - } else { // add new atom + } + else { // add new atom atom_vector.push_back(atomdata); } - } else if (element.is_string()) { // treat element as filename + } + else if (element.is_string()) { // treat element as filename const auto filename = element.get(); faunus_logger->info("reading atoms from external file '{}'", filename); from_json(Faunus::loadJSON(filename), atom_vector); - } else { + } + else { throw ConfigurationError("atom entry must be string or object").attachJson(element); } } @@ -189,8 +214,10 @@ void from_json(const json& j, std::vector& atom_vector) { for (size_t i = 0; i < atom_vector.size(); ++i) { atom_vector[i].id() = i; } - } catch (std::exception& e) { - std::throw_with_nested(ConfigurationError("error in atoms definition").attachJson(new_atoms)); + } + catch (std::exception& e) { + std::throw_with_nested( + ConfigurationError("error in atoms definition").attachJson(new_atoms)); } } @@ -198,7 +225,8 @@ std::vector atoms; TEST_SUITE_BEGIN("AtomData"); -TEST_CASE("[Faunus] AtomData") { +TEST_CASE("[Faunus] AtomData") +{ using doctest::Approx; json j = R"({ "atomlist" : [ @@ -217,7 +245,8 @@ TEST_CASE("[Faunus] AtomData") { CHECK_EQ(v.front().interaction.at("eps_custom"), Approx(0.1)); // raw number, no units CHECK_EQ(std::isnan(v.front().interaction.at("eps_unknown")), true); - // CHECK_THROWS_AS_MESSAGE(v.front().interaction.get("eps_unknown"), std::runtime_error, "unknown atom property"); + // CHECK_THROWS_AS_MESSAGE(v.front().interaction.get("eps_unknown"), std::runtime_error, + // "unknown atom property"); CHECK_EQ(v.front().sigma, Approx(2.5e-10_m)); CHECK_EQ(v.front().activity, Approx(0.01_molar)); CHECK_EQ(v.back().tfe, Approx(0.98_kJmol / (1.0_angstrom * 1.0_angstrom * 1.0_molar))); @@ -240,12 +269,16 @@ TEST_CASE("[Faunus] AtomData") { it = findName(v, "unknown atom"); CHECK_EQ(it, v.end()); } + TEST_SUITE_END(); UnknownAtomError::UnknownAtomError(std::string_view atom_name) - : GenericError("unknown atom: '{}'", atom_name) {} + : GenericError("unknown atom: '{}'", atom_name) +{ +} -AtomData& findAtomByName(std::string_view name) { +AtomData& findAtomByName(std::string_view name) +{ const auto iter = findName(Faunus::atoms, name); if (iter == Faunus::atoms.end()) { throw UnknownAtomError(name); @@ -253,7 +286,8 @@ AtomData& findAtomByName(std::string_view name) { return *iter; } -void from_json(const json& j, SpheroCylinderData& psc) { +void from_json(const json& j, SpheroCylinderData& psc) +{ psc.length = j.value("length", 0.0) * 1.0_angstrom; psc.type = j.value("type", SpheroCylinderData::PatchType::None); psc.patch_angle = j.value("patch_angle", 0.0) * 1.0_deg; @@ -261,7 +295,8 @@ void from_json(const json& j, SpheroCylinderData& psc) { psc.chiral_angle = j.value("chiral_angle", 0.0) * 1.0_deg; } -void to_json(json& j, const SpheroCylinderData& psc) { +void to_json(json& j, const SpheroCylinderData& psc) +{ j = {{"length", psc.length / 1.0_angstrom}, {"patch_angle", psc.patch_angle / 1.0_deg}, {"patch_angle_switch", psc.patch_angle_switch / 1.0_deg}, diff --git a/src/atomdata.h b/src/atomdata.h index 5c3a6aa12..edc2fd5f7 100644 --- a/src/atomdata.h +++ b/src/atomdata.h @@ -10,11 +10,12 @@ namespace Faunus { /** * @brief A stub to hold various parameters of interactions. * - * The class serves as a container for parameters of MixerPairPotentialBase. Currently only raw numbers - * without units are stored. In the future, it is supposed to keep a track which parameters are needed - * by potentials employed, as well as to convert units when initialized from JSON. + * The class serves as a container for parameters of MixerPairPotentialBase. Currently only raw + * numbers without units are stored. In the future, it is supposed to keep a track which parameters + * are needed by potentials employed, as well as to convert units when initialized from JSON. */ -class InteractionData { +class InteractionData +{ using key_type = std::string; std::map data; //!< arbitrary additional properties friend void to_json(json&, const InteractionData&); @@ -33,37 +34,42 @@ void from_single_use_json(SingleUseJSON& j, InteractionData& a); /** * @brief Static properties for patchy sphero cylinders (PSC) */ -class SpheroCylinderData { +class SpheroCylinderData +{ protected: friend void from_json(const json&, SpheroCylinderData&); friend void to_json(json&, const SpheroCylinderData&); public: - enum class PatchType { - None = 0, //!< No patch - Full = 1, //!< Patch runs the full length of the SC - Capped = 2, //!< Patch stops before the end caps - Invalid = 3 //!< Used to detect invalid input - }; //!< Type of PSC particle - double chiral_angle = 0.0; //!< Rotation of patch relative to direction (radians) - double length = 0.0; //!< Sphere-cylinder length - double patch_angle = 0.0; //!< Opening angle of attrative patch (radians) - double patch_angle_switch = 0.0; //!< Gradually switch on patch interaction over angular interval (radians) + enum class PatchType + { + None = 0, //!< No patch + Full = 1, //!< Patch runs the full length of the SC + Capped = 2, //!< Patch stops before the end caps + Invalid = 3 //!< Used to detect invalid input + }; //!< Type of PSC particle + double chiral_angle = 0.0; //!< Rotation of patch relative to direction (radians) + double length = 0.0; //!< Sphere-cylinder length + double patch_angle = 0.0; //!< Opening angle of attrative patch (radians) + double patch_angle_switch = + 0.0; //!< Gradually switch on patch interaction over angular interval (radians) PatchType type = PatchType::None; //!< Patch type of spherocylinder }; void from_json(const json& j, SpheroCylinderData& psc); void to_json(json& j, const SpheroCylinderData& psc); -NLOHMANN_JSON_SERIALIZE_ENUM(SpheroCylinderData::PatchType, {{SpheroCylinderData::PatchType::Invalid, nullptr}, - {SpheroCylinderData::PatchType::Full, "full"}, - {SpheroCylinderData::PatchType::Capped, "capped"}, - {SpheroCylinderData::PatchType::None, "none"}}) +NLOHMANN_JSON_SERIALIZE_ENUM(SpheroCylinderData::PatchType, + {{SpheroCylinderData::PatchType::Invalid, nullptr}, + {SpheroCylinderData::PatchType::Full, "full"}, + {SpheroCylinderData::PatchType::Capped, "capped"}, + {SpheroCylinderData::PatchType::None, "none"}}) /** * @brief General properties for atoms */ -class AtomData { // has to be a class when a constant reference is used +class AtomData +{ // has to be a class when a constant reference is used public: using index_type = std::size_t; //!< Unsigned int used for atom id and atom indexing @@ -73,22 +79,23 @@ class AtomData { // has to be a class when a constant reference is used friend void from_json(const json&, AtomData&); public: - std::string name; //!< Name - double charge = 0; //!< Particle charge [e] - double mw = 1; //!< Weight - double sigma = 0; //!< Diameter for e.g Lennard-Jones etc. [angstrom] - //!< Do not set! Only a temporal class member during the refactorization - double activity = 0; //!< Chemical activity [mol/l] - double alphax = 0; //!< Excess polarisability (unit-less) - double dp = 0; //!< Translational displacement parameter [angstrom] - double dprot = 0; //!< Rotational displacement parameter [degrees] - double tension = 0; //!< Surface tension [kT/Γ…^2] - double tfe = 0; //!< Transfer free energy [J/mol/angstrom^2/M] - Point mu = {0, 0, 0}; //!< Dipole moment unit vector - double mulen = 0; //!< Dipole moment length - bool hydrophobic = false; //!< Is the particle hydrophobic? - bool implicit = false; //!< Is the particle implicit (e.g. proton)? - InteractionData interaction; //!< Arbitrary interaction parameters, e.g., epsilons in various potentials + std::string name; //!< Name + double charge = 0; //!< Particle charge [e] + double mw = 1; //!< Weight + double sigma = 0; //!< Diameter for e.g Lennard-Jones etc. [angstrom] + //!< Do not set! Only a temporal class member during the refactorization + double activity = 0; //!< Chemical activity [mol/l] + double alphax = 0; //!< Excess polarisability (unit-less) + double dp = 0; //!< Translational displacement parameter [angstrom] + double dprot = 0; //!< Rotational displacement parameter [degrees] + double tension = 0; //!< Surface tension [kT/Γ…^2] + double tfe = 0; //!< Transfer free energy [J/mol/angstrom^2/M] + Point mu = {0, 0, 0}; //!< Dipole moment unit vector + double mulen = 0; //!< Dipole moment length + bool hydrophobic = false; //!< Is the particle hydrophobic? + bool implicit = false; //!< Is the particle implicit (e.g. proton)? + InteractionData + interaction; //!< Arbitrary interaction parameters, e.g., epsilons in various potentials SpheroCylinderData sphero_cylinder; //!< Data for patchy sphero cylinders (PSCs) index_type& id(); //!< Type id @@ -111,9 +118,9 @@ extern std::vector atoms; //!< Global instance of atom list /** Concept for named database such as vector, vector etc. */ template concept RequireNamedElements = requires(T db) { - {db.begin()}; + { db.begin() }; { db.begin()->name } -> std::convertible_to; - {std::is_integral_v::index_type>}; + { std::is_integral_v::index_type> }; }; /** @@ -124,18 +131,21 @@ concept RequireNamedElements = requires(T db) { * @return an iterator to the first element, or `last` if not found * @see findAtomByName(), findMoleculeByName() */ -auto findName(RequireNamedElements auto& range, std::string_view name) { +auto findName(RequireNamedElements auto& range, std::string_view name) +{ return std::find_if(range.begin(), range.end(), [&](auto& i) { return i.name == name; }); } -auto findName(const RequireNamedElements auto& range, std::string_view name) { +auto findName(const RequireNamedElements auto& range, std::string_view name) +{ return std::find_if(range.begin(), range.end(), [&](const auto& i) { return i.name == name; }); } /** * @brief An exception to indicate an unknown atom name in the input. */ -struct UnknownAtomError : public GenericError { +struct UnknownAtomError : public GenericError +{ explicit UnknownAtomError(std::string_view atom_name); }; @@ -164,7 +174,9 @@ AtomData& findAtomByName(std::string_view name); * a sequence containing all id's of the database, i.e. * `0, ..., database.size()-1`. */ -template auto names2ids(const T& database, const std::vector& names) { +template +auto names2ids(const T& database, const std::vector& names) +{ namespace rv = ranges::cpp20::views; auto is_wildcard = [](auto& name) { return name == "*"; }; diff --git a/src/aux/arange.h b/src/aux/arange.h index 5bd3c1a17..10ea398f3 100644 --- a/src/aux/arange.h +++ b/src/aux/arange.h @@ -46,17 +46,23 @@ namespace Faunus { * @param step Spacing between values * @return Range of lazily generated values */ -template constexpr auto arange(const T start, const T stop, const T step) { - static_assert(std::is_floating_point_v || std::is_integral_v, "floating point or integral type required"); +template constexpr auto arange(const T start, const T stop, const T step) +{ + static_assert(std::is_floating_point_v || std::is_integral_v, + "floating point or integral type required"); using float_type = typename std::conditional, T, long double>::type; using int_type = typename std::conditional, T, int>::type; - const auto length = static_cast(std::ceil((stop - start) / static_cast(step))); + const auto length = + static_cast(std::ceil((stop - start) / static_cast(step))); return ranges::cpp20::views::iota(int_type(0), length) | - ranges::cpp20::views::transform([start, step](auto i) -> T { return start + static_cast(i) * step; }); + ranges::cpp20::views::transform( + [start, step](auto i) -> T { return start + static_cast(i) * step; }); } -TEST_CASE("[Faunus] arange") { - SUBCASE("Step = 1 (float)") { +TEST_CASE("[Faunus] arange") +{ + SUBCASE("Step = 1 (float)") + { auto r = arange(4.0, 10.0, 1.0); // --> 4 5 6 7 8 9 auto pos = r.begin(); CHECK_EQ(ranges::size(r), 6); @@ -67,7 +73,8 @@ TEST_CASE("[Faunus] arange") { CHECK_EQ(*(pos++), doctest::Approx(8.0)); CHECK_EQ(*(pos++), doctest::Approx(9.0)); } - SUBCASE("Step = 1 (int)") { + SUBCASE("Step = 1 (int)") + { auto r = arange(4, 10, 1); // --> 4 5 6 7 8 9 auto pos = r.begin(); CHECK_EQ(ranges::size(r), 6); @@ -78,7 +85,8 @@ TEST_CASE("[Faunus] arange") { CHECK_EQ(*(pos++), 8); CHECK_EQ(*(pos++), 9); } - SUBCASE("Step > 1 (float)") { + SUBCASE("Step > 1 (float)") + { auto r = arange(4.0, 20.0, 3.0); // --> 4 7 10 13 16 19 auto pos = r.begin(); CHECK_EQ(ranges::size(r), 6); @@ -89,7 +97,8 @@ TEST_CASE("[Faunus] arange") { CHECK_EQ(*(pos++), doctest::Approx(16.0)); CHECK_EQ(*(pos++), doctest::Approx(19.0)); } - SUBCASE("Step > 1 (int)") { + SUBCASE("Step > 1 (int)") + { auto r = arange(4, 20, 3); // --> 4 7 10 13 16 19 auto pos = r.begin(); CHECK_EQ(ranges::size(r), 6); @@ -101,7 +110,8 @@ TEST_CASE("[Faunus] arange") { CHECK_EQ(*(pos++), 19); } - SUBCASE("Step < 1") { + SUBCASE("Step < 1") + { auto r = arange(-1.0, 1.0, 0.5); // --> -1 -0.5 0 0.5 auto pos = r.begin(); CHECK_EQ(ranges::size(r), 4); diff --git a/src/aux/eigen_cerealisation.h b/src/aux/eigen_cerealisation.h index 80c7f00ff..f9a98c9d8 100644 --- a/src/aux/eigen_cerealisation.h +++ b/src/aux/eigen_cerealisation.h @@ -47,9 +47,12 @@ namespace cereal { * @param[in] ar The archive to serialise to. * @param[in] matrix The matrix to serialise. */ -template -inline typename std::enable_if, Archive>::value, void>::type -save(Archive &ar, const Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols> &matrix) { +template +inline typename std::enable_if, Archive>::value, + void>::type +save(Archive& ar, const Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& matrix) +{ const std::int32_t rows = static_cast(matrix.rows()); const std::int32_t cols = static_cast(matrix.cols()); ar(rows); @@ -65,9 +68,12 @@ save(Archive &ar, const Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, * @param[in] ar The archive to deserialise from. * @param[in] matrix The matrix to deserialise into. */ -template -inline typename std::enable_if, Archive>::value, void>::type -load(Archive &ar, Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols> &matrix) { +template +inline typename std::enable_if, Archive>::value, + void>::type +load(Archive& ar, Eigen::Matrix<_Scalar, _Rows, _Cols, _Options, _MaxRows, _MaxCols>& matrix) +{ std::int32_t rows; std::int32_t cols; ar(rows); diff --git a/src/aux/eigensupport.h b/src/aux/eigensupport.h index 50976fa89..b8635fe5a 100644 --- a/src/aux/eigensupport.h +++ b/src/aux/eigensupport.h @@ -7,21 +7,21 @@ // Eigen<->JSON (de)serialization namespace Eigen { -template -void to_json(nlohmann::json& j, const T &p) { +template void to_json(nlohmann::json& j, const T& p) +{ auto d = p.data(); - for (int i=0; i<(int)p.size(); ++i) - j.push_back( d[i] ); + for (int i = 0; i < (int)p.size(); ++i) + j.push_back(d[i]); } -template -void from_json(const nlohmann::json& j, Eigen::Matrix &p) { - if ( j.size()==3 ) { - int i=0; +template void from_json(const nlohmann::json& j, Eigen::Matrix& p) +{ + if (j.size() == 3) { + int i = 0; for (auto d : j.get>()) p[i++] = d; return; } throw std::runtime_error("JSON->Eigen conversion error"); } -} // Eigen namespace +} // namespace Eigen diff --git a/src/aux/equidistant_table.h b/src/aux/equidistant_table.h index 7156c12e4..8baaf89a0 100644 --- a/src/aux/equidistant_table.h +++ b/src/aux/equidistant_table.h @@ -22,61 +22,88 @@ namespace Faunus { * - unit tests * - *much* faster, leaner than `std::map`-based Table2D */ -template class Equidistant2DTable { +template class Equidistant2DTable +{ private: Tx _dxinv, _xmin; int offset; std::vector vec; - inline int to_bin(Tx x) const { + inline int to_bin(Tx x) const + { if constexpr (centerbin) { return (x < 0) ? int(x * _dxinv - 0.5) : int(x * _dxinv + 0.5); - } else { + } + else { return std::floor((x - _xmin) * _dxinv); } } //!< x-value to vector index - Tx from_bin(int i) const { + Tx from_bin(int i) const + { assert(i >= 0); return i / _dxinv + _xmin; } //!< vector index to x-value public: - class iterator { + class iterator + { public: typedef Equidistant2DTable T; - iterator(T &tbl, size_t i) : tbl(tbl), i(i) {} - iterator operator++() { + + iterator(T& tbl, size_t i) + : tbl(tbl) + , i(i) + { + } + + iterator operator++() + { ++i; return *this; } - bool operator!=(const iterator &other) const { return i != other.i; } + + bool operator!=(const iterator& other) const { return i != other.i; } + auto operator*() { return tbl[i]; } protected: - T &tbl; + T& tbl; size_t i; }; // enable range-based for loops - class const_iterator { + class const_iterator + { public: typedef Equidistant2DTable T; - const_iterator(const T &tbl, size_t i) : tbl(tbl), i(i) {} - const_iterator operator++() { + + const_iterator(const T& tbl, size_t i) + : tbl(tbl) + , i(i) + { + } + + const_iterator operator++() + { ++i; return *this; } - bool operator!=(const const_iterator &other) const { return i != other.i; } + + bool operator!=(const const_iterator& other) const { return i != other.i; } + auto operator*() const { return tbl[i]; } protected: - const T &tbl; + const T& tbl; size_t i; }; // enable range-based for loops iterator begin() { return iterator(*this, 0); } + iterator end() { return iterator(*this, size()); } + const_iterator begin() const { return const_iterator(*this, 0); } + const_iterator end() const { return const_iterator(*this, size()); } Equidistant2DTable() = default; @@ -87,9 +114,13 @@ template class * @param xmin minimum x value * @param xmax maximum x value (for more efficient memory handling, only) */ - Equidistant2DTable(Tx dx, Tx xmin, Tx xmax = std::numeric_limits::infinity()) { setResolution(dx, xmin, xmax); } + Equidistant2DTable(Tx dx, Tx xmin, Tx xmax = std::numeric_limits::infinity()) + { + setResolution(dx, xmin, xmax); + } - void setResolution(Tx dx, Tx xmin, Tx xmax = std::numeric_limits::infinity()) { + void setResolution(Tx dx, Tx xmin, Tx xmax = std::numeric_limits::infinity()) + { _xmin = xmin; _dxinv = 1 / dx; offset = -to_bin(_xmin); @@ -98,17 +129,19 @@ template class } } - double sumy() const { + double sumy() const + { double sum = 0; - for (auto &i : vec) { + for (auto& i : vec) { sum += double(i); } return sum; } //!< sum all y-values - const std::vector &yvec() const { return vec; } //!< vector with y-values + const std::vector& yvec() const { return vec; } //!< vector with y-values - std::vector xvec() const { + std::vector xvec() const + { std::vector v; v.reserve(vec.size()); for (size_t i = 0; i < vec.size(); i++) { @@ -118,14 +151,22 @@ template class } void clear() { vec.clear(); } + size_t size() const { return vec.size(); } + bool empty() const { return vec.empty(); } + Tx dx() const { return 1 / _dxinv; } + Tx xmin() const { return _xmin; } //!< minimum x-value - Tx xmax() const { return (vec.empty()) ? _xmin : from_bin(vec.size() - 1); } //!< maximum stored x-value + Tx xmax() const + { + return (vec.empty()) ? _xmin : from_bin(vec.size() - 1); + } //!< maximum stored x-value - Ty &operator()(Tx x) { + Ty& operator()(Tx x) + { assert(x >= _xmin); int i = to_bin(x) + offset; if (i >= vec.size()) { @@ -134,18 +175,21 @@ template class return vec[i]; } // return y value for given x - std::pair operator[](size_t index) { + std::pair operator[](size_t index) + { assert(index >= 0); assert(index < size()); return {from_bin(index), vec[index]}; } // pair with x,y& value - std::pair operator[](size_t index) const { + std::pair operator[](size_t index) const + { assert(size() > index); return {from_bin(index), vec[index]}; } // const pair with x,y& value - const Ty &operator()(Tx x) const { + const Ty& operator()(Tx x) const + { assert(x >= _xmin); int i = to_bin(x) + offset; assert(i < vec.size()); @@ -153,20 +197,24 @@ template class } // return y value for given x // can be optinally used to customize streaming out, normalise etc. - std::function stream_decorator = nullptr; + std::function stream_decorator = nullptr; - friend std::ostream &operator<<(std::ostream &o, const Equidistant2DTable &tbl) { + friend std::ostream& operator<<(std::ostream& o, + const Equidistant2DTable& tbl) + { o.precision(16); for (auto d : tbl) { if (tbl.stream_decorator == nullptr) { o << d.first << " " << d.second << "\n"; - } else + } + else tbl.stream_decorator(o, d.first, d.second); } return o; } // write to stream - auto &operator<<(std::istream &in) { + auto& operator<<(std::istream& in) + { assert(stream_decorator == nullptr && "you probably don't want to load a decorated file"); clear(); Tx x; @@ -178,7 +226,8 @@ template class if (x >= _xmin) { y << o; operator()(x) = y; - } else { + } + else { throw std::runtime_error("table load error: x smaller than xmin"); } } @@ -187,10 +236,12 @@ template class } // load from stream }; -TEST_CASE("[Faunus] Equidistant2DTable") { +TEST_CASE("[Faunus] Equidistant2DTable") +{ using doctest::Approx; - SUBCASE("centered") { + SUBCASE("centered") + { Equidistant2DTable y(0.5, -3.0); CHECK_EQ(y.xmin(), Approx(-3.0)); CHECK_EQ(y.dx(), Approx(0.5)); @@ -205,7 +256,8 @@ TEST_CASE("[Faunus] Equidistant2DTable") { CHECK_EQ(y.xmax(), Approx(1.5)); } - SUBCASE("lower bound") { + SUBCASE("lower bound") + { Equidistant2DTable y(0.5, -3.0); CHECK_EQ(y.xmin(), Approx(-3.0)); CHECK_EQ(y.dx(), Approx(0.5)); diff --git a/src/aux/error_function.h b/src/aux/error_function.h index 3b4036693..d3207c129 100644 --- a/src/aux/error_function.h +++ b/src/aux/error_function.h @@ -23,7 +23,8 @@ namespace Faunus { * * @warning Needs modification if x < 0 */ -template inline T erfc_x(T x) { +template inline T erfc_x(T x) +{ T t = 1.0 / (1.0 + 0.3275911 * x); const T a1 = 0.254829592; const T a2 = -0.284496736; @@ -33,7 +34,8 @@ template inline T erfc_x(T x) { return t * (a1 + t * (a2 + t * (a3 + t * (a4 + t * a5)))) * std::exp(-x * x); } -TEST_CASE("[Faunus] erfc_x") { +TEST_CASE("[Faunus] erfc_x") +{ double infty = std::numeric_limits::infinity(); using doctest::Approx; CHECK_EQ(erfc_x(infty), Approx(0)); @@ -47,6 +49,9 @@ TEST_CASE("[Faunus] erfc_x") { * @brief Approximate 1 - erfc_x * @param x Value for which erf should be calculated */ -template T inline erf_x(T x) { return (1 - erfc_x(x)); } +template T inline erf_x(T x) +{ + return (1 - erfc_x(x)); +} } // namespace Faunus \ No newline at end of file diff --git a/src/aux/exp_function.h b/src/aux/exp_function.h index ca0a40ede..82a1b27cf 100644 --- a/src/aux/exp_function.h +++ b/src/aux/exp_function.h @@ -11,30 +11,39 @@ namespace Faunus { * * Update 2019: http://www.federicoperini.info/wp-content/uploads/FastExp-Final.pdf */ -template double exp_cawley(double y) { +template double exp_cawley(double y) +{ static_assert(2 * sizeof(Tint) == sizeof(double), "Approximate exp() requires 4-byte integer"); - union { + + union + { double d; - struct { + + struct + { Tint j, i; } n; // little endian + // struct { int i, j; } n; // bin endian } eco; + eco.n.i = 1072632447 + (Tint)(y * 1512775.39519519); eco.n.j = 0; return eco.d; } -inline double exp_untested(double y) { +inline double exp_untested(double y) +{ typedef std::int32_t Tint; static_assert(2 * sizeof(Tint) == sizeof(double), "Approximate exp() requires 4-byte integer"); double d(0); - *((Tint *)(&d) + 0) = 0; - *((Tint *)(&d) + 1) = (Tint)(1512775 * y + 1072632447); + *((Tint*)(&d) + 0) = 0; + *((Tint*)(&d) + 1) = (Tint)(1512775 * y + 1072632447); return d; } -TEST_CASE("[Faunus] exp_cawley") { +TEST_CASE("[Faunus] exp_cawley") +{ double infty = std::numeric_limits::infinity(); using doctest::Approx; WARN(exp_cawley(-infty) == Approx(0)); // clang=OK; GCC=not OK @@ -43,7 +52,8 @@ TEST_CASE("[Faunus] exp_cawley") { CHECK_EQ(exp_cawley(-2), Approx(0.13207829)); } -TEST_CASE("[Faunus] exp_untested") { +TEST_CASE("[Faunus] exp_untested") +{ double infty = std::numeric_limits::infinity(); using doctest::Approx; CHECK_EQ(exp_untested(-infty), Approx(0)); // clang=OK; GCC=not OK diff --git a/src/aux/invsqrt_function.h b/src/aux/invsqrt_function.h index 4d2ec8c12..8fbedc5c1 100644 --- a/src/aux/invsqrt_function.h +++ b/src/aux/invsqrt_function.h @@ -21,16 +21,17 @@ namespace Faunus { * @remarks Code comments supposedly from the original Quake III Arena code * @see https://en.wikipedia.org/wiki/Fast_inverse_square_root */ -template inline float_t inv_sqrt(float_t x) { +template inline float_t inv_sqrt(float_t x) +{ static_assert(iterations == 1 or iterations == 2); static_assert(std::is_floating_point::value); typedef typename std::conditional::type int_t; static_assert(sizeof(float_t) == sizeof(int_t)); float_t x_half = x * 0.5; float_t y = x; - int_t i = *reinterpret_cast(&y); // evil floating point bit level hacking + int_t i = *reinterpret_cast(&y); // evil floating point bit level hacking i = (sizeof(float_t) == 8 ? 0x5fe6eb50c7b537a9 : 0x5f3759df) - (i >> 1); // what the fuck? - y = *reinterpret_cast(&i); + y = *reinterpret_cast(&i); y = y * (1.5 - x_half * y * y); // 1st iteration if constexpr (iterations == 2) { y = y * (1.5 - x_half * y * y); // 2nd iteration, this can be removed @@ -39,7 +40,8 @@ template inline float_t inv_sqr } } // namespace Faunus -TEST_CASE_TEMPLATE("inv_sqrt", T, double, float) { +TEST_CASE_TEMPLATE("inv_sqrt", T, double, float) +{ std::vector vals = {0.23, 3.3, 10.2, 100.45, 512.06}; for (auto x : vals) { CHECK_EQ(Faunus::inv_sqrt(x), doctest::Approx(1.0 / std::sqrt(x))); diff --git a/src/aux/iteratorsupport.h b/src/aux/iteratorsupport.h index 21e804139..d872fd555 100644 --- a/src/aux/iteratorsupport.h +++ b/src/aux/iteratorsupport.h @@ -4,12 +4,14 @@ namespace Faunus { -template auto distance(T1 first, T2 last) { - return std::distance( &(*first), &(*last) ); +template auto distance(T1 first, T2 last) +{ + return std::distance(&(*first), &(*last)); } //!< Distance between two arbitrary contiguous iterators -template size_t range_size(Trange &rng) { +template size_t range_size(Trange& rng) +{ return ranges::cpp20::distance(rng.begin(), rng.end()); } //!< Size of a Ranges object using the `cpp20::distance()` -}//end of namespace +} // namespace Faunus diff --git a/src/aux/legendre.h b/src/aux/legendre.h index 35a23daac..a2bc6e235 100644 --- a/src/aux/legendre.h +++ b/src/aux/legendre.h @@ -1,5 +1,6 @@ #pragma once #include + namespace Faunus { /** * @brief Evaluate n'th degree Legendre polynomial @@ -20,12 +21,14 @@ namespace Faunus { * @tparam use_table Use lookup table for 1+1/i? Default = false * @todo Benchmark `use_table` */ -template class Legendre { +template class Legendre +{ private: std::array y; //!< Lookup table for 1+1/i (overkill?) std::array P; //!< Legendre terms stored here public: - Legendre() { + Legendre() + { P[0] = 1.0; if constexpr (use_table) { for (std::size_t i = 1; i < max_order; ++i) { @@ -35,13 +38,15 @@ template } /** @brief Evaluate polynomials at x */ - const auto &eval(T x) { + const auto& eval(T x) + { if constexpr (max_order > 0) { P[1] = x; for (std::size_t i = 1; i < max_order; ++i) { if constexpr (use_table) { P[i + 1] = ((y[i] + 1.0) * x * P[i] - P[i - 1]) / y[i]; - } else { + } + else { P[i + 1] = ((2.0 + 1.0 / T(i)) * x * P[i] - P[i - 1]) / (1.0 + 1.0 / T(i)); } } @@ -50,7 +55,9 @@ template } }; #ifdef DOCTEST_LIBRARY_INCLUDED__ -TEST_CASE_TEMPLATE("[Faunus] Legendre", LegendreType, Legendre, Legendre) { +TEST_CASE_TEMPLATE("[Faunus] Legendre", LegendreType, Legendre, + Legendre) +{ using doctest::Approx; LegendreType l; double x = 2.2; diff --git a/src/aux/matrixmarket.h b/src/aux/matrixmarket.h index 4151ed10b..bcf21ed6c 100644 --- a/src/aux/matrixmarket.h +++ b/src/aux/matrixmarket.h @@ -17,12 +17,14 @@ namespace Faunus { * @warning Untested for symmetric matrices with non-zero diagonal */ template -bool streamMarket(const SparseMatrixType& mat, std::ostream& out, bool symmetric = false) { +bool streamMarket(const SparseMatrixType& mat, std::ostream& out, bool symmetric = false) +{ using Scalar = Eigen::SparseMatrix::Scalar; if (!out) { return false; } - out << fmt::format("%%MatrixMarket matrix coordinate {} {}\n", "real", symmetric ? "symmetric" : "general") + out << fmt::format("%%MatrixMarket matrix coordinate {} {}\n", "real", + symmetric ? "symmetric" : "general") << mat.rows() << " " << mat.cols() << " " << mat.nonZeros() / (symmetric ? 2 : 1) << "\n"; for (int col = 0; col < mat.outerSize(); ++col) { diff --git a/src/aux/multimatrix.h b/src/aux/multimatrix.h index cc04e590c..3e4ebb4cc 100644 --- a/src/aux/multimatrix.h +++ b/src/aux/multimatrix.h @@ -11,14 +11,16 @@ namespace Faunus { * * https://eli.thegreenplace.net/2015/memory-layout-of-multi-dimensional-arrays */ -template > struct RowMajorOffset { +template > struct RowMajorOffset +{ /** * @brief memory offset * @param N matrix dimentions * @param n matrix indices * @return memory offset */ - inline int operator()(const Tindices &N, const Tindices &n) { + inline int operator()(const Tindices& N, const Tindices& n) + { int offset = 0; for (int i = 0; i < dim; i++) { int prod = 1; @@ -32,34 +34,50 @@ template > struct RowMajorOffs }; // dynamic 2d matrix with contiguous memory -template > class DynamicArray2D { +template > class DynamicArray2D +{ public: std::vector data; // contiguous data block; row- layout Tindices dim; // matrix size - void resize(const Tindices &dimensions) { + + void resize(const Tindices& dimensions) + { dim = dimensions; data.resize(dim[0] * dim[1], T()); } - inline size_t index(const Tindices &i) { return i[1] + dim[1] * i[0]; } - inline T &operator()(const Tindices &i) { return i[1] + dim[1] * i[0]; } - inline const T &operator()(const Tindices &i) const { return i[1] + dim[1] * i[0]; } + + inline size_t index(const Tindices& i) { return i[1] + dim[1] * i[0]; } + + inline T& operator()(const Tindices& i) { return i[1] + dim[1] * i[0]; } + + inline const T& operator()(const Tindices& i) const { return i[1] + dim[1] * i[0]; } }; // dynamic 3d matrix with contiguous memory -template > class DynamicArray3D { +template > class DynamicArray3D +{ public: std::vector data; // contiguous data block; row-column-depth layout Tindices dim; // matrix size - void resize(const Tindices &dimensions) { + + void resize(const Tindices& dimensions) + { dim = dimensions; data.resize(dim[0] * dim[1] * dim[2], T()); } - inline size_t index(const Tindices &i) { return i[2] + dim[2] * (i[1] + dim[1] * i[0]); } - inline T &operator()(const Tindices &i) { return data[i[2] + dim[2] * (i[1] + dim[1] * i[0])]; } - inline const T &operator()(const Tindices &i) const { return data[i[2] + dim[2] * (i[1] + dim[1] * i[0])]; } + + inline size_t index(const Tindices& i) { return i[2] + dim[2] * (i[1] + dim[1] * i[0]); } + + inline T& operator()(const Tindices& i) { return data[i[2] + dim[2] * (i[1] + dim[1] * i[0])]; } + + inline const T& operator()(const Tindices& i) const + { + return data[i[2] + dim[2] * (i[1] + dim[1] * i[0])]; + } }; -TEST_CASE("[Faunus] RowMajor3DMatrix") { +TEST_CASE("[Faunus] RowMajor3DMatrix") +{ DynamicArray3D matrix; Eigen::Vector3i dim = {4, 10, 2}; Eigen::Vector3i one = {1, 1, 1}; diff --git a/src/aux/pairmatrix.h b/src/aux/pairmatrix.h index 63c599f9a..686cef044 100644 --- a/src/aux/pairmatrix.h +++ b/src/aux/pairmatrix.h @@ -1,6 +1,7 @@ #pragma once #include #include + namespace Faunus { /** * @brief Container for data between pairs @@ -20,28 +21,36 @@ namespace Faunus { * * @note Vector of vector does not allocate contiguous memory */ -template class PairMatrix { +template class PairMatrix +{ private: T default_value; // default value when resizing std::vector> matrix; public: - void resize(size_t n) { + void resize(size_t n) + { matrix.resize(n); for (size_t i = 0; i < matrix.size(); i++) { if constexpr (triangular) { matrix[i].resize(i + 1, default_value); - } else { + } + else { matrix[i].resize(n, default_value); } } } - PairMatrix(size_t n = 0, T val = T()) : default_value(val) { resize(n); } + PairMatrix(size_t n = 0, T val = T()) + : default_value(val) + { + resize(n); + } auto size() const { return matrix.size(); } - inline const T &operator()(size_t i, size_t j) const { + inline const T& operator()(size_t i, size_t j) const + { if constexpr (triangular) { if (j > i) { std::swap(i, j); @@ -52,7 +61,8 @@ template class PairMatrix { return matrix[i][j]; } - void set(size_t i, size_t j, T val) { + void set(size_t i, size_t j, T val) + { if (j > i) { std::swap(i, j); } @@ -66,7 +76,8 @@ template class PairMatrix { } /** Set a uniform value */ - void set(T val) { + void set(T val) + { for (size_t i = 0; i < matrix.size(); i++) { for (size_t j = 0; j < matrix.size(); j++) { set(i, j, val); @@ -77,10 +88,12 @@ template class PairMatrix { void setZero() { set(T()); } }; -TEST_CASE("[Faunus] PairMatrix") { +TEST_CASE("[Faunus] PairMatrix") +{ int i = 2, j = 3; // particle type, for example - SUBCASE("full matrix") { + SUBCASE("full matrix") + { PairMatrix m; m.set(i, j, 12.1); CHECK_EQ(m.size(), 4); @@ -90,21 +103,24 @@ TEST_CASE("[Faunus] PairMatrix") { CHECK_EQ(m(2, 0), 0); } - SUBCASE("full matrix - default value") { + SUBCASE("full matrix - default value") + { PairMatrix m(5, 3.1); for (size_t i = 0; i < 5; i++) for (size_t j = 0; j < 5; j++) CHECK_EQ(m(i, j), 3.1); } - SUBCASE("triangular matrix - default value") { + SUBCASE("triangular matrix - default value") + { PairMatrix m(5, 3.1); for (size_t i = 0; i < 5; i++) for (size_t j = 0; j < 5; j++) CHECK_EQ(m(i, j), 3.1); } - SUBCASE("triangular matrix") { + SUBCASE("triangular matrix") + { PairMatrix m; m.set(i, j, 12.1); CHECK_EQ(m.size(), 4); diff --git a/src/aux/pow_function.h b/src/aux/pow_function.h index 4f44d4d4f..d8c63170a 100644 --- a/src/aux/pow_function.h +++ b/src/aux/pow_function.h @@ -11,7 +11,8 @@ namespace Faunus { * - https://martin.ankerl.com/2012/01/25/optimized-approximative-pow-in-c-and-cpp * - https://martin.ankerl.com/2007/10/04/optimized-pow-approximation-for-java-and-c-c/ */ -template inline constexpr T powi(T x, unsigned int n) { +template inline constexpr T powi(T x, unsigned int n) +{ #if defined(__GNUG__) return __builtin_powi(x, n); #else @@ -19,7 +20,8 @@ template inline constexpr T powi(T x, unsigned int n) { #endif } -TEST_CASE("[Faunus] powi") { +TEST_CASE("[Faunus] powi") +{ using doctest::Approx; double x = 3.1; CHECK_EQ(powi(x, 0), Approx(1)); diff --git a/src/aux/sparsehistogram.h b/src/aux/sparsehistogram.h index 9733fbecc..3504148ad 100644 --- a/src/aux/sparsehistogram.h +++ b/src/aux/sparsehistogram.h @@ -14,25 +14,36 @@ namespace Faunus { * Builds a histogram by binning given values to a specified resolution. Values are stored * in a memory efficient map-structure with log(N) lookup complexity. */ -template class SparseHistogram { +template class SparseHistogram +{ const T resolution; using map_type = std::map; map_type data; public: - explicit SparseHistogram(T resolution) : resolution(resolution) {} + explicit SparseHistogram(T resolution) + : resolution(resolution) + { + } + const T getResolution() const { return resolution; } - void add(const T value) { + + void add(const T value) + { if (std::isfinite(value)) { const auto index = static_cast(std::round(value / resolution)); data[index]++; - } else { + } + else { faunus_logger->warn("histogram: skipping inf/nan number"); } } - friend auto& operator<<(std::ostream& stream, const SparseHistogram& histogram) { + + friend auto& operator<<(std::ostream& stream, const SparseHistogram& histogram) + { std::for_each(histogram.data.begin(), histogram.data.end(), [&](const auto& sample) { - stream << fmt::format("{:.6E} {}\n", static_cast(sample.first) * histogram.resolution, sample.second); + stream << fmt::format( + "{:.6E} {}\n", static_cast(sample.first) * histogram.resolution, sample.second); }); return stream; } diff --git a/src/aux/table_1d.h b/src/aux/table_1d.h index a77ff67a4..d8d67e18c 100644 --- a/src/aux/table_1d.h +++ b/src/aux/table_1d.h @@ -10,38 +10,52 @@ namespace Faunus { * @brief Dynamic table for 1d data * @todo This really needs documentation and general refactoring! */ -template > -class Table : public base { +template > +class Table : public base +{ private: using Tvec = std::vector; Tvec _bw, _lo, _hi; using index_t = typename base::Index; - static_assert(std::is_integral_v && std::is_signed_v, "signed integral value expected"); + static_assert(std::is_integral_v && std::is_signed_v, + "signed integral value expected"); index_t _rows; index_t _cols; public: - explicit Table(const Tvec& bw = {1, 1}, const Tvec& lo = {0, 0}, const Tvec& hi = {2, 2}) { + explicit Table(const Tvec& bw = {1, 1}, const Tvec& lo = {0, 0}, const Tvec& hi = {2, 2}) + { reInitializer(bw, lo, hi); } // required for assignment from Eigen::Matrix and Eigen::Array objects template explicit Table(const Eigen::MatrixBase& other) - : base(other) {} - template Table &operator=(const Eigen::MatrixBase &other) { + : base(other) + { + } + + template Table& operator=(const Eigen::MatrixBase& other) + { this->base::operator=(other); return *this; } + template explicit Table(const Eigen::ArrayBase& other) - : base(other) {} - template Table &operator=(const Eigen::ArrayBase &other) { + : base(other) + { + } + + template Table& operator=(const Eigen::ArrayBase& other) + { this->base::operator=(other); return *this; } - void reInitializer(const Tvec &bw, const Tvec &lo, const Tvec &hi) { + void reInitializer(const Tvec& bw, const Tvec& lo, const Tvec& hi) + { assert(bw.size() == 1 || bw.size() == 2); assert(bw.size() == lo.size() && lo.size() == hi.size()); _bw = bw; @@ -50,20 +64,24 @@ class Table : public base { _rows = (_hi[0] - _lo[0]) / _bw[0] + 1.; if (bw.size() == 2) { _cols = (_hi[1] - _lo[1]) / _bw[1] + 1; - } else { + } + else { _cols = 1; } base::resize(_rows, _cols); base::setZero(); } - void round(Tvec &v) const { + void round(Tvec& v) const + { for (Tvec::size_type i = 0; i != v.size(); ++i) { - v[i] = (v[i] >= 0) ? int(v[i] / _bw[i] + 0.5) * _bw[i] : int(v[i] / _bw[i] - 0.5) * _bw[i]; + v[i] = + (v[i] >= 0) ? int(v[i] / _bw[i] + 0.5) * _bw[i] : int(v[i] / _bw[i] - 0.5) * _bw[i]; } } - void to_index(Tvec &v) const { + void to_index(Tvec& v) const + { for (Tvec::size_type i = 0; i != v.size(); ++i) { v[i] = (v[i] >= 0) ? int(v[i] / _bw[i] + 0.5) : int(v[i] / _bw[i] - 0.5); v[i] = v[i] - _lo[i] / _bw[i]; @@ -71,11 +89,13 @@ class Table : public base { v.resize(2, 0); } - Tcoeff& operator[](const Tvec& v) { + Tcoeff& operator[](const Tvec& v) + { return base::operator()(static_cast(v[0]), static_cast(v[1])); } - bool isInRange(const Tvec &v) const { + bool isInRange(const Tvec& v) const + { bool in_range = true; for (Tvec::size_type i = 0; i != v.size(); ++i) { in_range = in_range && v[i] >= _lo[i] && v[i] <= _hi[i]; @@ -83,7 +103,8 @@ class Table : public base { return in_range; } - Tvec hist2buf(int) const { + Tvec hist2buf(int) const + { Tvec sendBuf; for (index_t i = 0; i < _cols; ++i) { for (index_t j = 0; j < _rows; ++j) { @@ -93,7 +114,8 @@ class Table : public base { return sendBuf; } - void buf2hist(const Tvec &v) { + void buf2hist(const Tvec& v) + { assert(!v.empty()); base::setZero(); auto p = static_cast(v.size()) / this->size(); @@ -108,7 +130,8 @@ class Table : public base { } } - base getBlock(const Tvec& slice) { // {xmin,xmax} or {xmin,xmax,ymin,ymax} + base getBlock(const Tvec& slice) + { // {xmin,xmax} or {xmin,xmax,ymin,ymax} Tvec w(4, 0); switch (slice.size()) { case 1: @@ -132,9 +155,10 @@ class Table : public base { return this->block(w[0], w[2], w[1] - w[0] + 1, w[3] - w[2] + 1); // xmin,ymin,rows,cols } - Tcoeff avg(const Tvec &v) const { return this->getBlock(v).mean(); } + Tcoeff avg(const Tvec& v) const { return this->getBlock(v).mean(); } - void save(const std::string &filename, Tcoeff scale = 1, Tcoeff translate = 0) const { + void save(const std::string& filename, Tcoeff scale = 1, Tcoeff translate = 0) const + { Eigen::VectorXd v1(_cols + 1); Eigen::VectorXd v2(_rows + 1); v1(0) = v2(0) = base::size(); @@ -164,7 +188,8 @@ class Table : public base { } } - void saveRow(const std::string &filename, const Tvec &v, Tcoeff scale = 1, Tcoeff translate = 0) { + void saveRow(const std::string& filename, const Tvec& v, Tcoeff scale = 1, Tcoeff translate = 0) + { if (!this->isInRange(v)) { return; } @@ -190,7 +215,8 @@ class Table : public base { } } - void load(const std::string &filename) { + void load(const std::string& filename) + { if (std::ifstream f(filename.c_str()); f) { index_t i = 0; index_t j = -1; diff --git a/src/aux/table_2d.h b/src/aux/table_2d.h index 13359d9c2..c88982f81 100644 --- a/src/aux/table_2d.h +++ b/src/aux/table_2d.h @@ -15,13 +15,15 @@ namespace Faunus { * We have not experienced any issues with this, though. This uses * `std::map` and table lookup is of complexity logarithmic with N. */ -template class Table2D { +template class Table2D +{ protected: typedef std::map Tmap; - Ty count() { + Ty count() + { Ty cnt = 0; - for (auto &m : map) + for (auto& m : map) cnt += m.second; return cnt; } @@ -36,13 +38,19 @@ template class Table2D { double get(Tx x) { return operator()(x); } public: - enum type { HISTOGRAM, XYDATA }; + enum type + { + HISTOGRAM, + XYDATA + }; + type tabletype; /** @brief Sum of all y values (same as `count()`) */ - Ty sumy() const { + Ty sumy() const + { Ty sum = 0; - for (auto &m : map) + for (auto& m : map) sum += m.second; return sum; } @@ -52,17 +60,19 @@ template class Table2D { * @param resolution Resolution of the x axis * @param key Table type: HISTOGRAM or XYDATA */ - Table2D(Tx resolution = 0.2, type key = XYDATA) { + Table2D(Tx resolution = 0.2, type key = XYDATA) + { tabletype = key; setResolution(resolution); } /** @brief Convert to map */ - std::map> to_map() { + std::map> to_map() + { std::map> m; m["x"].reserve(map.size()); m["y"].reserve(map.size()); - for (auto &i : map) { + for (auto& i : map) { m["x"].push_back(i.first); m["y"].push_back(get(i.first)); } @@ -71,26 +81,29 @@ template class Table2D { void clear() { map.clear(); } - void setResolution(Tx resolution) { + void setResolution(Tx resolution) + { assert(resolution > 0); dx = resolution; map.clear(); } - void setResolution(std::vector &resolution) { + void setResolution(std::vector& resolution) + { assert(resolution[0] > 0); dx = resolution[0]; map.clear(); } /** @brief Access operator - returns reference to y(x) */ - Ty &operator()(Tx x) { return map[round(x)]; } + Ty& operator()(Tx x) { return map[round(x)]; } /** @brief Access operator - returns reference to y(x) */ - Ty &operator()(std::vector &x) { return map[round(x[0])]; } + Ty& operator()(std::vector& x) { return map[round(x[0])]; } /** @brief Find key and return corresponding value otherwise zero*/ - Ty find(std::vector &x) { + Ty find(std::vector& x) + { Ty value = 0; auto it = map.find(round(x[0])); if (it != map.end()) @@ -99,7 +112,8 @@ template class Table2D { } /** @brief Save table to disk */ - template void save(const std::string &filename, T scale = 1, T translate = 0) { + template void save(const std::string& filename, T scale = 1, T translate = 0) + { if (tabletype == HISTOGRAM) { if (!map.empty()) map.begin()->second *= 2; // compensate for half bin width @@ -111,7 +125,7 @@ template class Table2D { std::ofstream f(filename.c_str()); f.precision(10); if (f) { - for (auto &m : map) + for (auto& m : map) f << m.first << " " << (m.second + translate) * scale << "\n"; } } @@ -125,7 +139,8 @@ template class Table2D { } /** @brief Save normalized table to disk */ - template void normSave(const std::string &filename) { + template void normSave(const std::string& filename) + { if (tabletype == HISTOGRAM) { if (!map.empty()) map.begin()->second *= 2; // compensate for half bin width @@ -138,7 +153,7 @@ template class Table2D { f.precision(10); Ty cnt = count() * dx; if (f) { - for (auto &m : map) + for (auto& m : map) f << m.first << " " << m.second / cnt << "\n"; } } @@ -152,7 +167,8 @@ template class Table2D { } /** @brief Sums up all previous elements and saves table to disk */ - template void sumSave(std::string filename, T scale = 1) { + template void sumSave(std::string filename, T scale = 1) + { if (tabletype == HISTOGRAM) { if (!map.empty()) map.begin()->second *= 2; // compensate for half bin width @@ -165,7 +181,7 @@ template class Table2D { f.precision(10); if (f) { Ty sum_t = 0.0; - for (auto &m : map) { + for (auto& m : map) { sum_t += m.second; f << m.first << " " << sum_t * scale << "\n"; } @@ -180,33 +196,36 @@ template class Table2D { } } - const Tmap &getMap() const { return map; } + const Tmap& getMap() const { return map; } - Tmap &getMap() { return map; } + Tmap& getMap() { return map; } Tx getResolution() { return dx; } /*! Returns average */ - Tx mean() { + Tx mean() + { assert(!map.empty()); Tx avg = 0; - for (auto &m : map) + for (auto& m : map) avg += m.first * m.second; return avg / count(); } /*! Returns standard deviation */ - Tx std() { + Tx std() + { assert(!map.empty()); Tx std2 = 0; Tx avg = mean(); - for (auto &m : map) + for (auto& m : map) std2 += m.second * (m.first - avg) * (m.first - avg); return sqrt(std2 / count()); } /*! Returns iterator of minumum y */ - typename Tmap::const_iterator min() { + typename Tmap::const_iterator min() + { assert(!map.empty()); Ty min = std::numeric_limits::max(); typename Tmap::const_iterator it; @@ -219,7 +238,8 @@ template class Table2D { } /*! Returns iterator of maximum y */ - typename Tmap::const_iterator max() { + typename Tmap::const_iterator max() + { assert(!map.empty()); Ty max = std::numeric_limits::min(); typename Tmap::const_iterator it; @@ -232,10 +252,11 @@ template class Table2D { } /*! Returns x at minumum x */ - Tx minx() { + Tx minx() + { assert(!map.empty()); Tx x = 0; - for (auto &m : map) { + for (auto& m : map) { x = m.first; break; } @@ -243,11 +264,12 @@ template class Table2D { } /*! Returns average in interval */ - Ty avg(const std::vector &limits) { + Ty avg(const std::vector& limits) + { Ty avg = 0; int cnt = 0; assert(!map.empty()); - for (auto &m : map) { + for (auto& m : map) { if (m.first >= limits[0] && m.first <= limits[1]) { avg += m.second; ++cnt; @@ -261,10 +283,11 @@ template class Table2D { /** * @brief Convert table2D to vector of floats */ - std::vector hist2buf(int &size) { + std::vector hist2buf(int& size) + { std::vector sendBuf; assert(!map.empty()); - for (auto &m : map) { + for (auto& m : map) { sendBuf.push_back(m.first); sendBuf.push_back(m.second); } @@ -275,14 +298,15 @@ template class Table2D { /** * @brief Convert vector of floats to table2D */ - void buf2hist(std::vector &v) { + void buf2hist(std::vector& v) + { this->clear(); assert(!v.empty()); std::map> all; for (int i = 0; i < int(v.size()) - 1; i += 2) if (v.at(i + 1) != -1) all[v.at(i)] += v.at(i + 1); - for (auto &m : all) + for (auto& m : all) this->operator()(m.first) = m.second.avg(); } @@ -292,7 +316,8 @@ template class Table2D { * @todo Implement end bin compensation as in the save() * function when loading HISTOGRAMs */ - bool load(const std::string &filename) { + bool load(const std::string& filename) + { std::ifstream f(filename.c_str()); if (f) { map.clear(); @@ -316,12 +341,13 @@ template class Table2D { /** * @brief Convert table to matrix */ - Eigen::MatrixXd tableToMatrix() { + Eigen::MatrixXd tableToMatrix() + { assert(!this->map.empty() && "Map is empty!"); Eigen::MatrixXd table(2, map.size()); table.setZero(); int I = 0; - for (auto &m : this->map) { + for (auto& m : this->map) { table(0, I) = m.first; table(1, I) = m.second; I++; @@ -333,7 +359,8 @@ template class Table2D { /** * @brief Subtract two tables */ -template Table2D operator-(Table2D &a, Table2D &b) { +template Table2D operator-(Table2D& a, Table2D& b) +{ assert(a.tabletype == b.tabletype && "Table a and b needs to be of same type"); Table2D c(std::min(a.getResolution(), b.getResolution()), a.tabletype); auto a_map = a.getMap(); @@ -350,8 +377,8 @@ template Table2D operator-(Table2D &a, Tabl (--b_map.end())->second *= 2; // -//- } - for (auto &m1 : a_map) { - for (auto &m2 : b_map) { + for (auto& m1 : a_map) { + for (auto& m2 : b_map) { c(m1.first) = m1.second - m2.second; break; } @@ -373,7 +400,8 @@ template Table2D operator-(Table2D &a, Tabl /** * @brief Addition two tables */ -template Table2D operator+(Table2D &a, Table2D &b) { +template Table2D operator+(Table2D& a, Table2D& b) +{ assert(a.tabletype == b.tabletype && "Table a and b needs to be of same type"); Table2D c(std::min(a.getResolution(), b.getResolution()), a.tabletype); auto a_map = a.getMap(); @@ -390,10 +418,10 @@ template Table2D operator+(Table2D &a, Tabl (--b_map.end())->second *= 2; // -//- } - for (auto &m : a_map) { + for (auto& m : a_map) { c(m.first) += m.second; } - for (auto &m : b_map) { + for (auto& m : b_map) { c(m.first) += m.second; } diff --git a/src/aux/timers.h b/src/aux/timers.h index a9e4c86fa..edc3ce2a1 100644 --- a/src/aux/timers.h +++ b/src/aux/timers.h @@ -10,21 +10,30 @@ namespace Faunus { * `stop()` calls can be made multiple times. The result is * the fraction of total time, consumed in between start/stop calls. */ -template class TimeRelativeOfTotal { +template class TimeRelativeOfTotal +{ private: Tunit delta; std::chrono::steady_clock::time_point t0, tx; public: - TimeRelativeOfTotal() : delta(0) { t0 = std::chrono::steady_clock::now(); } + TimeRelativeOfTotal() + : delta(0) + { + t0 = std::chrono::steady_clock::now(); + } operator bool() const { return delta.count() != 0 ? true : false; } void start() { tx = std::chrono::steady_clock::now(); } - void stop() { delta += std::chrono::duration_cast(std::chrono::steady_clock::now() - tx); } + void stop() + { + delta += std::chrono::duration_cast(std::chrono::steady_clock::now() - tx); + } - double result() const { + double result() const + { auto now = std::chrono::steady_clock::now(); auto total = std::chrono::duration_cast(now - t0); return delta.count() / double(total.count()); @@ -46,13 +55,16 @@ template class TimeRelativeOfTotal * `stop()` takes an optional template parameter to specify * the time resolution. Default is milliseconds. */ -class Stopwatch { +class Stopwatch +{ using clock = std::chrono::high_resolution_clock; std::chrono::time_point starting_time, ending_time; public: inline Stopwatch() { start(); } + inline void start() { starting_time = clock::now(); } + inline void stop() { ending_time = clock::now(); } }; diff --git a/src/aux/typeerasure.h b/src/aux/typeerasure.h index 16f467f6b..fe785bc08 100644 --- a/src/aux/typeerasure.h +++ b/src/aux/typeerasure.h @@ -5,8 +5,8 @@ namespace Faunus { /** * @namespace Type Erasure * - * Class templates to assist implementing the type erasure pattern. The pairwise composition of concepts is also - * supported. + * Class templates to assist implementing the type erasure pattern. The pairwise composition of + * concepts is also supported. * * Type erasure pattern in general * https://www.modernescpp.com/index.php/c-core-guidelines-type-erasure-with-templates @@ -49,7 +49,8 @@ namespace Faunus { * }; * using PrintableType = TypeErasure::TypeErasure * using PrintableExportableType = - * TypeErasure::TypeErasure> + * TypeErasure::TypeErasure> * @endcode * * @brief Class templates to assist implementing the type erasure pattern. @@ -60,17 +61,25 @@ namespace TypeErasure { */ namespace Internal { /** - * @brief A universal storage of the concrete implementation with the move semantic in the constructor. + * @brief A universal storage of the concrete implementation with the move semantic in the + * constructor. * @tparam TImplementation a concrete implementation of the required interface */ -template class Holder { +template class Holder +{ public: using Implementation = TImplementation; - Holder(TImplementation implementation) : implementation(std::move(implementation)) {} + Holder(TImplementation implementation) + : implementation(std::move(implementation)) + { + } + virtual ~Holder() = default; - TImplementation &get() { return implementation; } - const TImplementation &get() const { return implementation; } + + TImplementation& get() { return implementation; } + + const TImplementation& get() const { return implementation; } private: TImplementation implementation; @@ -80,17 +89,24 @@ template class Holder { * @brief A universal storage of a reference to the concrete implementation. * @tparam TImplementation a concrete implementation of the required interface */ -template class ReferenceHolder { +template class ReferenceHolder +{ public: using Implementation = TImplementation; - ReferenceHolder(TImplementation &implementation) : implementation(implementation) {} + ReferenceHolder(TImplementation& implementation) + : implementation(implementation) + { + } + virtual ~ReferenceHolder() = default; - TImplementation &get() { return implementation; } - const TImplementation &get() const { return implementation; } + + TImplementation& get() { return implementation; } + + const TImplementation& get() const { return implementation; } private: - TImplementation &implementation; + TImplementation& implementation; }; /** @@ -99,18 +115,22 @@ template class ReferenceHolder { * @tparam TModel model template */ template class TModel> // template template sytax for TModel<...> -class Container { +class Container +{ public: using Concept = TConcept; /** - * @brief Passes the implementation instance to the storage as an r-value reference using the move semantic. + * @brief Passes the implementation instance to the storage as an r-value reference using the + * move semantic. * @tparam TImplementation a concrete implementation of the required interface * @param implementation an implementation as an r-value, e.g., std::move(implementation) */ template - Container(TImplementation &&implementation) - : self_ptr(std::make_shared>>(std::move(implementation))) {} + Container(TImplementation&& implementation) + : self_ptr(std::make_shared>>(std::move(implementation))) + { + } /** * @brief Passes the implementation instance to the storage as an l-value reference. @@ -118,15 +138,18 @@ class Container { * @param implementation an implementation as an l-value */ template - Container(TImplementation &implementation) - : self_ptr(std::make_shared>>(implementation)) {} + Container(TImplementation& implementation) + : self_ptr(std::make_shared>>(implementation)) + { + } - const Concept &get() const { return *self_ptr; } - Concept &get() { return *self_ptr; } + const Concept& get() const { return *self_ptr; } + + Concept& get() { return *self_ptr; } private: std::shared_ptr self_ptr; - //std::shared_ptr self_ptr; + // std::shared_ptr self_ptr; }; /** @@ -135,7 +158,8 @@ class Container { //! @brief Extracts the concept type from the specification. template using ConceptOf = typename TSpecification::Concept; //! @brief Extracts the model type (including its holder type) from the specification. -template using ModelOf = typename TSpecification::template Model; +template +using ModelOf = typename TSpecification::template Model; //! @brief Extracts the interface type from the specification. template using InterfaceOf = typename TSpecification::template Interface; @@ -150,7 +174,9 @@ using ContainerOf = Container -class TypeErasure : public Internal::InterfaceOf> { +class TypeErasure + : public Internal::InterfaceOf> +{ using Base = Internal::InterfaceOf>; public: @@ -163,22 +189,31 @@ class TypeErasure : public Internal::InterfaceOf struct MergeSpecifications { +template struct MergeSpecifications +{ struct Concept : public virtual Internal::ConceptOf, - public virtual Internal::ConceptOf {}; + public virtual Internal::ConceptOf + { + }; template - struct Model : public Internal::ModelOf>, - public virtual Concept { - using Base = Internal::ModelOf>; + struct Model + : public Internal::ModelOf>, + public virtual Concept + { + using Base = + Internal::ModelOf>; using Base::Base; // include parent constructor }; template struct Interface - : public Internal::InterfaceOf> { + : public Internal::InterfaceOf> + { - using Base = Internal::InterfaceOf>; + using Base = Internal::InterfaceOf>; using Base::Base; // include parent constructor }; }; diff --git a/src/auxiliary.cpp b/src/auxiliary.cpp index 91d61f9e6..ad1533d26 100644 --- a/src/auxiliary.cpp +++ b/src/auxiliary.cpp @@ -9,19 +9,19 @@ using doctest::Approx; TEST_SUITE_BEGIN("Auxiliary"); -TEST_CASE("[Faunus] for_each_pair") { +TEST_CASE("[Faunus] for_each_pair") +{ int x; std::vector a = {1, 2, 3}; - x = for_each_unique_pair( - a.begin(), a.end(), [](int i, int j) { return i * j; }, std::plus<>()); + x = for_each_unique_pair(a.begin(), a.end(), [](int i, int j) { return i * j; }, std::plus<>()); CHECK_EQ(x, 2 + 3 + 6); a.resize(1); - x = for_each_unique_pair( - a.begin(), a.end(), [](int i, int j) { return i * j; }, std::plus<>()); + x = for_each_unique_pair(a.begin(), a.end(), [](int i, int j) { return i * j; }, std::plus<>()); CHECK_EQ(x, 0); } -TEST_CASE("[Faunus] ordered_pair") { +TEST_CASE("[Faunus] ordered_pair") +{ ordered_pair a = {1, 2}, b = {2, 1}; CHECK(((a.first == 1 && a.second == 2))); CHECK(((b.first == 1 && b.second == 2))); @@ -31,12 +31,14 @@ TEST_CASE("[Faunus] ordered_pair") { CHECK(!a.contains(3)); } -TEST_CASE("[Faunus] Text manipulation") { +TEST_CASE("[Faunus] Text manipulation") +{ CHECK_EQ(joinToString(std::vector{1.0, -1.2, 0}), "1 -1.2 0"); CHECK_EQ(splitConvert("1 -1.2 0"), std::vector{1.0, -1.2, 0}); } -TEST_CASE("numeric_cast") { +TEST_CASE("numeric_cast") +{ CHECK_EQ(numeric_cast(2.2), 2); CHECK_EQ(numeric_cast(-2.2), -2); CHECK_EQ(numeric_cast(2.8), 3); @@ -50,10 +52,10 @@ TEST_CASE("numeric_cast") { CHECK_THROWS_AS(numeric_cast(short_underflow), std::overflow_error); CHECK_NOTHROW(numeric_cast(-1.0)); CHECK_THROWS_AS(numeric_cast(-1.0), std::overflow_error); - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wdiv-by-zero" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdiv-by-zero" CHECK_THROWS_AS(numeric_cast(1. / 0), std::overflow_error); - #pragma GCC diagnostic pop +#pragma GCC diagnostic pop } TEST_SUITE_END(); diff --git a/src/auxiliary.h b/src/auxiliary.h index 0aa32b8c2..1c3d3fe99 100644 --- a/src/auxiliary.h +++ b/src/auxiliary.h @@ -27,14 +27,16 @@ namespace Faunus { * @return number (integral type) * @throw std::overflow_error */ -template inline TOut numeric_cast(const TIn number) { +template inline TOut numeric_cast(const TIn number) +{ if (std::isfinite(number)) { // The number is finite ... if (number < std::nextafter(static_cast(std::numeric_limits::max()), 0) && number > std::nextafter(static_cast(std::numeric_limits::min()), 0)) { // ... and fits into the integral type range. // The nextafter function is used to mitigate possible rounding up or down. - return static_cast(number >= 0 ? number + TIn(0.5) : number - TIn(0.5)); // round before cast + return static_cast(number >= 0 ? number + TIn(0.5) + : number - TIn(0.5)); // round before cast } } // not-a-number, infinite, or outside the representable range @@ -49,8 +51,11 @@ template inline TOut numeric_cast( * @param f Function to apply to the pair * @param aggregator Function to aggregate the result from each pair. Default: `std::plus` */ -template -T for_each_unique_pair(Titer begin, Titer end, Tfunction f, Taggregate_function aggregator = std::plus()) { +template +T for_each_unique_pair(Titer begin, Titer end, Tfunction f, + Taggregate_function aggregator = std::plus()) +{ T x = T(); for (auto i = begin; i != end; ++i) { for (auto j = i; ++j != end;) { @@ -61,9 +66,13 @@ T for_each_unique_pair(Titer begin, Titer end, Tfunction f, Taggregate_function } /** @brief Erase from `target` range all values found in `values` range */ -template T erase_range(T target, const T& values) { +template T erase_range(T target, const T& values) +{ target.erase(std::remove_if(target.begin(), target.end(), - [&](auto i) { return std::find(values.begin(), values.end(), i) != values.end(); }), + [&](auto i) { + return std::find(values.begin(), values.end(), i) != + values.end(); + }), target.end()); return target; } @@ -76,10 +85,16 @@ template T erase_range(T target, const T& values) { * * @todo Add std::pair copy operator */ -template struct ordered_pair : public std::pair { +template struct ordered_pair : public std::pair +{ using base = std::pair; ordered_pair() = default; - ordered_pair(const T &a, const T &b) : base(std::minmax(a, b)) {} + + ordered_pair(const T& a, const T& b) + : base(std::minmax(a, b)) + { + } + bool contains(const T& value) const { return value == base::first || value == base::second; } }; @@ -100,7 +115,8 @@ template struct ordered_pair : public std::pair { * ~~~~ * */ -template Tint to_bin(T x, T dx = 1) { +template Tint to_bin(T x, T dx = 1) +{ return (x < 0) ? Tint(x / dx - 0.5) : Tint(x / dx + 0.5); } @@ -126,7 +142,8 @@ template Tint to_bin(T x, T dx * ~~~ * */ -template class Quantize { +template class Quantize +{ private: Tfloat xmin, dx, x; @@ -136,33 +153,42 @@ template class Quantize { * @param dx resolution * @param xmin minimum value if converting to integral type (binning) */ - Quantize(Tfloat dx, Tfloat xmin = 0) : xmin(xmin), dx(dx) {} + Quantize(Tfloat dx, Tfloat xmin = 0) + : xmin(xmin) + , dx(dx) + { + } /** @brief Assigment operator */ - Quantize &operator=(Tfloat val) { + Quantize& operator=(Tfloat val) + { assert(val >= xmin); if (val >= 0) { x = int(val / dx + 0.5) * dx; - } else { + } + else { x = int(val / dx - 0.5) * dx; } assert(x >= xmin); return *this; } - Quantize &frombin(unsigned int i) { + Quantize& frombin(unsigned int i) + { x = i * dx + xmin; return *this; } /** @brief Assignment with function operator */ - Quantize &operator()(Tfloat val) { + Quantize& operator()(Tfloat val) + { *this = val; return *this; } /** @brief Implicit convertion to integral (bin) or float (rounded) */ - template operator T() { + template operator T() + { if (std::is_integral::value) { return T((x - xmin) / dx + 0.5); } @@ -172,8 +198,8 @@ template class Quantize { template concept StringStreamable = requires(T a) { - {std::istringstream() >> a}; - {std::ostringstream() << a}; + { std::istringstream() >> a }; + { std::ostringstream() << a }; }; /** @@ -185,7 +211,8 @@ concept StringStreamable = requires(T a) { * * @returns std::vector of type T */ -template auto splitConvert(const std::string& words) { +template auto splitConvert(const std::string& words) +{ auto stream = std::istringstream(words); return std::vector(std::istream_iterator(stream), std::istream_iterator()); } // space separated string to vector of values @@ -196,7 +223,9 @@ template auto splitConvert(const std::string& words) { * @return String with space sepatated values */ template -std::string joinToString(const Range& values) requires StringStreamable> { +std::string joinToString(const Range& values) + requires StringStreamable> +{ std::ostringstream o; if (!values.empty()) { o << *values.begin(); @@ -205,51 +234,71 @@ std::string joinToString(const Range& values) requires StringStreamable struct BasePointerVector { +template struct BasePointerVector +{ using value_type = std::shared_ptr; std::vector vec; //!< Vector of shared pointers to base class auto begin() noexcept { return vec.begin(); } + auto begin() const noexcept { return vec.begin(); } + auto end() noexcept { return vec.end(); } + auto end() const noexcept { return vec.end(); } + auto empty() const noexcept { return vec.empty(); } + auto size() const noexcept { return vec.size(); } - auto &back() noexcept { return vec.back(); } - auto &back() const noexcept { return vec.back(); } - auto &front() noexcept { return vec.front(); } - auto &front() const noexcept { return vec.front(); } - auto &at(size_t n) { return vec.at(n); } - auto &at(size_t n) const { return vec.at(n); } - - template ::value>> - void emplace_back(Args &... args) { + + auto& back() noexcept { return vec.back(); } + + auto& back() const noexcept { return vec.back(); } + + auto& front() noexcept { return vec.front(); } + + auto& front() const noexcept { return vec.front(); } + + auto& at(size_t n) { return vec.at(n); } + + auto& at(size_t n) const { return vec.at(n); } + + template ::value>> + void emplace_back(Args&... args) + { vec.push_back(std::make_shared(args...)); } //!< Create an (derived) instance and append a pointer to it to the vector - template ::value>> - void emplace_back(const Args &... args) { + template ::value>> + void emplace_back(const Args&... args) + { vec.push_back(std::make_shared(args...)); } //!< Create an (derived) instance and append a pointer to it to the vector - // template ::value>> - // auto& push_back(std::shared_ptr arg) { + // template ::value>> auto& push_back(std::shared_ptr arg) { // vec.push_back(arg); // return vec.back(); // reference to element just added // } //!< Append a pointer to a (derived) instance to the vector - auto& push_back(value_type arg) { + auto& push_back(value_type arg) + { vec.push_back(arg); return vec.back(); // reference to element just added - } //!< Append a pointer to a (derived) instance to the vector + } //!< Append a pointer to a (derived) instance to the vector template ::value>> - auto &operator=(const BasePointerVector &d) { + auto& operator=(const BasePointerVector& d) + { vec.assign(d.vec.begin(), d.vec.end()); return *this; } //!< Allow assignment to a vector of ancestors - template ::value>> auto find() const { + template ::value>> + auto find() const + { BasePointerVector _v; for (auto& base : vec) { if (auto derived = std::dynamic_pointer_cast(base); derived) { @@ -260,7 +309,8 @@ template struct BasePointerVector { } //!< Pointer list to all matching type template ::value>> - std::shared_ptr findFirstOf() const { + std::shared_ptr findFirstOf() const + { for (auto& base : vec) { if (auto derived = std::dynamic_pointer_cast(base); derived) { return derived; @@ -270,28 +320,33 @@ template struct BasePointerVector { } template - friend void to_json(nlohmann::json &, const BasePointerVector &); //!< Allow serialization to JSON + friend void to_json(nlohmann::json&, + const BasePointerVector&); //!< Allow serialization to JSON }; //!< Helper class for storing vectors of base pointers -template void to_json(nlohmann::json &j, const BasePointerVector &b) { +template void to_json(nlohmann::json& j, const BasePointerVector& b) +{ using namespace std::string_literals; try { for (auto& shared_ptr : b.vec) { j.push_back(*shared_ptr); } - } catch (const std::exception &e) { + } + catch (const std::exception& e) { throw std::runtime_error("error converting to json: "s + e.what()); } } -template void from_json(const nlohmann::json &j, BasePointerVector &b) { +template void from_json(const nlohmann::json& j, BasePointerVector& b) +{ using namespace std::string_literals; try { for (const auto& it : j) { std::shared_ptr ptr = it; b.push_back(ptr); } - } catch (const std::exception &e) { + } + catch (const std::exception& e) { throw std::runtime_error("error converting from json: "s + e.what()); } } diff --git a/src/average.cpp b/src/average.cpp index e255ca383..a600127e4 100644 --- a/src/average.cpp +++ b/src/average.cpp @@ -3,7 +3,8 @@ namespace Faunus { -TEST_CASE("[Faunus] Average") { +TEST_CASE("[Faunus] Average") +{ Faunus::Average a; a += 1.0; a += 2.0; @@ -25,7 +26,8 @@ TEST_CASE("[Faunus] Average") { b = 1.0; // assign from double CHECK_EQ(b.size(), 1); - SUBCASE("overflow") { + SUBCASE("overflow") + { Average a; const auto max_number_of_samples = std::numeric_limits::max(); for (std::uint8_t i = 0; i < max_number_of_samples; i++) { @@ -36,7 +38,8 @@ TEST_CASE("[Faunus] Average") { } } -TEST_CASE("[Faunus] AverageStd") { +TEST_CASE("[Faunus] AverageStd") +{ Faunus::AverageStdev a; a += 1.0; a += 2.0; @@ -59,7 +62,8 @@ TEST_CASE("[Faunus] AverageStd") { CHECK_EQ(b.size(), 1); } -TEST_CASE("[Faunus] AverageObj") { +TEST_CASE("[Faunus] AverageObj") +{ using doctest::Approx; Faunus::AverageObj a; a += 1.0; @@ -67,13 +71,18 @@ TEST_CASE("[Faunus] AverageObj") { CHECK_EQ(a.avg(), Approx(1.5)); CHECK_EQ(a, Approx(1.5)); // implicit conversion to double - struct MyClass { + struct MyClass + { double x; - MyClass& operator+=(const MyClass& other) { + + MyClass& operator+=(const MyClass& other) + { x += other.x; return *this; } // required - MyClass operator*(double value) const { + + MyClass operator*(double value) const + { MyClass scaled; scaled.x = x * value; return scaled; diff --git a/src/average.h b/src/average.h index 05cac6bec..2bcab6c2d 100644 --- a/src/average.h +++ b/src/average.h @@ -14,26 +14,35 @@ namespace Faunus { * @brief Class to collect averages * @todo replace static assert w. concept in c++20 */ -template -class Average { +template +class Average +{ protected: counter_type number_of_samples = 0; //!< number of values in average value_type value_sum = 0.0; //!< Sum of all recorded values public: - void clear() { *this = Average(); } //!< Clear all data - [[nodiscard]] bool empty() const; //!< True if empty - auto size() const { return number_of_samples; } //!< Number of samples + void clear() { *this = Average(); } //!< Clear all data + + [[nodiscard]] bool empty() const; //!< True if empty + + auto size() const { return number_of_samples; } //!< Number of samples + auto avg() const { return value_sum / static_cast(number_of_samples); } //!< Average - explicit operator value_type() const { return avg(); } //!< Static cast operator - bool operator<(const Average& other) const { return avg() < other.avg(); } //!< Compare means - explicit operator bool() const { return number_of_samples > 0; } //!< Check if not empty + + explicit operator value_type() const { return avg(); } //!< Static cast operator + + bool operator<(const Average& other) const { return avg() < other.avg(); } //!< Compare means + + explicit operator bool() const { return number_of_samples > 0; } //!< Check if not empty /** * @brief Add value to average * @param value Value to add * @throw If overflow in either the counter, or sum of squared values */ - void add(const value_type value) { + void add(const value_type value) + { if (number_of_samples == std::numeric_limits::max()) { throw std::overflow_error("max. number of samples reached"); } @@ -46,15 +55,20 @@ class Average { * @param value Value to add * @throw If overflow in either the counter, or sum of squared values */ - Average& operator+=(const value_type value) { + Average& operator+=(const value_type value) + { add(value); return *this; } - bool operator==(const Average& other) const { return (size() == other.size()) && (value_sum == other.value_sum); } + bool operator==(const Average& other) const + { + return (size() == other.size()) && (value_sum == other.value_sum); + } /** Clear and assign a new value */ - Average& operator=(const value_type value) { + Average& operator=(const value_type value) + { clear(); add(value); return *this; @@ -66,8 +80,10 @@ class Average { * @return Merged average * @throw if numeric overflow */ - auto operator+(const Average& other) const { - if (std::numeric_limits::max() - other.number_of_samples < number_of_samples) { + auto operator+(const Average& other) const + { + if (std::numeric_limits::max() - other.number_of_samples < + number_of_samples) { throw std::overflow_error("maximum number of samples reached"); } if (std::numeric_limits::max() - other.value_sum < value_sum) { @@ -79,27 +95,38 @@ class Average { return summed_average; } - friend std::ostream& operator<<(std::ostream& stream, const Average& average) { + friend std::ostream& operator<<(std::ostream& stream, const Average& average) + { stream << average.number_of_samples << " " << average.value_sum; return stream; } // serialize to stream - Average& operator<<(std::istream& stream) { + Average& operator<<(std::istream& stream) + { stream >> number_of_samples >> value_sum; return *this; } // de-serialize from stream - template void serialize(Archive& archive) { archive(value_sum, number_of_samples); } + template void serialize(Archive& archive) + { + archive(value_sum, number_of_samples); + } }; + template -bool Average::empty() const { return number_of_samples == 0; } +bool Average::empty() const +{ + return number_of_samples == 0; +} /** * @brief Class to collect averages and standard deviation * @todo inherit from `Average` */ -template -class AverageStdev : public Average { +template +class AverageStdev : public Average +{ private: value_type squared_value_sum = 0.0; //!< Square sum of all recorded values using base = Average; @@ -109,23 +136,29 @@ class AverageStdev : public Average { public: using base::avg; using base::empty; + void clear() { *this = AverageStdev(); } //!< Clear all data - auto rms() const { + + auto rms() const + { return std::sqrt(squared_value_sum / static_cast(number_of_samples)); } //!< Root-mean-square - auto rsd() const { + auto rsd() const + { return empty() ? 0.0 : stdev() / avg(); } //!< Relative standard deviation or coefficient of variation - value_type stdev() const { + value_type stdev() const + { if (empty()) { return 0.0; } const auto mean = avg(); - return std::sqrt( - (squared_value_sum + static_cast(number_of_samples) * mean * mean - 2.0 * value_sum * mean) / - static_cast(number_of_samples - 1)); + return std::sqrt((squared_value_sum + + static_cast(number_of_samples) * mean * mean - + 2.0 * value_sum * mean) / + static_cast(number_of_samples - 1)); } //!< Standard deviation /** @@ -133,7 +166,8 @@ class AverageStdev : public Average { * @param value Value to add * @throw If counter overflow */ - void add(const value_type value) { + void add(const value_type value) + { base::add(value); squared_value_sum += value * value; } @@ -143,34 +177,41 @@ class AverageStdev : public Average { * @param value Value to add * @throw If overflow in either the counter, or sum of squared values */ - AverageStdev& operator+=(const value_type value) { + AverageStdev& operator+=(const value_type value) + { add(value); return *this; } - bool operator==(const AverageStdev& other) const { + bool operator==(const AverageStdev& other) const + { return (this->size() == other.size()) && (value_sum == other.value_sum) && (squared_value_sum == other.squared_value_sum); } /** Clear and assign a new value */ - AverageStdev& operator=(const value_type value) { + AverageStdev& operator=(const value_type value) + { clear(); add(value); return *this; } - friend std::ostream& operator<<(std::ostream& stream, const AverageStdev& average) { - stream << average.number_of_samples << " " << average.value_sum << " " << average.squared_value_sum; + friend std::ostream& operator<<(std::ostream& stream, const AverageStdev& average) + { + stream << average.number_of_samples << " " << average.value_sum << " " + << average.squared_value_sum; return stream; } // serialize to stream - AverageStdev& operator<<(std::istream& stream) { + AverageStdev& operator<<(std::istream& stream) + { stream >> number_of_samples >> value_sum >> squared_value_sum; return *this; } // de-serialize from stream - template void serialize(AverageStdev& archive) { + template void serialize(AverageStdev& archive) + { archive(value_sum, squared_value_sum, number_of_samples); } }; @@ -195,8 +236,10 @@ class AverageStdev : public Average { * @todo Bundle current vectors into single vector of struct; unittests; check correctness * @warning Under construction and untested */ -template -class Decorrelation { +template +class Decorrelation +{ private: using Statistic = AverageStdev; unsigned int nsamples = 0; @@ -205,7 +248,8 @@ class Decorrelation { std::vector waiting_sample_exists = {false}; public: - Decorrelation& add(value_type new_sample) { + Decorrelation& add(value_type new_sample) + { nsamples++; if (nsamples >= static_cast(std::pow(2, blocked_statistics.size()))) { blocked_statistics.template emplace_back(); @@ -221,7 +265,8 @@ class Decorrelation { carry = new_sample; blocked_statistics.at(i) += new_sample; waiting_sample_exists.at(i) = false; - } else { + } + else { waiting_sample_exists.at(i) = true; waiting_sample.at(i) = carry; break; @@ -231,52 +276,63 @@ class Decorrelation { } auto size() const { return nsamples; } + [[nodiscard]] bool empty() const { return nsamples == 0; } - [[maybe_unused]] void to_disk(const std::string& filename) const { + [[maybe_unused]] void to_disk(const std::string& filename) const + { if (auto stream = std::ofstream(filename); stream) { for (size_t i = 0; i < blocked_statistics.size(); i++) { - stream << fmt::format("{} {} {}\n", i, blocked_statistics[i].avg(), blocked_statistics[i].stdev()); + stream << fmt::format("{} {} {}\n", i, blocked_statistics[i].avg(), + blocked_statistics[i].stdev()); } } } - void to_json(nlohmann::json& j) const { + void to_json(nlohmann::json& j) const + { j = nlohmann::json::array(); for (size_t i = 0; i < blocked_statistics.size(); i++) { - j.push_back(nlohmann::json::array({blocked_statistics[i].avg(), blocked_statistics[i].stdev()})); + j.push_back(nlohmann::json::array( + {blocked_statistics[i].avg(), blocked_statistics[i].stdev()})); } } }; /** Requirements for types used with `AverageObj` */ -template concept Averageable = requires(T a) { +template +concept Averageable = requires(T a) { a * 1.0; // please implement `T operator*(double) const` // a* a; // please implement `T operator*(const T&) const` - a += a; // please implement `T& operator+=(const T&)` + a += a; // please implement `T& operator+=(const T&)` }; - /** - * @brief Simple class to average data contained in objects - * @tparam T Type to average - * @tparam int_t Unsigned interger type - * - * It is required that `T` has the following operator overloads: - * - `T operator*(double) const` - * - `T operator*(const T&) const` - * - `T& operator+=(const &T)` - */ -template class AverageObj { +/** + * @brief Simple class to average data contained in objects + * @tparam T Type to average + * @tparam int_t Unsigned interger type + * + * It is required that `T` has the following operator overloads: + * - `T operator*(double) const` + * - `T operator*(const T&) const` + * - `T& operator+=(const &T)` + */ +template class AverageObj +{ protected: counter_type number_of_samples = 0; T sum; // make sure constructors zero this! public: - AverageObj() : sum(T()){}; //!< Construct from empty object + AverageObj() + : sum(T()) {}; //!< Construct from empty object - explicit AverageObj(const T& value) : number_of_samples(1), sum(value){}; + explicit AverageObj(const T& value) + : number_of_samples(1) + , sum(value) {}; //! Add to average - AverageObj& operator+=(const T& value) { + AverageObj& operator+=(const T& value) + { if (number_of_samples == std::numeric_limits::max()) { throw std::overflow_error("maximum samples reached"); } @@ -284,8 +340,10 @@ template class Averag ++number_of_samples; return *this; } + //! Calculate average - T avg() const { + T avg() const + { if (number_of_samples > 0) { return sum * (1.0 / static_cast(number_of_samples)); } @@ -313,7 +371,9 @@ template class Averag * requires a pow(T, double) function for squaring and taking the square-root. * How could this best be implemented and maintain compatibility when T=double? */ -template class AverageObjStdev : public AverageObj { +template +class AverageObjStdev : public AverageObj +{ private: T sum_squared; // make sure constructors zero this! using AverageObj::avg; @@ -321,19 +381,24 @@ template class AverageObjStdev using AverageObj::number_of_samples; public: - AverageObjStdev() : sum_squared(T()){}; //!< Construct from empty object + AverageObjStdev() + : sum_squared(T()) {}; //!< Construct from empty object - explicit AverageObjStdev(const T& value) : AverageObj(value), sum_squared(value * value){}; + explicit AverageObjStdev(const T& value) + : AverageObj(value) + , sum_squared(value * value) {}; //! Add to average - AverageObjStdev& operator+=(const T& value) { + AverageObjStdev& operator+=(const T& value) + { AverageObj::operator+=()(value); sum_squared += std::pow(value, 2); return *this; } //! Root-mean-square - T rms() const { + T rms() const + { if (number_of_samples == 0) { return T(); } @@ -341,13 +406,15 @@ template class AverageObjStdev } //! Standard deviation - T stdev() const { + T stdev() const + { if (number_of_samples == 0) { return T(); } const auto N = static_cast(number_of_samples); const auto mean = avg(); - return std::pow((sum_squared + N * mean * mean - 2.0 * sum * mean) * (1.0 / (N - 1.0)), 0.5); + return std::pow((sum_squared + N * mean * mean - 2.0 * sum * mean) * (1.0 / (N - 1.0)), + 0.5); } //!< Clear all data diff --git a/src/bonds.cpp b/src/bonds.cpp index 3d9483f37..9897bcb78 100644 --- a/src/bonds.cpp +++ b/src/bonds.cpp @@ -5,16 +5,21 @@ namespace Faunus::pairpotential { -void to_json(Faunus::json& j, const std::shared_ptr& bond) { to_json(j, *bond); } +void to_json(Faunus::json& j, const std::shared_ptr& bond) +{ + to_json(j, *bond); +} -void to_json(Faunus::json& j, const BondData& bond) { +void to_json(Faunus::json& j, const BondData& bond) +{ json val; bond.to_json(val); val["index"] = bond.indices; j = {{bond.type(), val}}; } -void from_json(const json& j, std::shared_ptr& bond) { +void from_json(const json& j, std::shared_ptr& bond) +{ try { const auto& [bondtype, parameters] = jsonSingleItem(j); const BondData::Variant variant = json(bondtype); @@ -50,42 +55,68 @@ void from_json(const json& j, std::shared_ptr& bond) { usageTip.pick(bondtype); throw ConfigurationError("exactly {} indices required", bond->numindex()); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("{} -> {}", bondtype, e.what()); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("bond making: {}", e.what()).attachJson(j); } } -void BondData::shiftIndices(const int offset) { +void BondData::shiftIndices(const int offset) +{ for (auto& i : indices) { i += offset; } } -bool BondData::hasEnergyFunction() const { return energyFunc != nullptr; } +bool BondData::hasEnergyFunction() const +{ + return energyFunc != nullptr; +} -bool BondData::hasForceFunction() const { return forceFunc != nullptr; } +bool BondData::hasForceFunction() const +{ + return forceFunc != nullptr; +} -BondData::BondData(const std::vector& indices) : indices(indices) {} +BondData::BondData(const std::vector& indices) + : indices(indices) +{ +} -HarmonicBond::HarmonicBond(double force_constant, double equilibrium_distance, const std::vector& indices) - : StretchData(indices), half_force_constant(force_constant / 2.0), equilibrium_distance(equilibrium_distance) {} +HarmonicBond::HarmonicBond(double force_constant, double equilibrium_distance, + const std::vector& indices) + : StretchData(indices) + , half_force_constant(force_constant / 2.0) + , equilibrium_distance(equilibrium_distance) +{ +} -void HarmonicBond::from_json(const Faunus::json& j) { - half_force_constant = j.at("k").get() * 1.0_kJmol / std::pow(1.0_angstrom, 2) / 2.0; // k - equilibrium_distance = j.at("req").get() * 1.0_angstrom; // req +void HarmonicBond::from_json(const Faunus::json& j) +{ + half_force_constant = + j.at("k").get() * 1.0_kJmol / std::pow(1.0_angstrom, 2) / 2.0; // k + equilibrium_distance = j.at("req").get() * 1.0_angstrom; // req } -void HarmonicBond::to_json(Faunus::json& j) const { +void HarmonicBond::to_json(Faunus::json& j) const +{ j = {{"k", 2.0 * half_force_constant / 1.0_kJmol * 1.0_angstrom * 1.0_angstrom}, {"req", equilibrium_distance / 1.0_angstrom}}; } -std::shared_ptr HarmonicBond::clone() const { return std::make_shared(*this); } +std::shared_ptr HarmonicBond::clone() const +{ + return std::make_shared(*this); +} -BondData::Variant HarmonicBond::type() const { return BondData::Variant::HARMONIC; } +BondData::Variant HarmonicBond::type() const +{ + return BondData::Variant::HARMONIC; +} /** * @param particles Particle vector to all particles in the system @@ -94,11 +125,13 @@ BondData::Variant HarmonicBond::type() const { return BondData::Variant::HARMONI * for calculating the potential energy and the forces on the * participating atoms */ -void HarmonicBond::setEnergyFunction(const ParticleVector& particles) { +void HarmonicBond::setEnergyFunction(const ParticleVector& particles) +{ energyFunc = [&](Geometry::DistanceFunction calculateDistance) -> double { const auto& particle1 = particles[indices[0]]; const auto& particle2 = particles[indices[1]]; - const auto distance = equilibrium_distance - calculateDistance(particle1.pos, particle2.pos).norm(); + const auto distance = + equilibrium_distance - calculateDistance(particle1.pos, particle2.pos).norm(); return half_force_constant * distance * distance; // kT/Γ… }; forceFunc = [&](Geometry::DistanceFunction calculateDistance) -> std::vector { @@ -106,45 +139,61 @@ void HarmonicBond::setEnergyFunction(const ParticleVector& particles) { const auto& particle2 = particles[indices[1]]; const auto distance_vec = calculateDistance(particle1.pos, particle2.pos); const auto distance = distance_vec.norm(); // distance between particles - auto force = 2.0 * half_force_constant * (equilibrium_distance - distance) * distance_vec / distance; + auto force = + 2.0 * half_force_constant * (equilibrium_distance - distance) * distance_vec / distance; return {{indices[0], force}, {indices[1], -force}}; }; } FENEBond::FENEBond(double force_constant, double max_distance, const std::vector& indices) - : StretchData(indices), half_force_constant(0.5 * force_constant), - max_squared_distance(max_distance * max_distance) {} + : StretchData(indices) + , half_force_constant(0.5 * force_constant) + , max_squared_distance(max_distance * max_distance) +{ +} -std::shared_ptr FENEBond::clone() const { return std::make_shared(*this); } +std::shared_ptr FENEBond::clone() const +{ + return std::make_shared(*this); +} -BondData::Variant FENEBond::type() const { return BondData::Variant::FENE; } +BondData::Variant FENEBond::type() const +{ + return BondData::Variant::FENE; +} -void FENEBond::from_json(const Faunus::json& j) { +void FENEBond::from_json(const Faunus::json& j) +{ half_force_constant = 0.5 * j.at("k").get() * 1.0_kJmol / (1.0_angstrom * 1.0_angstrom); max_squared_distance = std::pow(j.at("rmax").get() * 1.0_angstrom, 2); } -void FENEBond::to_json(Faunus::json& j) const { +void FENEBond::to_json(Faunus::json& j) const +{ j = {{"k", 2.0 * half_force_constant / (1.0_kJmol / std::pow(1.0_angstrom, 2))}, {"rmax", std::sqrt(max_squared_distance) / 1.0_angstrom}}; } -void FENEBond::setEnergyFunction(const ParticleVector& particles) { +void FENEBond::setEnergyFunction(const ParticleVector& particles) +{ energyFunc = [&](Geometry::DistanceFunction calcDistance) { - const auto squared_distance = calcDistance(particles[indices[0]].pos, particles[indices[1]].pos).squaredNorm(); + const auto squared_distance = + calcDistance(particles[indices[0]].pos, particles[indices[1]].pos).squaredNorm(); if (squared_distance >= max_squared_distance) { return pc::infty; } - return -half_force_constant * max_squared_distance * std::log(1.0 - squared_distance / max_squared_distance); + return -half_force_constant * max_squared_distance * + std::log(1.0 - squared_distance / max_squared_distance); }; forceFunc = [&](Geometry::DistanceFunction distance) -> std::vector { const Point ba = distance(particles[indices[0]].pos, particles[indices[1]].pos); // b->a const auto squared_distance = ba.squaredNorm(); if (squared_distance >= max_squared_distance) { - throw std::runtime_error("Fene potential: Force undefined for distances greater than rmax."); + throw std::runtime_error( + "Fene potential: Force undefined for distances greater than rmax."); }; - const auto force_magnitude = - -2.0 * half_force_constant * ba.norm() / (1.0 - squared_distance / max_squared_distance); + const auto force_magnitude = -2.0 * half_force_constant * ba.norm() / + (1.0 - squared_distance / max_squared_distance); Point force0 = force_magnitude * ba.normalized(); Point force1 = -force0; return {{indices[0], force0}, {indices[1], force1}}; @@ -153,31 +202,46 @@ void FENEBond::setEnergyFunction(const ParticleVector& particles) { FENEWCABond::FENEWCABond(double force_constant, double max_distance, double epsilon, double sigma, const std::vector& indices) - : StretchData(indices), half_force_constant(force_constant / 2.0), - max_distance_squared(max_distance * max_distance), epsilon(epsilon), sigma_squared(sigma * sigma) {} + : StretchData(indices) + , half_force_constant(force_constant / 2.0) + , max_distance_squared(max_distance * max_distance) + , epsilon(epsilon) + , sigma_squared(sigma * sigma) +{ +} -std::shared_ptr FENEWCABond::clone() const { return std::make_shared(*this); } +std::shared_ptr FENEWCABond::clone() const +{ + return std::make_shared(*this); +} -BondData::Variant FENEWCABond::type() const { return BondData::Variant::FENEWCA; } +BondData::Variant FENEWCABond::type() const +{ + return BondData::Variant::FENEWCA; +} -void FENEWCABond::from_json(const Faunus::json& j) { +void FENEWCABond::from_json(const Faunus::json& j) +{ half_force_constant = j.at("k").get() * 1.0_kJmol / std::pow(1.0_angstrom, 2) / 2.0; max_distance_squared = std::pow(j.at("rmax").get() * 1.0_angstrom, 2); epsilon = j.at("eps").get() * 1.0_kJmol; sigma_squared = std::pow(j.at("sigma").get() * 1.0_angstrom, 2); } -void FENEWCABond::to_json(Faunus::json& j) const { +void FENEWCABond::to_json(Faunus::json& j) const +{ j = {{"k", 2.0 * half_force_constant / (1.0_kJmol / std::pow(1.0_angstrom, 2))}, {"rmax", std::sqrt(max_distance_squared) / 1.0_angstrom}, {"eps", epsilon / 1.0_kJmol}, {"sigma", std::sqrt(sigma_squared) / 1.0_angstrom}}; } -void FENEWCABond::setEnergyFunction(const ParticleVector& particles) { +void FENEWCABond::setEnergyFunction(const ParticleVector& particles) +{ energyFunc = [&](Geometry::DistanceFunction calculateDistance) { double wca = 0.0; - constexpr auto two_to_the_power_of_two_sixths = 1.01944064370214482816981563263103378007648819; // 2^((1/6)^2) + constexpr auto two_to_the_power_of_two_sixths = + 1.01944064370214482816981563263103378007648819; // 2^((1/6)^2) const auto squared_distance = calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos).squaredNorm(); if (squared_distance <= sigma_squared * two_to_the_power_of_two_sixths) { @@ -188,52 +252,74 @@ void FENEWCABond::setEnergyFunction(const ParticleVector& particles) { if (squared_distance > max_distance_squared) { return pc::infty; } - return -half_force_constant * max_distance_squared * std::log(1.0 - squared_distance / max_distance_squared) + + return -half_force_constant * max_distance_squared * + std::log(1.0 - squared_distance / max_distance_squared) + wca; }; forceFunc = [&](Geometry::DistanceFunction calculateDistance) -> std::vector { - const Point ba = calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos); // b->a + const Point ba = + calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos); // b->a const auto distance = ba.norm(); const auto squared_distance = distance * distance; if (squared_distance >= max_distance_squared) { - throw std::runtime_error("Fene+WCA potential: Force undefined for distances greater than rmax."); + throw std::runtime_error( + "Fene+WCA potential: Force undefined for distances greater than rmax."); } double wca_force = 0.0; - constexpr auto two_to_the_power_of_two_sixths = 1.01944064370214482816981563263103378007648819; // 2^((1/6)^2) + constexpr auto two_to_the_power_of_two_sixths = + 1.01944064370214482816981563263103378007648819; // 2^((1/6)^2) if (squared_distance <= sigma_squared * two_to_the_power_of_two_sixths) { double sigma6 = sigma_squared / squared_distance; sigma6 = sigma6 * sigma6 * sigma6; wca_force = -24.0 * epsilon * (2.0 * sigma6 * sigma6 - sigma6) / distance; } - const auto force_magnitude = - -(2.0 * half_force_constant * distance / (1.0 - squared_distance / max_distance_squared) + wca_force); + const auto force_magnitude = -(2.0 * half_force_constant * distance / + (1.0 - squared_distance / max_distance_squared) + + wca_force); Point force0 = force_magnitude * ba.normalized(); Point force1 = -force0; return {{indices[0], force0}, {indices[1], force1}}; }; } -void HarmonicTorsion::from_json(const Faunus::json& j) { +void HarmonicTorsion::from_json(const Faunus::json& j) +{ half_force_constant = j.at("k").get() * 1.0_kJmol / std::pow(1.0_rad, 2) / 2.0; equilibrium_angle = j.at("aeq").get() * 1.0_deg; } -void HarmonicTorsion::to_json(Faunus::json& j) const { - j = {{"k", 2 * half_force_constant / (1.0_kJmol / std::pow(1.0_rad, 2))}, {"aeq", equilibrium_angle / 1.0_deg}}; +void HarmonicTorsion::to_json(Faunus::json& j) const +{ + j = {{"k", 2 * half_force_constant / (1.0_kJmol / std::pow(1.0_rad, 2))}, + {"aeq", equilibrium_angle / 1.0_deg}}; roundJSON(j, 6); } -HarmonicTorsion::HarmonicTorsion(double force_constant, double equilibrium_angle, const std::vector& indices) - : TorsionData(indices), half_force_constant(force_constant / 2.0), equilibrium_angle(equilibrium_angle) {} +HarmonicTorsion::HarmonicTorsion(double force_constant, double equilibrium_angle, + const std::vector& indices) + : TorsionData(indices) + , half_force_constant(force_constant / 2.0) + , equilibrium_angle(equilibrium_angle) +{ +} -BondData::Variant HarmonicTorsion::type() const { return BondData::Variant::HARMONIC_TORSION; } +BondData::Variant HarmonicTorsion::type() const +{ + return BondData::Variant::HARMONIC_TORSION; +} -std::shared_ptr HarmonicTorsion::clone() const { return std::make_shared(*this); } +std::shared_ptr HarmonicTorsion::clone() const +{ + return std::make_shared(*this); +} -void HarmonicTorsion::setEnergyFunction(const ParticleVector& particles) { +void HarmonicTorsion::setEnergyFunction(const ParticleVector& particles) +{ energyFunc = [&](Geometry::DistanceFunction calculateDistance) -> double { - const auto vec1 = calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos).normalized(); - const auto vec2 = calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos).normalized(); + const auto vec1 = + calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos).normalized(); + const auto vec2 = + calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos).normalized(); const auto delta_angle = equilibrium_angle - std::acos(vec1.dot(vec2)); return half_force_constant * delta_angle * delta_angle; }; @@ -247,16 +333,21 @@ void HarmonicTorsion::setEnergyFunction(const ParticleVector& particles) { vec1 *= inverse_norm1; vec2 *= inverse_norm2; const auto cosine_angle = vec1.dot(vec2); - const auto inverse_sine_angle = 1.0 / std::sqrt(std::fabs(1.0 - cosine_angle * cosine_angle)); + const auto inverse_sine_angle = + 1.0 / std::sqrt(std::fabs(1.0 - cosine_angle * cosine_angle)); const auto angle = std::acos(cosine_angle); - const auto prefactor = 2.0 * half_force_constant * (angle - equilibrium_angle) * inverse_sine_angle; + const auto prefactor = + 2.0 * half_force_constant * (angle - equilibrium_angle) * inverse_sine_angle; Point force0 = prefactor * inverse_norm1 * (vec2 - vec1 * cosine_angle); Point force2 = prefactor * inverse_norm2 * (vec1 - vec2 * cosine_angle); Point force1 = -(force0 + force2); // no net force return {{indices[0], force0}, {indices[1], force1}, {indices[2], force2}}; - } else { // @todo which is faster? - const Point ba = calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos); // b->a - const Point bc = calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos); // b->c + } + else { // @todo which is faster? + const Point ba = + calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos); // b->a + const Point bc = + calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos); // b->c const auto inverse_norm_ba = 1.0 / ba.norm(); const auto inverse_norm_bc = 1.0 / bc.norm(); const auto angle = std::acos(ba.dot(bc) * inverse_norm_ba * inverse_norm_bc); @@ -270,27 +361,43 @@ void HarmonicTorsion::setEnergyFunction(const ParticleVector& particles) { }; } -void GromosTorsion::from_json(const Faunus::json& j) { +void GromosTorsion::from_json(const Faunus::json& j) +{ half_force_constant = 0.5 * j.at("k").get() * 1.0_kJmol; cosine_equilibrium_angle = std::cos(j.at("aeq").get() * 1.0_deg); } -void GromosTorsion::to_json(Faunus::json& j) const { - j = {{"k", 2.0 * half_force_constant / 1.0_kJmol}, {"aeq", std::acos(cosine_equilibrium_angle) / 1.0_deg}}; +void GromosTorsion::to_json(Faunus::json& j) const +{ + j = {{"k", 2.0 * half_force_constant / 1.0_kJmol}, + {"aeq", std::acos(cosine_equilibrium_angle) / 1.0_deg}}; } -GromosTorsion::GromosTorsion(double force_constant, double cosine_equilibrium_angle, const std::vector& indices) - : TorsionData(indices), half_force_constant(0.5 * force_constant), - cosine_equilibrium_angle(cosine_equilibrium_angle) {} +GromosTorsion::GromosTorsion(double force_constant, double cosine_equilibrium_angle, + const std::vector& indices) + : TorsionData(indices) + , half_force_constant(0.5 * force_constant) + , cosine_equilibrium_angle(cosine_equilibrium_angle) +{ +} -BondData::Variant GromosTorsion::type() const { return BondData::Variant::GROMOS_TORSION; } +BondData::Variant GromosTorsion::type() const +{ + return BondData::Variant::GROMOS_TORSION; +} -std::shared_ptr GromosTorsion::clone() const { return std::make_shared(*this); } +std::shared_ptr GromosTorsion::clone() const +{ + return std::make_shared(*this); +} -void GromosTorsion::setEnergyFunction(const ParticleVector& particles) { +void GromosTorsion::setEnergyFunction(const ParticleVector& particles) +{ energyFunc = [&](Geometry::DistanceFunction calculateDistance) { - auto vec1 = calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos).normalized(); - auto vec2 = calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos).normalized(); + auto vec1 = + calculateDistance(particles[indices[0]].pos, particles[indices[1]].pos).normalized(); + auto vec2 = + calculateDistance(particles[indices[2]].pos, particles[indices[1]].pos).normalized(); const auto cosine_angle_displacement = cosine_equilibrium_angle - vec1.dot(vec2); return half_force_constant * cosine_angle_displacement * cosine_angle_displacement; }; @@ -301,8 +408,8 @@ void GromosTorsion::setEnergyFunction(const ParticleVector& particles) { const auto inverse_norm_bc = 1.0 / bc.norm(); const auto cosine_angle = ba.dot(bc) * inverse_norm_ba * inverse_norm_bc; const auto angle = std::acos(cosine_angle); - const auto force_magnitude = - -2.0 * half_force_constant * std::sin(angle) * (cosine_angle - cosine_equilibrium_angle); + const auto force_magnitude = -2.0 * half_force_constant * std::sin(angle) * + (cosine_angle - cosine_equilibrium_angle); const Point plane_abc = ba.cross(bc).eval(); Point force0 = force_magnitude * inverse_norm_ba * ba.cross(plane_abc).normalized(); Point force2 = force_magnitude * inverse_norm_bc * -bc.cross(plane_abc).normalized(); @@ -311,27 +418,44 @@ void GromosTorsion::setEnergyFunction(const ParticleVector& particles) { }; } -int PeriodicDihedral::numindex() const { return 4; } +int PeriodicDihedral::numindex() const +{ + return 4; +} -std::shared_ptr PeriodicDihedral::clone() const { return std::make_shared(*this); } +std::shared_ptr PeriodicDihedral::clone() const +{ + return std::make_shared(*this); +} -void PeriodicDihedral::from_json(const Faunus::json& j) { +void PeriodicDihedral::from_json(const Faunus::json& j) +{ force_constant = j.at("k").get() * 1.0_kJmol; periodicity = j.at("n").get(); phase_angle = j.at("phi").get() * 1.0_deg; } -void PeriodicDihedral::to_json(Faunus::json& j) const { +void PeriodicDihedral::to_json(Faunus::json& j) const +{ j = {{"k", force_constant / 1.0_kJmol}, {"n", periodicity}, {"phi", phase_angle / 1.0_deg}}; } PeriodicDihedral::PeriodicDihedral(double force_constant, double phase_angle, double periodicity, const std::vector& indices) - : BondData(indices), force_constant(force_constant), phase_angle(phase_angle), periodicity(periodicity) {} + : BondData(indices) + , force_constant(force_constant) + , phase_angle(phase_angle) + , periodicity(periodicity) +{ +} -BondData::Variant PeriodicDihedral::type() const { return BondData::Variant::PERIODIC_DIHEDRAL; } +BondData::Variant PeriodicDihedral::type() const +{ + return BondData::Variant::PERIODIC_DIHEDRAL; +} -void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) { +void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) +{ // Torsion on the form a(0) - b(1) - c(2) - d(3) energyFunc = [&](Geometry::DistanceFunction distance) { auto ab = distance(particles[indices[1]].pos, particles[indices[0]].pos); // a->b @@ -340,8 +464,8 @@ void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) { auto normal_abc = ab.cross(bc).eval(); // ab x bc auto normal_bcd = bc.cross(cd).eval(); // bc x cd // atan2( [abΓ—bc]Γ—[bcΓ—cd]β‹…[bc/|bc|], [abΓ—bc]β‹…[bcΓ—cd] ) - const auto dihedral_angle = - std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), normal_abc.dot(normal_bcd)); + const auto dihedral_angle = std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), + normal_abc.dot(normal_bcd)); return force_constant * (1.0 + std::cos(periodicity * dihedral_angle - phase_angle)); }; forceFunc = [&](Geometry::DistanceFunction distance) -> std::vector { @@ -350,8 +474,8 @@ void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) { auto cd = distance(particles[indices[3]].pos, particles[indices[2]].pos); // c->d auto normal_abc = ab.cross(bc).eval(); auto normal_bcd = bc.cross(cd).eval(); - const auto dihedral_angle = - std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), normal_abc.dot(normal_bcd)); + const auto dihedral_angle = std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), + normal_abc.dot(normal_bcd)); // Calculation of the energy derivative with respect to the dihedral angle. const auto force_magnitude = @@ -367,8 +491,10 @@ void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) { const auto theta_d_derivative = inverse_norm_cd / std::sin(angle_bcd); // Calculation of directional vectors on particle a and d. - const Point ortho_normalized_abc = -normal_abc.normalized(); // normalized vector orthogonal to the plane abc. - const Point ortho_normalized_bcd = cd.cross(-bc).normalized(); // normalized vector orthogonal to the plane bcd. + const Point ortho_normalized_abc = + -normal_abc.normalized(); // normalized vector orthogonal to the plane abc. + const Point ortho_normalized_bcd = + cd.cross(-bc).normalized(); // normalized vector orthogonal to the plane bcd. // Calculation of forces on particle a and d Point force_a = force_magnitude * ortho_normalized_abc * theta_a_derivative; @@ -376,33 +502,55 @@ void PeriodicDihedral::setEnergyFunction(const ParticleVector& particles) { // Calculation of force and associated vectors for atom c. const Point bc_midpoint = 0.5 * bc; - const Point torque_c = -(bc_midpoint.cross(force_d) + 0.5 * cd.cross(force_d) - 0.5 * ab.cross(force_a)); + const Point torque_c = + -(bc_midpoint.cross(force_d) + 0.5 * cd.cross(force_d) - 0.5 * ab.cross(force_a)); Point force_c = torque_c.cross(bc_midpoint) / bc_midpoint.squaredNorm(); Point force_b = -(force_a + force_c + force_d); // Newton's third law for force on atom b. - return {{indices[0], force_a}, {indices[1], force_b}, {indices[2], force_c}, {indices[3], force_d}}; + return {{indices[0], force_a}, + {indices[1], force_b}, + {indices[2], force_c}, + {indices[3], force_d}}; }; } -void HarmonicDihedral::from_json(const Faunus::json& j) { +void HarmonicDihedral::from_json(const Faunus::json& j) +{ half_force_constant = j.at("k").get() * 1.0_kJmol / std::pow(1.0_rad, 2) / 2.0; equilibrium_dihedral = j.at("deq").get() * 1.0_deg; } -void HarmonicDihedral::to_json(Faunus::json& j) const { - j = {{"k", 2 * half_force_constant / (1.0_kJmol / std::pow(1.0_rad, 2))}, {"deq", equilibrium_dihedral / 1.0_deg}}; +void HarmonicDihedral::to_json(Faunus::json& j) const +{ + j = {{"k", 2 * half_force_constant / (1.0_kJmol / std::pow(1.0_rad, 2))}, + {"deq", equilibrium_dihedral / 1.0_deg}}; roundJSON(j, 6); } -int HarmonicDihedral::numindex() const { return 4; } +int HarmonicDihedral::numindex() const +{ + return 4; +} -HarmonicDihedral::HarmonicDihedral(double force_constant, double equilibrium_dihedral, const std::vector& indices) - : BondData(indices), half_force_constant(force_constant / 2.0), equilibrium_dihedral(equilibrium_dihedral) {} +HarmonicDihedral::HarmonicDihedral(double force_constant, double equilibrium_dihedral, + const std::vector& indices) + : BondData(indices) + , half_force_constant(force_constant / 2.0) + , equilibrium_dihedral(equilibrium_dihedral) +{ +} -BondData::Variant HarmonicDihedral::type() const { return BondData::Variant::HARMONIC_DIHEDRAL; } +BondData::Variant HarmonicDihedral::type() const +{ + return BondData::Variant::HARMONIC_DIHEDRAL; +} -std::shared_ptr HarmonicDihedral::clone() const { return std::make_shared(*this); } +std::shared_ptr HarmonicDihedral::clone() const +{ + return std::make_shared(*this); +} -void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) { +void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) +{ // Torsion on the form a(0) - b(1) - c(2) - d(3) energyFunc = [&](Geometry::DistanceFunction distance) { auto ab = distance(particles[indices[1]].pos, particles[indices[0]].pos); // a->b @@ -411,8 +559,8 @@ void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) { auto normal_abc = ab.cross(bc).eval(); // ab x bc auto normal_bcd = bc.cross(cd).eval(); // bc x cd // atan2( [abΓ—bc]Γ—[bcΓ—cd]β‹…[bc/|bc|], [abΓ—bc]β‹…[bcΓ—cd] ) - const auto dihedral_angle = - std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), normal_abc.dot(normal_bcd)); + const auto dihedral_angle = std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), + normal_abc.dot(normal_bcd)); const auto delta_dihedral = equilibrium_dihedral - dihedral_angle; return half_force_constant * delta_dihedral * delta_dihedral; }; @@ -422,11 +570,12 @@ void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) { auto cd = distance(particles[indices[3]].pos, particles[indices[2]].pos); // c->d auto normal_abc = ab.cross(bc).eval(); auto normal_bcd = bc.cross(cd).eval(); - const auto dihedral_angle = - std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), normal_abc.dot(normal_bcd)); + const auto dihedral_angle = std::atan2((normal_abc.cross(normal_bcd)).dot(bc) / bc.norm(), + normal_abc.dot(normal_bcd)); // Calculation of the energy derivative with respect to the dihedral angle. - const auto force_magnitude = -2.0 * half_force_constant * (dihedral_angle - equilibrium_dihedral); + const auto force_magnitude = + -2.0 * half_force_constant * (dihedral_angle - equilibrium_dihedral); // Calculation of the dihedral angle derivative with respect to the position vector. const auto inverse_norm_ab = 1.0 / ab.norm(); @@ -438,8 +587,10 @@ void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) { const auto theta_d_derivative = inverse_norm_cd / std::sin(angle_bcd); // Calculation of directional vectors on particle a and d. - const Point ortho_normalized_abc = -normal_abc.normalized(); // normalized vector orthogonal to the plane abc. - const Point ortho_normalized_bcd = cd.cross(-bc).normalized(); // normalized vector orthogonal to the plane bcd. + const Point ortho_normalized_abc = + -normal_abc.normalized(); // normalized vector orthogonal to the plane abc. + const Point ortho_normalized_bcd = + cd.cross(-bc).normalized(); // normalized vector orthogonal to the plane bcd. // Calculation of forces on particle a and d Point force_a = force_magnitude * ortho_normalized_abc * theta_a_derivative; @@ -447,22 +598,41 @@ void HarmonicDihedral::setEnergyFunction(const ParticleVector& particles) { // Calculation of force and associated vectors for atom c. const Point bc_midpoint = 0.5 * bc; - const Point torque_c = -(bc_midpoint.cross(force_d) + 0.5 * cd.cross(force_d) - 0.5 * ab.cross(force_a)); + const Point torque_c = + -(bc_midpoint.cross(force_d) + 0.5 * cd.cross(force_d) - 0.5 * ab.cross(force_a)); Point force_c = torque_c.cross(bc_midpoint) / bc_midpoint.squaredNorm(); Point force_b = -(force_a + force_c + force_d); // Newton's third law for force on atom b. - return {{indices[0], force_a}, {indices[1], force_b}, {indices[2], force_c}, {indices[3], force_d}}; + return {{indices[0], force_a}, + {indices[1], force_b}, + {indices[2], force_c}, + {indices[3], force_d}}; }; } -StretchData::StretchData(const std::vector& indices) : BondData(indices) {} -int StretchData::numindex() const { return 2; } +StretchData::StretchData(const std::vector& indices) + : BondData(indices) +{ +} + +int StretchData::numindex() const +{ + return 2; +} -TorsionData::TorsionData(const std::vector& indices) : BondData(indices) {} -int TorsionData::numindex() const { return 3; } +TorsionData::TorsionData(const std::vector& indices) + : BondData(indices) +{ +} + +int TorsionData::numindex() const +{ + return 3; +} TEST_SUITE_BEGIN("Bonds"); -TEST_CASE("[Faunus] BondData") { +TEST_CASE("[Faunus] BondData") +{ using doctest::Approx; ParticleVector p_4a(2, Particle()); @@ -478,21 +648,28 @@ TEST_CASE("[Faunus] BondData") { p_90deg_4a[2].pos = {1.0, 0.0, 0.0}; Geometry::DistanceFunction distance = [](auto& a, auto& b) -> Point { return a - b; }; - Geometry::DistanceFunction distance_3a = [](const Point&, const Point&) -> Point { return {0, 3, 0}; }; - Geometry::DistanceFunction distance_5a = [](const Point&, const Point&) -> Point { return {0, 3, 4}; }; + Geometry::DistanceFunction distance_3a = [](const Point&, const Point&) -> Point { + return {0, 3, 0}; + }; + Geometry::DistanceFunction distance_5a = [](const Point&, const Point&) -> Point { + return {0, 3, 4}; + }; typedef std::shared_ptr BondDataPtr; BondDataPtr bond_ptr; - SUBCASE("HarmonicBond") { - SUBCASE("HarmonicBond Energy") { + SUBCASE("HarmonicBond") + { + SUBCASE("HarmonicBond Energy") + { HarmonicBond bond(100.0, 5.0, {0, 1}); bond.setEnergyFunction(p_4a); CHECK_EQ(bond.energyFunc(distance_5a), Approx(0)); CHECK_EQ(bond.energyFunc(distance_3a), Approx(200)); CHECK_EQ(bond.energyFunc(distance), Approx(50)); } - SUBCASE("HarmonicBond Force") { + SUBCASE("HarmonicBond Force") + { HarmonicBond bond(100.0, 4, {0, 1}); bond.setEnergyFunction(p_4a); auto forces = bond.forceFunc(distance_3a); @@ -502,33 +679,41 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(forces[0].second.y(), Approx(-forces[1].second.y())); CHECK_EQ(forces[0].second.z(), Approx(0)); } - SUBCASE("HarmonicBond JSON") { + SUBCASE("HarmonicBond JSON") + { json j = R"({"harmonic": {"index":[1,2], "k":10.0, "req":2.0}})"_json; bond_ptr = j; CHECK_EQ(json(bond_ptr), j); std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_60deg_4a); CHECK_EQ(bond_ptr->energyFunc(distance), Approx(10.0_kJmol / 2 * 4)); } - SUBCASE("HarmonicBond JSON Invalid") { - CHECK_NOTHROW((R"({"harmonic": {"index":[0,9], "k":0.5, "req":2.1}})"_json).get()); + SUBCASE("HarmonicBond JSON Invalid") + { + CHECK_NOTHROW( + (R"({"harmonic": {"index":[0,9], "k":0.5, "req":2.1}})"_json).get()); CHECK_THROWS((R"({"harmoNIC": {"index":[2,3], "k":0.5, "req":2.1}})"_json) .get()); // exact match required - CHECK_THROWS( - (R"({"harmonic": {"index":[2], "k":0.5, "req":2.1}})"_json).get()); // 2 atom indices - CHECK_THROWS((R"({"harmonic": {"index":[2,3], "req":2.1}})"_json).get()); // k missing - CHECK_THROWS((R"({"harmonic": {"index":[2,3], "k":2.1}})"_json).get()); // req missing + CHECK_THROWS((R"({"harmonic": {"index":[2], "k":0.5, "req":2.1}})"_json) + .get()); // 2 atom indices + CHECK_THROWS((R"({"harmonic": {"index":[2,3], "req":2.1}})"_json) + .get()); // k missing + CHECK_THROWS((R"({"harmonic": {"index":[2,3], "k":2.1}})"_json) + .get()); // req missing } } - SUBCASE("FENEBond") { - SUBCASE("FENEBond Energy") { + SUBCASE("FENEBond") + { + SUBCASE("FENEBond Energy") + { FENEBond bond(100.0, 5.0, {0, 1}); bond.setEnergyFunction(p_4a); CHECK_EQ(bond.energyFunc(distance_5a), pc::infty); CHECK_EQ(bond.energyFunc(distance_3a), Approx(557.86)); CHECK_EQ(bond.energyFunc(distance), Approx(1277.06)); } - SUBCASE("FENEBond Force") { + SUBCASE("FENEBond Force") + { FENEBond bond(100.0, 5.0, {0, 1}); bond.setEnergyFunction(p_4a); auto forces = bond.forceFunc(distance_3a); @@ -542,7 +727,8 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(forces[1].second.y(), Approx(468.75)); CHECK_EQ(forces[1].second.z(), Approx(0.0)); } - SUBCASE("FENEBond JSON") { + SUBCASE("FENEBond JSON") + { json j = R"({"fene": {"index":[1,2], "k":8, "rmax":6.0 }})"_json; bond_ptr = j; // json --> object json j_roundtrip = json(bond_ptr); // object --> json @@ -551,24 +737,31 @@ TEST_CASE("[Faunus] BondData") { std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_60deg_4a); CHECK_EQ(bond_ptr->energyFunc(distance), Approx(84.641_kJmol)); } - SUBCASE("FENEBond JSON Invalid") { - CHECK_NOTHROW((R"({"fene": {"index":[0,9], "k":0.5, "rmax":2.1}})"_json).get()); - CHECK_THROWS((R"({"FENE": {"index":[0,9], "k":0.5, "rmax":2.1}})"_json).get()); - CHECK_THROWS((R"({"fene": {"index":[2,3,4], "k":1, "rmax":2.1}})"_json).get()); + SUBCASE("FENEBond JSON Invalid") + { + CHECK_NOTHROW( + (R"({"fene": {"index":[0,9], "k":0.5, "rmax":2.1}})"_json).get()); + CHECK_THROWS( + (R"({"FENE": {"index":[0,9], "k":0.5, "rmax":2.1}})"_json).get()); + CHECK_THROWS( + (R"({"fene": {"index":[2,3,4], "k":1, "rmax":2.1}})"_json).get()); CHECK_THROWS((R"({"fene": {"index":[2,3], "rmax":2.1}})"_json).get()); CHECK_THROWS((R"({"fene": {"index":[2,3], "k":1}})"_json).get()); } } - SUBCASE("FENEWCABond") { - SUBCASE("FENEWCABond Energy") { + SUBCASE("FENEWCABond") + { + SUBCASE("FENEWCABond Energy") + { FENEWCABond bond(100.0, 5.0, 20.0, 3.2, {0, 1}); bond.setEnergyFunction(p_4a); CHECK_EQ(bond.energyFunc(distance_5a), pc::infty); CHECK_EQ(bond.energyFunc(distance_3a), Approx(557.86 + 18.931)); CHECK_EQ(bond.energyFunc(distance), Approx(1277.06)); } - SUBCASE("FENEWCABond Force") { + SUBCASE("FENEWCABond Force") + { FENEWCABond bond(100, 5.0, 20.0, 3.2, {0, 1}); bond.setEnergyFunction(p_4a); auto forces = bond.forceFunc(distance_3a); @@ -582,8 +775,10 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(forces[1].second.y(), Approx(10.1974323155)); CHECK_EQ(forces[1].second.z(), Approx(0.0)); } - SUBCASE("FENEWCABond JSON") { - json j = R"({"fene+wca": {"index":[1,2], "k":8, "rmax":6.0, "eps":3.5, "sigma":4.5}})"_json; + SUBCASE("FENEWCABond JSON") + { + json j = + R"({"fene+wca": {"index":[1,2], "k":8, "rmax":6.0, "eps":3.5, "sigma":4.5}})"_json; bond_ptr = j; // json --> object json j_roundtrip = json(bond_ptr); // object --> json roundJSON(j_roundtrip, 3); // round decimals before comparison @@ -591,26 +786,36 @@ TEST_CASE("[Faunus] BondData") { std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_60deg_4a); CHECK_EQ(bond_ptr->energyFunc(distance), Approx(92.805_kJmol)); } - SUBCASE("FENEWCABond JSON Invalid") { + SUBCASE("FENEWCABond JSON Invalid") + { CHECK_NOTHROW( - (R"({"fene+wca": {"index":[0,9], "k":1, "rmax":2.1, "eps":2.48, "sigma":2}})"_json).get()); - CHECK_THROWS((R"({"fene+wca": {"index":[2,3,4], "k":1, "rmax":2.1, "eps":2.48, "sigma":2}})"_json) - .get()); + (R"({"fene+wca": {"index":[0,9], "k":1, "rmax":2.1, "eps":2.48, "sigma":2}})"_json) + .get()); + CHECK_THROWS( + (R"({"fene+wca": {"index":[2,3,4], "k":1, "rmax":2.1, "eps":2.48, "sigma":2}})"_json) + .get()); CHECK_THROWS( - (R"({"fene+wca": {"index":[2,3], "rmax":2.1, "eps":2.48, "sigma":2}})"_json).get()); - CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "eps":2.48, "sigma":2}})"_json).get()); - CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "rmax":2.1, "eps":2.48}})"_json).get()); - CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "rmax":2.1, "sigma":2}})"_json).get()); + (R"({"fene+wca": {"index":[2,3], "rmax":2.1, "eps":2.48, "sigma":2}})"_json) + .get()); + CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "eps":2.48, "sigma":2}})"_json) + .get()); + CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "rmax":2.1, "eps":2.48}})"_json) + .get()); + CHECK_THROWS((R"({"fene+wca": {"index":[2,3], "k":1, "rmax":2.1, "sigma":2}})"_json) + .get()); } } - SUBCASE("HarmonicTorsion") { - SUBCASE("HarmonicTorsion Energy") { + SUBCASE("HarmonicTorsion") + { + SUBCASE("HarmonicTorsion Energy") + { HarmonicTorsion bond(100.0, 45.0_deg, {0, 1, 2}); bond.setEnergyFunction(p_60deg_4a); CHECK_EQ(bond.energyFunc(distance), Approx(100.0 / 2 * std::pow(15.0_deg, 2))); } - SUBCASE("HarmonicTorsion Force") { + SUBCASE("HarmonicTorsion Force") + { HarmonicTorsion bond(1, 45.0_deg, {0, 1, 2}); bond.setEnergyFunction(p_90deg_4a); auto forces = bond.forceFunc(distance); @@ -628,46 +833,57 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(forces[2].second.y(), Approx(0.78539816)); CHECK_EQ(forces[2].second.z(), Approx(0.0)); } - SUBCASE("HarmonicTorsion JSON") { + SUBCASE("HarmonicTorsion JSON") + { json j = R"({"harmonic_torsion": {"index":[0,1,2], "k":0.5, "aeq":65}})"_json; bond_ptr = j; CHECK_EQ(json(bond_ptr), j); std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_60deg_4a); CHECK_EQ(bond_ptr->energyFunc(distance), Approx(0.5_kJmol / 2 * std::pow(5.0_deg, 2))); } - SUBCASE("HarmonicTorsion JSON Invalid") { - CHECK_NOTHROW((R"({"harmonic_torsion": {"index":[0,1,9], "k":0.5, "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"harmonic_torsion": {"index":[2], "k":0.5, "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"harmonic_torsion": {"index":[0,1,2], "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"harmonic_torsion": {"index":[0,1,3], "k":0.5}})"_json).get()); + SUBCASE("HarmonicTorsion JSON Invalid") + { + CHECK_NOTHROW((R"({"harmonic_torsion": {"index":[0,1,9], "k":0.5, "aeq":30.5}})"_json) + .get()); + CHECK_THROWS((R"({"harmonic_torsion": {"index":[2], "k":0.5, "aeq":30.5}})"_json) + .get()); + CHECK_THROWS( + (R"({"harmonic_torsion": {"index":[0,1,2], "aeq":30.5}})"_json).get()); + CHECK_THROWS( + (R"({"harmonic_torsion": {"index":[0,1,3], "k":0.5}})"_json).get()); } } - SUBCASE("GromosTorsion") { - SUBCASE("GromosTorsion Energy") { + SUBCASE("GromosTorsion") + { + SUBCASE("GromosTorsion Energy") + { GromosTorsion bond(100.0, std::cos(45.0_deg), {0, 1, 2}); bond.setEnergyFunction(p_60deg_4a); - CHECK_EQ(bond.energyFunc(distance), Approx(100.0 / 2 * std::pow(cos(60.0_deg) - cos(45.0_deg), 2))); + CHECK_EQ(bond.energyFunc(distance), + Approx(100.0 / 2 * std::pow(cos(60.0_deg) - cos(45.0_deg), 2))); } - SUBCASE("GromosTorsion Force") { + SUBCASE("GromosTorsion Force") + { GromosTorsion bond(100.0, std::cos(45.0_deg), {0, 1, 2}); bond.setEnergyFunction(p_90deg_4a); auto forces = bond.forceFunc(distance); - CHECK_EQ(forces.size(), 3); - CHECK_EQ(forces[0].first, 0); - CHECK_EQ(forces[1].first, 1); - CHECK_EQ(forces[2].first, 2); - CHECK_EQ(forces[0].second.x(), Approx(-70.7106781187)); - CHECK_EQ(forces[0].second.y(), Approx(0.0)); - CHECK_EQ(forces[0].second.z(), Approx(0.0)); - CHECK_EQ(forces[1].second.x(), Approx(70.7106781187)); - CHECK_EQ(forces[1].second.y(), Approx(70.7106781187)); - CHECK_EQ(forces[1].second.z(), Approx(0.0)); - CHECK_EQ(forces[2].second.x(), Approx(0.0)); - CHECK_EQ(forces[2].second.y(), Approx(-70.7106781187)); - CHECK_EQ(forces[2].second.z(), Approx(0.0)); - } - SUBCASE("GromosTorsion JSON") { + CHECK_EQ(forces.size(), 3); + CHECK_EQ(forces[0].first, 0); + CHECK_EQ(forces[1].first, 1); + CHECK_EQ(forces[2].first, 2); + CHECK_EQ(forces[0].second.x(), Approx(-70.7106781187)); + CHECK_EQ(forces[0].second.y(), Approx(0.0)); + CHECK_EQ(forces[0].second.z(), Approx(0.0)); + CHECK_EQ(forces[1].second.x(), Approx(70.7106781187)); + CHECK_EQ(forces[1].second.y(), Approx(70.7106781187)); + CHECK_EQ(forces[1].second.z(), Approx(0.0)); + CHECK_EQ(forces[2].second.x(), Approx(0.0)); + CHECK_EQ(forces[2].second.y(), Approx(-70.7106781187)); + CHECK_EQ(forces[2].second.z(), Approx(0.0)); + } + SUBCASE("GromosTorsion JSON") + { json j = R"({"gromos_torsion": {"index":[0,1,2], "k":0.5, "aeq":65}})"_json; bond_ptr = j; // json --> object json j_roundtrip = json(bond_ptr); // object --> json @@ -677,15 +893,21 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(bond_ptr->energyFunc(distance), Approx(0.5_kJmol / 2 * std::pow(cos(60.0_deg) - cos(65.0_deg), 2))); } - SUBCASE("GromosTorsion JSON Invalid") { - CHECK_NOTHROW((R"({"gromos_torsion": {"index":[0,1,9], "k":0.5, "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"gromos_torsion": {"index":[2], "k":0.5, "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"gromos_torsion": {"index":[0,1,2], "aeq":30.5}})"_json).get()); - CHECK_THROWS((R"({"gromos_torsion": {"index":[0,1,3], "k":0.5}})"_json).get()); + SUBCASE("GromosTorsion JSON Invalid") + { + CHECK_NOTHROW((R"({"gromos_torsion": {"index":[0,1,9], "k":0.5, "aeq":30.5}})"_json) + .get()); + CHECK_THROWS((R"({"gromos_torsion": {"index":[2], "k":0.5, "aeq":30.5}})"_json) + .get()); + CHECK_THROWS( + (R"({"gromos_torsion": {"index":[0,1,2], "aeq":30.5}})"_json).get()); + CHECK_THROWS( + (R"({"gromos_torsion": {"index":[0,1,3], "k":0.5}})"_json).get()); } } - SUBCASE("PeriodicDihedral") { + SUBCASE("PeriodicDihedral") + { ParticleVector p_45deg(4); p_45deg[1].pos = {0.0, 0.0, 0.0}; p_45deg[2].pos = {0.0, 0.0, 2.0}; @@ -699,7 +921,8 @@ TEST_CASE("[Faunus] BondData") { ParticleVector p_120deg(p_60deg); p_120deg[3].pos[0] *= -1.0; - SUBCASE("PeriodicDihedral Energy") { + SUBCASE("PeriodicDihedral Energy") + { PeriodicDihedral bond(100.0, 0.0_deg, 3, {0, 1, 2, 3}); bond.setEnergyFunction(p_120deg); CHECK_EQ(bond.energyFunc(distance), Approx(200.0)); @@ -708,7 +931,8 @@ TEST_CASE("[Faunus] BondData") { bond.setEnergyFunction(p_90deg); CHECK_EQ(bond.energyFunc(distance), Approx(100.0)); } - SUBCASE("PeriodicDihedral Forces") { + SUBCASE("PeriodicDihedral Forces") + { PeriodicDihedral bond(100.0, 0.0_deg, 3, {0, 1, 2, 3}); bond.setEnergyFunction(p_90deg); auto forces = bond.forceFunc(distance); @@ -730,25 +954,34 @@ TEST_CASE("[Faunus] BondData") { CHECK_EQ(forces[3].second.y(), Approx(0)); CHECK_EQ(forces[3].second.z(), Approx(0)); } - SUBCASE("PeriodicDihedral JSON") { - json j = R"({"periodic_dihedral": {"index":[0,1,2,3], "k":10, "phi":0.0, "n": 3}})"_json; + SUBCASE("PeriodicDihedral JSON") + { + json j = + R"({"periodic_dihedral": {"index":[0,1,2,3], "k":10, "phi":0.0, "n": 3}})"_json; bond_ptr = j; CHECK_EQ(json(bond_ptr), j); std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_90deg); CHECK_EQ(bond_ptr->energyFunc(distance), Approx(10.0_kJmol)); } - SUBCASE("PeriodicDihedral JSON Invalid") { + SUBCASE("PeriodicDihedral JSON Invalid") + { CHECK_NOTHROW( - (R"({"periodic_dihedral": {"index":[0,1,2,9], "k":0.5, "phi":2.1, "n": 2}})"_json).get()); + (R"({"periodic_dihedral": {"index":[0,1,2,9], "k":0.5, "phi":2.1, "n": 2}})"_json) + .get()); CHECK_THROWS( - (R"({"periodic_dihedral": {"index":[0,1,2], "k":0.5, "phi":2.1, "n": 2}})"_json).get()); - CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "phi":2.1, "n": 2}})"_json).get()); - CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "k":0.5, "n": 2}})"_json).get()); - CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "k":0.5, "phi":2.1}})"_json).get()); + (R"({"periodic_dihedral": {"index":[0,1,2], "k":0.5, "phi":2.1, "n": 2}})"_json) + .get()); + CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "phi":2.1, "n": 2}})"_json) + .get()); + CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "k":0.5, "n": 2}})"_json) + .get()); + CHECK_THROWS((R"({"periodic_dihedral": {"index":[0,1,2,3], "k":0.5, "phi":2.1}})"_json) + .get()); } } - SUBCASE("HarmonicDihedral") { + SUBCASE("HarmonicDihedral") + { ParticleVector p_45deg(4); p_45deg[1].pos = {0.0, 0.0, 0.0}; p_45deg[2].pos = {0.0, 0.0, 2.0}; @@ -762,64 +995,74 @@ TEST_CASE("[Faunus] BondData") { ParticleVector p_120deg(p_60deg); p_120deg[3].pos[0] *= -1.0; - SUBCASE("HarmonicDihedral Energy") { + SUBCASE("HarmonicDihedral Energy") + { HarmonicDihedral bond(100.0, 90.0_deg, {0, 1, 2, 3}); bond.setEnergyFunction(p_120deg); - CHECK_EQ(bond.energyFunc(distance), Approx(13.7077838904)); + CHECK_EQ(bond.energyFunc(distance), Approx(13.7077838904)); bond.setEnergyFunction(p_60deg); - CHECK_EQ(bond.energyFunc(distance), Approx(13.7077838904)); + CHECK_EQ(bond.energyFunc(distance), Approx(13.7077838904)); bond.setEnergyFunction(p_90deg); - CHECK_EQ(bond.energyFunc(distance), Approx(0.0)); + CHECK_EQ(bond.energyFunc(distance), Approx(0.0)); } - SUBCASE("HarmonicDihedral Forces") { + SUBCASE("HarmonicDihedral Forces") + { HarmonicDihedral bond(100.0, 90.0_deg, {0, 1, 2, 3}); bond.setEnergyFunction(p_120deg); auto forces = bond.forceFunc(distance); - CHECK_EQ(forces.size(), 4); - CHECK_EQ(forces[0].first, 0); - CHECK_EQ(forces[1].first, 1); - CHECK_EQ(forces[2].first, 2); - CHECK_EQ(forces[3].first, 3); - CHECK_EQ(forces[0].second.x(), Approx(0)); - CHECK_EQ(forces[0].second.y(), Approx(10.471975512)); - CHECK_EQ(forces[0].second.z(), Approx(0)); - CHECK_EQ(forces[1].second.x(), Approx(0)); - CHECK_EQ(forces[1].second.y(), Approx(-10.471975512)); - CHECK_EQ(forces[1].second.z(), Approx(0)); - CHECK_EQ(forces[2].second.x(), Approx(-2.2672492053)); - CHECK_EQ(forces[2].second.y(), Approx(-1.308996939)); - CHECK_EQ(forces[2].second.z(), Approx(0)); - CHECK_EQ(forces[3].second.x(), Approx(2.2672492053)); - CHECK_EQ(forces[3].second.y(), Approx(1.308996939)); - CHECK_EQ(forces[3].second.z(), Approx(0)); - } - SUBCASE("HarmonicDihedral JSON") { + CHECK_EQ(forces.size(), 4); + CHECK_EQ(forces[0].first, 0); + CHECK_EQ(forces[1].first, 1); + CHECK_EQ(forces[2].first, 2); + CHECK_EQ(forces[3].first, 3); + CHECK_EQ(forces[0].second.x(), Approx(0)); + CHECK_EQ(forces[0].second.y(), Approx(10.471975512)); + CHECK_EQ(forces[0].second.z(), Approx(0)); + CHECK_EQ(forces[1].second.x(), Approx(0)); + CHECK_EQ(forces[1].second.y(), Approx(-10.471975512)); + CHECK_EQ(forces[1].second.z(), Approx(0)); + CHECK_EQ(forces[2].second.x(), Approx(-2.2672492053)); + CHECK_EQ(forces[2].second.y(), Approx(-1.308996939)); + CHECK_EQ(forces[2].second.z(), Approx(0)); + CHECK_EQ(forces[3].second.x(), Approx(2.2672492053)); + CHECK_EQ(forces[3].second.y(), Approx(1.308996939)); + CHECK_EQ(forces[3].second.z(), Approx(0)); + } + SUBCASE("HarmonicDihedral JSON") + { json j = R"({"harmonic_dihedral": {"index":[0,1,2,3], "k":100.0, "deq":90}})"_json; bond_ptr = j; - CHECK_EQ(json(bond_ptr), j); + CHECK_EQ(json(bond_ptr), j); std::dynamic_pointer_cast(bond_ptr)->setEnergyFunction(p_120deg); - CHECK_EQ(bond_ptr->energyFunc(distance), Approx(100.0_kJmol / 2.0 * std::pow(30.0_deg, 2))); + CHECK_EQ(bond_ptr->energyFunc(distance), + Approx(100.0_kJmol / 2.0 * std::pow(30.0_deg, 2))); } - SUBCASE("HarmonicDihedral JSON Invalid") { - CHECK_NOTHROW( - (R"({"harmonic_dihedral": {"index":[0,1,2,3], "k":0.5, "deq":90}})"_json).get()); - CHECK_THROWS( - (R"({"harmonic_dihedral": {"index":[0,1,2], "k":0.5, "deq":90}})"_json).get()); - CHECK_THROWS((R"({"harmonic_dihedral": {"index":[0,1,2,3], "deq":90}})"_json).get()); - CHECK_THROWS((R"({"harmonic_dihedral": {"index":[0,1,2,3], "k":0.5}})"_json).get()); + SUBCASE("HarmonicDihedral JSON Invalid") + { + CHECK_NOTHROW((R"({"harmonic_dihedral": {"index":[0,1,2,3], "k":0.5, "deq":90}})"_json) + .get()); + CHECK_THROWS((R"({"harmonic_dihedral": {"index":[0,1,2], "k":0.5, "deq":90}})"_json) + .get()); + CHECK_THROWS((R"({"harmonic_dihedral": {"index":[0,1,2,3], "deq":90}})"_json) + .get()); + CHECK_THROWS( + (R"({"harmonic_dihedral": {"index":[0,1,2,3], "k":0.5}})"_json).get()); } } - SUBCASE("Find") { + SUBCASE("Find") + { BasePointerVector bonds; bonds.emplace_back(1.0, 2.1, std::vector{2, 3}); bonds.emplace_back(1.0, 2.1, std::vector{2, 3}); auto harmonic_bonds = bonds.find(); CHECK_EQ(harmonic_bonds.size(), 1); CHECK_EQ(harmonic_bonds.front()->type(), BondData::Variant::HARMONIC); - CHECK_EQ(harmonic_bonds.front(), bonds.back()); // harmonic_bonds should contain references to bonds + CHECK_EQ(harmonic_bonds.front(), + bonds.back()); // harmonic_bonds should contain references to bonds } } + TEST_SUITE_END(); } // namespace Faunus::pairpotential diff --git a/src/bonds.h b/src/bonds.h index a82fd7311..7157295fc 100644 --- a/src/bonds.h +++ b/src/bonds.h @@ -20,8 +20,10 @@ namespace Faunus::pairpotential { * * @todo Memory inefficient to store energy and force functors. Virtual functions? */ -struct BondData { - enum class Variant { +struct BondData +{ + enum class Variant + { HARMONIC = 0, FENE, FENEWCA, @@ -36,41 +38,47 @@ struct BondData { /** Calculates potential energy of bonded atoms(kT) */ std::function energyFunc = nullptr; - using IndexAndForce = std::pair; //!< Force (second) on particle w. absolute index (first) + using IndexAndForce = + std::pair; //!< Force (second) on particle w. absolute index (first) /** Calculates forces on bonded atoms (kT/Γ…) */ std::function(Geometry::DistanceFunction)> forceFunc = nullptr; virtual void from_json(const json&) = 0; virtual void to_json(json&) const = 0; - [[nodiscard]] virtual int numindex() const = 0; //!< Required number of atom indices for bond - [[nodiscard]] virtual Variant type() const = 0; //!< Returns bond type (sett `Variant` enum) - [[nodiscard]] virtual std::shared_ptr clone() const = 0; //!< Make shared pointer *copy* of data - virtual void setEnergyFunction(const ParticleVector& particles) = 0; //!< Set energy function; store particles ref. - [[nodiscard]] bool hasEnergyFunction() const; //!< test if energy function has been set - [[nodiscard]] bool hasForceFunction() const; //!< test if force function has been set - void shiftIndices(int offset); //!< Add offset to particle indices + [[nodiscard]] virtual int numindex() const = 0; //!< Required number of atom indices for bond + [[nodiscard]] virtual Variant type() const = 0; //!< Returns bond type (sett `Variant` enum) + [[nodiscard]] virtual std::shared_ptr + clone() const = 0; //!< Make shared pointer *copy* of data + virtual void setEnergyFunction( + const ParticleVector& particles) = 0; //!< Set energy function; store particles ref. + [[nodiscard]] bool hasEnergyFunction() const; //!< test if energy function has been set + [[nodiscard]] bool hasForceFunction() const; //!< test if force function has been set + void shiftIndices(int offset); //!< Add offset to particle indices BondData() = default; explicit BondData(const std::vector& indices); virtual ~BondData() = default; }; -NLOHMANN_JSON_SERIALIZE_ENUM(BondData::Variant, {{BondData::Variant::INVALID, nullptr}, - {BondData::Variant::HARMONIC, "harmonic"}, - {BondData::Variant::FENE, "fene"}, - {BondData::Variant::FENEWCA, "fene+wca"}, - {BondData::Variant::HARMONIC_TORSION, "harmonic_torsion"}, - {BondData::Variant::GROMOS_TORSION, "gromos_torsion"}, - {BondData::Variant::PERIODIC_DIHEDRAL, "periodic_dihedral"}, - {BondData::Variant::HARMONIC_DIHEDRAL, "harmonic_dihedral"}}) - -struct StretchData : public BondData { +NLOHMANN_JSON_SERIALIZE_ENUM(BondData::Variant, + {{BondData::Variant::INVALID, nullptr}, + {BondData::Variant::HARMONIC, "harmonic"}, + {BondData::Variant::FENE, "fene"}, + {BondData::Variant::FENEWCA, "fene+wca"}, + {BondData::Variant::HARMONIC_TORSION, "harmonic_torsion"}, + {BondData::Variant::GROMOS_TORSION, "gromos_torsion"}, + {BondData::Variant::PERIODIC_DIHEDRAL, "periodic_dihedral"}, + {BondData::Variant::HARMONIC_DIHEDRAL, "harmonic_dihedral"}}) + +struct StretchData : public BondData +{ [[nodiscard]] int numindex() const override; StretchData() = default; explicit StretchData(const std::vector& indices); }; -struct TorsionData : public BondData { +struct TorsionData : public BondData +{ [[nodiscard]] int numindex() const override; TorsionData() = default; explicit TorsionData(const std::vector& indices); @@ -81,14 +89,16 @@ struct TorsionData : public BondData { * * U(r) = k/2 * (r - r_eq)^2 */ -struct HarmonicBond : public StretchData { +struct HarmonicBond : public StretchData +{ double half_force_constant = 0.0; double equilibrium_distance = 0.0; [[nodiscard]] Variant type() const override; [[nodiscard]] std::shared_ptr clone() const override; void from_json(const json& j) override; void to_json(json& j) const override; - void setEnergyFunction(const ParticleVector& particles) override; //!< Set energy and force functors + void + setEnergyFunction(const ParticleVector& particles) override; //!< Set energy and force functors HarmonicBond() = default; HarmonicBond(double k, double req, const std::vector& indices); }; @@ -98,7 +108,8 @@ struct HarmonicBond : public StretchData { * * U(r) = -k/2 * r_max^2 * ln(1 - r^2 / r_max^2) if r < r_max, ∞ otherwise */ -struct FENEBond : public StretchData { +struct FENEBond : public StretchData +{ double half_force_constant = 0.0; double max_squared_distance = 0.0; [[nodiscard]] Variant type() const override; @@ -113,7 +124,8 @@ struct FENEBond : public StretchData { /** * @brief FENE+WCA bond */ -struct FENEWCABond : public StretchData { +struct FENEWCABond : public StretchData +{ double half_force_constant = 0.0; double max_distance_squared = 0.0; double epsilon = 0.0; @@ -125,7 +137,8 @@ struct FENEWCABond : public StretchData { void to_json(json& j) const override; void setEnergyFunction(const ParticleVector& calculateDistance) override; FENEWCABond() = default; - FENEWCABond(double k, double rmax, double epsilon, double sigma, const std::vector& indices); + FENEWCABond(double k, double rmax, double epsilon, double sigma, + const std::vector& indices); }; /** @@ -133,7 +146,8 @@ struct FENEWCABond : public StretchData { * * U(a) = k/2 * (a - a_eq)^2 */ -struct HarmonicTorsion : public TorsionData { +struct HarmonicTorsion : public TorsionData +{ double half_force_constant = 0.0; double equilibrium_angle = 0.0; [[nodiscard]] std::shared_ptr clone() const override; @@ -150,7 +164,8 @@ struct HarmonicTorsion : public TorsionData { * * U(a) = k/2 * (cos(a) - cos(a_eq))^2 */ -struct GromosTorsion : public TorsionData { +struct GromosTorsion : public TorsionData +{ double half_force_constant = 0.0; double cosine_equilibrium_angle = 0.0; [[nodiscard]] std::shared_ptr clone() const override; @@ -167,7 +182,8 @@ struct GromosTorsion : public TorsionData { * * U(a) = k * (1 + cos(n * a - phi)) */ -struct PeriodicDihedral : public BondData { +struct PeriodicDihedral : public BondData +{ double force_constant = 0.0; double phase_angle = 0.0; double periodicity = 1.0; @@ -186,7 +202,8 @@ struct PeriodicDihedral : public BondData { * * U(a) = k/2 * (phi - phi_eq)^2) */ -struct HarmonicDihedral : public BondData { +struct HarmonicDihedral : public BondData +{ double half_force_constant = 0.0; double equilibrium_dihedral = 0.0; [[nodiscard]] int numindex() const override; @@ -203,4 +220,4 @@ void from_json(const json& j, std::shared_ptr& bond); void to_json(json& j, const std::shared_ptr& bond); void to_json(json& j, const BondData& bond); -} // namespace Faunus::Potential \ No newline at end of file +} // namespace Faunus::pairpotential \ No newline at end of file diff --git a/src/celllist.h b/src/celllist.h index d40255a3c..3aaf3666b 100644 --- a/src/celllist.h +++ b/src/celllist.h @@ -1,9 +1,10 @@ #pragma once + /** * @brief Cell list class templates. * - * Minimal interface to be used in other header file to avoid number of class templates. Full class templates - * definitions are provided in "celllistimpl.h" file. + * Minimal interface to be used in other header file to avoid number of class templates. Full class + * templates definitions are provided in "celllistimpl.h" file. * * @author Richard Chudoba * @date 2021-02-01 @@ -30,11 +31,15 @@ template using PointOf = typename T::Point; * @tparam TCellIndex type of a single coordinate in cell space; also an absolute cell index * @tparam TSpaceAxis type of a single coordinate in physical space */ -template -struct GridType { - constexpr static unsigned int Dimension = VDimension; //!< number of dimmensions, e.g., 3 for the real 3D world - using SpaceAxis = TSpaceAxis; //!< a single coordinate in physical space, e.g., double - using CellIndex = TCellIndex; //!< a single coordinate in cell space, e.g., int; also an absolute cell index +template +struct GridType +{ + constexpr static unsigned int Dimension = + VDimension; //!< number of dimmensions, e.g., 3 for the real 3D world + using SpaceAxis = TSpaceAxis; //!< a single coordinate in physical space, e.g., double + using CellIndex = + TCellIndex; //!< a single coordinate in cell space, e.g., int; also an absolute cell index using Point = Eigen::Array; //!< a point in the VDimensional space using CellCoord = Eigen::Array; //!< VDimensional cell coordinates }; @@ -43,16 +48,18 @@ struct GridType { using Grid3DType = GridType<3, int, double>; /** - * @brief A simple data holder for common offsets in 3D grids, e.g., nearest neighbors or forward neighbors. + * @brief A simple data holder for common offsets in 3D grids, e.g., nearest neighbors or forward + * neighbors. */ -class GridOffsets3D { +class GridOffsets3D +{ using CellCoord = CoordOf; //!< 3-dimension cell coordinates using CellIndex = IndexOf; //!< cell index public: const std::vector self{{0, 0, 0}}; //!< the cell itself - std::vector neighbors; //!< all neighbors (excluding self); (1 + 2Γ—distance)Β³ - 1 - std::vector forward_neighbors; //!< only the preceding half of all neighbors - CellIndex distance; //!< maximal distance of neighbours (1 for the nearest neighbors) + std::vector neighbors; //!< all neighbors (excluding self); (1 + 2Γ—distance)Β³ - 1 + std::vector forward_neighbors; //!< only the preceding half of all neighbors + CellIndex distance; //!< maximal distance of neighbours (1 for the nearest neighbors) /** * @param distance maximal distance of neighbours (1 for the nearest neighbors) @@ -77,13 +84,14 @@ template using MembersOf = typename ContainerTypeOf::Members; /** * @brief An interface of a container. * - * The container is responsible for storing (and retriving) members under a given cell index. Various storage policies - * may be used in different implementations, e.g., a vector or a map. + * The container is responsible for storing (and retriving) members under a given cell index. + * Various storage policies may be used in different implementations, e.g., a vector or a map. * * @tparam TMember a member type * @tparam TIndex an index type, see GridType::CellIndex */ -template struct AbstractContainer { +template struct AbstractContainer +{ using ContainerType = AbstractContainer; using Index = TIndex; //!< the cell index (key) type using Member = TMember; //!< the cell member (primitive value) type @@ -109,26 +117,32 @@ template using ContainerOf = typename T::Container; template using GridOf = typename T::Grid; using Container::ContainerTypeOf; using Grid::GridTypeOf; -template using IndexOf = typename Grid::IndexOf>; -template using CoordOf = typename Grid::CoordOf>; -template using PointOf = typename Grid::PointOf>; -template using SpaceAxisOf = typename Grid::SpaceAxisOf>; +template +using IndexOf = typename Grid::IndexOf>; +template +using CoordOf = typename Grid::CoordOf>; +template +using PointOf = typename Grid::PointOf>; +template +using SpaceAxisOf = typename Grid::SpaceAxisOf>; template using MemberOf = typename Container::MemberOf; template using MembersOf = typename Container::MembersOf; /** - * @brief An interface of a immutable cell list. No methods for manipulating (adding, removing) members are provided. + * @brief An interface of a immutable cell list. No methods for manipulating (adding, removing) + * members are provided. * @tparam TContainerType a container type to obtain Member and Members subtypes * @tparam TGridType a grid type to obtain index and coordinates subtypes */ -template struct AbstractImmutableCellList { +template struct AbstractImmutableCellList +{ using AbstractCellList = AbstractImmutableCellList; //! self - using ContainerType = TContainerType; //!< abstract container types - using GridType = TGridType; //!< abstract grid type - using Members = typename Container::MembersOf; //!< members type - using Member = typename Container::MemberOf; //!< member type - using CellIndex = typename Grid::IndexOf; //!< grid (cell) index type - using CellCoord = typename Grid::CoordOf; //!< grid (cell) coordinates type + using ContainerType = TContainerType; //!< abstract container types + using GridType = TGridType; //!< abstract grid type + using Members = typename Container::MembersOf; //!< members type + using Member = typename Container::MemberOf; //!< member type + using CellIndex = typename Grid::IndexOf; //!< grid (cell) index type + using CellCoord = typename Grid::CoordOf; //!< grid (cell) coordinates type //! @brief Gets cell members at given cell coordinates. virtual const Members& getMembers(const CellCoord&) = 0; @@ -151,7 +165,9 @@ template struct AbstractImmutableCellLis * @tparam TGridType a grid type to obtain index and coordinates subtypes */ template -struct AbstractSortableCellList : virtual public AbstractImmutableCellList { +struct AbstractSortableCellList + : virtual public AbstractImmutableCellList +{ using typename AbstractImmutableCellList::AbstractCellList; using typename AbstractCellList::CellCoord; using typename AbstractCellList::Members; diff --git a/src/celllistimpl.cpp b/src/celllistimpl.cpp index 0b003c872..505ebf6d5 100644 --- a/src/celllistimpl.cpp +++ b/src/celllistimpl.cpp @@ -1,15 +1,19 @@ #include #include "celllistimpl.h" + namespace Faunus { namespace CellList { namespace Grid { -GridOffsets3D::GridOffsets3D(CellIndex distance) : distance(distance) { +GridOffsets3D::GridOffsets3D(CellIndex distance) + : distance(distance) +{ initNeighbors(); initForwardNeighbors(); } -void GridOffsets3D::initNeighbors() { +void GridOffsets3D::initNeighbors() +{ const auto number_of_neighbors = std::pow(1 + 2 * distance, 3) - 1; neighbors.clear(); neighbors.reserve(number_of_neighbors); @@ -26,20 +30,21 @@ void GridOffsets3D::initNeighbors() { assert(neighbors.size() == number_of_neighbors); } -void GridOffsets3D::initForwardNeighbors() { +void GridOffsets3D::initForwardNeighbors() +{ const auto forward_neighbors_count = neighbors.size() / 2; forward_neighbors.clear(); forward_neighbors.reserve(forward_neighbors_count); - std::copy_if(neighbors.begin(), neighbors.end(), std::back_inserter(forward_neighbors), [](auto& offset) { - return (offset > CellCoord{0, 0, 0}).all(); - }); + std::copy_if(neighbors.begin(), neighbors.end(), std::back_inserter(forward_neighbors), + [](auto& offset) { return (offset > CellCoord{0, 0, 0}).all(); }); assert(forward_neighbors.size() == forward_neighbors_count); } /** * Tests */ -TEST_CASE("Grid3DFixed") { +TEST_CASE("Grid3DFixed") +{ using TestGridBase = Grid3DFixed; using CellCoord = TestGridBase::CellCoord; TestGridBase grid({20., 30., 40.}, 1.5); @@ -50,7 +55,8 @@ TEST_CASE("Grid3DFixed") { REQUIRE_EQ(grid.size(), end.prod()); - SUBCASE("Cell coordinates are limited to [origin, last]") { + SUBCASE("Cell coordinates are limited to [origin, last]") + { CHECK(grid.isCell(first)); CHECK_FALSE(grid.isCell(first - CellCoord{1, 0, 0})); CHECK_FALSE(grid.isCell(first - CellCoord{0, 1, 0})); @@ -73,7 +79,8 @@ TEST_CASE("Grid3DFixed") { CHECK((grid.coordinates(grid.index(coord1)) == coord1).all()); CHECK_NE(grid.index(coord1.reverse()), grid.index(coord1)); - SUBCASE("Offsets cannot wrap around") { + SUBCASE("Offsets cannot wrap around") + { CHECK(grid.isNeighborCell(first, {0, 0, 0})); CHECK(grid.isNeighborCell(first, {0, 0, 1})); CHECK_FALSE(grid.isNeighborCell(first, {0, 0, -1})); @@ -82,7 +89,8 @@ TEST_CASE("Grid3DFixed") { CHECK_FALSE(grid.isNeighborCell(last, {0, 0, 1})); } - SUBCASE("Spatial coordinates has to be inside the box") { + SUBCASE("Spatial coordinates has to be inside the box") + { CHECK(grid.isCellAt({0., 0., 0.})); CHECK(grid.isCellAt({1., 1., 1.})); CHECK(grid.isCellAt({19., 29., 39.})); @@ -97,7 +105,8 @@ TEST_CASE("Grid3DFixed") { } } -TEST_CASE("Grid3DPeriodic") { +TEST_CASE("Grid3DPeriodic") +{ using TestGridBase = Grid3DPeriodic; using CellCoord = TestGridBase::CellCoord; TestGridBase grid({20., 30., 40.}, 1.5); @@ -108,7 +117,8 @@ TEST_CASE("Grid3DPeriodic") { REQUIRE_EQ(grid.size(), end.prod()); - SUBCASE("All cell coordinates are allowed") { + SUBCASE("All cell coordinates are allowed") + { CHECK(grid.isCell(first)); CHECK(grid.isCell(first - CellCoord{1, 0, 0})); CHECK(grid.isCell(first - CellCoord{0, 1, 0})); @@ -131,7 +141,8 @@ TEST_CASE("Grid3DPeriodic") { CHECK((grid.coordinates(grid.index(coord1)) == coord1).all()); CHECK_NE(grid.index(coord1.reverse()), grid.index(coord1)); - SUBCASE("Offsets wrap around, but not double around") { + SUBCASE("Offsets wrap around, but not double around") + { CHECK(grid.isNeighborCell(first, {0, 0, 0})); CHECK(grid.isNeighborCell(first, {0, 0, 1})); CHECK(grid.isNeighborCell(first, {0, 0, -1})); @@ -143,7 +154,8 @@ TEST_CASE("Grid3DPeriodic") { CHECK_FALSE(grid.isNeighborCell(first, {0, 0, 1 + 200 / 15})); } - SUBCASE("Spatial coordinates wrap around the box") { + SUBCASE("Spatial coordinates wrap around the box") + { CHECK(grid.isCellAt({0., 0., 0.})); CHECK(grid.isCellAt({1., 1., 1.})); CHECK(grid.isCellAt({19., 29., 39.})); @@ -164,27 +176,32 @@ TEST_CASE("Grid3DPeriodic") { namespace Container { TEST_CASE_TEMPLATE("Cell List Container", TContainer, Container>, - Container>) { + Container>) +{ const int max = 1000; const int cell_ndx = 20; TContainer container(max); - SUBCASE("Empty cell") { + SUBCASE("Empty cell") + { CHECK_EQ(container.indices().size(), 0); CHECK_EQ(container.get(cell_ndx).size(), 0); - SUBCASE("Add two elements to a single cell") { + SUBCASE("Add two elements to a single cell") + { container.insert(1, cell_ndx); container.insert(2, cell_ndx); CHECK_EQ(container.indices().size(), 1); CHECK_EQ(container.get(cell_ndx).size(), 2); - SUBCASE("Remove elements from the cell") { + SUBCASE("Remove elements from the cell") + { container.erase(1, cell_ndx); CHECK_EQ(container.get(cell_ndx).size(), 1); } - SUBCASE("Remove non-existing element") { + SUBCASE("Remove non-existing element") + { container.erase(-1, cell_ndx); CHECK_EQ(container.get(cell_ndx).size(), 2); } @@ -193,24 +210,28 @@ TEST_CASE_TEMPLATE("Cell List Container", TContainer, Container> cell_list({10., 10., 10.}, 1.); using CellCoord = GridOf::CellCoord; const CellCoord cell{2, 3, 2}; REQUIRE_EQ(cell_list.getMembers(cell).size(), 0); - SUBCASE("when insert") { + SUBCASE("when insert") + { cell_list.addMember(10, cell); cell_list.addMember(11, cell); CHECK_EQ(cell_list.getMembers(cell).size(), 2); - SUBCASE("when removed") { + SUBCASE("when removed") + { cell_list.removeMember(10); CHECK_EQ(cell_list.getMembers(cell).size(), 1); } } } -TEST_CASE("CellListSpatial") { +TEST_CASE("CellListSpatial") +{ CellListSpatial> cell_list({10., 10., 10.}, 1.); using CellCoord = GridOf::CellCoord; using Point = GridOf::Point; @@ -218,18 +239,21 @@ TEST_CASE("CellListSpatial") { const CellCoord cell{2, 3, 2}; REQUIRE_EQ(cell_list.getMembers(cell).size(), 0); - SUBCASE("when insert") { + SUBCASE("when insert") + { cell_list.insertMember(10, pos); cell_list.insertMember(11, pos); CHECK_EQ(cell_list.getMembers(cell).size(), 2); - SUBCASE("when removed") { + SUBCASE("when removed") + { cell_list.removeMember(10); CHECK_EQ(cell_list.getMembers(cell).size(), 1); } } } -TEST_CASE("CellListDifference") { +TEST_CASE("CellListDifference") +{ using CellList = CellListReverseMap>; CellList x({10., 10., 10.}, 1.); auto minuend = std::make_shared(PointOf{10., 10., 10.}, 1.); diff --git a/src/celllistimpl.h b/src/celllistimpl.h index 48d1899a2..7182ba004 100644 --- a/src/celllistimpl.h +++ b/src/celllistimpl.h @@ -25,29 +25,30 @@ * @namespace CellList * # Cell List * - * A cell list allow to organize members, e.g., particles, into a grid cells hence allowing fast determination of their - * approximate location in space. Cell lists can be used to quickly compute distances or interaction only between - * close members. The granuality of distance is determined by the cell size and ussualy arise from some sort of cut off - * distance. + * A cell list allow to organize members, e.g., particles, into a grid cells hence allowing fast + * determination of their approximate location in space. Cell lists can be used to quickly compute + * distances or interaction only between close members. The granuality of distance is determined by + * the cell size and ussualy arise from some sort of cut off distance. * - * Each cell list is composed of a grid and a container by double inheritence. The grid (Grid) is responsible for - * the cell indexing and the container (Container) holds he members assigned to respective cells. The class template - * CellListBase is a bare-bone implementation with the the essential functionality only. It should be extended using - * templated mixin to insert features as needed. + * Each cell list is composed of a grid and a container by double inheritence. The grid (Grid) is + * responsible for the cell indexing and the container (Container) holds he members assigned to + * respective cells. The class template CellListBase is a bare-bone implementation with the the + * essential functionality only. It should be extended using templated mixin to insert features as + * needed. * * ## Cell Grid * - * The GridType defines basic data types, GridBase calculates the cell index from the cell coordinates and vice versa, - * generally assuming fixed boundaries. Respective outer classes (FixedBoundaryGrid, PeriodicBoundaryGrid) wrapps - * boundary conditions. + * The GridType defines basic data types, GridBase calculates the cell index from the cell + * coordinates and vice versa, generally assuming fixed boundaries. Respective outer classes + * (FixedBoundaryGrid, PeriodicBoundaryGrid) wrapps boundary conditions. * - * The convenient template aliases GridBoundary, Grid3DBoundary, Grid3DFixed and Grid3DPeriodic are provided. A simple - * structure GridOffsets3D contains commonly used neighbours' offsets. + * The convenient template aliases GridBoundary, Grid3DBoundary, Grid3DFixed and Grid3DPeriodic are + * provided. A simple structure GridOffsets3D contains commonly used neighbours' offsets. * * ## Containers * - * Containers holds the members and allow a simple manipulation (insertion and deletion). A dense (using a vector) and - * sparse (using a map) implementations are provided. + * Containers holds the members and allow a simple manipulation (insertion and deletion). A dense + * (using a vector) and sparse (using a map) implementations are provided. */ namespace Faunus::CellList { @@ -60,7 +61,8 @@ namespace Grid { * @brief Interface of a grid * @tparam TGridType a cell grid types declaration */ -template struct AbstractGrid : private TGridType { +template struct AbstractGrid : private TGridType +{ using GridType = TGridType; using typename GridType::CellCoord; using typename GridType::CellIndex; @@ -77,7 +79,8 @@ template struct AbstractGrid : private TGridType { virtual ~AbstractGrid() = default; }; -template struct AbstractNeighborGrid : public virtual AbstractGrid { +template struct AbstractNeighborGrid : public virtual AbstractGrid +{ using GridType = TGridType; using typename TGridType::CellCoord; @@ -88,12 +91,13 @@ template struct AbstractNeighborGrid : public virtual Abstr /** * @brief A cell grid core class assuming fixed boundary conditions. * - * The grid converts spatial coordinates to cell coordinates and cell coordinates to the scalar cell index. The grid - * is designed for arbitrary number of dimensions. + * The grid converts spatial coordinates to cell coordinates and cell coordinates to the scalar cell + * index. The grid is designed for arbitrary number of dimensions. * * @tparam TGridType a cell grid type declaration */ -template class GridBase : public virtual AbstractGrid { +template class GridBase : public virtual AbstractGrid +{ public: using GridType = typename AbstractGrid::GridType; using typename GridType::CellCoord; @@ -109,10 +113,12 @@ template class GridBase : public virtual AbstractGrid class GridBase : public virtual AbstractGrid class GridBase : public virtual AbstractGrid(); } @@ -143,7 +151,10 @@ template class GridBase : public virtual AbstractGrid class GridBase : public virtual AbstractGridgetCellListEnd()).all() && (coordinates >= CellCoord::Zero()).all(); + bool isCell(const CellCoord& coordinates) const override + { + return (coordinates < this->getCellListEnd()).all() && + (coordinates >= CellCoord::Zero()).all(); } /** @@ -165,7 +178,8 @@ template class GridBase : public virtual AbstractGridgetBox()).all() && (point >= Point::Zero()).all(); } @@ -178,17 +192,22 @@ template class GridBase : public virtual AbstractGrid class GridBase : public virtual AbstractGrid(); cell = box / cell_list_end.template cast(); @@ -225,7 +246,8 @@ template class GridBase : public virtual AbstractGrid -class FixedBoundaryGrid : public GridBase, virtual public AbstractNeighborGrid { +class FixedBoundaryGrid : public GridBase, virtual public AbstractNeighborGrid +{ using Base = GridBase; public: @@ -241,7 +263,8 @@ class FixedBoundaryGrid : public GridBase, virtual public AbstractNei * @param offset * @return true if cell coordinates are valid, false otherwise */ - bool isNeighborCell(const CellCoord& coordinates, const CellCoord& offset) const { + bool isNeighborCell(const CellCoord& coordinates, const CellCoord& offset) const + { return Base::isCell(coordinates + offset); } @@ -251,17 +274,19 @@ class FixedBoundaryGrid : public GridBase, virtual public AbstractNei /** * @brief Periodic boundary conditions. * - * Both spatial coordinates and cell coordinates are first converted to the original image and then evaluated. - * To avoid repeated cell inclusion by completing a full circle in periodic boundary conditions, offsets are limited - * in both directions assuming symetry. + * Both spatial coordinates and cell coordinates are first converted to the original image and then + * evaluated. To avoid repeated cell inclusion by completing a full circle in periodic boundary + * conditions, offsets are limited in both directions assuming symetry. * - * @todo Currently only full or none PBC is supperted. To support geometries like a slit, more fine distingtion is - * needed. + * @todo Currently only full or none PBC is supperted. To support geometries like a slit, more fine + * distingtion is needed. * * @tparam TGridType a cell grid type declaration */ template -class PeriodicBoundaryGrid : public GridBase, virtual public AbstractNeighborGrid { +class PeriodicBoundaryGrid : public GridBase, + virtual public AbstractNeighborGrid +{ using Base = GridBase; public: @@ -289,13 +314,16 @@ class PeriodicBoundaryGrid : public GridBase, virtual public Abstract * @param coordinates cell coordinates * @return cell index */ - CellIndex index(const CellCoord& coordinates) const override { + CellIndex index(const CellCoord& coordinates) const override + { auto pbc_coordinates = coordinates; auto& boundary_coords = this->getCellListEnd(); for (auto i = 0; i < pbc_coordinates.size(); ++i) { if (coordinates[i] < 0) { - pbc_coordinates[i] += boundary_coords[i] * (std::abs(coordinates[i]) / boundary_coords[i] + 1); - } else if (coordinates[i] >= boundary_coords[i]) { + pbc_coordinates[i] += + boundary_coords[i] * (std::abs(coordinates[i]) / boundary_coords[i] + 1); + } + else if (coordinates[i] >= boundary_coords[i]) { pbc_coordinates[i] -= boundary_coords[i] * (coordinates[i] / boundary_coords[i]); } } @@ -310,7 +338,8 @@ class PeriodicBoundaryGrid : public GridBase, virtual public Abstract * @param position * @return cell coordinates */ - CellCoord coordinatesAt(const Point& position) const override { + CellCoord coordinatesAt(const Point& position) const override + { assert(isCellAt(position)); return (position / this->getCell()).floor().template cast(); } @@ -320,7 +349,10 @@ class PeriodicBoundaryGrid : public GridBase, virtual public Abstract * @param position spatial position * @return cell index */ - CellIndex indexAt(const Point& position) const override { return this->index(this->coordinatesAt(position)); } + CellIndex indexAt(const Point& position) const override + { + return this->index(this->coordinatesAt(position)); + } /** * @brief Verifies if coordinates are valid in the grid. @@ -343,30 +375,43 @@ class PeriodicBoundaryGrid : public GridBase, virtual public Abstract bool isCellAt(const Point&) const override { return true; } /** - * @brief Verifies if offseted coordinates are valid taking into account periodic boundary conditions. + * @brief Verifies if offseted coordinates are valid taking into account periodic boundary + * conditions. * - * Symmetric offsets around the reference cell are assumed. To avoid repeated inclusion of a cell, the offset - * is limited to be at most the half of the cell grid, with an attention paid to the odd and even number of cells. + * Symmetric offsets around the reference cell are assumed. To avoid repeated inclusion of a + * cell, the offset is limited to be at most the half of the cell grid, with an attention paid + * to the odd and even number of cells. * * @param coordinates * @param offset * @return true if cell coordinates are valid, false otherwise */ - bool isNeighborCell([[maybe_unused]] const CellCoord& coordinates, const CellCoord& offset) const override { - return (offset >= neighbours_cell_offset_min).all() && (offset <= neighbours_cell_offset_max).all(); + bool isNeighborCell([[maybe_unused]] const CellCoord& coordinates, + const CellCoord& offset) const override + { + return (offset >= neighbours_cell_offset_min).all() && + (offset <= neighbours_cell_offset_max).all(); } - PeriodicBoundaryGrid(const Point& box, SpaceAxis cell_dimension) : Base(box, cell_dimension) { updateCellGrid(); } + PeriodicBoundaryGrid(const Point& box, SpaceAxis cell_dimension) + : Base(box, cell_dimension) + { + updateCellGrid(); + } private: - CellCoord neighbours_cell_offset_min; //!< utmost negative offset of neighbours to prevent circular match in PBC - CellCoord neighbours_cell_offset_max; //!< utmost positive offset of neighbours to prevent circular match in PBC + CellCoord neighbours_cell_offset_min; //!< utmost negative offset of neighbours to prevent + //!< circular match in PBC + CellCoord neighbours_cell_offset_max; //!< utmost positive offset of neighbours to prevent + //!< circular match in PBC /** - * @brief Setup immutable parameters based on the box dimensions. To be called in constructors only. + * @brief Setup immutable parameters based on the box dimensions. To be called in constructors + * only. * @param new_box */ - void updateCellGrid() { + void updateCellGrid() + { const auto cell_list_middle = (this->getCellListEnd() - 1).template cast() * 0.5; neighbours_cell_offset_min = -cell_list_middle.floor().template cast(); neighbours_cell_offset_max = cell_list_middle.ceil().template cast(); @@ -378,8 +423,8 @@ class PeriodicBoundaryGrid : public GridBase, virtual public Abstract * @tparam PBC if periodic boundary condition shall be employed */ template -using GridBoundary = - typename std::conditional, FixedBoundaryGrid>::type; +using GridBoundary = typename std::conditional, + FixedBoundaryGrid>::type; /** * @brief Provides a 3D cell grid. * @tparam PBC if periodic boundary condition shall be employed @@ -392,31 +437,35 @@ using Grid3DPeriodic = Grid3DBoundary; /** * @namespace Container * - * Containers store members in individual cells with a common minimalistic interface. DenseContainer (based on - * a vector) and Sparse container (based on a map) are currently provided. + * Containers store members in individual cells with a common minimalistic interface. DenseContainer + * (based on a vector) and Sparse container (based on a map) are currently provided. */ namespace Container { /** * @brief Container based on a vector suitable for system with a uniform and high member density. * - * As all cells (even the empty ones) reside in memory, the vector has size of gridBase::size(). Very fast access at - * the expense of a bigger memory footprint, expecialy in sparse systems. + * As all cells (even the empty ones) reside in memory, the vector has size of gridBase::size(). + * Very fast access at the expense of a bigger memory footprint, expecialy in sparse systems. * * @tparam TMember member (value) * @tparam TIndex index (key); corresponds do the CellIndex of GridType */ -template class DenseContainer : virtual public AbstractContainer { +template +class DenseContainer : virtual public AbstractContainer +{ public: using ContainerType = AbstractContainer; using typename ContainerType::Index; using typename ContainerType::Members; - const Members& get(Index index) const override { + const Members& get(Index index) const override + { assert(index >= 0 && index < indexEnd()); return container[index]; } - Members& get(Index index) override { + Members& get(Index index) override + { assert(index >= 0 && index < indexEnd()); return container[index]; } @@ -426,23 +475,27 @@ template class DenseContainer : virtual publ /** * @brief Return all cell indices that may contain members. * - * It is not guaranteed that all returned cells are non-empty though. However all non-empty cells are returned. + * It is not guaranteed that all returned cells are non-empty though. However all non-empty + * cells are returned. * * @return vector of cell indices */ - std::vector indices() const override { + std::vector indices() const override + { std::vector indices; indices.reserve(std::distance(container.begin(), container.end())); auto iterator = container.begin(); - while ((iterator = std::find_if(iterator, container.end(), - [](const auto& members) { return !members.empty(); })) != container.end()) { + while ((iterator = std::find_if(iterator, container.end(), [](const auto& members) { + return !members.empty(); + })) != container.end()) { indices.push_back(std::distance(iterator, container.begin())); std::advance(iterator, 1); } return indices; } - explicit DenseContainer(std::size_t size) { + explicit DenseContainer(std::size_t size) + { if (size >= max_container_size) { faunus_logger->error("Size of the cell list container is too large! \n" "Try using a sparse version (i.e. dense: false)"); @@ -468,24 +521,32 @@ template class DenseContainer : virtual publ * @tparam TMember member (value) * @tparam TIndex index (key); corresponds do CellIndex of GridBase */ -template class SparseContainer : virtual public AbstractContainer { +template +class SparseContainer : virtual public AbstractContainer +{ public: using ContainerType = AbstractContainer; using typename ContainerType::Index; using typename ContainerType::Members; - const Members& get(Index index) const override { + const Members& get(Index index) const override + { assert(index >= 0 && index < indexEnd()); try { return container.at(index); - } catch (std::out_of_range& e) { return empty_set; } + } + catch (std::out_of_range& e) { + return empty_set; + } } - Members& get(Index index) override { + Members& get(Index index) override + { assert(index >= 0 && index < indexEnd()); try { return container.at(index); - } catch (std::out_of_range& e) { + } + catch (std::out_of_range& e) { [[maybe_unused]] auto [iterator, flag] = container.emplace(index, Members{}); return iterator->second; } @@ -500,7 +561,8 @@ template class SparseContainer : virtual pub * * @return vector of cell indices */ - std::vector indices() const override { + std::vector indices() const override + { std::vector indices; indices.reserve(std::distance(container.begin(), container.end())); std::transform(container.begin(), container.end(), back_inserter(indices), @@ -508,14 +570,17 @@ template class SparseContainer : virtual pub return indices; } - explicit SparseContainer(std::size_t size) : index_end(size) {} + explicit SparseContainer(std::size_t size) + : index_end(size) + { + } protected: Index indexEnd() const { return index_end; } private: - Index index_end; //!< the lowest index not allowed to appear, i.e., the grid size - const Members empty_set; //!< an empty set, e.g., for out of boundary conditions + Index index_end; //!< the lowest index not allowed to appear, i.e., the grid size + const Members empty_set; //!< an empty set, e.g., for out of boundary conditions std::unordered_map container; //!< container itself }; @@ -525,18 +590,21 @@ template class SparseContainer : virtual pub * @tparam TIndex * @tparam TContainer */ -template class Container : public TContainer { +template class Container : public TContainer +{ public: using ContainerType = ContainerTypeOf; using Member = MemberOf; using Index = IndexOf; - void insert(const Member& member, const Index index_new) { + void insert(const Member& member, const Index index_new) + { assert(index_new < this->indexEnd()); this->get(index_new).push_back(member); } - void erase(const Member& member, const Index index_old) { + void erase(const Member& member, const Index index_old) + { assert(index_old < this->indexEnd()); auto& members = this->get(index_old); // std::erase_if available in C++20 @@ -545,7 +613,8 @@ template class Container : public TContainer { // end of std::erase_if } - void move(const Member& member, const Index index_old, const Index index_new) { + void move(const Member& member, const Index index_old, const Index index_new) + { erase(member, index_old); insert(member, index_new); } @@ -564,11 +633,14 @@ template class Container : public TContainer { * @tparam TGrid */ template -class CellListBase : protected TGrid, - protected TContainer, - virtual public AbstractImmutableCellList, GridTypeOf> { +class CellListBase + : protected TGrid, + protected TContainer, + virtual public AbstractImmutableCellList, GridTypeOf> +{ public: - using AbstractCellList = AbstractImmutableCellList, GridTypeOf>; + using AbstractCellList = + AbstractImmutableCellList, GridTypeOf>; using Grid = TGrid; using Container = TContainer; using typename AbstractCellList::CellCoord; //!< grid (cell) coordinates type @@ -582,19 +654,24 @@ class CellListBase : protected TGrid, * @param cell_coordinates * @return vector of cell members */ - const Members& getMembers(const CellCoord& cell_coordinates) override { + const Members& getMembers(const CellCoord& cell_coordinates) override + { return this->get(this->index(cell_coordinates)); } /** - * @brief Get cell members at given offseted cell coordinates, or empty if offseted dcoordinates do not exist. + * @brief Get cell members at given offseted cell coordinates, or empty if offseted dcoordinates + * do not exist. * @param cell_coordinates * @return vector of cell members or empty vector if cell does not exist */ - const Members& getNeighborMembers(const CellCoord& cell_coordinates, const CellCoord& cell_offset) override { + const Members& getNeighborMembers(const CellCoord& cell_coordinates, + const CellCoord& cell_offset) override + { if (this->isNeighborCell(cell_coordinates, cell_offset)) { return getMembers(cell_coordinates + cell_offset); - } else { + } + else { return getEmpty(); } } @@ -602,10 +679,12 @@ class CellListBase : protected TGrid, /** * @return range of containg coordinates of all possibly non-empty cells */ - std::vector getCells() const override { + std::vector getCells() const override + { const auto indices = this->indices(); return ranges::cpp20::views::all(indices) | - ranges::cpp20::views::transform([this](auto index) { return this->coordinates(index); }) | + ranges::cpp20::views::transform( + [this](auto index) { return this->coordinates(index); }) | ranges::to_vector; } @@ -627,7 +706,11 @@ class CellListBase : protected TGrid, * @brief Construct an empty cell list from a grid. * @param cell_grid */ - explicit CellListBase(const TGrid& cell_grid) : Grid(cell_grid), Container(cell_grid.size()) {} + explicit CellListBase(const TGrid& cell_grid) + : Grid(cell_grid) + , Container(cell_grid.size()) + { + } /** * @brief Construct an empty cell list knowing a box dimension and minimal cell dimension. @@ -635,7 +718,10 @@ class CellListBase : protected TGrid, * @param cell_dimension minimal length of the cell's edge */ CellListBase(const typename TGrid::Point& box, const typename TGrid::SpaceAxis cell_dimension) - : Grid(box, cell_dimension), Container(Grid::size()) {} + : Grid(box, cell_dimension) + , Container(Grid::size()) + { + } protected: // const auto& getMembers(const CellIndex cell_index) const { return this->get(cell_index); } @@ -652,25 +738,30 @@ class CellListBase : protected TGrid, /** * @brief A template type alias for CellList. */ -template class TCellList = CellListBase, +template class TCellList = CellListBase, template class TContainer = Container::DenseContainer> -using CellListType = TCellList>>, TGrid>; +using CellListType = + TCellList>>, TGrid>; /** * @brief A mixing returning members in a cell in sorted order, e.g., for lookup or filtering. * - * Sorting is performed on as-needed basis, i.e., only when requested and if modified since the last request. + * Sorting is performed on as-needed basis, i.e., only when requested and if modified since the last + * request. * * @tparam TBase */ template -class SortableCellList : public CellListBase, - virtual public AbstractSortableCellList, GridTypeOf> { +class SortableCellList + : public CellListBase, + virtual public AbstractSortableCellList, GridTypeOf> +{ using TBase = CellListBase; public: - using AbstractCellList = - typename AbstractSortableCellList, GridTypeOf>::AbstractCellList; + using AbstractCellList = typename AbstractSortableCellList, + GridTypeOf>::AbstractCellList; using Grid = TGrid; using Container = TContainer; using typename AbstractCellList::CellCoord; //!< grid (cell) coordinates type @@ -683,7 +774,8 @@ class SortableCellList : public CellListBase, * @param cell_coordinates * @return sorted members */ - const Members& getSortedMembers(const CellCoord& cell_coordinates) override { + const Members& getSortedMembers(const CellCoord& cell_coordinates) override + { return getSorted(this->index(cell_coordinates)); } @@ -693,10 +785,13 @@ class SortableCellList : public CellListBase, * @param cell_offset * @return sorted members */ - const Members& getNeighborSortedMembers(const CellCoord& cell_coordinates, const CellCoord& cell_offset) override { + const Members& getNeighborSortedMembers(const CellCoord& cell_coordinates, + const CellCoord& cell_offset) override + { if (this->isNeighborCell(cell_coordinates, cell_offset)) { return getSortedMembers(cell_coordinates + cell_offset); - } else { + } + else { return this->getEmpty(); } } @@ -705,27 +800,35 @@ class SortableCellList : public CellListBase, * @brief Construct an empty cell list from a grid. * @param cell_grid */ - explicit SortableCellList(const GridOf& cell_grid) : TBase(cell_grid) { is_sorted.resize(this->size(), false); } + explicit SortableCellList(const GridOf& cell_grid) + : TBase(cell_grid) + { + is_sorted.resize(this->size(), false); + } /** * @brief Construct an empty cell list knowing a box dimension and minimal cell dimension. * @param box spatial dimension of the box * @param cell_dimension minimal length of the cell's edge */ - SortableCellList(const typename GridOf::Point& box, const typename GridOf::SpaceAxis cell_dimension) - : TBase(box, cell_dimension) { + SortableCellList(const typename GridOf::Point& box, + const typename GridOf::SpaceAxis cell_dimension) + : TBase(box, cell_dimension) + { is_sorted.resize(this->size(), false); } protected: - void insert(const Member& member, const CellIndex& new_cell_index) { + void insert(const Member& member, const CellIndex& new_cell_index) + { TBase::insert(member, new_cell_index); if (is_sorted[new_cell_index]) { is_sorted[new_cell_index] = false; } } - void remove(const Member& member, const CellIndex& old_cell_index) { + void remove(const Member& member, const CellIndex& old_cell_index) + { TBase::remove(member, old_cell_index); if (is_sorted[old_cell_index]) { is_sorted[old_cell_index] = false; @@ -737,7 +840,8 @@ class SortableCellList : public CellListBase, * @param cell_index * @return sorted members */ - const Members& getSorted(const CellIndex cell_index) { + const Members& getSorted(const CellIndex cell_index) + { auto& members = this->get(cell_index); if (!is_sorted[cell_index]) { std::sort(members.begin(), members.end()); @@ -753,12 +857,13 @@ class SortableCellList : public CellListBase, }; /** - * @brief Reverse mapping (from particle to its cell) is stored. Particles can be thus removed or update without - * providing their previous cell. + * @brief Reverse mapping (from particle to its cell) is stored. Particles can be thus removed or + * update without providing their previous cell. * * @tparam TBase */ -template class CellListReverseMap : public TBase { +template class CellListReverseMap : public TBase +{ public: using typename TBase::AbstractCellList; //!< a structure with types definitions using typename AbstractCellList::CellCoord; //!< grid (cell) coordinates type @@ -767,17 +872,20 @@ template class CellListReverseMap : public TBase { using typename TBase::Container; using typename TBase::Grid; - void addMember(const Member& member, const CellCoord& new_cell_coordinates) { + void addMember(const Member& member, const CellCoord& new_cell_coordinates) + { this->insert(member, this->index(new_cell_coordinates)); } - void removeMember(const Member& member) { + void removeMember(const Member& member) + { const auto old_cell_index = member2cell.at(member); this->erase(member, old_cell_index); member2cell.erase(member); } - void updateMember(const Member& member, const CellCoord& new_cell_coordinates) { + void updateMember(const Member& member, const CellCoord& new_cell_coordinates) + { this->update(member, this->index(new_cell_coordinates)); } @@ -788,12 +896,14 @@ template class CellListReverseMap : public TBase { bool containsMember(const Member& member) { return member2cell.count(member) != 0; } /** - * @brief Imports members from other list without computing cell coordinates from member positions. + * @brief Imports members from other list without computing cell coordinates from member + * positions. * @tparam T * @param source * @param members */ - template void importMembers(CellListReverseMap& source, const T& members) { + template void importMembers(CellListReverseMap& source, const T& members) + { for (const auto& member : members) { insert(member, source.member2cell[member]); } @@ -802,13 +912,15 @@ template class CellListReverseMap : public TBase { using TBase::TBase; protected: - void insert(const Member& member, const CellIndex& new_cell_index) { + void insert(const Member& member, const CellIndex& new_cell_index) + { assert(member2cell.count(member) == 0); // FIXME C++20 contains TBase::insert(member, new_cell_index); member2cell.insert({member, new_cell_index}); } - void update(const Member& member, const CellIndex& new_cell_index) { + void update(const Member& member, const CellIndex& new_cell_index) + { const auto old_cell_index = member2cell.at(member); if (new_cell_index != old_cell_index) { TBase::move(member, old_cell_index, new_cell_index); @@ -822,24 +934,29 @@ template class CellListReverseMap : public TBase { }; /** - * @brief A mixin using spatial coordinates instead of cell coordinates when inserting or removing a member. + * @brief A mixin using spatial coordinates instead of cell coordinates when inserting or removing a + * member. * * @tparam TBase */ -template class CellListSpatial : public CellListReverseMap { +template class CellListSpatial : public CellListReverseMap +{ public: - using typename CellListReverseMap::AbstractCellList; //!< a structure with types definitions + using typename CellListReverseMap::AbstractCellList; //!< a structure with types + //!< definitions using typename CellListReverseMap::Grid; using typename CellListReverseMap::Container; using typename AbstractCellList::Member; //!< member type using typename AbstractCellList::GridType::Point; //!< point - void insertMember(const Member& member, const Point& new_position) { + void insertMember(const Member& member, const Point& new_position) + { const auto new_cell_index = this->indexAt(new_position); this->insert(member, new_cell_index); } - void updateMemberAt(const Member& member, const Point& new_position) { + void updateMemberAt(const Member& member, const Point& new_position) + { const auto new_cell_index = this->indexAt(new_position); this->update(member, new_cell_index); } @@ -848,8 +965,8 @@ template class CellListSpatial : public CellListReverseMap }; /** - * @brief Wrapper of two cell list to efficiently provide difference of members, i.e., members presented in the minuend - * and not in the subtrahend. + * @brief Wrapper of two cell list to efficiently provide difference of members, i.e., members + * presented in the minuend and not in the subtrahend. * * @todo Allow cache invalidation when underlying cell lists change * @@ -857,8 +974,10 @@ template class CellListSpatial : public CellListReverseMap * @tparam TCellListSubtrahend */ template -class CellListDifference : virtual public AbstractSortableCellList>, - GridTypeOf>> { +class CellListDifference + : virtual public AbstractSortableCellList>, + GridTypeOf>> +{ using CellListMinuend = TCellListMinuend; using CellListSubtrahend = TCellListSubtrahend; @@ -871,47 +990,59 @@ class CellListDifference : virtual public AbstractSortableCellListgetSortedMembers(cell_coordinates); if (std::size(subtrahend_cell) == 0) { // nothing to subtract return minuend->getSortedMembers(cell_coordinates); - } else { + } + else { const auto cell_index = minuend->getGrid().index(cell_coordinates); auto difference_cell_it = difference_cache.find(cell_index); if (difference_cell_it == difference_cache.end()) { // not cached yet auto minuend_cell = minuend->getSortedMembers(cell_coordinates); - std::tie(difference_cell_it, std::ignore) = difference_cache.emplace(cell_index, Members{}); - std::set_difference(minuend_cell.begin(), minuend_cell.end(), subtrahend_cell.begin(), - subtrahend_cell.end(), std::back_inserter(difference_cell_it->second)); + std::tie(difference_cell_it, std::ignore) = + difference_cache.emplace(cell_index, Members{}); + std::set_difference(minuend_cell.begin(), minuend_cell.end(), + subtrahend_cell.begin(), subtrahend_cell.end(), + std::back_inserter(difference_cell_it->second)); } return difference_cell_it->second; } } - const Members& getMembers(const CellCoord& cell_coordinates) override { + const Members& getMembers(const CellCoord& cell_coordinates) override + { if (subtrahend->getMembers(cell_coordinates).empty()) { // avoid difference and soring when not necessary return minuend->getMembers(cell_coordinates); - } else { + } + else { return getSortedMembers(cell_coordinates); } } - const Members& getNeighborSortedMembers(const CellCoord& cell_coordinates, const CellCoord& cell_offset) override { + const Members& getNeighborSortedMembers(const CellCoord& cell_coordinates, + const CellCoord& cell_offset) override + { if (minuend->getGrid().isNeighborCell(cell_coordinates, cell_offset)) { // avoid difference and soring when not necessary return getSortedMembers(cell_coordinates + cell_offset); - } else { + } + else { return minuend->getNeighborMembers(cell_coordinates, cell_offset); } } - const Members& getNeighborMembers(const CellCoord& cell_coordinates, const CellCoord& cell_offset) override { + const Members& getNeighborMembers(const CellCoord& cell_coordinates, + const CellCoord& cell_offset) override + { if (minuend->getGrid().isNeighborCell(cell_coordinates, cell_offset)) { return getMembers(cell_coordinates + cell_offset); - } else { + } + else { return minuend->getNeighborMembers(cell_coordinates, cell_offset); } } @@ -922,8 +1053,12 @@ class CellListDifference : virtual public AbstractSortableCellList getCells() const override { return minuend->getCells(); } - CellListDifference(std::shared_ptr minuend, std::shared_ptr subtrahend) - : minuend(minuend), subtrahend(subtrahend) {} + CellListDifference(std::shared_ptr minuend, + std::shared_ptr subtrahend) + : minuend(minuend) + , subtrahend(subtrahend) + { + } private: std::shared_ptr minuend; @@ -932,4 +1067,3 @@ class CellListDifference : virtual public AbstractSortableCellList 0) { - j["skipped"] = double(small_box_encountered) / double(number_of_attempted_moves); // todo rename the json attribute + j["skipped"] = double(small_box_encountered) / + double(number_of_attempted_moves); // todo rename the json attribute } roundJSON(j, 3); } -void ChainRotationMoveBase::_move(Change &change) { +void ChainRotationMoveBase::_move(Change& change) +{ permit_move = true; sqdispl = 0; if (std::fabs(dprot) > 1e-9) { @@ -34,33 +39,53 @@ void ChainRotationMoveBase::_move(Change &change) { } } -void ChainRotationMoveBase::_accept(Change &) { msqdispl += sqdispl; } -void ChainRotationMoveBase::_reject(Change &) { msqdispl += 0; } -double ChainRotationMoveBase::bias(Change &, double, double) { return permit_move ? 0 : pc::infty; } +void ChainRotationMoveBase::_accept(Change&) +{ + msqdispl += sqdispl; +} -ChainRotationMoveBase::ChainRotationMoveBase(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) {} +void ChainRotationMoveBase::_reject(Change&) +{ + msqdispl += 0; +} + +double ChainRotationMoveBase::bias(Change&, double, double) +{ + return permit_move ? 0 : pc::infty; +} -ChainRotationMove::ChainRotationMove(Space &spc, const std::string& name, const std::string& cite) - : ChainRotationMoveBase(spc, name, cite) { +ChainRotationMoveBase::ChainRotationMoveBase(Space& spc, const std::string& name, + const std::string& cite) + : Move(spc, name, cite) +{ +} + +ChainRotationMove::ChainRotationMove(Space& spc, const std::string& name, const std::string& cite) + : ChainRotationMoveBase(spc, name, cite) +{ repeat = -1; } -void ChainRotationMove::_from_json(const json &j) { +void ChainRotationMove::_from_json(const json& j) +{ TBase::_from_json(j); molid = findMoleculeByName(molname).id(); } -void ChainRotationMove::rotate_segment(double angle) { +void ChainRotationMove::rotate_segment(double angle) +{ if (!segment_ndx.empty()) { - auto &chain = *molecule_iter; + auto& chain = *molecule_iter; auto old_cm = chain.mass_center; - // Uses an implementation from the old Pivot class. The translation of the chain might be unnecessary. + // Uses an implementation from the old Pivot class. The translation of the chain might be + // unnecessary. auto shift_pos = spc.particles.at(axis_ndx[0]).pos; // chain.unwrap(spc.geo.getDistanceFunc()); // remove pbc chain.translate(-shift_pos, spc.geometry.getBoundaryFunc()); - auto origin_pos = spc.particles.at(axis_ndx[0]).pos; // != shift_pos because of chain.translate - auto axis_pos = spc.geometry.vdist(origin_pos, spc.particles.at(axis_ndx[1]).pos).normalized(); + auto origin_pos = + spc.particles.at(axis_ndx[0]).pos; // != shift_pos because of chain.translate + auto axis_pos = + spc.geometry.vdist(origin_pos, spc.particles.at(axis_ndx[1]).pos).normalized(); Eigen::Quaterniond Q(Eigen::AngleAxisd(angle, axis_pos)); auto M = Q.toRotationMatrix(); for (auto index : segment_ndx) { @@ -76,23 +101,30 @@ void ChainRotationMove::rotate_segment(double angle) { } } } -void ChainRotationMove::store_change(Change &change) { + +void ChainRotationMove::store_change(Change& change) +{ if (!segment_ndx.empty()) { - auto &chain = *molecule_iter; + auto& chain = *molecule_iter; auto offset = std::distance(spc.particles.begin(), chain.begin()); Change::GroupChange change_data; for (auto i : segment_ndx) { - change_data.relative_atom_indices.push_back(i - offset); // `atoms` index are relative to chain + change_data.relative_atom_indices.push_back( + i - offset); // `atoms` index are relative to chain } - change_data.group_index = Faunus::distance(spc.groups.begin(), &chain); // integer *index* of moved group + change_data.group_index = + Faunus::distance(spc.groups.begin(), &chain); // integer *index* of moved group change_data.all = false; change_data.internal = true; // trigger internal interactions change.groups.push_back(change_data); // add to list of moved groups } } -bool ChainRotationMove::box_big_enough() { - auto &chain = *molecule_iter; - auto cm_pbc = Geometry::massCenter(chain.begin(), chain.end(), spc.geometry.getBoundaryFunc(), -chain.mass_center); + +bool ChainRotationMove::box_big_enough() +{ + auto& chain = *molecule_iter; + auto cm_pbc = Geometry::massCenter(chain.begin(), chain.end(), spc.geometry.getBoundaryFunc(), + -chain.mass_center); double cm_diff = spc.geometry.sqdist(chain.mass_center, cm_pbc); if (cm_diff > 1e-6) { small_box_encountered++; @@ -105,33 +137,48 @@ bool ChainRotationMove::box_big_enough() { return true; } -CrankshaftMove::CrankshaftMove(Space &spc, const std::string& name, const std::string& cite) : ChainRotationMove(spc, name, cite) {} +CrankshaftMove::CrankshaftMove(Space& spc, const std::string& name, const std::string& cite) + : ChainRotationMove(spc, name, cite) +{ +} -CrankshaftMove::CrankshaftMove(Space &spc) : CrankshaftMove(spc, "crankshaft", "") {} +CrankshaftMove::CrankshaftMove(Space& spc) + : CrankshaftMove(spc, "crankshaft", "") +{ +} -void CrankshaftMove::_from_json(const json &j) { +void CrankshaftMove::_from_json(const json& j) +{ TBase::_from_json(j); // maximum number of bonds between the joints of a crankshaft joint_max = j.value("joint_max", std::numeric_limits::max()); if (this->repeat < 0) { - // set the number of repetitions to the length of the chain (minus 2) times the number of the chains + // set the number of repetitions to the length of the chain (minus 2) times the number of + // the chains auto moliter = this->spc.findMolecules(this->molid); - auto &molecule = *moliter.begin(); + auto& molecule = *moliter.begin(); this->repeat = std::distance(moliter.begin(), moliter.end()) * (molecule.size() - 2); } } -size_t CrankshaftMove::select_segment() { + +size_t CrankshaftMove::select_segment() +{ size_t segment_size = 0; this->segment_ndx.clear(); - this->molecule_iter = this->spc.randomMolecule(this->molid, Faunus::move::CrankshaftMove::slump); // a random chain + this->molecule_iter = this->spc.randomMolecule( + this->molid, Faunus::move::CrankshaftMove::slump); // a random chain if (this->molecule_iter != this->spc.groups.end()) { - auto &molecule = *this->molecule_iter; + auto& molecule = *this->molecule_iter; if (molecule.size() > 2) { // must have at least three atoms - auto joint0 = Faunus::move::CrankshaftMove::slump.sample(molecule.begin(), molecule.end()); + auto joint0 = + Faunus::move::CrankshaftMove::slump.sample(molecule.begin(), molecule.end()); assert(joint0 >= molecule.begin() && joint0 < molecule.end()); - size_t range_begin = std::min(static_cast(std::distance(molecule.begin(), joint0)), joint_max); - size_t range_end = std::min(static_cast(std::distance(joint0, molecule.end()) - 1), joint_max); - auto joint1 = joint0 + Faunus::move::CrankshaftMove::slump.range(-range_begin, range_end); + size_t range_begin = + std::min(static_cast(std::distance(molecule.begin(), joint0)), joint_max); + size_t range_end = + std::min(static_cast(std::distance(joint0, molecule.end()) - 1), joint_max); + auto joint1 = + joint0 + Faunus::move::CrankshaftMove::slump.range(-range_begin, range_end); assert(joint1 >= molecule.begin() && joint1 < molecule.end()); auto joint_distance = std::distance(joint0, joint1); if (joint_distance < 0) { @@ -155,18 +202,26 @@ size_t CrankshaftMove::select_segment() { return segment_size; } -PivotMove::PivotMove(Space &spc, const std::string& name, const std::string& cite) : ChainRotationMove(spc, name, cite) {} +PivotMove::PivotMove(Space& spc, const std::string& name, const std::string& cite) + : ChainRotationMove(spc, name, cite) +{ +} -PivotMove::PivotMove(Space &spc) : PivotMove(spc, "pivot", "") {} +PivotMove::PivotMove(Space& spc) + : PivotMove(spc, "pivot", "") +{ +} -void PivotMove::_from_json(const json &j) { +void PivotMove::_from_json(const json& j) +{ TBase::_from_json(j); bonds = molecules[this->molid].bonds.find(); if (bonds.empty()) { throw ConfigurationError("no harmonic bonds found for pivot move"); } if (repeat < 0) { - // set the number of repetitions to the length of the chain (minus 2) times the number of the chains + // set the number of repetitions to the length of the chain (minus 2) times the number of + // the chains auto moliter = this->spc.findMolecules(this->molid); this->repeat = std::distance(moliter.begin(), moliter.end()); if (this->repeat > 0) { @@ -174,14 +229,17 @@ void PivotMove::_from_json(const json &j) { } } } -size_t PivotMove::select_segment() { + +size_t PivotMove::select_segment() +{ size_t segment_size = 0; this->segment_ndx.clear(); this->molecule_iter = this->spc.randomMolecule(this->molid, Faunus::move::PivotMove::slump); if (this->molecule_iter != this->spc.groups.end()) { - auto &chain = *this->molecule_iter; - if (chain.size() > 2) { // must have at least three atoms - auto bond = Faunus::move::PivotMove::slump.sample(bonds.begin(), bonds.end()); // a random harmonic bond + auto& chain = *this->molecule_iter; + if (chain.size() > 2) { // must have at least three atoms + auto bond = Faunus::move::PivotMove::slump.sample( + bonds.begin(), bonds.end()); // a random harmonic bond if (bond != bonds.end()) { auto chain_offset = std::distance(this->spc.particles.begin(), chain.begin()); auto atom0_ndx = (*bond)->indices.at(0) + chain_offset; @@ -193,7 +251,8 @@ size_t PivotMove::select_segment() { for (size_t i = atom1_ndx + 1; i < chain_offset + chain.size(); i++) this->segment_ndx.push_back(i); std::swap(atom0_ndx, atom1_ndx); // the second atom is the origin - } else { + } + else { for (size_t i = chain_offset; i < atom0_ndx; i++) this->segment_ndx.push_back(i); } @@ -206,4 +265,3 @@ size_t PivotMove::select_segment() { } } // namespace Faunus::move - diff --git a/src/chainmove.h b/src/chainmove.h index d47e3539d..ef7c7eb48 100644 --- a/src/chainmove.h +++ b/src/chainmove.h @@ -8,7 +8,8 @@ namespace move { /** * @brief An abstract base class for rotational movements of a polymer chain */ -class ChainRotationMoveBase : public Move { +class ChainRotationMoveBase : public Move +{ protected: using Move::spc; std::string molname; @@ -20,39 +21,43 @@ class ChainRotationMoveBase : public Move { bool allow_small_box = false; int small_box_encountered = 0; //!< number of skipped moves due to too small container private: - virtual size_t select_segment() = 0; //!< selects a chain segment and return the number of atoms in it + virtual size_t + select_segment() = 0; //!< selects a chain segment and return the number of atoms in it virtual void rotate_segment(double angle) = 0; //!< rotates the selected chain segment - virtual void store_change(Change &change) = 0; //!< stores changes made to atoms - void _move(Change &change) override; - void _accept(Change &change) override; - void _reject(Change &change) override; + virtual void store_change(Change& change) = 0; //!< stores changes made to atoms + void _move(Change& change) override; + void _accept(Change& change) override; + void _reject(Change& change) override; protected: - ChainRotationMoveBase(Space &spc, const std::string& name, const std::string& cite); - void _from_json(const json &j) override; - void _to_json(json &j) const override; + ChainRotationMoveBase(Space& spc, const std::string& name, const std::string& cite); + void _from_json(const json& j) override; + void _to_json(json& j) const override; public: - double bias(Change &, double uold, double unew) override; + double bias(Change&, double uold, double unew) override; }; /** - * @brief An abstract class that rotates a selected segment of a polymer chain in the given simulation box. + * @brief An abstract class that rotates a selected segment of a polymer chain in the given + * simulation box. */ -class ChainRotationMove : public ChainRotationMoveBase { +class ChainRotationMove : public ChainRotationMoveBase +{ using TBase = ChainRotationMoveBase; protected: using TBase::spc; typename Space::GroupVector::iterator molecule_iter; - //! Indices of atoms in the spc.p vector that mark the origin and the direction of the axis of rotation. + //! Indices of atoms in the spc.p vector that mark the origin and the direction of the axis of + //! rotation. std::array axis_ndx; //! Indices of atoms in the spc.p vector that shall be rotated. std::vector segment_ndx; - ChainRotationMove(Space &spc, const std::string& name, const std::string& cite); + ChainRotationMove(Space& spc, const std::string& name, const std::string& cite); - void _from_json(const json &j) override; + void _from_json(const json& j) override; private: /** @@ -65,10 +70,10 @@ class ChainRotationMove : public ChainRotationMoveBase { * Stores changes of atoms after the move attempt. * @param change */ - void store_change(Change &change) override; + void store_change(Change& change) override; - /** In periodic systems (cuboid, slit, etc.) a chain rotational move can cause the molecule to be larger - * than half the box length which we catch here. + /** In periodic systems (cuboid, slit, etc.) a chain rotational move can cause the molecule to + * be larger than half the box length which we catch here. * @throws std::runtime_error */ bool box_big_enough(); @@ -81,23 +86,23 @@ class ChainRotationMove : public ChainRotationMoveBase { * The chain segment between the joints is then rotated by a random angle around the axis * determined by the joints. The extend of the angle is limited by dprot. */ -class CrankshaftMove : public ChainRotationMove { +class CrankshaftMove : public ChainRotationMove +{ using TBase = ChainRotationMove; public: - explicit CrankshaftMove(Space &spc); + explicit CrankshaftMove(Space& spc); protected: - CrankshaftMove(Space &spc, const std::string& name, const std::string& cite); - void _from_json(const json &j) override; + CrankshaftMove(Space& spc, const std::string& name, const std::string& cite); + void _from_json(const json& j) override; private: size_t joint_max; //!< maximum number of bonds between the joints of a crankshaft - /** Randomly selects two atoms as joints in a random chain. The joints then determine the axis of rotation - * of the chain segment between the joints. - * The member vectors containing atoms' indices of the axis and the segment are populated accordingly. - * Returns the segment size as atom count. - * A non-branched chain is assumed having atom indices in a dense sequence. + /** Randomly selects two atoms as joints in a random chain. The joints then determine the axis + * of rotation of the chain segment between the joints. The member vectors containing atoms' + * indices of the axis and the segment are populated accordingly. Returns the segment size as + * atom count. A non-branched chain is assumed having atom indices in a dense sequence. */ size_t select_segment() override; }; @@ -105,29 +110,31 @@ class CrankshaftMove : public ChainRotationMove { /** * @brief Performs a pivot move of a random tail part of a polymer chain. * - * A random harmonic bond is selected from a random polymer chain. The bond determines the axes the rotation. - * A part of the chain either before or after the bond (the selection has an equal probability ) - * then constitutes a segment which is rotated by a random angle. The extend of the angle is limited by dprot. + * A random harmonic bond is selected from a random polymer chain. The bond determines the axes the + * rotation. A part of the chain either before or after the bond (the selection has an equal + * probability ) then constitutes a segment which is rotated by a random angle. The extend of the + * angle is limited by dprot. */ -class PivotMove : public ChainRotationMove { +class PivotMove : public ChainRotationMove +{ using TBase = ChainRotationMove; private: BasePointerVector bonds; public: - explicit PivotMove(Space &spc); + explicit PivotMove(Space& spc); protected: - PivotMove(Space &spc, const std::string& name, const std::string& cite); + PivotMove(Space& spc, const std::string& name, const std::string& cite); - void _from_json(const json &j) override; + void _from_json(const json& j) override; - /** Selects a random harmonic bond of a random polymer chain which atoms then create an axis of rotation. - * Atoms between the randomly selected chain's end and the bond atom compose a segment to be rotated. - * The member vectors containing atoms' indices of the axis and the segment are populated accordingly. - * Returns the segment size as atom count. - * A non-branched chain is assumed having atom indices in a dense sequence. + /** Selects a random harmonic bond of a random polymer chain which atoms then create an axis of + * rotation. Atoms between the randomly selected chain's end and the bond atom compose a segment + * to be rotated. The member vectors containing atoms' indices of the axis and the segment are + * populated accordingly. Returns the segment size as atom count. A non-branched chain is + * assumed having atom indices in a dense sequence. */ size_t select_segment() override; }; diff --git a/src/clustermove.cpp b/src/clustermove.cpp index 558a841e4..eb21adafa 100644 --- a/src/clustermove.cpp +++ b/src/clustermove.cpp @@ -7,18 +7,26 @@ namespace Faunus { namespace move { -ClusterShapeAnalysis::ClusterShapeAnalysis(bool shape_anisotropy_use_com, const std::string &filename, - bool dump_pqr_files) - : save_pqr_files(dump_pqr_files), shape_anisotropy_use_com(shape_anisotropy_use_com) { +ClusterShapeAnalysis::ClusterShapeAnalysis(bool shape_anisotropy_use_com, + const std::string& filename, bool dump_pqr_files) + : save_pqr_files(dump_pqr_files) + , shape_anisotropy_use_com(shape_anisotropy_use_com) +{ if (!filename.empty()) { stream = IO::openCompressedOutputStream(MPI::prefix + filename, true); *stream << "# size seed shape_anisotropy\n"; } } -ClusterShapeAnalysis::ClusterShapeAnalysis(const json &j) - : ClusterShapeAnalysis(j.value("com", true), j.value("file", std::string()), j.value("save_pqr", false)) {} -decltype(ClusterShapeAnalysis::pqr_distribution)::iterator ClusterShapeAnalysis::findPQRstream(size_t cluster_size) { +ClusterShapeAnalysis::ClusterShapeAnalysis(const json& j) + : ClusterShapeAnalysis(j.value("com", true), j.value("file", std::string()), + j.value("save_pqr", false)) +{ +} + +decltype(ClusterShapeAnalysis::pqr_distribution)::iterator +ClusterShapeAnalysis::findPQRstream(size_t cluster_size) +{ auto it = pqr_distribution.find(cluster_size); if (it == pqr_distribution.end()) { const std::string file = MPI::prefix + fmt::format("clustersize{}.pqr", cluster_size); @@ -27,16 +35,18 @@ decltype(ClusterShapeAnalysis::pqr_distribution)::iterator ClusterShapeAnalysis: return it; } -void to_json(json &j, const ClusterShapeAnalysis &shape) { +void to_json(json& j, const ClusterShapeAnalysis& shape) +{ j["size distribution"] = shape.size_distribution; - auto &json_shape = j["shape distribution"] = json::array(); - for (const auto &[size, shape_anisotropy] : shape.shape_distribution) { + auto& json_shape = j["shape distribution"] = json::array(); + for (const auto& [size, shape_anisotropy] : shape.shape_distribution) { json_shape.push_back({size, shape_anisotropy.avg()}); } } FindCluster::FindCluster(const Space& spc, const json& j) - : spc(spc) { + : spc(spc) +{ single_layer = j.value("single_layer", false); if (j.contains("spread")) { faunus_logger->error("cluster: 'spread' is deprecated, use 'single_layer' instead"); @@ -52,19 +62,23 @@ FindCluster::FindCluster(const Space& spc, const json& j) parseThresholds(j.at("threshold")); } -double FindCluster::clusterProbability(const Group& group1, const Group& group2) const { +double FindCluster::clusterProbability(const Group& group1, const Group& group2) const +{ if (group1.empty() || group2.empty()) { return 0.0; } if (use_mass_center_threshold) { - const auto mass_center_sepatation_squared = spc.geometry.sqdist(group1.mass_center, group2.mass_center); - return static_cast(mass_center_sepatation_squared <= thresholds_squared(group1.id, group2.id)); + const auto mass_center_sepatation_squared = + spc.geometry.sqdist(group1.mass_center, group2.mass_center); + return static_cast(mass_center_sepatation_squared <= + thresholds_squared(group1.id, group2.id)); } for (auto& particle_1 : group1) { for (auto& particle_2 : group2) { - if (spc.geometry.sqdist(particle_1.pos, particle_2.pos) <= thresholds_squared(group1.id, group2.id)) { + if (spc.geometry.sqdist(particle_1.pos, particle_2.pos) <= + thresholds_squared(group1.id, group2.id)) { return 1.0; } } @@ -80,7 +94,8 @@ double FindCluster::clusterProbability(const Group& group1, const Group& group2) * * Atomic groups and inactive groups are ignored. */ -void FindCluster::updateMoleculeIndex() { +void FindCluster::updateMoleculeIndex() +{ namespace rv = ranges::cpp20::views; auto group_to_index = [&](auto& group) { return &group - &spc.groups.front(); }; auto matching_molid = [&](auto& group) { @@ -93,11 +108,14 @@ void FindCluster::updateMoleculeIndex() { ranges::to(); } -void FindCluster::registerSatellites(const std::vector &satellite_names) { +void FindCluster::registerSatellites(const std::vector& satellite_names) +{ const auto ids = Faunus::names2ids(Faunus::molecules, satellite_names); // names --> molids satellites = std::set(ids.begin(), ids.end()); - auto invalid_satellite = [&](auto molid) { return std::find(molids.begin(), molids.end(), molid) == molids.end(); }; + auto invalid_satellite = [&](auto molid) { + return std::find(molids.begin(), molids.end(), molid) == molids.end(); + }; if (std::any_of(satellites.begin(), satellites.end(), invalid_satellite)) { throw std::runtime_error("satellite molecules must be defined in `molecules`"); } @@ -113,28 +131,34 @@ void FindCluster::registerSatellites(const std::vector &satellite_n * This reads the cluster threshold(s) either as a single number, or * as a matrix of molecule-molecule thresholds. */ -void FindCluster::parseThresholds(const json &j) { +void FindCluster::parseThresholds(const json& j) +{ if (j.is_number()) { // threshold is given as a single number for (auto [id1, id2] : ranges::views::cartesian_product(molids, molids)) { thresholds_squared.set(id1, id2, std::pow(j.get(), 2)); } - } else if (j.is_object()) { // threshold is given as pairs of clustering molecules + } + else if (j.is_object()) { // threshold is given as pairs of clustering molecules const auto threshold_combinations = molids.size() * (molids.size() + 1) / 2; // N*(N+1)/2 if (j.size() != threshold_combinations) { - throw ConfigurationError( - "exactly {} molecule pairs must be given in threshold matrix to cover all combinations", - threshold_combinations); + throw ConfigurationError("exactly {} molecule pairs must be given in threshold matrix " + "to cover all combinations", + threshold_combinations); } - for (const auto &[key, value] : j.items()) { + for (const auto& [key, value] : j.items()) { if (auto name_pair = Faunus::splitConvert(key); name_pair.size() == 2) { const auto molecule1 = findMoleculeByName(name_pair[0]); const auto molecule2 = findMoleculeByName(name_pair[1]); - thresholds_squared.set(molecule1.id(), molecule2.id(), std::pow(value.get(), 2)); - } else { - throw ConfigurationError("threshold requires exactly two space-separated molecules"); + thresholds_squared.set(molecule1.id(), molecule2.id(), + std::pow(value.get(), 2)); + } + else { + throw ConfigurationError( + "threshold requires exactly two space-separated molecules"); } } - } else { + } + else { throw ConfigurationError("cluster threshold must be a number or object"); } } @@ -144,9 +168,12 @@ void FindCluster::parseThresholds(const json &j) { * part of it, i.e. rotated and translated. Here we therefore filter out any * satellite molecules, and pick a random molecule index of what remains. */ -std::optional FindCluster::findSeed(Random &random) { +std::optional FindCluster::findSeed(Random& random) +{ updateMoleculeIndex(); - auto avoid_satellites = [&](auto index) { return (satellites.count(spc.groups.at(index).id) == 0); }; + auto avoid_satellites = [&](auto index) { + return (satellites.count(spc.groups.at(index).id) == 0); + }; auto not_satellites = molecule_index | ranges::cpp20::views::filter(avoid_satellites); if (ranges::cpp20::empty(not_satellites)) { return std::nullopt; @@ -161,25 +188,29 @@ std::optional FindCluster::findSeed(Random &random) { * @returns Pair w. vector for group indices in cluster and a bool if the cluster * can be safely rotated in a PBC environment */ -std::pair, bool> FindCluster::findCluster(size_t seed_index) { +std::pair, bool> FindCluster::findCluster(size_t seed_index) +{ namespace rv = ranges::cpp20::views; assert(seed_index < spc.particles.size()); - std::set pool(molecule_index.begin(), molecule_index.end()); // decreasing pool of candidate groups + std::set pool(molecule_index.begin(), + molecule_index.end()); // decreasing pool of candidate groups assert(pool.count(seed_index) == 1); std::vector cluster; // molecular index cluster.reserve(molecule_index.size()); // ensures safe resizing without invalidating iterators cluster.push_back(seed_index); // 'seed_index' is the index of the seed molecule - pool.erase(seed_index); // ...which is already in the cluster and not part of pool + pool.erase(seed_index); // ...which is already in the cluster and not part of pool // cluster search algorithm for (auto it1 = cluster.begin(); it1 != cluster.end(); it1++) { for (auto it2 = pool.begin(); it2 != pool.end();) { - const auto p = clusterProbability(spc.groups.at(*it1), spc.groups.at(*it2)); // probability to cluster - if (Move::slump() <= p) { // is group part of cluster? - cluster.push_back(*it2); // yes, expand cluster... - it2 = pool.erase(it2); // ...and remove from pool - } else { + const auto p = clusterProbability(spc.groups.at(*it1), + spc.groups.at(*it2)); // probability to cluster + if (Move::slump() <= p) { // is group part of cluster? + cluster.push_back(*it2); // yes, expand cluster... + it2 = pool.erase(it2); // ...and remove from pool + } + else { ++it2; // not part of cluster, move along to next group in pool } } @@ -190,7 +221,9 @@ std::pair, bool> FindCluster::findCluster(size_t seed_index) std::sort(cluster.begin(), cluster.end()); // required for correct energy evaluation // check if cluster is too large to be rotated (larger than half the box length) - auto mass_centers = cluster | rv::transform([&](auto i) -> const Point& { return spc.groups.at(i).mass_center; }); + auto mass_centers = cluster | rv::transform([&](auto i) -> const Point& { + return spc.groups.at(i).mass_center; + }); const auto max = 0.5 * spc.geometry.getLength().minCoeff(); bool safe_to_rotate = true; for (auto i = mass_centers.begin(); i != mass_centers.end(); ++i) { @@ -202,18 +235,21 @@ std::pair, bool> FindCluster::findCluster(size_t seed_index) break; } } - assert(std::adjacent_find(cluster.begin(), cluster.end()) == cluster.end()); // check for duplicates + assert(std::adjacent_find(cluster.begin(), cluster.end()) == + cluster.end()); // check for duplicates return {cluster, safe_to_rotate}; } -void to_json(json &j, const FindCluster &cluster) { +void to_json(json& j, const FindCluster& cluster) +{ j["single_layer"] = cluster.single_layer; j["com"] = cluster.use_mass_center_threshold; - auto &_j = j["threshold"]; + auto& _j = j["threshold"]; for (const auto k : cluster.molids) { for (const auto l : cluster.molids) { if (k >= l) { - const auto key = fmt::format("{} {}", Faunus::molecules.at(k).name, Faunus::molecules.at(l).name); + const auto key = fmt::format("{} {}", Faunus::molecules.at(k).name, + Faunus::molecules.at(l).name); _j[key] = std::sqrt(cluster.thresholds_squared(k, l)); roundJSON(_j[key], 3); } @@ -222,14 +258,15 @@ void to_json(json &j, const FindCluster &cluster) { // print satellite molecules if (not cluster.satellites.empty()) { - auto &satellites_json = j["satellites"] = json::array(); + auto& satellites_json = j["satellites"] = json::array(); for (auto id : cluster.satellites) { satellites_json.push_back(Faunus::molecules[id].name); } } } -void Cluster::_to_json(json &j) const { +void Cluster::_to_json(json& j) const +{ j = {{"dir", translate->direction}, {"dp", translate->displacement_factor}, {"dprot", rotate->displacement_factor}, @@ -244,8 +281,10 @@ void Cluster::_to_json(json &j) const { roundJSON(j, 3); } -void Cluster::_from_json(const json &j) { - translate = std::make_unique(j.at("dp").get(), j.value("dir", Point(1.0, 1.0, 1.0))); +void Cluster::_from_json(const json& j) +{ + translate = std::make_unique(j.at("dp").get(), + j.value("dir", Point(1.0, 1.0, 1.0))); rotate = std::make_unique(j.at("dprot"), j.value("dirrot", Point(0, 0, 0))); find_cluster = std::make_unique(spc, j); repeat = std::max(1, find_cluster->molecule_index.size()); @@ -259,16 +298,19 @@ void Cluster::_from_json(const json &j) { * @param indices Indices of groups in cluster * @return Mass center of the cluster */ -Point Cluster::clusterMassCenter(const std::vector& indices) const { +Point Cluster::clusterMassCenter(const std::vector& indices) const +{ assert(!indices.empty()); namespace rv = ranges::cpp20::views; auto groups = indices | rv::transform(index_to_group); auto positions = groups | rv::transform(&Group::mass_center); auto masses = groups | rv::transform(&Group::mass); - return Geometry::weightedCenter(positions, masses, spc.geometry.getBoundaryFunc(), -groups.begin()->mass_center); + return Geometry::weightedCenter(positions, masses, spc.geometry.getBoundaryFunc(), + -groups.begin()->mass_center); } -void Cluster::_move(Change& change) { +void Cluster::_move(Change& change) +{ clearForMove(); auto seed_index = find_cluster->findSeed(slump); if (!seed_index) { @@ -294,7 +336,8 @@ void Cluster::_move(Change& change) { setChange(change, cluster); } -void Cluster::clearForMove() { +void Cluster::clearForMove() +{ translate->current_displacement.setZero(); rotate->current_displacement = 0.0; bias_energy = 0.0; @@ -305,20 +348,23 @@ void Cluster::clearForMove() { * Current implementation works only for the binary 0/1 probability function * currently implemented in `findCluster()`. */ -void Cluster::calculateBias(const size_t seed_index, const std::vector& cluster_index) { +void Cluster::calculateBias(const size_t seed_index, const std::vector& cluster_index) +{ [[maybe_unused]] auto [aftercluster, safe_to_rotate] = find_cluster->findCluster(seed_index); if (aftercluster == cluster_index) { bias_energy = 0.0; - } else { + } + else { bias_energy = pc::infty; // bias is infinite --> reject - bias_rejected++; // count how many times we reject due to bias + bias_rejected++; // count how many times we reject due to bias } } /** * Update change object with changed molecules */ -void Cluster::setChange(Change& change, const std::vector& group_indices) const { +void Cluster::setChange(Change& change, const std::vector& group_indices) const +{ change.groups.reserve(group_indices.size()); std::for_each(group_indices.begin(), group_indices.end(), [&](auto index) { // register changes auto& change_data = change.groups.emplace_back(); @@ -330,27 +376,32 @@ void Cluster::setChange(Change& change, const std::vector& group_indices } double Cluster::bias([[maybe_unused]] Change& change, [[maybe_unused]] double old_energy, - [[maybe_unused]] double new_energy) { + [[maybe_unused]] double new_energy) +{ return bias_energy; } -void Cluster::_reject([[maybe_unused]] Change& change) { +void Cluster::_reject([[maybe_unused]] Change& change) +{ translate->mean_square_displacement += 0.0; rotate->mean_square_displacement += 0.0; } -void Cluster::_accept([[maybe_unused]] Change& change) { +void Cluster::_accept([[maybe_unused]] Change& change) +{ translate->mean_square_displacement += translate->current_displacement.squaredNorm(); rotate->mean_square_displacement += std::pow(rotate->current_displacement, 2); } Cluster::Cluster(Space& spc, std::string_view name, std::string_view cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = -1; } Cluster::Cluster(Space& spc) - : Cluster(spc, "cluster", "doi:10/cj9gnn") { + : Cluster(spc, "cluster", "doi:10/cj9gnn") +{ index_to_group = [&](auto index) -> Group& { return this->spc.groups.at(index); }; } @@ -358,15 +409,21 @@ Cluster::Cluster(Space& spc) GroupMover::GroupMover(double displacement_factor, const Point& direction) : displacement_factor(displacement_factor) - , direction(direction) {} + , direction(direction) +{ +} // ------------------------------------------- GroupTranslator::GroupTranslator(double displacement_factor, const Point& direction) - : GroupMover(displacement_factor, direction) {} + : GroupMover(displacement_factor, direction) +{ +} /** Lambda to translate group in cluster. Will set a random displacement */ -std::function GroupTranslator::getLambda(Geometry::BoundaryFunction boundary, Random& slump) { +std::function GroupTranslator::getLambda(Geometry::BoundaryFunction boundary, + Random& slump) +{ current_displacement = randomUnitVector(slump, direction) * displacement_factor * slump(); if (std::fabs(displacement_factor) <= pc::epsilon_dbl) { // if zero displacement... return [](auto&) {}; // ...then return NOP lambda @@ -377,18 +434,23 @@ std::function GroupTranslator::getLambda(Geometry::BoundaryFunctio // ------------------------------------------- GroupRotator::GroupRotator(double displacement_factor, const Point& rotation_axis) - : GroupMover(displacement_factor, - (rotation_axis.squaredNorm() > pc::epsilon_dbl) ? rotation_axis.normalized() : Point::Zero()) {} + : GroupMover(displacement_factor, (rotation_axis.squaredNorm() > pc::epsilon_dbl) + ? rotation_axis.normalized() + : Point::Zero()) +{ +} -Eigen::Quaterniond GroupRotator::setRandomRotation(Random& random) { +Eigen::Quaterniond GroupRotator::setRandomRotation(Random& random) +{ Point axis = (direction.squaredNorm() > pc::epsilon_dbl) ? direction : randomUnitVector(random); current_displacement = displacement_factor * (random() - 0.5); return static_cast(Eigen::AngleAxisd(current_displacement, axis)); } /** Lambda to rotate a group. Will set a random rotation axis and angle. */ -std::function GroupRotator::getLambda(Geometry::BoundaryFunction boundary, const Point& rotation_origin, - Random& random) { +std::function GroupRotator::getLambda(Geometry::BoundaryFunction boundary, + const Point& rotation_origin, Random& random) +{ if (std::fabs(displacement_factor) <= pc::epsilon_dbl) { // if zero displacement... return [](auto&) {}; // ...then return NOP lambda } diff --git a/src/clustermove.h b/src/clustermove.h index eff92429f..5d7f60e35 100644 --- a/src/clustermove.h +++ b/src/clustermove.h @@ -10,59 +10,70 @@ namespace move { /** * @brief Helper class for Cluster move for analysing shape and size of found clusters */ -class ClusterShapeAnalysis { +class ClusterShapeAnalysis +{ private: unsigned long int number_of_samples = 0; - bool save_pqr_files = false; //!< Save cluster to PQR files? One file for each size. - bool shape_anisotropy_use_com = true; //!< true if kappa^2 should be based on mass centers instead of particles - std::unique_ptr stream; //!< log current cluster into (compressed) stream - std::map size_distribution; //!< distribution of cluster sizes + bool save_pqr_files = false; //!< Save cluster to PQR files? One file for each size. + bool shape_anisotropy_use_com = + true; //!< true if kappa^2 should be based on mass centers instead of particles + std::unique_ptr stream; //!< log current cluster into (compressed) stream + std::map size_distribution; //!< distribution of cluster sizes std::map> shape_distribution; //!< average shape - std::map pqr_distribution; //!< Each cluster size is saved to disk - decltype(pqr_distribution)::iterator findPQRstream(size_t cluster_size); //!< Create or find PQR files stream + std::map pqr_distribution; //!< Each cluster size is saved to disk + decltype(pqr_distribution)::iterator + findPQRstream(size_t cluster_size); //!< Create or find PQR files stream /** brief Calculates the gyration tensor for a collection of groups */ template Tensor gyrationFromMassCenterPositions( const Range& groups, const Point& mass_center_of_groups, - const Geometry::BoundaryFunction boundary = [](auto&) {}) { + const Geometry::BoundaryFunction boundary = [](auto&) {}) + { using namespace ranges::cpp20::views; auto positions = groups | transform(&Group::mass_center); auto masses = groups | transform(&Group::mass); - return Geometry::gyration(positions.begin(), positions.end(), masses.begin(), mass_center_of_groups, boundary); + return Geometry::gyration(positions.begin(), positions.end(), masses.begin(), + mass_center_of_groups, boundary); } /** brief Calculates the gyration tensor for a collection of groups */ template Tensor gyrationFromParticlePositions( const Range& groups, const Point& mass_center_of_groups, - const Geometry::BoundaryFunction boundary = [](auto&) {}) { + const Geometry::BoundaryFunction boundary = [](auto&) {}) + { using namespace ranges::cpp20::views; auto positions = groups | join | transform(&Particle::pos); auto masses = groups | join | transform(&Particle::traits) | transform(&AtomData::mw); - return Geometry::gyration(positions.begin(), positions.end(), masses.begin(), mass_center_of_groups, boundary); + return Geometry::gyration(positions.begin(), positions.end(), masses.begin(), + mass_center_of_groups, boundary); } - friend void to_json(json &, const ClusterShapeAnalysis &); + friend void to_json(json&, const ClusterShapeAnalysis&); public: template - void sample(const Range& groups, const Point& mass_center_of_groups, const Space& spc) { + void sample(const Range& groups, const Point& mass_center_of_groups, const Space& spc) + { const auto cluster_size = std::distance(groups.begin(), groups.end()); Tensor gyration_tensor; if (shape_anisotropy_use_com) { - gyration_tensor = - gyrationFromMassCenterPositions(groups, mass_center_of_groups, spc.geometry.getBoundaryFunc()); - } else { - gyration_tensor = - gyrationFromParticlePositions(groups, mass_center_of_groups, spc.geometry.getBoundaryFunc()); + gyration_tensor = gyrationFromMassCenterPositions(groups, mass_center_of_groups, + spc.geometry.getBoundaryFunc()); + } + else { + gyration_tensor = gyrationFromParticlePositions(groups, mass_center_of_groups, + spc.geometry.getBoundaryFunc()); } const auto shape = Geometry::ShapeDescriptors(gyration_tensor); size_distribution[cluster_size]++; shape_distribution[cluster_size] += shape; if (stream) { - const auto seed_index = &(*groups.begin()) - &spc.groups.at(0); // index of first molecule in cluster - *stream << fmt::format("{} {} {:.3f}\n", cluster_size, seed_index, shape.relative_shape_anisotropy); + const auto seed_index = + &(*groups.begin()) - &spc.groups.at(0); // index of first molecule in cluster + *stream << fmt::format("{} {} {:.3f}\n", cluster_size, seed_index, + shape.relative_shape_anisotropy); } if (save_pqr_files) { auto it = findPQRstream(cluster_size); @@ -75,44 +86,50 @@ class ClusterShapeAnalysis { } ++number_of_samples; } - ClusterShapeAnalysis(bool shape_anisotropy_use_com, const std::string &filename, bool dump_pqr_files); - ClusterShapeAnalysis(const json &j); + + ClusterShapeAnalysis(bool shape_anisotropy_use_com, const std::string& filename, + bool dump_pqr_files); + ClusterShapeAnalysis(const json& j); }; -void to_json(json &j, const ClusterShapeAnalysis &shape); +void to_json(json& j, const ClusterShapeAnalysis& shape); /** * @brief Helper class for Cluster move for finding clusters * @todo This is a break-out from `Cluster` and further separation of functionality * could be made */ -class FindCluster { +class FindCluster +{ private: const Space& spc; - bool use_mass_center_threshold = true; //!< use distance threshold between mass-centers instead of particles - bool single_layer = false; //!< stop cluster search after first layer of neighbors + bool use_mass_center_threshold = + true; //!< use distance threshold between mass-centers instead of particles + bool single_layer = false; //!< stop cluster search after first layer of neighbors std::vector molecule_names; //!< names of molecules to be considered - std::vector molids; //!< molecule id's of molecules to be considered (must be sorted!) - std::set satellites; //!< subset of molecule id's to cluster, but NOT act as nuclei (cluster centers) + std::vector molids; //!< molecule id's of molecules to be considered (must be sorted!) + std::set + satellites; //!< subset of molecule id's to cluster, but NOT act as nuclei (cluster centers) PairMatrix thresholds_squared; //!< Cluster thresholds for pairs of groups - void parseThresholds(const json &j); //!< Read thresholds from json input + void parseThresholds(const json& j); //!< Read thresholds from json input double clusterProbability(const Group& group1, const Group& group2) const; - void registerSatellites(const std::vector &); //!< Register satellites - void updateMoleculeIndex(); //!< update `molecule_index` - friend void to_json(json &j, const FindCluster &cluster); + void registerSatellites(const std::vector&); //!< Register satellites + void updateMoleculeIndex(); //!< update `molecule_index` + friend void to_json(json& j, const FindCluster& cluster); public: - std::vector molecule_index; //!< index of all possible molecules to be considered - std::optional findSeed(Random &random); //!< Find first group; exclude satellites + std::vector molecule_index; //!< index of all possible molecules to be considered + std::optional findSeed(Random& random); //!< Find first group; exclude satellites std::pair, bool> findCluster(size_t seed_index); //!< Find cluster FindCluster(const Space& spc, const json& j); }; -void to_json(json &j, const FindCluster &cluster); //!< Serialize to json +void to_json(json& j, const FindCluster& cluster); //!< Serialize to json /** @brief Helper base class for translating and rotating groups */ -class GroupMover { +class GroupMover +{ public: Average mean_square_displacement; //!< Must be updated manually const double displacement_factor; //!< Displacement to be scaled by a random number @@ -121,7 +138,8 @@ class GroupMover { }; /** @brief Helper class to translate a group */ -class GroupTranslator : public GroupMover { +class GroupTranslator : public GroupMover +{ public: Point current_displacement = Point::Zero(); //!< Currently set displacement GroupTranslator(double displacement_factor, const Point& direction = Point::Ones()); @@ -132,21 +150,24 @@ class GroupTranslator : public GroupMover { * * If `direction` is zero, then generate random rotation axis */ -class GroupRotator : public GroupMover { +class GroupRotator : public GroupMover +{ private: Eigen::Quaterniond setRandomRotation(Random& random); public: double current_displacement = 0.0; //!< Current displacement angle GroupRotator(double displacement_factor, const Point& rotation_axis = Point::Zero()); - std::function getLambda(Geometry::BoundaryFunction boundary, const Point& rotation_origin, + std::function getLambda(Geometry::BoundaryFunction boundary, + const Point& rotation_origin, Random& random); //!< Lambda to rotate group in cluster }; /** * @brief Molecular cluster move */ -class Cluster : public Move { +class Cluster : public Move +{ private: std::unique_ptr find_cluster; std::unique_ptr shape_analysis; @@ -155,14 +176,16 @@ class Cluster : public Move { std::function index_to_group; Average average_cluster_size; - double bias_energy = 0; //!< Current bias energy (currently zero or infinity) - unsigned int bias_rejected = 0; //!< Number of times rejection occurred due to bias rejection - unsigned int shape_analysis_interval = 0; //!< Number of sample events between shape analysis - - Point clusterMassCenter(const std::vector& indices) const; //!< Calculates cluster mass center - void _move(Change& change) override; //!< Performs the move - double bias(Change& change, double old_energy, - double new_energy) override; //!< adds extra energy change not captured by the Hamiltonian + double bias_energy = 0; //!< Current bias energy (currently zero or infinity) + unsigned int bias_rejected = 0; //!< Number of times rejection occurred due to bias rejection + unsigned int shape_analysis_interval = 0; //!< Number of sample events between shape analysis + + Point + clusterMassCenter(const std::vector& indices) const; //!< Calculates cluster mass center + void _move(Change& change) override; //!< Performs the move + double + bias(Change& change, double old_energy, + double new_energy) override; //!< adds extra energy change not captured by the Hamiltonian void _reject(Change& change) override; void _accept(Change& change) override; void _to_json(json& j) const override; @@ -175,7 +198,7 @@ class Cluster : public Move { Cluster(Space& spc, std::string_view name, std::string_view cite); public: - explicit Cluster(Space &spc); + explicit Cluster(Space& spc); }; } // namespace move diff --git a/src/core.cpp b/src/core.cpp index 0e930b10d..cb386302f 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -16,26 +16,30 @@ namespace Faunus { -double roundValue(const double value, const int number_of_digits) { +double roundValue(const double value, const int number_of_digits) +{ std::stringstream o; o << std::setprecision(number_of_digits) << value; return std::stod(o.str()); } -void roundJSON(json& j, const int number_of_digits) { +void roundJSON(json& j, const int number_of_digits) +{ if (!j.is_object()) { return; } for (auto& value : j) { if (value.is_number_float()) { value = roundValue(value, number_of_digits); - } else if (value.is_object() && !value.empty()) { + } + else if (value.is_object() && !value.empty()) { roundJSON(value, number_of_digits); } } } -double getValueInfinity(const json& j, const std::string& key) { +double getValueInfinity(const json& j, const std::string& key) +{ auto value = j.find(key); if (value == j.end()) { throw std::runtime_error("unknown json key '" + key + "'"); @@ -59,7 +63,8 @@ double getValueInfinity(const json& j, const std::string& key) { * @throw IOError when file cannot be read * @throw json::parse_error when json ill-formatted */ -json loadJSON(const std::string& filename) { +json loadJSON(const std::string& filename) +{ if (std::ifstream stream(filename); stream) { json j; stream >> j; @@ -69,7 +74,8 @@ json loadJSON(const std::string& filename) { } TipFromTheManual::TipFromTheManual() - : random(std::make_unique()) { + : random(std::make_unique()) +{ random->seed(); } @@ -81,20 +87,24 @@ TipFromTheManual::TipFromTheManual() * file must consist of objects with `key`s and markdown formatted * tips. If no file can be opened, the database is left empty. */ -void TipFromTheManual::load(const std::vector& files) { +void TipFromTheManual::load(const std::vector& files) +{ for (const auto& file : files) { try { if (database = loadJSON(file); !database.empty()) { break; } - } catch (...) {} + } + catch (...) { + } } } /** * @brief If possible, give help based on short keys/tags */ -std::string TipFromTheManual::operator[](std::string_view key) { +std::string TipFromTheManual::operator[](std::string_view key) +{ std::string tip; if (tip_already_given || quiet) { return tip; @@ -103,11 +113,13 @@ std::string TipFromTheManual::operator[](std::string_view key) { tip = "\nNeed help, my young apprentice?\n\n" + it->get(); if (key == "coulomb") { // for the Coulomb potential, add additional table w. types tip += "\n" + database.at("coulomb types").get(); - } else if (key == "custom") { // for the custom potential, add also list of symbols + } + else if (key == "custom") { // for the custom potential, add also list of symbols tip += "\n" + database.at("symbol").get(); } if (asciiart) { - if (it = database.find("ascii"); it != database.end() && !it->empty() && it->is_array()) { + if (it = database.find("ascii"); + it != database.end() && !it->empty() && it->is_array()) { tip += random->sample(it->begin(), it->end())->get() + "\n"; } } @@ -117,7 +129,10 @@ std::string TipFromTheManual::operator[](std::string_view key) { return tip; } -void TipFromTheManual::pick(const std::string& key) { operator[](key); } +void TipFromTheManual::pick(const std::string& key) +{ + operator[](key); +} TipFromTheManual usageTip; // Global instance @@ -126,8 +141,10 @@ TipFromTheManual usageTip; // Global instance std::shared_ptr faunus_logger = spdlog::create("null"); std::shared_ptr mcloop_logger = faunus_logger; -[[maybe_unused]] std::string addGrowingSuffix(const std::string& file) { - // using std::experimental::filesystem; // exp. c++17 feature, not available on MacOS (Dec. 2018) +[[maybe_unused]] std::string addGrowingSuffix(const std::string& file) +{ + // using std::experimental::filesystem; // exp. c++17 feature, not available on MacOS (Dec. + // 2018) int cnt = 0; std::string newfile; auto exists = [&]() { return std::ifstream(newfile).good(); }; @@ -137,7 +154,8 @@ std::shared_ptr mcloop_logger = faunus_logger; return newfile; } -std::tuple jsonSingleItem(const json& j) { +std::tuple jsonSingleItem(const json& j) +{ if (j.is_object() && j.size() == 1) { const auto it = j.cbegin(); return {it.key(), it.value()}; @@ -145,28 +163,55 @@ std::tuple jsonSingleItem(const json& j) { throw std::runtime_error("invalid data: single key expected"); } -json::size_type SingleUseJSON::count(const std::string& key) const { return json::count(key); } +json::size_type SingleUseJSON::count(const std::string& key) const +{ + return json::count(key); +} -bool SingleUseJSON::empty() const { return json::empty(); } +bool SingleUseJSON::empty() const +{ + return json::empty(); +} -SingleUseJSON::SingleUseJSON(const json& j) : json(j) {} +SingleUseJSON::SingleUseJSON(const json& j) + : json(j) +{ +} -std::string SingleUseJSON::dump(const int width) const { return json::dump(width); } +std::string SingleUseJSON::dump(const int width) const +{ + return json::dump(width); +} -void SingleUseJSON::clear() { json::clear(); } +void SingleUseJSON::clear() +{ + json::clear(); +} -json SingleUseJSON::at(const std::string& key) { +json SingleUseJSON::at(const std::string& key) +{ json val = json::at(key); json::erase(key); return val; } -json SingleUseJSON::operator[](const std::string& key) { return at(key); } +json SingleUseJSON::operator[](const std::string& key) +{ + return at(key); +} -void SingleUseJSON::erase(const std::string& key) { json::erase(key); } -bool SingleUseJSON::is_object() const { return json::is_object(); } +void SingleUseJSON::erase(const std::string& key) +{ + json::erase(key); +} + +bool SingleUseJSON::is_object() const +{ + return json::is_object(); +} -Point xyz2rth(const Point& p, const Point& origin, const Point& dir, const Point& dir2) { +Point xyz2rth(const Point& p, const Point& origin, const Point& dir, const Point& dir2) +{ assert(fabs(dir.norm() - 1.0) < 1e-6); assert(fabs(dir2.norm() - 1.0) < 1e-6); assert(fabs(dir.dot(dir2)) < 1e-6); // check if unit-vectors are perpendicular @@ -180,22 +225,25 @@ Point xyz2rth(const Point& p, const Point& origin, const Point& dir, const Point return {radius, theta, h}; } -Point xyz2rtp(const Point& p, const Point& origin) { +Point xyz2rtp(const Point& p, const Point& origin) +{ Point xyz = p - origin; const auto radius = xyz.norm(); return {radius, std::atan2(xyz.y(), xyz.x()), std::acos(xyz.z() / radius)}; } -Point rtp2xyz(const Point& rtp, const Point& origin) { - return origin + rtp.x() * Point(std::cos(rtp.y()) * std::sin(rtp.z()), std::sin(rtp.y()) * std::sin(rtp.z()), - std::cos(rtp.z())); +Point rtp2xyz(const Point& rtp, const Point& origin) +{ + return origin + rtp.x() * Point(std::cos(rtp.y()) * std::sin(rtp.z()), + std::sin(rtp.y()) * std::sin(rtp.z()), std::cos(rtp.z())); } /** * Generates random points on a cube with side-lengths 1 and origin at [0,0,0]. * Keep going until inside embedded sphere of radius 0.5, then return normalized vector. */ -Point randomUnitVector(Random& rand, const Point& directions) { +Point randomUnitVector(Random& rand, const Point& directions) +{ constexpr double squared_radius = 0.25; // 0.5^2 double squared_norm; Point position; @@ -208,7 +256,8 @@ Point randomUnitVector(Random& rand, const Point& directions) { return position / std::sqrt(squared_norm); } -TEST_CASE("[Faunus] randomUnitVector") { +TEST_CASE("[Faunus] randomUnitVector") +{ Random r; int n = 4e5; Point rtp(0, 0, 0); @@ -221,9 +270,13 @@ TEST_CASE("[Faunus] randomUnitVector") { CHECK_EQ(rtp.z(), doctest::Approx(M_PI / 2.0).epsilon(0.005)); // phi [0:pi] --> =pi/2 } -Point randomUnitVectorPolar(Random& rand) { return rtp2xyz({1.0, 2.0 * M_PI * rand(), std::acos(2.0 * rand() - 1.0)}); } +Point randomUnitVectorPolar(Random& rand) +{ + return rtp2xyz({1.0, 2.0 * M_PI * rand(), std::acos(2.0 * rand() - 1.0)}); +} -TEST_CASE("[Faunus] randomUnitVectorPolar") { +TEST_CASE("[Faunus] randomUnitVectorPolar") +{ Random r; int n = 2e5; Point rtp(0, 0, 0); @@ -236,19 +289,39 @@ TEST_CASE("[Faunus] randomUnitVectorPolar") { CHECK_EQ(rtp.z(), doctest::Approx(0.5 * M_PI).epsilon(0.005)); // phi [0:pi] --> =pi/2 } -GenericError::GenericError(const std::exception& e) : GenericError(e.what()) {} -GenericError::GenericError(const std::runtime_error& e) : std::runtime_error(e) {} -GenericError::GenericError(const std::string& msg) : std::runtime_error(msg) {} -GenericError::GenericError(const char* msg) : std::runtime_error(msg) {} +GenericError::GenericError(const std::exception& e) + : GenericError(e.what()) +{ +} + +GenericError::GenericError(const std::runtime_error& e) + : std::runtime_error(e) +{ +} + +GenericError::GenericError(const std::string& msg) + : std::runtime_error(msg) +{ +} -const json& ConfigurationError::attachedJson() const { return attached_json; } +GenericError::GenericError(const char* msg) + : std::runtime_error(msg) +{ +} -ConfigurationError& ConfigurationError::attachJson(const json& j) { +const json& ConfigurationError::attachedJson() const +{ + return attached_json; +} + +ConfigurationError& ConfigurationError::attachJson(const json& j) +{ attached_json = j; return *this; } -void displayError(spdlog::logger& logger, const std::exception& e, int level) { +void displayError(spdlog::logger& logger, const std::exception& e, int level) +{ const std::string padding = level > 0 ? "... " : ""; logger.error(padding + e.what()); @@ -257,7 +330,8 @@ void displayError(spdlog::logger& logger, const std::exception& e, int level) { config_error != nullptr && !config_error->attachedJson().empty()) { if (level > 0) { logger.debug("... JSON snippet:\n{}", config_error->attachedJson().dump(4)); - } else { + } + else { logger.debug("JSON snippet:\n{}", config_error->attachedJson().dump(4)); } } @@ -265,12 +339,16 @@ void displayError(spdlog::logger& logger, const std::exception& e, int level) { // Process nested exceptions in a tail recursion. try { std::rethrow_if_nested(e); - } catch (const std::exception& e) { displayError(logger, e, level + 1); } + } + catch (const std::exception& e) { + displayError(logger, e, level + 1); + } } TEST_SUITE_BEGIN("Core"); -TEST_CASE("[Faunus] infinite/nan") { +TEST_CASE("[Faunus] infinite/nan") +{ CHECK(std::isnan(0.0 / 0.0)); CHECK(std::isnan(0.0 / 0.0 * 1.0)); CHECK(std::isinf(std::numeric_limits::infinity())); @@ -281,7 +359,8 @@ TEST_CASE("[Faunus] infinite/nan") { CHECK(std::isnan(std::numeric_limits::signaling_NaN() * 1.0)); } -TEST_CASE("[Faunus] distance") { +TEST_CASE("[Faunus] distance") +{ std::vector v = {10, 20, 30, 40, 30}; auto rng = v | ranges::cpp20::views::filter([](auto i) { return i == 30; }); CHECK_EQ(Faunus::distance(v.begin(), rng.begin()), 2); @@ -289,7 +368,8 @@ TEST_CASE("[Faunus] distance") { CHECK_EQ(Faunus::distance(v.begin(), ++it), 4); } -TEST_CASE("[Faunus] asEigenMatrix") { +TEST_CASE("[Faunus] asEigenMatrix") +{ using doctest::Approx; std::vector v(4); v[0].pos.x() = 5; @@ -318,55 +398,75 @@ TEST_SUITE_END(); /** * @param molarity Molar salt concentration - * @param valencies valencies for participating ions {1,-1} ~ NaCl, {2,-1} ~ MgCl2, {1,3,-2} ~ KAl(SO4)2 + * @param valencies valencies for participating ions {1,-1} ~ NaCl, {2,-1} ~ MgCl2, {1,3,-2} ~ + * KAl(SO4)2 * @throw If stoichiometry cannot be resolved */ Electrolyte::Electrolyte(const double molarity, const std::vector& valencies) : molarity(molarity) - , valencies(valencies) { + , valencies(valencies) +{ namespace rv = ranges::cpp20::views; - const auto sum_positive = ranges::accumulate(valencies | rv::filter([](auto v) { return v > 0; }), 0); - const auto sum_negative = ranges::accumulate(valencies | rv::filter([](auto v) { return v < 0; }), 0); + const auto sum_positive = + ranges::accumulate(valencies | rv::filter([](auto v) { return v > 0; }), 0); + const auto sum_negative = + ranges::accumulate(valencies | rv::filter([](auto v) { return v < 0; }), 0); const auto gcd = std::gcd(sum_positive, sum_negative); if (sum_positive == 0 || sum_negative == 0 || gcd == 0) { - throw std::runtime_error("cannot resolve stoichiometry; did you provide both + and - ions?"); + throw std::runtime_error( + "cannot resolve stoichiometry; did you provide both + and - ions?"); } auto nu_times_squared_valency = valencies | rv::transform([&](const auto valency) { - const auto nu = (valency > 0 ? -sum_negative : sum_positive) / gcd; + const auto nu = + (valency > 0 ? -sum_negative : sum_positive) / gcd; return nu * valency * valency; }); - ionic_strength = 0.5 * molarity * static_cast(ranges::accumulate(nu_times_squared_valency, 0)); - faunus_logger->debug("salt molarity {} --> ionic strength = {:.3f} mol/l", molarity, ionic_strength); + ionic_strength = + 0.5 * molarity * static_cast(ranges::accumulate(nu_times_squared_valency, 0)); + faunus_logger->debug("salt molarity {} --> ionic strength = {:.3f} mol/l", molarity, + ionic_strength); } /** * Back calculates the molarity, assuming 1:-1 salt charges. Note that stored properties * are temperature dependent which is why the bjerrum_length is required. */ -Electrolyte::Electrolyte(const double debye_length, const double bjerrum_length) { +Electrolyte::Electrolyte(const double debye_length, const double bjerrum_length) +{ valencies = {1, -1}; - ionic_strength = molarity = - std::pow(1.0 / debye_length, 2) / (8.0 * pc::pi * bjerrum_length * 1.0_angstrom * 1.0_molar); + ionic_strength = molarity = std::pow(1.0 / debye_length, 2) / + (8.0 * pc::pi * bjerrum_length * 1.0_angstrom * 1.0_molar); faunus_logger->debug("debyelength {} Γ… --> 1:1 salt molarity = {:.3f}", debye_length, molarity); } /** - * The salt composition is automatically resolved, and the ionic strength, I, is calculated according to - * I = 0.5 * concentration * sum( nu_i * valency_i^2 ) - * where `nu` are the minimum stoichiometric coefficients, deduced by assuming a net-neutral salt. + * The salt composition is automatically resolved, and the ionic strength, I, is calculated + * according to I = 0.5 * concentration * sum( nu_i * valency_i^2 ) where `nu` are the minimum + * stoichiometric coefficients, deduced by assuming a net-neutral salt. */ -double Electrolyte::ionicStrength() const { return ionic_strength; } +double Electrolyte::ionicStrength() const +{ + return ionic_strength; +} -double Electrolyte::getMolarity() const { return molarity; } +double Electrolyte::getMolarity() const +{ + return molarity; +} -const std::vector& Electrolyte::getValencies() const { return valencies; } +const std::vector& Electrolyte::getValencies() const +{ + return valencies; +} /** * @param bjerrum_length Bjerrum length in Angstrom * @return Debye screening length in Angstrom */ -double Electrolyte::debyeLength(const double bjerrum_length) const { - return 1.0 / std::sqrt(8.0 * pc::pi * bjerrum_length * 1.0_angstrom * ionic_strength * 1.0_molar); +double Electrolyte::debyeLength(const double bjerrum_length) const +{ + return 1.0 / + std::sqrt(8.0 * pc::pi * bjerrum_length * 1.0_angstrom * ionic_strength * 1.0_molar); } /** @@ -385,7 +485,8 @@ double Electrolyte::debyeLength(const double bjerrum_length) const { * * @throw if `debyelength` is found but no dielectric constant, `epsr`. */ -std::optional makeElectrolyte(const json& j) { +std::optional makeElectrolyte(const json& j) +{ if (auto it = j.find("debyelength"); it != j.end()) { const auto debye_length = it->get() * 1.0_angstrom; const auto relative_dielectric_constant = j.at("epsr").get(); // may throw! @@ -400,30 +501,38 @@ std::optional makeElectrolyte(const json& j) { return std::nullopt; } -TEST_CASE("[Faunus] Electrolyte") { +TEST_CASE("[Faunus] Electrolyte") +{ using doctest::Approx; CHECK_EQ(Electrolyte(0.1, {1, -1}).ionicStrength(), Approx(0.1)); // NaCl CHECK_EQ(Electrolyte(0.1, {2, -2}).ionicStrength(), Approx(0.5 * (0.1 * 4 + 0.1 * 4))); // CaSOβ‚„ - CHECK_EQ(Electrolyte(0.1, {2, -1}).ionicStrength(), Approx(0.5 * (0.1 * 4 + 0.2))); // CaClβ‚‚ - CHECK_EQ(Electrolyte(0.1, {1, -2}).ionicStrength(), Approx(0.5 * (0.2 + 0.1 * 4))); // Kβ‚‚SOβ‚„ - CHECK_EQ(Electrolyte(0.1, {1, -3}).ionicStrength(), Approx(0.5 * (0.3 + 0.1 * 9))); // Na₃Cit - CHECK_EQ(Electrolyte(0.1, {3, -1}).ionicStrength(), Approx(0.5 * (0.3 + 0.1 * 9))); // LaCl₃ - CHECK_EQ(Electrolyte(0.1, {2, -3}).ionicStrength(), Approx(0.5 * (0.3 * 4 + 0.2 * 9))); // Ca₃(POβ‚„)β‚‚ - CHECK_EQ(Electrolyte(0.1, {1, 3, -2}).ionicStrength(), Approx(0.5 * (0.1 * 1 + 0.1 * 9 + 0.1 * 2 * 4))); // KAl(SOβ‚„)β‚‚ - CHECK_EQ(Electrolyte(1.0, {2, 3, -2}).ionicStrength(), Approx(0.5 * (2 * 4 + 2 * 9 + 5 * 4))); // Caβ‚‚Alβ‚‚(SOβ‚„)β‚… + CHECK_EQ(Electrolyte(0.1, {2, -1}).ionicStrength(), Approx(0.5 * (0.1 * 4 + 0.2))); // CaClβ‚‚ + CHECK_EQ(Electrolyte(0.1, {1, -2}).ionicStrength(), Approx(0.5 * (0.2 + 0.1 * 4))); // Kβ‚‚SOβ‚„ + CHECK_EQ(Electrolyte(0.1, {1, -3}).ionicStrength(), Approx(0.5 * (0.3 + 0.1 * 9))); // Na₃Cit + CHECK_EQ(Electrolyte(0.1, {3, -1}).ionicStrength(), Approx(0.5 * (0.3 + 0.1 * 9))); // LaCl₃ + CHECK_EQ(Electrolyte(0.1, {2, -3}).ionicStrength(), + Approx(0.5 * (0.3 * 4 + 0.2 * 9))); // Ca₃(POβ‚„)β‚‚ + CHECK_EQ(Electrolyte(0.1, {1, 3, -2}).ionicStrength(), + Approx(0.5 * (0.1 * 1 + 0.1 * 9 + 0.1 * 2 * 4))); // KAl(SOβ‚„)β‚‚ + CHECK_EQ(Electrolyte(1.0, {2, 3, -2}).ionicStrength(), + Approx(0.5 * (2 * 4 + 2 * 9 + 5 * 4))); // Caβ‚‚Alβ‚‚(SOβ‚„)β‚… CHECK_THROWS(Electrolyte(0.1, {1, 1})); CHECK_THROWS(Electrolyte(0.1, {-1, -1})); CHECK_THROWS(Electrolyte(0.1, {0, 0})); - SUBCASE("debyeLength") { + SUBCASE("debyeLength") + { CHECK_EQ(Electrolyte(0.03, {1, -1}).debyeLength(7.0), Approx(17.7376102214)); CHECK_THROWS(makeElectrolyte(R"({"debyelength": 30.0"})"_json)); // 'epsr' is missing const auto bjerrum_length = pc::bjerrumLength(80); - CHECK_EQ(makeElectrolyte(R"({"debyelength": 30.0, "epsr": 80})"_json).value().debyeLength(bjerrum_length), - Approx(30)); + CHECK_EQ(makeElectrolyte(R"({"debyelength": 30.0, "epsr": 80})"_json) + .value() + .debyeLength(bjerrum_length), + Approx(30)); } - SUBCASE("debye length input") { + SUBCASE("debye length input") + { const auto bjerrum_length = 7.0; CHECK_EQ(Electrolyte(30, bjerrum_length).debyeLength(bjerrum_length), Approx(30)); CHECK_EQ(Electrolyte(30, bjerrum_length).getMolarity(), Approx(0.0104874272)); @@ -431,7 +540,8 @@ TEST_CASE("[Faunus] Electrolyte") { } } -void to_json(json& j, const Electrolyte& electrolyte) { +void to_json(json& j, const Electrolyte& electrolyte) +{ const auto bjerrum_length = pc::bjerrumLength(pc::T()); j = {{"molarity", electrolyte.getMolarity()}, {"valencies", electrolyte.getValencies()}, diff --git a/src/core.h b/src/core.h index 43a93ea26..da63ba660 100644 --- a/src/core.h +++ b/src/core.h @@ -24,7 +24,8 @@ class Random; /** Concept for a range of points */ template -concept RequirePoints = ranges::cpp20::range && std::is_same_v, Point>; +concept RequirePoints = + ranges::cpp20::range && std::is_same_v, Point>; /** Concept for an iterator to a `Point` */ template @@ -32,7 +33,8 @@ concept RequirePointIterator = std::is_convertible_v T value(const std::string& key, const T& fallback) { + template T value(const std::string& key, const T& fallback) + { return (count(key) > 0) ? at(key).get() : fallback; } }; -double roundValue(double value, int number_of_digits = 3); //!< Round to n number of significant digits -void roundJSON(json& j, int number_of_digits = 3); //!< Round float objects to n number of significant digits -double getValueInfinity(const json& j, - const std::string& key); //!< Extract floating point from json and allow for 'inf' and '-inf' +double roundValue(double value, + int number_of_digits = 3); //!< Round to n number of significant digits +void roundJSON(json& j, + int number_of_digits = 3); //!< Round float objects to n number of significant digits +double getValueInfinity( + const json& j, + const std::string& key); //!< Extract floating point from json and allow for 'inf' and '-inf' /** * @brief Returns a key-value pair from a JSON object which contains a single key. * - * JSON objects having a single key-value pair are a common pattern in JSON configuration used in Faunus. This - * function provides a convenient way to handle it. + * JSON objects having a single key-value pair are a common pattern in JSON configuration used in + * Faunus. This function provides a convenient way to handle it. * * @param j JSON object * @return tuple [key as a string, value as a JSON] - * @throw std::runtime_error when not a JSON object or the object is empty or the object contains more than a - * single value + * @throw std::runtime_error when not a JSON object or the object is empty or the object contains + * more than a single value */ std::tuple jsonSingleItem(const json& j); @@ -88,7 +95,8 @@ std::tuple jsonSingleItem(const json& j); * returned by the call operator. The idea is that this * functionality is completely optional. */ -class TipFromTheManual { +class TipFromTheManual +{ private: json database; // database std::unique_ptr random; @@ -96,7 +104,7 @@ class TipFromTheManual { public: std::string output_buffer; // accumulate output here - bool quiet = true; // if true, operator[] returns empty string + bool quiet = true; // if true, operator[] returns empty string bool asciiart = true; TipFromTheManual(); void load(const std::vector&); @@ -122,65 +130,79 @@ extern std::shared_ptr mcloop_logger; // global instance * * @warning Be careful that objects are properly aligned and divisible with `sizeof` */ -template auto asEigenMatrix(iter begin, iter end, memberptr m) { +template +auto asEigenMatrix(iter begin, iter end, memberptr m) +{ using T = typename std::iterator_traits::value_type; static_assert(sizeof(T) % sizeof(dbl) == 0, "value_type size must multiples of double"); constexpr size_t s = sizeof(T) / sizeof(dbl); constexpr size_t cols = sizeof((static_cast(0))->*m) / sizeof(dbl); using Tmatrix = Eigen::Matrix; - return Eigen::Map>((dbl*)&(*begin.*m), end - begin, cols).array(); + return Eigen::Map>((dbl*)&(*begin.*m), end - begin, cols) + .array(); } -template auto asEigenVector(iter begin, iter end, memberptr m) { +template +auto asEigenVector(iter begin, iter end, memberptr m) +{ using T = typename std::iterator_traits::value_type; - static_assert(std::is_same(0))->*m)>::value, "member must be a scalar"); + static_assert(std::is_same(0))->*m)>::value, + "member must be a scalar"); return asEigenMatrix(begin, end, m).col(0); } /** * @brief Convert cartesian- to cylindrical-coordinates - * @note Input (x,y,z), output \f$ (r,\theta, h) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in [-\pi,\pi) \f$, - * and \f$ h\in (-\infty,\infty] \f$. + * @note Input (x,y,z), output \f$ (r,\theta, h) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in + * [-\pi,\pi) \f$, and \f$ h\in (-\infty,\infty] \f$. */ Point xyz2rth(const Point&, const Point& origin = {0, 0, 0}, const Point& dir = {0, 0, 1}, const Point& dir2 = {1, 0, 0}); /** * @brief Convert cartesian- to spherical-coordinates - * @note Input (x,y,z), output \f$ (r,\theta,\phi) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in [-\pi,\pi) \f$, - * and \f$ \phi\in [0,\pi] \f$. + * @note Input (x,y,z), output \f$ (r,\theta,\phi) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in + * [-\pi,\pi) \f$, and \f$ \phi\in [0,\pi] \f$. */ Point xyz2rtp(const Point&, const Point& origin = {0, 0, 0}); /** * @brief Convert spherical- to cartesian-coordinates * @param origin The origin to be added (optional) - * @note Input \f$ (r,\theta,\phi) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in [0,2\pi) \f$, and \f$ \phi\in - * [0,\pi] \f$, and output (x,y,z). + * @note Input \f$ (r,\theta,\phi) \f$ where \f$ r\in [0,\infty) \f$, \f$ \theta\in [0,2\pi) \f$, + * and \f$ \phi\in [0,\pi] \f$, and output (x,y,z). */ Point rtp2xyz(const Point& rtp, const Point& origin = {0, 0, 0}); -[[maybe_unused]] std::string addGrowingSuffix(const std::string&); //!< Add growing suffix filename until non-existing name is found +[[maybe_unused]] std::string addGrowingSuffix( + const std::string&); //!< Add growing suffix filename until non-existing name is found Point randomUnitVector( Random& rand, - const Point& directions = Point::Ones()); //!< Random unit vector using Neuman's method ("sphere picking") + const Point& directions = + Point::Ones()); //!< Random unit vector using Neuman's method ("sphere picking") -Point randomUnitVectorPolar(Random& rand); //!< Random unit vector using polar coordinates ("sphere picking") +Point randomUnitVectorPolar( + Random& rand); //!< Random unit vector using polar coordinates ("sphere picking") //! Common ancestor of Faunus specific runtime errors -struct GenericError : public std::runtime_error { +struct GenericError : public std::runtime_error +{ explicit GenericError(const std::exception& e); explicit GenericError(const std::runtime_error& e); explicit GenericError(const std::string& msg); explicit GenericError(const char* msg); + template explicit GenericError(std::string_view fmt, const Args&... args) - : std::runtime_error(fmt::vformat(fmt, fmt::make_format_args(args...))) {} + : std::runtime_error(fmt::vformat(fmt, fmt::make_format_args(args...))) + { + } }; //! Exception to be thrown when parsing json configuration -struct ConfigurationError : public GenericError { +struct ConfigurationError : public GenericError +{ using GenericError::GenericError; [[nodiscard]] const json& attachedJson() const; ConfigurationError& attachJson(const json& j); @@ -190,7 +212,8 @@ struct ConfigurationError : public GenericError { }; //! Exception to be thrown on IO errors -struct IOError : public GenericError { +struct IOError : public GenericError +{ using GenericError::GenericError; }; @@ -209,7 +232,8 @@ void displayError(spdlog::logger& logger, const std::exception& e, int level = 0 * In this context a "salt" is an arbitrary set of cations and anions, combined to form * a net-neutral compound. The object state is _temperature independent_. */ -class Electrolyte { +class Electrolyte +{ private: double ionic_strength; double molarity; @@ -217,11 +241,14 @@ class Electrolyte { public: Electrolyte(double molarity, const std::vector& valencies); - Electrolyte(double debye_length, double bjerrum_length); //!< Initialize from existing Debye and Bjerrum length - [[nodiscard]] double ionicStrength() const; //!< Molar ionic strength (mol/l) - [[nodiscard]] double debyeLength(double bjerrum_length) const; //!< Debye screening length in Γ…ngstrom - [[nodiscard]] double getMolarity() const; //!< Input salt molarity (mol/l) - [[nodiscard]] const std::vector& getValencies() const; //!< Charges of each participating ion in the salt + Electrolyte(double debye_length, + double bjerrum_length); //!< Initialize from existing Debye and Bjerrum length + [[nodiscard]] double ionicStrength() const; //!< Molar ionic strength (mol/l) + [[nodiscard]] double + debyeLength(double bjerrum_length) const; //!< Debye screening length in Γ…ngstrom + [[nodiscard]] double getMolarity() const; //!< Input salt molarity (mol/l) + [[nodiscard]] const std::vector& + getValencies() const; //!< Charges of each participating ion in the salt }; void to_json(json& j, const Electrolyte& electrolyte); diff --git a/src/energy.cpp b/src/energy.cpp index df7caf209..fb60afd2d 100644 --- a/src/energy.cpp +++ b/src/energy.cpp @@ -12,8 +12,11 @@ #ifdef ENABLE_FREESASA #include -struct freesasa_parameters_fwd : public freesasa_parameters { - freesasa_parameters_fwd(const freesasa_parameters& p) : freesasa_parameters(p){}; + +struct freesasa_parameters_fwd : public freesasa_parameters +{ + freesasa_parameters_fwd(const freesasa_parameters& p) + : freesasa_parameters(p) {}; }; #endif @@ -22,35 +25,41 @@ struct freesasa_parameters_fwd : public freesasa_parameters { namespace Faunus::Energy { -EwaldData::EwaldData(const json &j) { - alpha = j.at("alpha"); // damping-parameter - r_cutoff = j.at("cutoff"); // real space cut-off - n_cutoff = j.at("ncutoff"); // reciprocal space cut-off - use_spherical_sum = j.value("spherical_sum", true); // Using spherical summation of k-vectors in reciprocal space? +EwaldData::EwaldData(const json& j) +{ + alpha = j.at("alpha"); // damping-parameter + r_cutoff = j.at("cutoff"); // real space cut-off + n_cutoff = j.at("ncutoff"); // reciprocal space cut-off + use_spherical_sum = j.value( + "spherical_sum", true); // Using spherical summation of k-vectors in reciprocal space? bjerrum_length = pc::bjerrumLength(j.at("epsr")); surface_dielectric_constant = j.value("epss", 0.0); // dielectric constant of surrounding medium - const_inf = - (surface_dielectric_constant < 1) ? 0 : 1; // if unphysical (<1) use epsr infinity for surrounding medium + const_inf = (surface_dielectric_constant < 1) + ? 0 + : 1; // if unphysical (<1) use epsr infinity for surrounding medium kappa = j.value("kappa", 0.0); kappa_squared = kappa * kappa; if (j.count("kcutoff")) { faunus_logger->warn("`kcutoff` is deprecated, use `ncutoff` instead"); n_cutoff = j.at("kcutoff"); - } else { + } + else { n_cutoff = j.at("ncutoff"); } if (j.value("ipbc", false)) { // look for legacy bool `ipbc` faunus_logger->warn("key `ipbc` is deprecated, use `ewaldscheme: ipbc` instead"); policy = EwaldData::IPBC; - } else { + } + else { policy = j.value("ewaldscheme", EwaldData::PBC); if (policy == EwaldData::INVALID) throw std::runtime_error("invalid `ewaldpolicy`"); } } -void to_json(json &j, const EwaldData &d) { +void to_json(json& j, const EwaldData& d) +{ j = {{"lB", d.bjerrum_length}, {"epss", d.surface_dielectric_constant}, {"alpha", d.alpha}, @@ -62,7 +71,8 @@ void to_json(json &j, const EwaldData &d) { {"ewaldscheme", d.policy}}; } -TEST_CASE("[Faunus] Ewald - EwaldData") { +TEST_CASE("[Faunus] Ewald - EwaldData") +{ using doctest::Approx; Space spc; @@ -90,7 +100,8 @@ TEST_CASE("[Faunus] Ewald - EwaldData") { //----------------- Ewald Policies ------------------- -std::unique_ptr EwaldPolicyBase::makePolicy(EwaldData::Policies policy) { +std::unique_ptr EwaldPolicyBase::makePolicy(EwaldData::Policies policy) +{ switch (policy) { case EwaldData::PBC: return std::make_unique(); @@ -106,18 +117,27 @@ std::unique_ptr EwaldPolicyBase::makePolicy(EwaldData::Policies return nullptr; } -PolicyIonIon::PolicyIonIon() { cite = "doi:10.1063/1.481216"; } -PolicyIonIonIPBC::PolicyIonIonIPBC() { cite = "doi:10/css8"; } +PolicyIonIon::PolicyIonIon() +{ + cite = "doi:10.1063/1.481216"; +} + +PolicyIonIonIPBC::PolicyIonIonIPBC() +{ + cite = "doi:10/css8"; +} /** * Resize k-vectors according to current variables and box length */ -void PolicyIonIon::updateBox(EwaldData &d, const Point &box) const { +void PolicyIonIon::updateBox(EwaldData& d, const Point& box) const +{ assert(d.policy == EwaldData::PBC or d.policy == EwaldData::PBCEigen); d.box_length = box; int n_cutoff_ceil = ceil(d.n_cutoff); d.check_k2_zero = 0.1 * std::pow(2 * pc::pi / d.box_length.maxCoeff(), 2); - int k_vector_size = (2 * n_cutoff_ceil + 1) * (2 * n_cutoff_ceil + 1) * (2 * n_cutoff_ceil + 1) - 1; + int k_vector_size = + (2 * n_cutoff_ceil + 1) * (2 * n_cutoff_ceil + 1) * (2 * n_cutoff_ceil + 1) - 1; if (k_vector_size == 0) { d.k_vectors.resize(3, 1); d.Aks.resize(1); @@ -126,7 +146,8 @@ void PolicyIonIon::updateBox(EwaldData &d, const Point &box) const { d.num_kvectors = 1; d.Q_ion.resize(1); d.Q_dipole.resize(1); - } else { + } + else { double nc2 = d.n_cutoff * d.n_cutoff; d.k_vectors.resize(3, k_vector_size); d.Aks.resize(k_vector_size); @@ -142,8 +163,9 @@ void PolicyIonIon::updateBox(EwaldData &d, const Point &box) const { auto dny2 = double(ny * ny); for (int nz = -n_cutoff_ceil * start_value; nz <= n_cutoff_ceil; nz++) { Point kv = 2 * pc::pi * Point(nx, ny, nz).cwiseQuotient(d.box_length); - double k2 = kv.squaredNorm() + d.kappa_squared; // last term is only for Yukawa-Ewald - if (k2 < d.check_k2_zero) // Check if k2 != 0 + double k2 = + kv.squaredNorm() + d.kappa_squared; // last term is only for Yukawa-Ewald + if (k2 < d.check_k2_zero) // Check if k2 != 0 continue; if (d.use_spherical_sum) { auto dnz2 = double(nz * nz); @@ -166,36 +188,44 @@ void PolicyIonIon::updateBox(EwaldData &d, const Point &box) const { /** * @todo Add OpenMP pragma to first loop */ -void PolicyIonIon::updateComplex(EwaldData& data, const Space::GroupVector& groups) const { +void PolicyIonIon::updateComplex(EwaldData& data, const Space::GroupVector& groups) const +{ for (int k = 0; k < data.k_vectors.cols(); k++) { - const Point &q = data.k_vectors.col(k); + const Point& q = data.k_vectors.col(k); EwaldData::Tcomplex Q(0, 0); - for (auto &g : groups) { // loop over molecules - for (auto &particle : g) { // loop over active particles + for (auto& g : groups) { // loop over molecules + for (auto& particle : g) { // loop over active particles double qr = q.dot(particle.pos); - Q += particle.charge * EwaldData::Tcomplex(std::cos(qr), - std::sin(qr)); // 'Q^q', see eq. 25 in ref. + Q += particle.charge * + EwaldData::Tcomplex(std::cos(qr), + std::sin(qr)); // 'Q^q', see eq. 25 in ref. } } data.Q_ion[k] = Q; } } -void PolicyIonIonEigen::updateComplex(EwaldData& data, const Space::GroupVector& groups) const { - auto [pos, charge] = mapGroupsToEigen(groups); // throws if inactive particles - Eigen::MatrixXd kr = pos.matrix() * data.k_vectors; // ( N x 3 ) * ( 3 x K ) = N x K - data.Q_ion.real() = (kr.array().cos().colwise() * charge).colwise().sum(); // real part of 'Q^q', see eq. 25 in ref. - data.Q_ion.imag() = kr.array().sin().colwise().sum(); // imaginary part of 'Q^q', see eq. 25 in ref. +void PolicyIonIonEigen::updateComplex(EwaldData& data, const Space::GroupVector& groups) const +{ + auto [pos, charge] = mapGroupsToEigen(groups); // throws if inactive particles + Eigen::MatrixXd kr = pos.matrix() * data.k_vectors; // ( N x 3 ) * ( 3 x K ) = N x K + data.Q_ion.real() = (kr.array().cos().colwise() * charge) + .colwise() + .sum(); // real part of 'Q^q', see eq. 25 in ref. + data.Q_ion.imag() = + kr.array().sin().colwise().sum(); // imaginary part of 'Q^q', see eq. 25 in ref. } -void PolicyIonIon::updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups, - const Space::GroupVector& oldgroups) const { +void PolicyIonIon::updateComplex(EwaldData& d, const Change& change, + const Space::GroupVector& groups, + const Space::GroupVector& oldgroups) const +{ assert(groups.size() == oldgroups.size()); for (int k = 0; k < d.k_vectors.cols(); k++) { - auto &Q = d.Q_ion[k]; - const Point &q = d.k_vectors.col(k); + auto& Q = d.Q_ion[k]; + const Point& q = d.k_vectors.col(k); - for (auto &changed_group : change.groups) { + for (auto& changed_group : change.groups) { auto& g_new = groups.at(changed_group.group_index); auto& g_old = oldgroups.at(changed_group.group_index); const auto max_group_size = std::max(g_new.size(), g_old.size()); @@ -216,7 +246,8 @@ void PolicyIonIon::updateComplex(EwaldData& d, const Change& change, const Space } } -TEST_CASE("[Faunus] Ewald - IonIonPolicy") { +TEST_CASE("[Faunus] Ewald - IonIonPolicy") +{ using doctest::Approx; Space spc; spc.particles.resize(2); @@ -236,31 +267,40 @@ TEST_CASE("[Faunus] Ewald - IonIonPolicy") { c.everything = true; data.policy = EwaldData::PBC; - SUBCASE("PBC") { + SUBCASE("PBC") + { PolicyIonIon ionion; ionion.updateBox(data, spc.geometry.getLength()); ionion.updateComplex(data, spc.groups); - CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), Approx(-1.0092530088080642 * data.bjerrum_length)); - CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), Approx(0.0020943951023931952 * data.bjerrum_length)); + CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), + Approx(-1.0092530088080642 * data.bjerrum_length)); + CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), + Approx(0.0020943951023931952 * data.bjerrum_length)); CHECK_EQ(ionion.reciprocalEnergy(data), Approx(0.21303063979675319 * data.bjerrum_length)); } - SUBCASE("PBCEigen") { + SUBCASE("PBCEigen") + { PolicyIonIonEigen ionion; ionion.updateBox(data, spc.geometry.getLength()); ionion.updateComplex(data, spc.groups); - CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), Approx(-1.0092530088080642 * data.bjerrum_length)); - CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), Approx(0.0020943951023931952 * data.bjerrum_length)); + CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), + Approx(-1.0092530088080642 * data.bjerrum_length)); + CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), + Approx(0.0020943951023931952 * data.bjerrum_length)); CHECK_EQ(ionion.reciprocalEnergy(data), Approx(0.21303063979675319 * data.bjerrum_length)); } - SUBCASE("IPBC") { + SUBCASE("IPBC") + { PolicyIonIonIPBC ionion; data.policy = EwaldData::IPBC; ionion.updateBox(data, spc.geometry.getLength()); ionion.updateComplex(data, spc.groups); - CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), Approx(-1.0092530088080642 * data.bjerrum_length)); - CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), Approx(0.0020943951023931952 * data.bjerrum_length)); + CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), + Approx(-1.0092530088080642 * data.bjerrum_length)); + CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), + Approx(0.0020943951023931952 * data.bjerrum_length)); CHECK_EQ(ionion.reciprocalEnergy(data), Approx(0.0865107467 * data.bjerrum_length)); } @@ -271,12 +311,13 @@ TEST_CASE("[Faunus] Ewald - IonIonPolicy") { ionion.updateBox(data, spc.geo.getLength()); ionion.updateComplex(data, spc.groups); CHECK_EQ(ionion.selfEnergy(data, c, spc.groups), Approx(-1.0092530088080642 * data.lB)); - CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), Approx(0.0020943951023931952 * data.lB)); - CHECK_EQ(ionion.reciprocalEnergy(data), Approx(0.0865107467 * data.lB)); + CHECK_EQ(ionion.surfaceEnergy(data, c, spc.groups), Approx(0.0020943951023931952 * + data.lB)); CHECK_EQ(ionion.reciprocalEnergy(data), Approx(0.0865107467 * data.lB)); }*/ } -TEST_CASE("[Faunus] Ewald - IonIonPolicy Benchmarks") { +TEST_CASE("[Faunus] Ewald - IonIonPolicy Benchmarks") +{ Space spc; spc.geometry = R"( {"type": "cuboid", "length": 80} )"_json; spc.particles.resize(200); @@ -312,7 +353,8 @@ TEST_CASE("[Faunus] Ewald - IonIonPolicy Benchmarks") { /** * Resize k-vectors according to current variables and box length */ -void PolicyIonIonIPBC::updateBox(EwaldData &data, const Point &box) const { +void PolicyIonIonIPBC::updateBox(EwaldData& data, const Point& box) const +{ assert(data.policy == EwaldData::IPBC or data.policy == EwaldData::IPBCEigen); data.box_length = box; int ncc = std::ceil(data.n_cutoff); @@ -326,7 +368,8 @@ void PolicyIonIonIPBC::updateBox(EwaldData &data, const Point &box) const { data.num_kvectors = 1; data.Q_ion.resize(1); data.Q_dipole.resize(1); - } else { + } + else { double nc2 = data.n_cutoff * data.n_cutoff; data.k_vectors.resize(3, k_vector_size); data.Aks.resize(k_vector_size); @@ -345,8 +388,9 @@ void PolicyIonIonIPBC::updateBox(EwaldData &data, const Point &box) const { if (nz > 0) factor *= 2; Point kv = 2 * pc::pi * Point(nx, ny, nz).cwiseQuotient(data.box_length); - double k2 = kv.squaredNorm() + data.kappa_squared; // last term is only for Yukawa-Ewald - if (k2 < data.check_k2_zero) // Check if k2 != 0 + double k2 = + kv.squaredNorm() + data.kappa_squared; // last term is only for Yukawa-Ewald + if (k2 < data.check_k2_zero) // Check if k2 != 0 continue; if (data.use_spherical_sum) { auto dnz2 = double(nz * nz); @@ -354,7 +398,8 @@ void PolicyIonIonIPBC::updateBox(EwaldData &data, const Point &box) const { continue; } data.k_vectors.col(data.num_kvectors) = kv; - data.Aks[data.num_kvectors] = factor * exp(-k2 / (4 * data.alpha * data.alpha)) / k2; + data.Aks[data.num_kvectors] = + factor * exp(-k2 / (4 * data.alpha * data.alpha)) / k2; data.num_kvectors++; } } @@ -366,21 +411,24 @@ void PolicyIonIonIPBC::updateBox(EwaldData &data, const Point &box) const { } } -void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Space::GroupVector& groups) const { +void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Space::GroupVector& groups) const +{ assert(d.policy == EwaldData::IPBC or d.policy == EwaldData::IPBCEigen); for (int k = 0; k < d.k_vectors.cols(); k++) { - const Point &q = d.k_vectors.col(k); + const Point& q = d.k_vectors.col(k); EwaldData::Tcomplex Q(0, 0); - for (auto &g : groups) { - for (auto &particle : g) { - Q += q.cwiseProduct(particle.pos).array().cos().prod() * particle.charge; // see eq. 2 in doi:10/css8 + for (auto& g : groups) { + for (auto& particle : g) { + Q += q.cwiseProduct(particle.pos).array().cos().prod() * + particle.charge; // see eq. 2 in doi:10/css8 } } d.Q_ion[k] = Q; } } -void PolicyIonIonIPBCEigen::updateComplex(EwaldData& d, const Space::GroupVector& groups) const { +void PolicyIonIonIPBCEigen::updateComplex(EwaldData& d, const Space::GroupVector& groups) const +{ assert(d.policy == EwaldData::IPBC or d.policy == EwaldData::IPBCEigen); auto [pos, charge] = mapGroupsToEigen(groups); // throws if inactive particles d.Q_ion.real() = (d.k_vectors.array().cwiseProduct(pos).array().cos().prod() * charge) @@ -388,15 +436,17 @@ void PolicyIonIonIPBCEigen::updateComplex(EwaldData& d, const Space::GroupVector .sum(); // see eq. 2 in doi:10/css8 } -void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups, - const Space::GroupVector& oldgroups) const { +void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Change& change, + const Space::GroupVector& groups, + const Space::GroupVector& oldgroups) const +{ assert(d.policy == EwaldData::IPBC or d.policy == EwaldData::IPBCEigen); assert(groups.size() == oldgroups.size()); for (int k = 0; k < d.k_vectors.cols(); k++) { - auto &Q = d.Q_ion[k]; - const Point &q = d.k_vectors.col(k); - for (auto &changed_group : change.groups) { + auto& Q = d.Q_ion[k]; + const Point& q = d.k_vectors.col(k); + for (auto& changed_group : change.groups) { auto& g_new = groups.at(changed_group.group_index); auto& g_old = oldgroups.at(changed_group.group_index); for (auto i : changed_group.relative_atom_indices) { @@ -414,24 +464,29 @@ void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Change& change, const S * the squared `qr`. Hence the `change` object is ignored. */ double PolicyIonIon::surfaceEnergy(const EwaldData& data, [[maybe_unused]] const Change& change, - const Space::GroupVector& groups) { + const Space::GroupVector& groups) +{ namespace rv = ranges::cpp20::views; if (data.const_inf < 0.5 || change.empty()) { return 0.0; } const auto volume = data.box_length.prod(); - auto charge_x_position = [](const Particle& particle) -> Point { return particle.charge * particle.pos; }; + auto charge_x_position = [](const Particle& particle) -> Point { + return particle.charge * particle.pos; + }; auto qr_range = groups | rv::join | rv::transform(charge_x_position); const auto qr_squared = ranges::accumulate(qr_range, Point(0, 0, 0)).squaredNorm(); - return data.const_inf * 2.0 * pc::pi / ((2.0 * data.surface_dielectric_constant + 1.0) * volume) * qr_squared * + return data.const_inf * 2.0 * pc::pi / + ((2.0 * data.surface_dielectric_constant + 1.0) * volume) * qr_squared * data.bjerrum_length; } -double PolicyIonIon::selfEnergy(const EwaldData& d, Change& change, Space::GroupVector& groups) { +double PolicyIonIon::selfEnergy(const EwaldData& d, Change& change, Space::GroupVector& groups) +{ double charges_squared = 0; double charge_total = 0; if (change.matter_change) { - for (auto &changed_group : change.groups) { + for (auto& changed_group : change.groups) { auto& g = groups.at(changed_group.group_index); for (auto i : changed_group.relative_atom_indices) { if (i < g.size()) { @@ -440,26 +495,34 @@ double PolicyIonIon::selfEnergy(const EwaldData& d, Change& change, Space::Group } } } - } else if (change.everything and not change.volume_change) { - for (auto &g : groups) { - for (auto &particle : g) { + } + else if (change.everything and not change.volume_change) { + for (auto& g : groups) { + for (auto& particle : g) { charges_squared += particle.charge * particle.charge; charge_total += particle.charge; } } } - double Vcc = -pc::pi / 2.0 / d.alpha / d.alpha / ( d.box_length[0] * d.box_length[1] * d.box_length[2] ) * charge_total * charge_total; // compensate with neutralizing background (if non-zero total charge in system) + double Vcc = -pc::pi / 2.0 / d.alpha / d.alpha / + (d.box_length[0] * d.box_length[1] * d.box_length[2]) * charge_total * + charge_total; // compensate with neutralizing background (if non-zero total charge + // in system) double beta = d.kappa / (2.0 * d.alpha); - if( beta > 1e-6 ) - Vcc *= ( 1.0 - exp( -beta * beta ) ) / beta / beta; // same as above but for Yukawa-systems - return ( -d.alpha * charges_squared / std::sqrt(pc::pi) * (std::exp(-beta*beta) + std::sqrt(pc::pi) * beta * std::erf(beta)) + Vcc) * d.bjerrum_length; + if (beta > 1e-6) + Vcc *= (1.0 - exp(-beta * beta)) / beta / beta; // same as above but for Yukawa-systems + return (-d.alpha * charges_squared / std::sqrt(pc::pi) * + (std::exp(-beta * beta) + std::sqrt(pc::pi) * beta * std::erf(beta)) + + Vcc) * + d.bjerrum_length; } /** * Updates the reciprocal space terms 'Q^q' and 'A_k'. * See eqs. 24 and 25 in ref. for PBC Ewald, and eq. 2 in doi:10/css8 for IPBC Ewald. */ -double PolicyIonIon::reciprocalEnergy(const EwaldData &d) { +double PolicyIonIon::reciprocalEnergy(const EwaldData& d) +{ double energy = 0; for (int k = 0; k < d.Q_ion.size(); k++) { energy += d.Aks[k] * std::norm(d.Q_ion[k]); @@ -467,14 +530,16 @@ double PolicyIonIon::reciprocalEnergy(const EwaldData &d) { return 2 * pc::pi * energy * d.bjerrum_length / d.box_length.prod(); } -double PolicyIonIonEigen::reciprocalEnergy(const EwaldData &d) { +double PolicyIonIonEigen::reciprocalEnergy(const EwaldData& d) +{ double energy = d.Aks.cwiseProduct(d.Q_ion.cwiseAbs2()).sum(); return 2 * pc::pi * d.bjerrum_length * energy / d.box_length.prod(); } Ewald::Ewald(const Space& spc, const EwaldData& data) : spc(spc) - , data(data) { + , data(data) +{ name = "ewald"; policy = EwaldPolicyBase::makePolicy(data.policy); citation_information = policy->cite; @@ -482,9 +547,12 @@ Ewald::Ewald(const Space& spc, const EwaldData& data) } Ewald::Ewald(const json& j, const Space& spc) - : Ewald(spc, static_cast(j)) {} + : Ewald(spc, static_cast(j)) +{ +} -void Ewald::init() { +void Ewald::init() +{ policy->updateBox(data, spc.geometry.getLength()); policy->updateComplex(data, spc.groups); // brute force. todo: be selective } @@ -493,11 +561,13 @@ void Ewald::init() { * If `old_groups` have been set and if the change object is only partial, this will attempt * to perform a faster, partial update of the k-vectors. Otherwise perform a full (slower) update. */ -void Ewald::updateState(const Change& change) { +void Ewald::updateState(const Change& change) +{ if (change) { if (!change.groups.empty() && old_groups && !change.everything && !change.volume_change) { policy->updateComplex(data, change, spc.groups, *old_groups); // partial update (fast) - } else { // full update (slow) + } + else { // full update (slow) policy->updateBox(data, spc.geometry.getLength()); policy->updateComplex(data, spc.groups); } @@ -508,7 +578,8 @@ void Ewald::updateState(const Change& change) { * the selfEnergy() is omitted as this is added as a separate term in `Hamiltonian` * (The pair-potential is responsible for this) */ -double Ewald::energy(const Change& change) { +double Ewald::energy(const Change& change) +{ if (change) { return policy->surfaceEnergy(data, change, spc.groups) + policy->reciprocalEnergy(data); } @@ -522,14 +593,16 @@ double Ewald::energy(const Change& change) { * the destination force vector will *not* be zeroed * before addition. */ -void Ewald::force(std::vector &forces) { +void Ewald::force(std::vector& forces) +{ assert(forces.size() == spc.particles.size()); const double volume = spc.geometry.getVolume(); // Surface contribution Point total_dipole_moment = {0.0, 0.0, 0.0}; for (auto& particle : spc.particles) { - auto mu = particle.hasExtension() ? particle.getExt().mu * particle.getExt().mulen : Point(0, 0, 0); + auto mu = particle.hasExtension() ? particle.getExt().mu * particle.getExt().mulen + : Point(0, 0, 0); total_dipole_moment += particle.pos * particle.charge + mu; } @@ -539,7 +612,8 @@ void Ewald::force(std::vector &forces) { data.Q_dipole.resize(data.Q_ion.size()); for (auto& particle : spc.particles) { // loop over particles - (*force) = total_dipole_moment * particle.charge / (2.0 * data.surface_dielectric_constant + 1.0); + (*force) = + total_dipole_moment * particle.charge / (2.0 * data.surface_dielectric_constant + 1.0); double mu_scalar = particle.hasExtension() ? particle.getExt().mulen : 0.0; std::complex qmu(mu_scalar, particle.charge); for (size_t i = 0; i < data.k_vectors.cols(); i++) { // loop over k vectors @@ -558,9 +632,13 @@ void Ewald::force(std::vector &forces) { * When updating k-vectors an optimization can be performed if the old group positions * are known. Use this function to set a pointer to the old groups (usually from the accepted Space) */ -void Ewald::setOldGroups(const Space::GroupVector& old_groups) { this->old_groups = &old_groups; } +void Ewald::setOldGroups(const Space::GroupVector& old_groups) +{ + this->old_groups = &old_groups; +} -void Ewald::sync(EnergyTerm* energybase, const Change& change) { +void Ewald::sync(EnergyTerm* energybase, const Change& change) +{ if (auto* other = dynamic_cast(energybase)) { if (!old_groups && other->state == MonteCarloState::ACCEPTED) { setOldGroups(other->spc.groups); @@ -569,17 +647,23 @@ void Ewald::sync(EnergyTerm* energybase, const Change& change) { if (change.everything or change.volume_change) { data.Q_dipole.resize(0); // dipoles are currently unsupported data = other->data; - } else { + } + else { data.Q_ion = other->data.Q_ion; } - } else { + } + else { throw std::runtime_error("sync error"); } } -void Ewald::to_json(json &j) const { j = data; } +void Ewald::to_json(json& j) const +{ + j = data; +} -TEST_CASE("[Faunus] Energy::Ewald") { +TEST_CASE("[Faunus] Energy::Ewald") +{ EwaldData data(R"({ "epsr": 1.0, "alpha": 0.894427190999916, "epss": 1.0, "ncutoff": 11.0, "spherical_sum": true, "cutoff": 5.0})"_json); @@ -590,26 +674,35 @@ TEST_CASE("[Faunus] Energy::Ewald") { Space space; SpaceFactory::makeNaCl(space, 1, R"( {"type": "cuboid", "length": 10} )"_json); PointVector positions = {{0, 0, 0}, {1, 0, 0}}; - space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), copy_position); + space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), + copy_position); auto ewald = Ewald(space, data); Change change; change.everything = true; // reference energy from IonIonPolicy: - const auto reference_energy = (0.0020943951023931952 + 0.21303063979675319) * data.bjerrum_length; + const auto reference_energy = + (0.0020943951023931952 + 0.21303063979675319) * data.bjerrum_length; const auto energy_change = -16.8380445846; // move first position (0,0,0) --> (0.1, 0.1, 0.1) - SUBCASE("energy") { CHECK_EQ(ewald.energy(change), doctest::Approx(reference_energy)); } + SUBCASE("energy") + { + CHECK_EQ(ewald.energy(change), doctest::Approx(reference_energy)); + } - SUBCASE("update and restore (full update)") { + SUBCASE("update and restore (full update)") + { positions[0] = {0.1, 0.1, 0.1}; - space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), copy_position); + space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), + copy_position); ewald.updateState(change); CHECK_EQ(ewald.energy(change), doctest::Approx(103.7300260099)); CHECK_EQ(ewald.energy(change) - reference_energy, doctest::Approx(energy_change)); positions[0] = {0.0, 0.0, 0.0}; - space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), copy_position); - CHECK((ewald.energy(change) != doctest::Approx(reference_energy))); // no match since k-vectors not updated + space.updateParticles(positions.begin(), positions.end(), space.particles.begin(), + copy_position); + CHECK((ewald.energy(change) != + doctest::Approx(reference_energy))); // no match since k-vectors not updated ewald.updateState(change); CHECK_EQ(ewald.energy(change), doctest::Approx(reference_energy)); // that's better } @@ -617,41 +710,48 @@ TEST_CASE("[Faunus] Energy::Ewald") { Space trial_space; SpaceFactory::makeNaCl(trial_space, 1, R"( {"type": "cuboid", "length": 10} )"_json); positions = {{0, 0, 0}, {1, 0, 0}}; - trial_space.updateParticles(positions.begin(), positions.end(), trial_space.particles.begin(), copy_position); + trial_space.updateParticles(positions.begin(), positions.end(), trial_space.particles.begin(), + copy_position); auto trial_ewald = Ewald(trial_space, data); CHECK_EQ(trial_ewald.energy(change), doctest::Approx(reference_energy)); - auto check_energy_change = [&](const Change& change) { // perturb from (0,0,0) --> (0.1, 0.1, 0.1) - positions[0] = {0.0, 0.0, 0.0}; - trial_space.updateParticles(positions.begin(), positions.end(), trial_space.particles.begin(), copy_position); - trial_ewald.updateState(change); - ewald.sync(&trial_ewald, change); - space.sync(trial_space, change); - const auto old_energy = trial_ewald.energy(change); - - positions[0] = {0.1, 0.1, 0.1}; - trial_space.updateParticles(positions.begin(), positions.end(), trial_space.particles.begin(), copy_position); - trial_ewald.updateState(change); - ewald.sync(&trial_ewald, change); - space.sync(trial_space, change); - const auto new_energy = trial_ewald.energy(change); // position (0.1,0.1,0.1) - CHECK_EQ(energy_change, doctest::Approx(new_energy - old_energy)); - }; + auto check_energy_change = + [&](const Change& change) { // perturb from (0,0,0) --> (0.1, 0.1, 0.1) + positions[0] = {0.0, 0.0, 0.0}; + trial_space.updateParticles(positions.begin(), positions.end(), + trial_space.particles.begin(), copy_position); + trial_ewald.updateState(change); + ewald.sync(&trial_ewald, change); + space.sync(trial_space, change); + const auto old_energy = trial_ewald.energy(change); + + positions[0] = {0.1, 0.1, 0.1}; + trial_space.updateParticles(positions.begin(), positions.end(), + trial_space.particles.begin(), copy_position); + trial_ewald.updateState(change); + ewald.sync(&trial_ewald, change); + space.sync(trial_space, change); + const auto new_energy = trial_ewald.energy(change); // position (0.1,0.1,0.1) + CHECK_EQ(energy_change, doctest::Approx(new_energy - old_energy)); + }; trial_ewald.setOldGroups(space.groups); // give access to positions in accepted state - SUBCASE("update and restore (trial ewald)") { + SUBCASE("update and restore (trial ewald)") + { change.everything = true; check_energy_change(change); } - SUBCASE("update and restore (partial using `all`)") { + SUBCASE("update and restore (partial using `all`)") + { change.clear(); auto& group_change = change.groups.emplace_back(); group_change.group_index = 0; group_change.all = true; check_energy_change(change); } - SUBCASE("update and restore (partial using `relative_atom_indices`)") { + SUBCASE("update and restore (partial using `relative_atom_indices`)") + { change.clear(); auto& group_change = change.groups.emplace_back(); group_change.group_index = 0; @@ -661,9 +761,10 @@ TEST_CASE("[Faunus] Energy::Ewald") { } } -double Example2D::energy(const Change&) { - double s = - 1 + std::sin(2.0 * pc::pi * particle.x()) + std::cos(2.0 * pc::pi * particle.y()) * static_cast(use_2d); +double Example2D::energy(const Change&) +{ + double s = 1 + std::sin(2.0 * pc::pi * particle.x()) + + std::cos(2.0 * pc::pi * particle.y()) * static_cast(use_2d); s *= scale_energy; if (particle.x() >= -2.00 && particle.x() <= -1.25) return 1 * s; @@ -678,18 +779,24 @@ double Example2D::energy(const Change&) { return 1e10; } -Example2D::Example2D(const json& j, Space& spc) : particle(spc.particles.at(0).pos) { +Example2D::Example2D(const json& j, Space& spc) + : particle(spc.particles.at(0).pos) +{ scale_energy = j.value("scale", 1.0); use_2d = j.value("2D", true); name = "Example2D"; } -void Example2D::to_json(json &j) const { + +void Example2D::to_json(json& j) const +{ j["scale"] = scale_energy; j["2D"] = use_2d; } -double ContainerOverlap::energy(const Change& change) { - if (change && spc.geometry.type != Geometry::Variant::CUBOID) { // no need to check in PBC systems +double ContainerOverlap::energy(const Change& change) +{ + if (change && + spc.geometry.type != Geometry::Variant::CUBOID) { // no need to check in PBC systems // *all* groups if (change.volume_change or change.everything) { return energyOfAllGroups(); @@ -707,10 +814,13 @@ double ContainerOverlap::energy(const Change& change) { /** * @return infinity if any active particle is outside; zero otherwise */ -double ContainerOverlap::energyOfAllGroups() const { - auto positions = spc.groups | ranges::cpp20::views::join | ranges::cpp20::views::transform(&Particle::pos); - bool outside = std::any_of(positions.begin(), positions.end(), - [&](const auto& position) { return spc.geometry.collision(position); }); +double ContainerOverlap::energyOfAllGroups() const +{ + auto positions = + spc.groups | ranges::cpp20::views::join | ranges::cpp20::views::transform(&Particle::pos); + bool outside = std::any_of(positions.begin(), positions.end(), [&](const auto& position) { + return spc.geometry.collision(position); + }); return outside ? pc::infty : 0.0; } @@ -718,12 +828,14 @@ double ContainerOverlap::energyOfAllGroups() const { * @brief Check a single group based on change object * @return true is any particle in group is outside; false otherwise */ -bool ContainerOverlap::groupIsOutsideContainer(const Change::GroupChange& group_change) const { +bool ContainerOverlap::groupIsOutsideContainer(const Change::GroupChange& group_change) const +{ const auto& group = spc.groups.at(group_change.group_index); // *all* atoms if (group_change.all) { - return std::any_of(group.begin(), group.end(), - [&](auto const& particle) { return spc.geometry.collision(particle.pos); }); + return std::any_of(group.begin(), group.end(), [&](auto const& particle) { + return spc.geometry.collision(particle.pos); + }); } // *subset* of atoms for (const auto particle_index : group_change.relative_atom_indices) { @@ -735,11 +847,18 @@ bool ContainerOverlap::groupIsOutsideContainer(const Change::GroupChange& group_ } return false; // no overlap } -ContainerOverlap::ContainerOverlap(const Space& spc) : spc(spc) { name = "ContainerOverlap"; } + +ContainerOverlap::ContainerOverlap(const Space& spc) + : spc(spc) +{ + name = "ContainerOverlap"; +} // ------------- Isobaric --------------- -Isobaric::Isobaric(const json& j, const Space& spc) : spc(spc) { +Isobaric::Isobaric(const json& j, const Space& spc) + : spc(spc) +{ name = "isobaric"; citation_information = "doi:10/dhn4v6 alt. Frenkel & Smith, 2nd Ed (Eq. 5.4.13)"; @@ -752,7 +871,8 @@ Isobaric::Isobaric(const json& j, const Space& spc) : spc(spc) { throw ConfigurationError("specify pressure"); } -void Isobaric::to_json(json& j) const { +void Isobaric::to_json(json& j) const +{ for (const auto& [key, conversion_factor] : pressure_units) { j[key] = pressure / conversion_factor; } @@ -763,26 +883,32 @@ void Isobaric::to_json(json& j) const { * @brief Calculates the energy contribution p Γ— V / kT - (N + 1) Γ— ln(V) * @return energy in kT */ -double Isobaric::energy(const Change& change) { +double Isobaric::energy(const Change& change) +{ if (change.volume_change || change.everything || change.matter_change) { auto group_is_active = [](const auto& group) { return !group.empty(); }; - auto count_particles = [](const auto& group) { return group.isAtomic() ? group.size() : 1; }; + auto count_particles = [](const auto& group) { + return group.isAtomic() ? group.size() : 1; + }; auto particles_per_group = spc.groups | ranges::cpp20::views::filter(group_is_active) | ranges::cpp20::views::transform(count_particles); - auto number_of_particles = std::accumulate(particles_per_group.begin(), particles_per_group.end(), 0); + auto number_of_particles = + std::accumulate(particles_per_group.begin(), particles_per_group.end(), 0); const auto volume = spc.geometry.getVolume(); return pressure * volume - static_cast(number_of_particles + 1) * std::log(volume); } return 0.0; } -const std::map Isobaric::pressure_units = {{"P/atm", 1.0_atm}, - {"P/bar", 1.0_bar}, - {"P/kT", 1.0_kT}, - {"P/mM", 1.0_millimolar}, - {"P/Pa", 1.0_Pa}}; // add more if you fancy... +const std::map Isobaric::pressure_units = { + {"P/atm", 1.0_atm}, + {"P/bar", 1.0_bar}, + {"P/kT", 1.0_kT}, + {"P/mM", 1.0_millimolar}, + {"P/Pa", 1.0_Pa}}; // add more if you fancy... -TEST_CASE("Energy::Isobaric") { +TEST_CASE("Energy::Isobaric") +{ Space spc; CHECK_NOTHROW(Isobaric(json({{"P/atm", 0.0}}), spc)); CHECK_NOTHROW(Isobaric(json({{"P/bar", 0.0}}), spc)); @@ -791,7 +917,8 @@ TEST_CASE("Energy::Isobaric") { CHECK_NOTHROW(Isobaric(json({{"P/Pa", 0.0}}), spc)); CHECK_THROWS(Isobaric(json({{"P/unknown_unit", 0.0}}), spc)); - SUBCASE("to_json") { + SUBCASE("to_json") + { json j; Isobaric(json({{"P/atm", 0.5}}), spc).to_json(j); CHECK_EQ(j.at("P/atm").get(), doctest::Approx(0.5)); @@ -806,7 +933,8 @@ TEST_CASE("Energy::Isobaric") { } } -Constrain::Constrain(const json& j, Space& spc) { +Constrain::Constrain(const json& j, Space& spc) +{ name = "constrain"; type = j.at("type").get(); if (const auto it = j.find("harmonic"); it != j.end()) { @@ -816,11 +944,13 @@ Constrain::Constrain(const json& j, Space& spc) { coordinate = ReactionCoordinate::createReactionCoordinate({{type, j}}, spc); } -double Constrain::energy(const Change& change) { +double Constrain::energy(const Change& change) +{ if (change) { const auto value = (*coordinate)(); // calculate reaction coordinate if (harmonic) { - return harmonic->half_force_constant * std::pow(harmonic->equilibrium_distance - value, 2); + return harmonic->half_force_constant * + std::pow(harmonic->equilibrium_distance - value, 2); } if (not coordinate->inRange(value)) { // if outside allowed range ... return pc::infty; // ... return infinite energy @@ -828,7 +958,9 @@ double Constrain::energy(const Change& change) { } return 0.0; } -void Constrain::to_json(json& j) const { + +void Constrain::to_json(json& j) const +{ j = json(*coordinate).at(type); j.erase("resolution"); j["type"] = type; @@ -839,7 +971,8 @@ void Constrain::to_json(json& j) const { } } -void Bonded::updateGroupBonds(const Space::GroupType& group) { +void Bonded::updateGroupBonds(const Space::GroupType& group) +{ const auto first_particle_index = spc.getFirstParticleIndex(group); const auto group_index = spc.getGroupIndex(group); auto& bonds = internal_bonds[group_index]; // access or insert @@ -853,14 +986,19 @@ void Bonded::updateGroupBonds(const Space::GroupType& group) { /** * Ensures that all internal bonds are updated according to bonds defined in the topology. */ -void Bonded::updateInternalBonds() { +void Bonded::updateInternalBonds() +{ internal_bonds.clear(); - std::for_each(spc.groups.begin(), spc.groups.end(), [&](auto& group) { updateGroupBonds(group); }); + std::for_each(spc.groups.begin(), spc.groups.end(), + [&](auto& group) { updateGroupBonds(group); }); } -double Bonded::sumBondEnergy(const Bonded::BondVector& bonds) const { +double Bonded::sumBondEnergy(const Bonded::BondVector& bonds) const +{ #if (defined(__clang__) && __clang_major__ >= 10) || (defined(__GNUC__) && __GNUC__ >= 10) - auto bond_energy = [&](const auto& bond) { return bond->energyFunc(spc.geometry.getDistanceFunc()); }; + auto bond_energy = [&](const auto& bond) { + return bond->energyFunc(spc.geometry.getDistanceFunc()); + }; return std::transform_reduce(bonds.begin(), bonds.end(), 0.0, std::plus<>(), bond_energy); #else double energy = 0.0; @@ -872,7 +1010,9 @@ double Bonded::sumBondEnergy(const Bonded::BondVector& bonds) const { } Bonded::Bonded(const Space& spc, BondVector external_bonds = BondVector()) - : spc(spc), external_bonds(std::move(external_bonds)) { + : spc(spc) + , external_bonds(std::move(external_bonds)) +{ name = "bonded"; updateInternalBonds(); for (auto& bond : this->external_bonds) { @@ -880,21 +1020,27 @@ Bonded::Bonded(const Space& spc, BondVector external_bonds = BondVector()) } } -Bonded::Bonded(const json& j, const Space& spc) : Bonded(spc, j.value("bondlist", BondVector())) {} +Bonded::Bonded(const json& j, const Space& spc) + : Bonded(spc, j.value("bondlist", BondVector())) +{ +} -void Bonded::to_json(json& j) const { +void Bonded::to_json(json& j) const +{ if (!external_bonds.empty()) { j["bondlist"] = external_bonds; } if (!internal_bonds.empty()) { json& array_of_bonds = j["bondlist-intramolecular"] = json::array(); for ([[maybe_unused]] const auto& [group_index, bonds] : internal_bonds) { - std::for_each(bonds.begin(), bonds.end(), [&](auto& bond) { array_of_bonds.push_back(bond); }); + std::for_each(bonds.begin(), bonds.end(), + [&](auto& bond) { array_of_bonds.push_back(bond); }); } } } -double Bonded::energy(const Change& change) { +double Bonded::energy(const Change& change) +{ double energy = 0.0; if (change) { energy += sumBondEnergy(external_bonds); @@ -904,7 +1050,8 @@ double Bonded::energy(const Change& change) { energy += sumBondEnergy(bonds); } } - } else { // calc. for a subset of groups + } + else { // calc. for a subset of groups for (const auto& group_change : change.groups) { energy += internalGroupEnergy(group_change); } @@ -913,7 +1060,8 @@ double Bonded::energy(const Change& change) { return energy; } -double Bonded::internalGroupEnergy(const Change::GroupChange& changed) { +double Bonded::internalGroupEnergy(const Change::GroupChange& changed) +{ using namespace ranges::cpp20::views; // @todo cpp20 --> std::ranges double energy = 0.0; const auto& group = spc.groups.at(changed.group_index); @@ -921,10 +1069,12 @@ double Bonded::internalGroupEnergy(const Change::GroupChange& changed) { const auto& bonds = internal_bonds.at(changed.group_index); if (changed.all) { // all internal positions updated energy += sumBondEnergy(bonds); - } else { // only partial update of affected atoms + } + else { // only partial update of affected atoms const auto first_particle_index = spc.getFirstParticleIndex(group); - auto particle_indices = changed.relative_atom_indices | - transform([first_particle_index](auto i) { return i + first_particle_index; }); + auto particle_indices = + changed.relative_atom_indices | + transform([first_particle_index](auto i) { return i + first_particle_index; }); energy += sumEnergy(bonds, particle_indices); } } @@ -944,7 +1094,8 @@ double Bonded::internalGroupEnergy(const Change::GroupChange& changed) { * * @warning Untested */ -void Bonded::force(std::vector& forces) { +void Bonded::force(std::vector& forces) +{ auto distance_function = spc.geometry.getDistanceFunc(); auto calculateForces = [&](const auto& bond) { @@ -969,11 +1120,14 @@ void Bonded::force(std::vector& forces) { //---------- Hamiltonian ------------ -void Hamiltonian::to_json(json &j) const { - std::for_each(energy_terms.cbegin(), energy_terms.cend(), [&](auto energy) { j.push_back(*energy); }); +void Hamiltonian::to_json(json& j) const +{ + std::for_each(energy_terms.cbegin(), energy_terms.cend(), + [&](auto energy) { j.push_back(*energy); }); } -void Hamiltonian::addEwald(const json& j, Space& spc) { +void Hamiltonian::addEwald(const json& j, Space& spc) +{ // note this will currently not detect multipolar energies ore // deeply nested "coulomb" pair-potentials json _j; @@ -984,9 +1138,11 @@ void Hamiltonian::addEwald(const json& j, Space& spc) { break; } } - } else if (j.count("coulomb") == 1) { + } + else if (j.count("coulomb") == 1) { _j = j["coulomb"]; - } else { + } + else { return; } @@ -998,14 +1154,18 @@ void Hamiltonian::addEwald(const json& j, Space& spc) { } } -void Hamiltonian::force(PointVector& forces) { - std::for_each(energy_terms.begin(), energy_terms.end(), [&](auto energy) { energy->force(forces); }); +void Hamiltonian::force(PointVector& forces) +{ + std::for_each(energy_terms.begin(), energy_terms.end(), + [&](auto energy) { energy->force(forces); }); } /** * @todo Move addEwald to Nonbonded as it now has access to Hamiltonian and can add */ -Hamiltonian::Hamiltonian(Space& spc, const json& j) : energy_terms(this->vec) { +Hamiltonian::Hamiltonian(Space& spc, const json& j) + : energy_terms(this->vec) +{ name = "hamiltonian"; if (!j.is_array()) { throw ConfigurationError("energy: json array expected"); @@ -1022,18 +1182,22 @@ Hamiltonian::Hamiltonian(Space& spc, const json& j) : energy_terms(this->vec) { const auto& [key, value] = Faunus::jsonSingleItem(j_energy); try { if (key == "maxenergy") { - // looks like an unfortumate json scheme decision that requires special handling here + // looks like an unfortumate json scheme decision that requires special handling + // here maximum_allowed_energy = value.get(); - } else { + } + else { energy_terms.push_back(createEnergy(spc, key, value)); faunus_logger->debug("hamiltonian expanded with {}", key); addEwald(value, spc); // add reciprocal Ewald terms if appropriate } - } catch (std::exception& e) { + } + catch (std::exception& e) { usageTip.pick(key); throw ConfigurationError("{} -> {}", key, e.what()); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("energy -> {}", e.what()).attachJson(j_energy); } } @@ -1041,18 +1205,22 @@ Hamiltonian::Hamiltonian(Space& spc, const json& j) : energy_terms(this->vec) { checkBondedMolecules(); } -void Hamiltonian::checkBondedMolecules() const { - if (find().empty()) { // no bond potential added? issue warning if molecules w. bonds - auto molecules_with_bonds = Faunus::molecules | ranges::cpp20::views::filter([](const auto& molecule) { - return !molecule.bonds.empty(); - }); +void Hamiltonian::checkBondedMolecules() const +{ + if (find() + .empty()) { // no bond potential added? issue warning if molecules w. bonds + auto molecules_with_bonds = + Faunus::molecules | ranges::cpp20::views::filter( + [](const auto& molecule) { return !molecule.bonds.empty(); }); for (const auto& molecule : molecules_with_bonds) { - faunus_logger->warn("{} bonds specified in topology but missing in energy", molecule.name); + faunus_logger->warn("{} bonds specified in topology but missing in energy", + molecule.name); } } } -double Hamiltonian::energy(const Change& change) { +double Hamiltonian::energy(const Change& change) +{ latest_energies.clear(); for (auto& energy_ptr : energy_terms) { energy_ptr->state = state; // is this needed? @@ -1066,15 +1234,20 @@ double Hamiltonian::energy(const Change& change) { } return std::accumulate(latest_energies.begin(), latest_energies.end(), 0.0); } -void Hamiltonian::init() { + +void Hamiltonian::init() +{ std::for_each(energy_terms.begin(), energy_terms.end(), [&](auto& energy) { energy->init(); }); } -void Hamiltonian::updateState(const Change& change) { - std::for_each(energy_terms.begin(), energy_terms.end(), [&](auto& energy) { energy->updateState(change); }); +void Hamiltonian::updateState(const Change& change) +{ + std::for_each(energy_terms.begin(), energy_terms.end(), + [&](auto& energy) { energy->updateState(change); }); } -void Hamiltonian::sync(EnergyTerm* other_hamiltonian, const Change& change) { +void Hamiltonian::sync(EnergyTerm* other_hamiltonian, const Change& change) +{ if (auto* other = dynamic_cast(other_hamiltonian)) { if (other->size() == size()) { latest_energies = other->latestEnergies(); @@ -1099,7 +1272,9 @@ void Hamiltonian::sync(EnergyTerm* other_hamiltonian, const Change& change) { * * New energy terms should be added to the if-else chain in the function */ -std::unique_ptr Hamiltonian::createEnergy(Space& spc, const std::string& name, const json& j) { +std::unique_ptr Hamiltonian::createEnergy(Space& spc, const std::string& name, + const json& j) +{ using namespace pairpotential; using CoulombLJ = CombinedPairPotential; using CoulombWCA = CombinedPairPotential; @@ -1111,31 +1286,39 @@ std::unique_ptr Hamiltonian::createEnergy(Space& spc, const std::str try { if (name == "nonbonded_coulomblj" || name == "nonbonded_newcoulomblj") { - return std::make_unique, PairingPolicy>>(j, spc, *this); + return std::make_unique, PairingPolicy>>(j, spc, + *this); } if (name == "nonbonded_coulomblj_EM") { - return std::make_unique, PairingPolicy>>(j, spc, *this); + return std::make_unique, PairingPolicy>>( + j, spc, *this); } if (name == "nonbonded_splined") { - return std::make_unique, PairingPolicy>>(j, spc, - *this); + return std::make_unique< + Nonbonded, PairingPolicy>>( + j, spc, *this); } if (name == "nonbonded" || name == "nonbonded_exact") { - return std::make_unique, PairingPolicy>>(j, spc, - *this); + return std::make_unique< + Nonbonded, PairingPolicy>>(j, spc, + *this); } if (name == "nonbonded_cached") { - return std::make_unique, PairingPolicy>>(j, spc, - *this); + return std::make_unique< + NonbondedCached, PairingPolicy>>(j, spc, + *this); } if (name == "nonbonded_coulombwca") { - return std::make_unique, PairingPolicy>>(j, spc, *this); + return std::make_unique, PairingPolicy>>(j, spc, + *this); } if (name == "nonbonded_pm" || name == "nonbonded_coulombhs") { - return std::make_unique, PairingPolicy>>(j, spc, *this); + return std::make_unique, PairingPolicy>>( + j, spc, *this); } if (name == "nonbonded_pmwca") { - return std::make_unique, PairingPolicy>>(j, spc, *this); + return std::make_unique, PairingPolicy>>( + j, spc, *this); } if (name == "bonded") { return std::make_unique(j, spc); @@ -1182,20 +1365,27 @@ std::unique_ptr Hamiltonian::createEnergy(Space& spc, const std::str return std::make_unique(j, spc); } throw ConfigurationError("'{}' unknown", name); - } catch (std::exception& e) { + } + catch (std::exception& e) { // @todo For unknown reasons, ConfigurationError displays an *empty* // what() message, which is why std::runtime_error is used instead. throw std::runtime_error("error creating energy -> "s + e.what()); } } -const std::vector& Hamiltonian::latestEnergies() const { return latest_energies; } +const std::vector& Hamiltonian::latestEnergies() const +{ + return latest_energies; +} #ifdef ENABLE_FREESASA -FreeSASAEnergy::FreeSASAEnergy(const Space& spc, const double cosolute_molarity, const double probe_radius) - : spc(spc), cosolute_molarity(cosolute_molarity), - parameters(std::make_unique(freesasa_default_parameters)) { +FreeSASAEnergy::FreeSASAEnergy(const Space& spc, const double cosolute_molarity, + const double probe_radius) + : spc(spc) + , cosolute_molarity(cosolute_molarity) + , parameters(std::make_unique(freesasa_default_parameters)) +{ name = "sasa"; citation_information = "doi:10.12688/f1000research.7931.1"; parameters->probe_radius = probe_radius; @@ -1206,33 +1396,41 @@ FreeSASAEnergy::FreeSASAEnergy(const Space& spc, const double cosolute_molarity, } FreeSASAEnergy::FreeSASAEnergy(const json& j, const Space& spc) - : FreeSASAEnergy(spc, j.value("molarity", 0.0) * 1.0_molar, j.value("radius", 1.4) * 1.0_angstrom) {} + : FreeSASAEnergy(spc, j.value("molarity", 0.0) * 1.0_molar, + j.value("radius", 1.4) * 1.0_angstrom) +{ +} -void FreeSASAEnergy::updateSASA(const Change& change) { +void FreeSASAEnergy::updateSASA(const Change& change) +{ const auto particles = spc.activeParticles(); const auto number_of_active_particles = std::distance(particles.begin(), particles.end()); updateRadii(particles.begin(), particles.end(), change); updatePositions(particles.begin(), particles.end(), change); - auto* result = freesasa_calc_coord(positions.data(), radii.data(), number_of_active_particles, parameters.get()); + auto* result = freesasa_calc_coord(positions.data(), radii.data(), number_of_active_particles, + parameters.get()); if (result != nullptr && result->n_atoms == number_of_active_particles) { sasa.clear(); sasa.reserve(number_of_active_particles); sasa.insert(sasa.begin(), result->sasa, result->sasa + result->n_atoms); // copy freesasa_result_free(result); assert(sasa.size() == number_of_active_particles); - } else { + } + else { throw std::runtime_error("FreeSASA failed"); } } -void FreeSASAEnergy::init() { +void FreeSASAEnergy::init() +{ Change change; change.everything = true; updateSASA(change); } -double FreeSASAEnergy::energy(const Change& change) { +double FreeSASAEnergy::energy(const Change& change) +{ double energy = 0.0; double surface_area = 0.0; updateSASA(change); @@ -1244,7 +1442,8 @@ double FreeSASAEnergy::energy(const Change& change) { return energy; } -void FreeSASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) { +void FreeSASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) +{ // since the full SASA is calculated in each energy evaluation, this is // currently not needed. if (auto* other = dynamic_cast(energybase_ptr); other != nullptr) { @@ -1252,12 +1451,14 @@ void FreeSASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) { if (change.everything || change.volume_change || change.matter_change) { radii = other->radii; positions = other->positions; - } else { + } + else { for (const auto& group_change : change.groups) { const auto& group = spc.groups.at(group_change.group_index); const auto offset = spc.getFirstActiveParticleIndex(group); - auto absolute_atom_index = group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); + auto absolute_atom_index = + group_change.relative_atom_indices | + ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); for (auto i : absolute_atom_index) { radii.at(i) = other->radii.at(i); for (size_t k = 0; k < 3; ++k) { @@ -1269,14 +1470,17 @@ void FreeSASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) { } } -void FreeSASAEnergy::to_json(json& j) const { +void FreeSASAEnergy::to_json(json& j) const +{ j["molarity"] = cosolute_molarity / 1.0_molar; j["radius"] = parameters->probe_radius / 1.0_angstrom; - j[unicode::bracket("SASA") + "/" + unicode::angstrom + unicode::squared] = mean_surface_area.avg() / 1.0_angstrom; + j[unicode::bracket("SASA") + "/" + unicode::angstrom + unicode::squared] = + mean_surface_area.avg() / 1.0_angstrom; roundJSON(j, 5); // set json output precision } -TEST_CASE("[Faunus] FreeSASA") { +TEST_CASE("[Faunus] FreeSASA") +{ using doctest::Approx; Change change; // change object telling that a full energy calculation change.everything = true; @@ -1296,12 +1500,15 @@ TEST_CASE("[Faunus] FreeSASA") { spc.particles.at(0).pos = {0.0, 0.0, 0.0}; spc.particles.at(1).pos = {0.0, 0.0, 20.0}; - SUBCASE("Separated atoms") { + SUBCASE("Separated atoms") + { FreeSASAEnergy sasa(spc, 1.5_molar, 1.4_angstrom); - CHECK_EQ(sasa.energy(change), Approx(4.0 * pc::pi * (3.4 * 3.4 + 2.6 * 2.6) * 1.5 * 1.0_kJmol)); + CHECK_EQ(sasa.energy(change), + Approx(4.0 * pc::pi * (3.4 * 3.4 + 2.6 * 2.6) * 1.5 * 1.0_kJmol)); } - SUBCASE("Intersecting atoms") { + SUBCASE("Intersecting atoms") + { FreeSASAEnergy sasa(spc, 1.5_molar, 1.4_angstrom); std::vector> distance_energy = { {0.0, 87.3576}, {2.5, 100.4612}, {5.0, 127.3487}, {7.5, 138.4422}, {10.0, 138.4422}}; @@ -1320,27 +1527,37 @@ TEST_CASE("[Faunus] FreeSASA") { * @param cosolute_molarity in particles per angstrom cubed * @param probe_radius in angstrom * @param slices_per_atom number of slices of spheres in SASA calculation - * @param dense_container flag specifying if a fast memory heavy version of cell_list container is used + * @param dense_container flag specifying if a fast memory heavy version of cell_list container is + * used */ -SASAEnergyReference::SASAEnergyReference(const Space& spc, double cosolute_molarity, double probe_radius, - int slices_per_atom, bool dense_container) +SASAEnergyReference::SASAEnergyReference(const Space& spc, double cosolute_molarity, + double probe_radius, int slices_per_atom, + bool dense_container) : spc(spc) - , cosolute_molarity(cosolute_molarity) { + , cosolute_molarity(cosolute_molarity) +{ using SASA::SASACellList; - const auto periodic_dimensions = spc.geometry.asSimpleGeometry()->boundary_conditions.isPeriodic().count(); + const auto periodic_dimensions = + spc.geometry.asSimpleGeometry()->boundary_conditions.isPeriodic().count(); switch (periodic_dimensions) { case 3: // PBC in all directions if (dense_container) { - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); - } else { - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); + sasa = std::make_unique>(spc, probe_radius, + slices_per_atom); + } + else { + sasa = std::make_unique>(spc, probe_radius, + slices_per_atom); } break; case 0: if (dense_container) { - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); - } else { - sasa = std::make_unique>(spc, probe_radius, slices_per_atom); + sasa = std::make_unique>(spc, probe_radius, + slices_per_atom); + } + else { + sasa = std::make_unique>(spc, probe_radius, + slices_per_atom); } break; default: @@ -1354,10 +1571,14 @@ SASAEnergyReference::SASAEnergyReference(const Space& spc, double cosolute_molar } SASAEnergyReference::SASAEnergyReference(const json& j, const Space& spc) - : SASAEnergyReference(spc, j.at("molarity").get() * 1.0_molar, j.value("radius", 1.4) * 1.0_angstrom, - j.value("slices", 25), j.value("dense", true)) {} + : SASAEnergyReference(spc, j.at("molarity").get() * 1.0_molar, + j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 25), + j.value("dense", true)) +{ +} -void SASAEnergyReference::init() { +void SASAEnergyReference::init() +{ sasa->init(spc); areas.resize(spc.particles.size(), 0.0); } @@ -1369,11 +1590,13 @@ void SASAEnergyReference::init() { * @param cosolute_molarity in particles per angstrom cubed * @param probe_radius in angstrom * @param slices_per_atom number of slices of spheres in SASA calculation - * @param dense_container flag specifying if a fast memory heavy version of cell_list container is used + * @param dense_container flag specifying if a fast memory heavy version of cell_list container is + * used */ -SASAEnergy::SASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius, int slices_per_atom, - bool dense_container) - : SASAEnergyReference(spc, cosolute_molarity, probe_radius, slices_per_atom, dense_container) { +SASAEnergy::SASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius, + int slices_per_atom, bool dense_container) + : SASAEnergyReference(spc, cosolute_molarity, probe_radius, slices_per_atom, dense_container) +{ name = "sasa"; citation_information = "doi:10.12688/f1000research.7931.1"; init(); @@ -1381,16 +1604,21 @@ SASAEnergy::SASAEnergy(const Space& spc, double cosolute_molarity, double probe_ } SASAEnergy::SASAEnergy(const json& j, const Space& spc) - : SASAEnergy(spc, j.at("molarity").get() * 1.0_molar, j.value("radius", 1.4) * 1.0_angstrom, - j.value("slices", 25), j.value("dense", true)) {} + : SASAEnergy(spc, j.at("molarity").get() * 1.0_molar, + j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 25), + j.value("dense", true)) +{ +} -void SASAEnergy::init() { +void SASAEnergy::init() +{ areas.resize(spc.particles.size(), 0.0); current_neighbours.resize(spc.particles.size()); changed_indices.reserve(spc.particles.size()); } -double SASAEnergyReference::energy(const Change& change) { +double SASAEnergyReference::energy(const Change& change) +{ double energy = 0.; if (state != MonteCarloState::ACCEPTED) { sasa->update(spc, change); @@ -1402,23 +1630,27 @@ double SASAEnergyReference::energy(const Change& change) { std::vector target_indices; auto to_index = [this](const auto& particle) { return indexOf(particle); }; - target_indices = particles | ranges::cpp20::views::transform(to_index) | ranges::to; + target_indices = + particles | ranges::cpp20::views::transform(to_index) | ranges::to; const auto neighbours = sasa->calcNeighbourData(spc, target_indices); sasa->updateSASA(neighbours, target_indices); const auto& new_areas = sasa->getAreas(); - ranges::cpp20::for_each(target_indices, - [this, &new_areas](const auto index) { areas.at(index) = new_areas.at(index); }); + ranges::cpp20::for_each(target_indices, [this, &new_areas](const auto index) { + areas.at(index) = new_areas.at(index); + }); auto accumulate_energy = [this, &energy](const auto& particle) { - energy += areas.at(indexOf(particle)) * (particle.traits().tension + cosolute_molarity * particle.traits().tfe); + energy += areas.at(indexOf(particle)) * + (particle.traits().tension + cosolute_molarity * particle.traits().tfe); }; ranges::cpp20::for_each(particles, accumulate_energy); return energy; } -void SASAEnergyReference::sync(EnergyTerm* energybase_ptr, const Change& change) { +void SASAEnergyReference::sync(EnergyTerm* energybase_ptr, const Change& change) +{ if (auto* other = dynamic_cast(energybase_ptr)) { areas = other->areas; if (sasa->needs_syncing) { @@ -1429,19 +1661,25 @@ void SASAEnergyReference::sync(EnergyTerm* energybase_ptr, const Change& change) } } -void SASAEnergyReference::to_json(json& j) const { +void SASAEnergyReference::to_json(json& j) const +{ j["molarity"] = cosolute_molarity / 1.0_molar; roundJSON(j, 6); // set json output precision } -const std::vector& SASAEnergyReference::getAreas() const { return areas; } +const std::vector& SASAEnergyReference::getAreas() const +{ + return areas; +} /** * @brief Finds absolute indices of particles whose SASA has changed * @param change Change object */ -void SASAEnergy::updateChangedIndices(const Change& change) { - //!< if the state is ACCEPTED there is no need to recalculate SASAs so return empty target_indices +void SASAEnergy::updateChangedIndices(const Change& change) +{ + //!< if the state is ACCEPTED there is no need to recalculate SASAs so return empty + //!< target_indices if (state == MonteCarloState::ACCEPTED) { return; } @@ -1459,14 +1697,17 @@ void SASAEnergy::updateChangedIndices(const Change& change) { if (group_change.relative_atom_indices.empty()) { const auto indices = ranges::cpp20::views::iota(offset, group.size() + offset); ranges::cpp20::for_each(indices, insert_changed); - } else { - const auto indices = group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return offset + i; }); + } + else { + const auto indices = + group_change.relative_atom_indices | + ranges::cpp20::views::transform([offset](auto i) { return offset + i; }); ranges::cpp20::for_each(indices, insert_changed); } } changed_indices.assign(target_indices.begin(), target_indices.end()); } + /** @brief * for each touched atoms we need to recalculate SASA for the touched atom @@ -1475,14 +1716,17 @@ void SASAEnergy::updateChangedIndices(const Change& change) { * * @param index_type index of particle whose neighbours sasa has changed * * @param target_indices placeholder to insert changed indices **/ -void SASAEnergy::insertChangedNeighboursOf(const index_type index, std::set& target_indices) const { +void SASAEnergy::insertChangedNeighboursOf(const index_type index, + std::set& target_indices) const +{ const auto& current_neighbour = sasa->calcNeighbourDataOfParticle(spc, index).indices; const auto& past_neighbour = current_neighbours.at(index); target_indices.insert(past_neighbour.begin(), past_neighbour.end()); target_indices.insert(current_neighbour.begin(), current_neighbour.end()); } -double SASAEnergy::energy(const Change& change) { +double SASAEnergy::energy(const Change& change) +{ double energy(0.); //! update not needed when the state is already accepted @@ -1493,8 +1737,10 @@ double SASAEnergy::energy(const Change& change) { changed_indices.clear(); if (change.everything) { //! all the active particles will be used for SASA calculation auto to_index = [this](const auto& particle) { return indexOf(particle); }; - changed_indices = particles | ranges::cpp20::views::transform(to_index) | ranges::to; - } else { + changed_indices = + particles | ranges::cpp20::views::transform(to_index) | ranges::to; + } + else { updateChangedIndices(change); sasa->needs_syncing = true; } @@ -1511,22 +1757,27 @@ double SASAEnergy::energy(const Change& change) { } auto accumulate_energy = [this, &energy](const auto& particle) { - energy += areas.at(indexOf(particle)) * (particle.traits().tension + cosolute_molarity * particle.traits().tfe); + energy += areas.at(indexOf(particle)) * + (particle.traits().tension + cosolute_molarity * particle.traits().tfe); }; ranges::cpp20::for_each(particles, accumulate_energy); return energy; } -void SASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) { +void SASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) +{ if (auto* other = dynamic_cast(energybase_ptr)) { const auto sync_data = [this, other](const size_t changed_index) { - this->current_neighbours.at(changed_index) = other->current_neighbours.at(changed_index); + this->current_neighbours.at(changed_index) = + other->current_neighbours.at(changed_index); this->areas.at(changed_index) = other->areas.at(changed_index); }; - if (state == MonteCarloState::TRIAL) { //! changed_indices get updated only in TRIAL state for speedup + if (state == MonteCarloState::TRIAL) { //! changed_indices get updated only in TRIAL state + //! for speedup ranges::for_each(this->changed_indices, sync_data); - } else { + } + else { ranges::for_each(other->changed_indices, sync_data); } @@ -1538,7 +1789,8 @@ void SASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) { } } -TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyReference, SASAEnergy) { +TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyReference, SASAEnergy) +{ using doctest::Approx; pc::temperature = 300.0_K; atoms = R"([ @@ -1563,7 +1815,8 @@ TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyRefe spc.particles.at(2).pos = {11.0, 0.0, 0.0}; spc.particles.at(3).pos = {50.0, 0.0, 0.0}; - SUBCASE("Update everything") { + SUBCASE("Update everything") + { Change change; change.everything = true; @@ -1572,12 +1825,14 @@ TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyRefe CHECK_EQ(sasa_energy.energy(change), Approx(98.0789260855)); CHECK_EQ(ref_energy.energy(change), Approx(98.0789260855)); - for (const auto& [area, ref_area] : ranges::views::zip(sasa_energy.getAreas(), ref_energy.getAreas())) { + for (const auto& [area, ref_area] : + ranges::views::zip(sasa_energy.getAreas(), ref_energy.getAreas())) { CHECK_EQ(area, Approx(ref_area)); } } - SUBCASE("Partial update") { + SUBCASE("Partial update") + { Change change; change.everything = true; EnergyTemplate sasa_energy(spc, 1.5_molar, 1.0_angstrom, 20); @@ -1585,7 +1840,8 @@ TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyRefe // we must caclulate SASAs of all 4 particles before updating position of one of them // since all SASAs are needed in the final energy calculation. - // these are usually obtained from previous MC step in simulations so here we need to calculate them explicitly + // these are usually obtained from previous MC step in simulations so here we need to + // calculate them explicitly sasa_energy.energy(change); ref_energy.energy(change); @@ -1598,7 +1854,8 @@ TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyRefe CHECK_EQ(sasa_energy.energy(change), Approx(105.7104501023)); CHECK_EQ(ref_energy.energy(change), Approx(105.7104501023)); - for (const auto& [area, ref_area] : ranges::views::zip(sasa_energy.getAreas(), ref_energy.getAreas())) { + for (const auto& [area, ref_area] : + ranges::views::zip(sasa_energy.getAreas(), ref_energy.getAreas())) { CHECK_EQ(area, Approx(ref_area)); } } @@ -1606,12 +1863,17 @@ TEST_CASE_TEMPLATE("[Faunus] SASAEnergy_updates", EnergyTemplate, SASAEnergyRefe //==================== GroupCutoff ==================== -GroupCutoff::GroupCutoff(Space::GeometryType& geometry) : geometry(geometry) {} +GroupCutoff::GroupCutoff(Space::GeometryType& geometry) + : geometry(geometry) +{ +} -void GroupCutoff::setSingleCutoff(const double cutoff) { +void GroupCutoff::setSingleCutoff(const double cutoff) +{ if (cutoff < std::sqrt(pc::max_value)) { default_cutoff_squared = cutoff * cutoff; - } else { + } + else { default_cutoff_squared = pc::max_value; } for (const auto& molecule1 : Faunus::molecules) { @@ -1621,7 +1883,8 @@ void GroupCutoff::setSingleCutoff(const double cutoff) { } } -double GroupCutoff::getCutoff(size_t id1, size_t id2) const { +double GroupCutoff::getCutoff(size_t id1, size_t id2) const +{ if (cutoff_squared.size() != Faunus::molecules.size() || id1 >= cutoff_squared.size() || id2 >= cutoff_squared.size()) { throw std::out_of_range("cutoff matrix doesn't fit molecules"); @@ -1629,14 +1892,16 @@ double GroupCutoff::getCutoff(size_t id1, size_t id2) const { return std::sqrt(cutoff_squared(id1, id2)); } -void from_json(const json& j, GroupCutoff& cutoff) { +void from_json(const json& j, GroupCutoff& cutoff) +{ // default: no group-to-group cutoff cutoff.setSingleCutoff(std::sqrt(pc::max_value)); if (const auto it = j.find("cutoff_g2g"); it != j.end()) { if (it->is_number()) { cutoff.setSingleCutoff(it->get()); - } else if (it->is_object()) { + } + else if (it->is_object()) { cutoff.setSingleCutoff(it->value("default", pc::max_value)); for (const auto& [named_pair, pair_cutoff] : it->items()) { if (named_pair == "default") { @@ -1649,23 +1914,27 @@ void from_json(const json& j, GroupCutoff& cutoff) { const auto& molecule2 = findMoleculeByName(molecules_names[1]); cutoff.cutoff_squared.set(molecule1.id(), molecule2.id(), std::pow(pair_cutoff.get(), 2)); - faunus_logger->debug("custom cutoff for {}-{} = {} Γ…", molecule1.name, molecule2.name, - pair_cutoff.get()); - } else { + faunus_logger->debug("custom cutoff for {}-{} = {} Γ…", molecule1.name, + molecule2.name, pair_cutoff.get()); + } + else { throw std::runtime_error("invalid molecules names"); } - } catch (const std::exception& e) { - throw ConfigurationError("Unable to set a custom cutoff for {}: {}", named_pair, e.what()); + } + catch (const std::exception& e) { + throw ConfigurationError("Unable to set a custom cutoff for {}: {}", named_pair, + e.what()); } } } } } -void to_json(json &j, const GroupCutoff &cutoff) { +void to_json(json& j, const GroupCutoff& cutoff) +{ auto _j = json::object(); - for (auto &a : Faunus::molecules) { - for (auto &b : Faunus::molecules) { + for (auto& a : Faunus::molecules) { + for (auto& b : Faunus::molecules) { if (a.id() >= b.id()) { if (not a.atomic && not b.atomic) { auto cutoff_squared = cutoff.cutoff_squared(a.id(), b.id()); @@ -1681,7 +1950,8 @@ void to_json(json &j, const GroupCutoff &cutoff) { } } -TEST_CASE("[Faunus] GroupCutoff") { +TEST_CASE("[Faunus] GroupCutoff") +{ Geometry::Chameleon geo; Faunus::atoms = R"([ { "A": { "sigma": 4.0 } }, @@ -1692,7 +1962,8 @@ TEST_CASE("[Faunus] GroupCutoff") { { "Q": { "atoms": ["A", "B"] } } ])"_json.get(); - SUBCASE("old style default") { + SUBCASE("old style default") + { GroupCutoff cutoff(geo); from_json(R"({"cutoff_g2g": 12.0})"_json, cutoff); CHECK_EQ(cutoff.getCutoff(0, 0), doctest::Approx(12.0)); @@ -1700,7 +1971,8 @@ TEST_CASE("[Faunus] GroupCutoff") { CHECK_EQ(cutoff.getCutoff(1, 1), doctest::Approx(12.0)); } - SUBCASE("new style default") { + SUBCASE("new style default") + { GroupCutoff cutoff(geo); from_json(R"({"cutoff_g2g": {"default": 13.0}})"_json, cutoff); CHECK_EQ(cutoff.getCutoff(0, 0), doctest::Approx(13.0)); @@ -1708,7 +1980,8 @@ TEST_CASE("[Faunus] GroupCutoff") { CHECK_EQ(cutoff.getCutoff(1, 1), doctest::Approx(13.0)); } - SUBCASE("custom") { + SUBCASE("custom") + { GroupCutoff cutoff(geo); from_json(R"({"cutoff_g2g": {"default": 13.0, "M Q": 14.0}})"_json, cutoff); CHECK_EQ(cutoff.getCutoff(0, 0), doctest::Approx(13.0)); @@ -1716,7 +1989,8 @@ TEST_CASE("[Faunus] GroupCutoff") { CHECK_EQ(cutoff.getCutoff(1, 1), doctest::Approx(13.0)); } - SUBCASE("custom - no default value") { + SUBCASE("custom - no default value") + { GroupCutoff cutoff(geo); from_json(R"({"cutoff_g2g": {"M Q": 11.0}})"_json, cutoff); CHECK_EQ(cutoff.getCutoff(0, 0), doctest::Approx(std::sqrt(pc::max_value))); @@ -1725,15 +1999,25 @@ TEST_CASE("[Faunus] GroupCutoff") { } } -EnergyAccumulatorBase::EnergyAccumulatorBase(double value) : value(value) {} +EnergyAccumulatorBase::EnergyAccumulatorBase(double value) + : value(value) +{ +} void EnergyAccumulatorBase::reserve([[maybe_unused]] size_t number_of_particles) {} -void EnergyAccumulatorBase::clear() { value = 0.0; } +void EnergyAccumulatorBase::clear() +{ + value = 0.0; +} -EnergyAccumulatorBase::operator double() { return value; } +EnergyAccumulatorBase::operator double() +{ + return value; +} -void EnergyAccumulatorBase::from_json(const json& j) { +void EnergyAccumulatorBase::from_json(const json& j) +{ scheme = j.value("summation_policy", Scheme::SERIAL); #ifndef HAS_PARALLEL_TRANSFORM_REDUCE if (scheme == Scheme::PARALLEL) { @@ -1750,11 +2034,15 @@ void EnergyAccumulatorBase::from_json(const json& j) { faunus_logger->debug("setting summation policy to {}", json(scheme).dump(1)); } -void EnergyAccumulatorBase::to_json(json& j) const { j["summation_policy"] = scheme; } +void EnergyAccumulatorBase::to_json(json& j) const +{ + j["summation_policy"] = scheme; +} CustomGroupGroup::CustomGroupGroup(const json& j, const Space& spc) : spc(spc) - , json_input_backup(j) { + , json_input_backup(j) +{ name = "custom-groupgroup"; const auto& molecule1 = findMoleculeByName(j.at("name1").get()); @@ -1780,7 +2068,8 @@ CustomGroupGroup::CustomGroupGroup(const json& j, const Space& spc) {"Z2", &properties.mean_charge2}}); } -double CustomGroupGroup::energy([[maybe_unused]] const Change& change) { +double CustomGroupGroup::energy([[maybe_unused]] const Change& change) +{ // matches active groups with either molid1 or molid2 auto match_groups = [&](const auto& group) -> bool { return group.isFull() & ((group.id == molid1) | (group.id == molid2)); @@ -1789,7 +2078,8 @@ double CustomGroupGroup::energy([[maybe_unused]] const Change& change) { auto group_group_energy = [&](auto index1, auto index2) -> double { const Group& group1 = spc.groups[index1]; const Group& group2 = spc.groups[index2]; - if (((group1.id == molid1) & (group2.id == molid2)) | ((group1.id == molid2) & (group2.id == molid1))) { + if (((group1.id == molid1) & (group2.id == molid2)) | + ((group1.id == molid2) & (group2.id == molid1))) { setParameters(group1, group2); return expr->operator()(); } @@ -1798,25 +2088,30 @@ double CustomGroupGroup::energy([[maybe_unused]] const Change& change) { // all indices matching either molid1 or molid2 auto indices = spc.groups | ranges::cpp20::views::filter(match_groups) | - ranges::cpp20::views::transform([&](const auto& group) { return spc.getGroupIndex(group); }) | + ranges::cpp20::views::transform( + [&](const auto& group) { return spc.getGroupIndex(group); }) | ranges::to_vector; return for_each_unique_pair(indices.begin(), indices.end(), group_group_energy, std::plus<>()); } -void CustomGroupGroup::setParameters(const Group& group1, const Group& group2) { +void CustomGroupGroup::setParameters(const Group& group1, const Group& group2) +{ auto& mean_charge1 = mean_charges[group1.id]; auto& mean_charge2 = mean_charges[group2.id]; mean_charge1 += monopoleMoment(group1.begin(), group1.end()); mean_charge2 += monopoleMoment(group2.begin(), group2.end()); properties.mean_charge1 = mean_charge1.avg(); properties.mean_charge2 = mean_charge2.avg(); - properties.mass_center_separation = sqrt(spc.geometry.sqdist(group1.mass_center, group2.mass_center)); + properties.mass_center_separation = + sqrt(spc.geometry.sqdist(group1.mass_center, group2.mass_center)); } -void CustomGroupGroup::to_json(json& j) const { +void CustomGroupGroup::to_json(json& j) const +{ j = json_input_backup; - j["mean_charges"] = {{"Z1", mean_charges.at(molid1).avg()}, {"Z2", mean_charges.at(molid2).avg()}}; + j["mean_charges"] = {{"Z1", mean_charges.at(molid1).avg()}, + {"Z2", mean_charges.at(molid2).avg()}}; }; } // end of namespace Faunus::Energy diff --git a/src/energy.h b/src/energy.h index bfecc257f..8e0f983b7 100644 --- a/src/energy.h +++ b/src/energy.h @@ -19,13 +19,14 @@ #include #include -struct freesasa_parameters_fwd; // workaround for freesasa unnamed struct that cannot be forward declared +struct freesasa_parameters_fwd; // workaround for freesasa unnamed struct that cannot be forward + // declared #if defined(__cpp_lib_parallel_algorithm) && __has_include() #include #endif -#if defined(__cpp_lib_parallel_algorithm) && \ +#if defined(__cpp_lib_parallel_algorithm) && \ __has_include() && ((defined(__clang__) && __clang_major__ >= 10) || (defined(__GNUC__) && __GNUC__ >= 10)) #define HAS_PARALLEL_TRANSFORM_REDUCE #endif @@ -43,19 +44,20 @@ class PairPotential; /** * @par Non-bonded energy * - * Several classes (class templates) are used together to allow computation in change of the non-bonded energy upon - * a MC move. + * Several classes (class templates) are used together to allow computation in change of the + * non-bonded energy upon a MC move. * - * The energy change is calculated by the Nonbonded class. It internally uses one of the pairing policies - * to efficiently get all pair interactions affected by the MC move (as described by the Change object). + * The energy change is calculated by the Nonbonded class. It internally uses one of the pairing + * policies to efficiently get all pair interactions affected by the MC move (as described by the + * Change object). * - * Pairing policies allow efficient summation of pair energies over the whole system, between groups, inside a group, - * etc. The pairing policy is optimized for performance in a different execution environment, e.g., sequential or - * OMP parallelism. + * Pairing policies allow efficient summation of pair energies over the whole system, between + * groups, inside a group, etc. The pairing policy is optimized for performance in a different + * execution environment, e.g., sequential or OMP parallelism. * - * Policies have direct access to the pair interaction energy functor represented by a simple PairEnergy template. - * Furthermore, the GroupCutoff object is provided to limit free energy computation using a cutoff distance between - * respective groups. + * Policies have direct access to the pair interaction energy functor represented by a simple + * PairEnergy template. Furthermore, the GroupCutoff object is provided to limit free energy + * computation using a cutoff distance between respective groups. * * @see Nonbonded, PairingBasePolicy, PairEnergy, GroupCutoff */ @@ -69,7 +71,8 @@ class Hamiltonian; * If any particles is ouside, infinite energy is returned; zero otherwirse. * This is not needed for cuboidal geometry as particles are always wrapped using PBC. */ -class ContainerOverlap : public EnergyTerm { +class ContainerOverlap : public EnergyTerm +{ private: const Space& spc; bool groupIsOutsideContainer(const Change::GroupChange& group_change) const; @@ -91,10 +94,11 @@ class ContainerOverlap : public EnergyTerm { * - IPBC Ewald (DOI:10/css8) * - Update optimization (DOI:10.1063/1.481216, Eq. 24) */ -struct EwaldData { +struct EwaldData +{ using Tcomplex = std::complex; - Eigen::Matrix3Xd k_vectors; //!< k-vectors, 3xK - Eigen::VectorXd Aks; //!< 1xK for update optimization (see Eq.24, DOI:10.1063/1.481216) + Eigen::Matrix3Xd k_vectors; //!< k-vectors, 3xK + Eigen::VectorXd Aks; //!< 1xK for update optimization (see Eq.24, DOI:10.1063/1.481216) Eigen::VectorXcd Q_ion, Q_dipole; //!< Complex 1xK vectors double r_cutoff = 0; //!< Real-space cutoff double n_cutoff = 0; //!< Inverse space cutoff @@ -107,10 +111,19 @@ struct EwaldData { double check_k2_zero = 0; bool use_spherical_sum = true; int num_kvectors = 0; - Point box_length = {0.0, 0.0, 0.0}; //!< Box dimensions - enum Policies { PBC, PBCEigen, IPBC, IPBCEigen, INVALID }; //!< Possible k-space updating schemes - Policies policy = PBC; //!< Policy for updating k-space - explicit EwaldData(const json& j); //!< Initialize from json + Point box_length = {0.0, 0.0, 0.0}; //!< Box dimensions + + enum Policies + { + PBC, + PBCEigen, + IPBC, + IPBCEigen, + INVALID + }; //!< Possible k-space updating schemes + + Policies policy = PBC; //!< Policy for updating k-space + explicit EwaldData(const json& j); //!< Initialize from json }; NLOHMANN_JSON_SERIALIZE_ENUM(EwaldData::Policies, { @@ -126,21 +139,27 @@ void to_json(json& j, const EwaldData& d); /** * @brief Base class for Ewald k-space updates policies */ -class EwaldPolicyBase { +class EwaldPolicyBase +{ public: std::string cite; //!< Optional reference, preferably DOI, to further information virtual ~EwaldPolicyBase() = default; - virtual void updateBox(EwaldData&, const Point&) const = 0; //!< Prepare k-vectors according to given box vector - virtual void updateComplex(EwaldData& d, - const Space::GroupVector& groups) const = 0; //!< Update all k vectors virtual void - updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups, - const Space::GroupVector& oldgroups) const = 0; //!< Update subset of k vectors. Require `old` pointer - virtual double selfEnergy(const EwaldData& d, Change& change, - Space::GroupVector& groups) = 0; //!< Self energy contribution due to a change - virtual double surfaceEnergy(const EwaldData& d, const Change& change, - const Space::GroupVector& groups) = 0; //!< Surface energy contribution due to a change - virtual double reciprocalEnergy(const EwaldData& d) = 0; //!< Total reciprocal energy + updateBox(EwaldData&, + const Point&) const = 0; //!< Prepare k-vectors according to given box vector + virtual void + updateComplex(EwaldData& d, + const Space::GroupVector& groups) const = 0; //!< Update all k vectors + virtual void updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups, + const Space::GroupVector& oldgroups) + const = 0; //!< Update subset of k vectors. Require `old` pointer + virtual double + selfEnergy(const EwaldData& d, Change& change, + Space::GroupVector& groups) = 0; //!< Self energy contribution due to a change + virtual double surfaceEnergy( + const EwaldData& d, const Change& change, + const Space::GroupVector& groups) = 0; //!< Surface energy contribution due to a change + virtual double reciprocalEnergy(const EwaldData& d) = 0; //!< Total reciprocal energy /** * @brief Represent charges and positions using an Eigen facade (Map) @@ -150,8 +169,11 @@ class EwaldPolicyBase { * @param groups Vector of groups to represent * @return tuple with positions, charges */ - static auto mapGroupsToEigen(Space::GroupVector& groups) { - auto is_partially_inactive = [](const Group& group) { return group.size() != group.capacity(); }; + static auto mapGroupsToEigen(Space::GroupVector& groups) + { + auto is_partially_inactive = [](const Group& group) { + return group.size() != group.capacity(); + }; if (ranges::cpp20::any_of(groups, is_partially_inactive)) { throw std::runtime_error("Eigen optimized Ewald not available with inactive groups"); } @@ -164,8 +186,11 @@ class EwaldPolicyBase { return std::make_tuple(pos, charge); } - static auto mapGroupsToEigen(const Space::GroupVector& groups) { - auto is_partially_inactive = [](const Group& group) { return group.size() != group.capacity(); }; + static auto mapGroupsToEigen(const Space::GroupVector& groups) + { + auto is_partially_inactive = [](const Group& group) { + return group.size() != group.capacity(); + }; if (ranges::cpp20::any_of(groups, is_partially_inactive)) { throw std::runtime_error("Eigen optimized Ewald not available with inactive groups"); } @@ -184,14 +209,16 @@ class EwaldPolicyBase { /** * @brief Ion-Ion Ewald using periodic boundary conditions (PBC) */ -struct PolicyIonIon : public EwaldPolicyBase { +struct PolicyIonIon : public EwaldPolicyBase +{ PolicyIonIon(); void updateBox(EwaldData& d, const Point& box) const override; void updateComplex(EwaldData& data, const Space::GroupVector& groups) const override; void updateComplex(EwaldData& d, const Change& change, const Space::GroupVector& groups, const Space::GroupVector& oldgroups) const override; double selfEnergy(const EwaldData& d, Change& change, Space::GroupVector& groups) override; - double surfaceEnergy(const EwaldData& data, const Change& change, const Space::GroupVector& groups) override; + double surfaceEnergy(const EwaldData& data, const Change& change, + const Space::GroupVector& groups) override; double reciprocalEnergy(const EwaldData& d) override; }; @@ -206,28 +233,32 @@ struct PolicyIonIon : public EwaldPolicyBase { * - Clang9: Eigen version is slower than generic version (macos/ubuntu) * - GCC9: Eigen is 4-5 times faster on x86 linux; ~1.5 times *lower on macos. */ -struct PolicyIonIonEigen : public PolicyIonIon { +struct PolicyIonIonEigen : public PolicyIonIon +{ using PolicyIonIon::updateComplex; void updateComplex(EwaldData&, const Space::GroupVector&) const override; - double reciprocalEnergy(const EwaldData &) override; + double reciprocalEnergy(const EwaldData&) override; }; /** * @brief Ion-Ion Ewald with isotropic periodic boundary conditions (IPBC) */ -struct PolicyIonIonIPBC : public PolicyIonIon { +struct PolicyIonIonIPBC : public PolicyIonIon +{ using PolicyIonIon::updateComplex; PolicyIonIonIPBC(); - void updateBox(EwaldData &, const Point &) const override; + void updateBox(EwaldData&, const Point&) const override; void updateComplex(EwaldData&, const Space::GroupVector&) const override; - void updateComplex(EwaldData&, const Change&, const Space::GroupVector&, const Space::GroupVector&) const override; + void updateComplex(EwaldData&, const Change&, const Space::GroupVector&, + const Space::GroupVector&) const override; }; /** * @brief Ion-Ion Ewald with isotropic periodic boundary conditions (IPBC) using Eigen operations * @warning Incomplete and under construction */ -struct PolicyIonIonIPBCEigen : public PolicyIonIonIPBC { +struct PolicyIonIonIPBCEigen : public PolicyIonIonIPBC +{ using PolicyIonIonIPBC::updateComplex; void updateComplex(EwaldData&, const Space::GroupVector&) const override; }; @@ -237,7 +268,8 @@ struct PolicyIonIonIPBCEigen : public PolicyIonIonIPBC { * @todo energy() currently has the responsibility to update k-vectors. * This is error prone and should be handled *before* this step. */ -class Ewald : public EnergyTerm { +class Ewald : public EnergyTerm +{ private: const Space& spc; EwaldData data; @@ -248,7 +280,8 @@ class Ewald : public EnergyTerm { Ewald(const Space& spc, const EwaldData& data); Ewald(const json& j, const Space& spc); void init() override; - void setOldGroups(const Space::GroupVector& old_groups); //!< Optimization if old groups are available (optional) + void setOldGroups(const Space::GroupVector& + old_groups); //!< Optimization if old groups are available (optional) void updateState(const Change& change) override; double energy(const Change& change) override; void sync(EnergyTerm* energybase, const Change& change) override; @@ -259,11 +292,13 @@ class Ewald : public EnergyTerm { /** * @brief Pressure term for NPT ensemble */ -class Isobaric : public EnergyTerm { +class Isobaric : public EnergyTerm +{ private: const Space& spc; - double pressure = 0.0; //!< Applied pressure - static const std::map pressure_units; //!< Possible ways pressure can be given + double pressure = 0.0; //!< Applied pressure + static const std::map + pressure_units; //!< Possible ways pressure can be given public: Isobaric(const json& j, const Space& spc); double energy(const Change& change) override; @@ -275,7 +310,8 @@ class Isobaric : public EnergyTerm { * * If outside specified `range`, infinity energy is returned, causing rejection. */ -class Constrain : public EnergyTerm { +class Constrain : public EnergyTerm +{ private: std::string type; std::unique_ptr coordinate; @@ -295,20 +331,22 @@ class Constrain : public EnergyTerm { * * @todo Optimize. */ -class Bonded : public EnergyTerm { +class Bonded : public EnergyTerm +{ private: using BondVector = BasePointerVector; const Space& spc; - BondVector external_bonds; //!< inter-molecular bonds - std::map internal_bonds; //!< intra-molecular bonds; key is group index - void updateGroupBonds(const Space::GroupType& group); //!< Update/set bonds internally in group - double sumBondEnergy(const BondVector& bonds) const; //!< sum energy in vector of BondData + BondVector external_bonds; //!< inter-molecular bonds + std::map internal_bonds; //!< intra-molecular bonds; key is group index + void updateGroupBonds(const Space::GroupType& group); //!< Update/set bonds internally in group + double sumBondEnergy(const BondVector& bonds) const; //!< sum energy in vector of BondData double internalGroupEnergy(const Change::GroupChange& changed); //!< Energy from internal bonds - double sumEnergy(const BondVector& bonds, const ranges::cpp20::range auto& particle_indices) const; + double sumEnergy(const BondVector& bonds, + const ranges::cpp20::range auto& particle_indices) const; void updateInternalBonds(); //!< finds and adds all intra-molecular bonds of active molecules public: - Bonded(const Space& spc, BondVector external_bonds); + Bonded(const Space& spc, BondVector external_bonds); Bonded(const json& j, const Space& spc); void to_json(json& j) const override; double energy(const Change& change) override; //!< brute force -- refine this! @@ -324,7 +362,9 @@ class Bonded : public EnergyTerm { * for binary search which on large systems provides superior performance compared * to simplistic search which scales as number_of_bonds x number_of_moved_particles */ -double Bonded::sumEnergy(const Bonded::BondVector& bonds, const ranges::cpp20::range auto& particle_indices) const { +double Bonded::sumEnergy(const Bonded::BondVector& bonds, + const ranges::cpp20::range auto& particle_indices) const +{ assert(std::is_sorted(particle_indices.begin(), particle_indices.end())); auto index_is_included = [&](auto index) { @@ -333,8 +373,11 @@ double Bonded::sumEnergy(const Bonded::BondVector& bonds, const ranges::cpp20::r auto affected_bonds = bonds | ranges::cpp20::views::filter([&](const auto& bond) { return ranges::cpp20::any_of(bond->indices, index_is_included); }); - auto bond_energy = [dist = spc.geometry.getDistanceFunc()](const auto& bond) { return bond->energyFunc(dist); }; - return std::transform_reduce(affected_bonds.begin(), affected_bonds.end(), 0.0, std::plus<>(), bond_energy); + auto bond_energy = [dist = spc.geometry.getDistanceFunc()](const auto& bond) { + return bond->energyFunc(dist); + }; + return std::transform_reduce(affected_bonds.begin(), affected_bonds.end(), 0.0, std::plus<>(), + bond_energy); } /** @@ -345,7 +388,8 @@ double Bonded::sumEnergy(const Bonded::BondVector& bonds, const ranges::cpp20::r * @param range an original set of integers (must be sorted) * @return a set of ints complementary to the original set */ -auto indexComplement(std::integral auto size, const ranges::cpp20::range auto& range) { +auto indexComplement(std::integral auto size, const ranges::cpp20::range auto& range) +{ namespace rv = ranges::cpp20::views; return rv::iota(0, static_cast(size)) | rv::filter([&](auto i) { return !std::binary_search(range.begin(), range.end(), i); }); @@ -355,14 +399,18 @@ auto indexComplement(std::integral auto size, const ranges::cpp20::range auto& r * @brief Provides a fast inlineable interface for non-bonded pair potential energy computation. * * @tparam TPairPotential a pair potential to compute with - * @tparam allow_anisotropic_pair_potential pass also a distance vector to the pair potential, slower + * @tparam allow_anisotropic_pair_potential pass also a distance vector to the pair potential, + * slower */ -template -class PairEnergy { - const Space::GeometryType& geometry; //!< geometry to operate with - TPairPotential pair_potential; //!< pair potential function/functor - Space& spc; //!< space to init ParticleSelfEnergy with addPairPotentialSelfEnergy - BasePointerVector& potentials; //!< registered non-bonded potentials, see addPairPotentialSelfEnergy +template +class PairEnergy +{ + const Space::GeometryType& geometry; //!< geometry to operate with + TPairPotential pair_potential; //!< pair potential function/functor + Space& spc; //!< space to init ParticleSelfEnergy with addPairPotentialSelfEnergy + BasePointerVector& + potentials; //!< registered non-bonded potentials, see addPairPotentialSelfEnergy public: /** * @param spc @@ -371,7 +419,9 @@ class PairEnergy { PairEnergy(Space& spc, BasePointerVector& potentials) : geometry(spc.geometry) , spc(spc) - , potentials(potentials) {} + , potentials(potentials) + { + } /** * @brief Computes pair potential energy. @@ -380,19 +430,23 @@ class PairEnergy { * @param b particle * @return pair potential energy between particles a and b */ - template inline double potential(const T& a, const T& b) const { + template inline double potential(const T& a, const T& b) const + { assert(&a != &b); // a and b cannot be the same particle if constexpr (allow_anisotropic_pair_potential) { const Point r = geometry.vdist(a.pos, b.pos); return pair_potential(a, b, r.squaredNorm(), r); - } else { + } + else { return pair_potential(a, b, geometry.sqdist(a.pos, b.pos), {0, 0, 0}); } } // just a temporary placement until PairForce class template will be implemented - template inline Point force(const ParticleType& a, const ParticleType& b) const { - assert(&a != &b); // a and b cannot be the same particle + template + inline Point force(const ParticleType& a, const ParticleType& b) const + { + assert(&a != &b); // a and b cannot be the same particle const Point b_towards_a = geometry.vdist(a.pos, b.pos); // vector b -> a = a - b return pair_potential.force(a, b, b_towards_a.squaredNorm(), b_towards_a); } @@ -401,7 +455,8 @@ class PairEnergy { * @brief A functor alias for potential(). * @see potential() */ - template inline auto operator()(Args&&... args) { + template inline auto operator()(Args&&... args) + { return potential(std::forward(args)...); } @@ -409,14 +464,16 @@ class PairEnergy { * @brief Registers the potential self-energy to hamiltonian if needed. * @see Hamiltonian::Hamiltonian */ - void addPairPotentialSelfEnergy() { + void addPairPotentialSelfEnergy() + { if (pair_potential.selfEnergy) { // only add if self energy is defined faunus_logger->debug("Adding self-energy from {} to hamiltonian", pair_potential.name); potentials.emplace_back(spc, pair_potential.selfEnergy); } } - void from_json(const json& j) { + void from_json(const json& j) + { pairpotential::from_json(j, pair_potential); if (!pair_potential.isotropic && !allow_anisotropic_pair_potential) { throw std::logic_error("Only isotropic pair potentials are allowed."); @@ -446,76 +503,97 @@ concept RequirePairEnergy = requires(T instance) { * where a custom `struct Tpair { const Particle &first, second; };` * outperforms `std::pair` due to missed compiler optimization. */ -class EnergyAccumulatorBase { +class EnergyAccumulatorBase +{ protected: double value = 0.0; //!< accumulated energy using ParticleRef = const std::reference_wrapper; //!< Particle reference - using ParticlePair = std::pair; //!< References to two particles + using ParticlePair = std::pair; //!< References to two particles public: - enum class Scheme { SERIAL, OPENMP, PARALLEL, INVALID }; + enum class Scheme + { + SERIAL, + OPENMP, + PARALLEL, + INVALID + }; Scheme scheme = Scheme::SERIAL; EnergyAccumulatorBase(double value); virtual ~EnergyAccumulatorBase() = default; virtual void reserve(size_t number_of_particles); virtual void clear(); - virtual void from_json(const json &j); - virtual void to_json(json &j) const; + virtual void from_json(const json& j); + virtual void to_json(json& j) const; virtual explicit operator double(); virtual EnergyAccumulatorBase& operator=(double new_value) = 0; virtual EnergyAccumulatorBase& operator+=(double new_value) = 0; virtual EnergyAccumulatorBase& operator+=(ParticlePair&& pair) = 0; - template inline EnergyAccumulatorBase& operator+=(TOtherAccumulator& acc) { + template + inline EnergyAccumulatorBase& operator+=(TOtherAccumulator& acc) + { value += static_cast(acc); return *this; } }; -NLOHMANN_JSON_SERIALIZE_ENUM(EnergyAccumulatorBase::Scheme, {{EnergyAccumulatorBase::Scheme::INVALID, nullptr}, - {EnergyAccumulatorBase::Scheme::SERIAL, "serial"}, - {EnergyAccumulatorBase::Scheme::OPENMP, "openmp"}, - {EnergyAccumulatorBase::Scheme::PARALLEL, "parallel"}}) +NLOHMANN_JSON_SERIALIZE_ENUM(EnergyAccumulatorBase::Scheme, + {{EnergyAccumulatorBase::Scheme::INVALID, nullptr}, + {EnergyAccumulatorBase::Scheme::SERIAL, "serial"}, + {EnergyAccumulatorBase::Scheme::OPENMP, "openmp"}, + {EnergyAccumulatorBase::Scheme::PARALLEL, "parallel"}}) template concept RequireEnergyAccumulator = std::is_base_of_v; /** - * @brief A basic accumulator which immediately computes and adds energy of a pair of particles upon addition using - * the PairEnergy templated class. + * @brief A basic accumulator which immediately computes and adds energy of a pair of particles upon + * addition using the PairEnergy templated class. * - * Generally this is the original way how the pairwise nonbonded energy has been computed in Faunus. Due to compiler - * optimization, templated class method 'PairEnergy.potential' may be inlined to significantly improve performance. + * Generally this is the original way how the pairwise nonbonded energy has been computed in Faunus. + * Due to compiler optimization, templated class method 'PairEnergy.potential' may be inlined to + * significantly improve performance. * * @tparam PairEnergy pair energy implementing a potential(a, b) method for particles a and b */ -template class InstantEnergyAccumulator : public EnergyAccumulatorBase { +template +class InstantEnergyAccumulator : public EnergyAccumulatorBase +{ private: - const PairEnergy& pair_energy; //!< recipe to compute non-bonded energy between two particles, see PairEnergy + const PairEnergy& + pair_energy; //!< recipe to compute non-bonded energy between two particles, see PairEnergy public: InstantEnergyAccumulator(const PairEnergy& pair_energy, const double value = 0.0) - : EnergyAccumulatorBase(value), pair_energy(pair_energy) {} + : EnergyAccumulatorBase(value) + , pair_energy(pair_energy) + { + } - inline InstantEnergyAccumulator& operator=(const double new_value) override { + inline InstantEnergyAccumulator& operator=(const double new_value) override + { value = new_value; return *this; } - inline InstantEnergyAccumulator& operator+=(const double new_value) override { + inline InstantEnergyAccumulator& operator+=(const double new_value) override + { value += new_value; return *this; } - inline InstantEnergyAccumulator& operator+=(ParticlePair&& pair) override { + inline InstantEnergyAccumulator& operator+=(ParticlePair&& pair) override + { // keep this short to get inlined value += pair_energy.potential(pair.first.get(), pair.second.get()); return *this; } - void from_json(const json &j) override { + void from_json(const json& j) override + { EnergyAccumulatorBase::from_json(j); if (scheme != Scheme::SERIAL) { faunus_logger->warn("unsupported summation scheme; falling back to 'serial'"); @@ -528,47 +606,62 @@ template class InstantEnergyAccumulator : public * `operator double()` is called. Looping over the vector can be done in serial (as a fallback); * using OpenMP; or using C++17 parallel algorithms if available. */ -template class DelayedEnergyAccumulator : public EnergyAccumulatorBase { +template +class DelayedEnergyAccumulator : public EnergyAccumulatorBase +{ private: std::vector particle_pairs; - const PairEnergy& pair_energy; //!< recipe to compute non-bonded energy between two particles, see PairEnergy - const size_t max_particles_in_buffer = 10000; //!< this can be modified to suit memory requirements + const PairEnergy& + pair_energy; //!< recipe to compute non-bonded energy between two particles, see PairEnergy + const size_t max_particles_in_buffer = + 10000; //!< this can be modified to suit memory requirements public: explicit DelayedEnergyAccumulator(const PairEnergy& pair_energy, const double value = 0.0) - : EnergyAccumulatorBase(value), pair_energy(pair_energy) {} + : EnergyAccumulatorBase(value) + , pair_energy(pair_energy) + { + } /** Reserve memory for (N-1)*N/2 interaction pairs */ - void reserve(size_t number_of_particles) override { + void reserve(size_t number_of_particles) override + { try { number_of_particles = std::min(number_of_particles, max_particles_in_buffer); const auto number_of_pairs = (number_of_particles - 1U) * number_of_particles / 2U; - faunus_logger->debug(fmt::format("reserving memory for {} energy pairs ({} MB)", number_of_pairs, - number_of_pairs * sizeof(ParticlePair) / (1024U * 1024U))); + faunus_logger->debug( + fmt::format("reserving memory for {} energy pairs ({} MB)", number_of_pairs, + number_of_pairs * sizeof(ParticlePair) / (1024U * 1024U))); particle_pairs.reserve(number_of_pairs); - } catch (std::exception& e) { - throw std::runtime_error( - fmt::format("cannot allocate memory for energy pairs: {}. Use another summation policy.", e.what())); + } + catch (std::exception& e) { + throw std::runtime_error(fmt::format( + "cannot allocate memory for energy pairs: {}. Use another summation policy.", + e.what())); } } - void clear() override { + void clear() override + { value = 0.0; particle_pairs.clear(); } - DelayedEnergyAccumulator& operator=(const double new_value) override { + DelayedEnergyAccumulator& operator=(const double new_value) override + { clear(); value = new_value; return *this; } - inline DelayedEnergyAccumulator& operator+=(const double new_value) override { + inline DelayedEnergyAccumulator& operator+=(const double new_value) override + { value += new_value; return *this; } - inline DelayedEnergyAccumulator& operator+=(ParticlePair&& pair) override { + inline DelayedEnergyAccumulator& operator+=(ParticlePair&& pair) override + { assert(particle_pairs.capacity() > 0); if (particle_pairs.size() == particle_pairs.capacity()) { operator double(); // sum stored pairs and reset buffer @@ -577,7 +670,8 @@ template class DelayedEnergyAccumulator : public return *this; } - explicit operator double() override { + explicit operator double() override + { switch (scheme) { case Scheme::OPENMP: value += accumulateOpenMP(); @@ -593,7 +687,8 @@ template class DelayedEnergyAccumulator : public } private: - double accumulateSerial() const { + double accumulateSerial() const + { double sum = 0.0; for (const auto& [particle1, particle2] : particle_pairs) { sum += pair_energy.potential(particle1.get(), particle2.get()); @@ -601,17 +696,21 @@ template class DelayedEnergyAccumulator : public return sum; } - double accumulateParallel() const { + double accumulateParallel() const + { #if defined(HAS_PARALLEL_TRANSFORM_REDUCE) return std::transform_reduce( std::execution::par, particle_pairs.cbegin(), particle_pairs.cend(), 0.0, std::plus<>(), - [&](const auto& pair) { return pair_energy.potential(pair.first.get(), pair.second.get()); }); + [&](const auto& pair) { + return pair_energy.potential(pair.first.get(), pair.second.get()); + }); #else return accumulateSerial(); // fallback #endif } - double accumulateOpenMP() const { + double accumulateOpenMP() const + { double sum = 0.0; #pragma omp parallel for reduction(+ : sum) for (const auto& pair : particle_pairs) { @@ -622,14 +721,19 @@ template class DelayedEnergyAccumulator : public }; template -std::unique_ptr createEnergyAccumulator(const json& j, const TPairEnergy& pair_energy, - double initial_value) { +std::unique_ptr +createEnergyAccumulator(const json& j, const TPairEnergy& pair_energy, double initial_value) +{ std::unique_ptr accumulator; - if (j.value("summation_policy", EnergyAccumulatorBase::Scheme::SERIAL) != EnergyAccumulatorBase::Scheme::SERIAL) { - accumulator = std::make_unique>(pair_energy, initial_value); + if (j.value("summation_policy", EnergyAccumulatorBase::Scheme::SERIAL) != + EnergyAccumulatorBase::Scheme::SERIAL) { + accumulator = + std::make_unique>(pair_energy, initial_value); faunus_logger->debug("activated delayed energy summation"); - } else { - accumulator = std::make_unique>(pair_energy, initial_value); + } + else { + accumulator = + std::make_unique>(pair_energy, initial_value); faunus_logger->debug("activated instant energy summation"); } accumulator->from_json(j); @@ -639,15 +743,17 @@ std::unique_ptr createEnergyAccumulator(const json& j, co /** * @brief Determines if two groups are separated beyond the cutoff distance. * - * The distance between centers of mass is considered. The cutoff distance can be specified independently for each - * group pair to override the default value. + * The distance between centers of mass is considered. The cutoff distance can be specified + * independently for each group pair to override the default value. * * @see GroupPairingPolicy */ -class GroupCutoff { +class GroupCutoff +{ double default_cutoff_squared = pc::max_value; - PairMatrix cutoff_squared; //!< matrix with group-to-group cutoff distances squared in angstrom squared - Space::GeometryType& geometry; //!< geometry to compute the inter group distance with + PairMatrix + cutoff_squared; //!< matrix with group-to-group cutoff distances squared in angstrom squared + Space::GeometryType& geometry; //!< geometry to compute the inter group distance with friend void from_json(const json&, GroupCutoff&); friend void to_json(json&, const GroupCutoff&); void setSingleCutoff(double cutoff); @@ -657,11 +763,13 @@ class GroupCutoff { * @brief Determines if two groups are separated beyond the cutoff distance. * @return true if the group-to-group distance is beyond the cutoff distance, false otherwise */ - inline bool cut(const Group& group1, const Group& group2) { + inline bool cut(const Group& group1, const Group& group2) + { if (group1.isAtomic() || group2.isAtomic()) { return false; // atomic groups have ill-defined mass centers } - return geometry.sqdist(group1.mass_center, group2.mass_center) >= cutoff_squared(group1.id, group2.id); + return geometry.sqdist(group1.mass_center, group2.mass_center) >= + cutoff_squared(group1.id, group2.id); } double getCutoff(size_t id1, size_t id2) const; @@ -670,8 +778,10 @@ class GroupCutoff { * @brief A functor alias for cut(). * @see cut() */ - template - inline auto operator()(Args &&... args) { return cut(std::forward(args)...); } + template inline auto operator()(Args&&... args) + { + return cut(std::forward(args)...); + } /** * @brief Sets the geometry. @@ -680,26 +790,27 @@ class GroupCutoff { explicit GroupCutoff(Space::GeometryType& geometry); }; -void from_json(const json&, GroupCutoff &); -void to_json(json&, const GroupCutoff &); +void from_json(const json&, GroupCutoff&); +void to_json(json&, const GroupCutoff&); /** - * @brief Particle pairing to calculate pairαΊƒise interaction using particles' groups internally. Depending on - * the accumulator provided, raw particle pairs, energy sum, etc. can be obtained. + * @brief Particle pairing to calculate pairαΊƒise interaction using particles' groups internally. + * Depending on the accumulator provided, raw particle pairs, energy sum, etc. can be obtained. * - * Accumulator is used as the first argument in all methods. Accumulator shall overload '+=' operator to accept a pair - * of particle references as used in particle2particle method. + * Accumulator is used as the first argument in all methods. Accumulator shall overload '+=' + * operator to accept a pair of particle references as used in particle2particle method. * - * @remark Method arguments are generally not checked for correctness because of performance reasons. + * @remark Method arguments are generally not checked for correctness because of performance + * reasons. * * @tparam TCutoff a cutoff scheme between groups * @see InstantEnergyAccumulator, GroupCutoff */ -template -class GroupPairingPolicy { +template class GroupPairingPolicy +{ protected: - const Space &spc; //!< a space to operate on - TCutoff cut; //!< a cutoff functor that determines if energy between two groups can be ignored + const Space& spc; //!< a space to operate on + TCutoff cut; //!< a cutoff functor that determines if energy between two groups can be ignored public: /** @@ -707,49 +818,49 @@ class GroupPairingPolicy { */ explicit GroupPairingPolicy(Space& spc) : spc(spc) - , cut(spc.geometry) {} - - void from_json(const json &j) { - Energy::from_json(j, cut); + , cut(spc.geometry) + { } - void to_json(json &j) const { - Energy::to_json(j, cut); - } + void from_json(const json& j) { Energy::from_json(j, cut); } + + void to_json(json& j) const { Energy::to_json(j, cut); } /** * @brief Add two interacting particles to the accumulator. * - * Due to compiler optimization, the '+=' operator and this function itself may be inlined to significantly - * improve performance. + * Due to compiler optimization, the '+=' operator and this function itself may be inlined to + * significantly improve performance. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam T an interacting particle * @param pair_accumulator accumulator of interacting pairs of particles * @param a first particle * @param b second particle */ template - inline void particle2particle(TAccumulator& pair_accumulator, const T& a, const T& b) const { + inline void particle2particle(TAccumulator& pair_accumulator, const T& a, const T& b) const + { pair_accumulator += {std::cref(a), std::cref(b)}; } /** * @brief All pairings within a group. * - * All pair interaction within the group are accumulated. The pair exclusions defined in the molecule - * topology are honoured. + * All pair interaction within the group are accumulated. The pair exclusions defined in the + * molecule topology are honoured. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param group * @param pair_accumulator accumulator of interacting pairs of particles */ template - void groupInternal(TAccumulator& pair_accumulator, const TGroup& group) { - const auto &moldata = group.traits(); + void groupInternal(TAccumulator& pair_accumulator, const TGroup& group) + { + const auto& moldata = group.traits(); if (!moldata.rigid) { const int group_size = group.size(); for (int i = 0; i < group_size - 1; ++i) { @@ -769,26 +880,29 @@ class GroupPairingPolicy { * * The pair exclusions defined in the molecule topology are honoured. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles * @param group * @param index internal index of the selected particle within the group */ template - void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const std::size_t index) { - const auto &moldata = group.traits(); + void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const std::size_t index) + { + const auto& moldata = group.traits(); if (!moldata.rigid) { if (group.isAtomic()) { - // speed optimization: non-bonded interaction exclusions do not need to be checked for atomic groups + // speed optimization: non-bonded interaction exclusions do not need to be checked + // for atomic groups for (int i = 0; i < index; ++i) { particle2particle(pair_accumulator, group[index], group[i]); } for (int i = index + 1; i < group.size(); ++i) { particle2particle(pair_accumulator, group[index], group[i]); } - } else { + } + else { // molecular group for (int i = 0; i < index; ++i) { if (!moldata.isPairExcluded(index, i)) { @@ -807,11 +921,12 @@ class GroupPairingPolicy { /** * @brief Pairing in the group involving only the particles present in the index. * - * Only such non-bonded pair interactions within the group are considered if at least one particle is present - * in the index. The pair exclusions defined in the molecule topology are honoured. + * Only such non-bonded pair interactions within the group are considered if at least one + * particle is present in the index. The pair exclusions defined in the molecule topology are + * honoured. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @tparam TIndex * @param pair_accumulator accumulator of interacting pairs of particles @@ -819,12 +934,14 @@ class GroupPairingPolicy { * @param index internal indices of particles within the group */ template - void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const TIndex& index) { - auto &moldata = group.traits(); + void groupInternal(TAccumulator& pair_accumulator, const TGroup& group, const TIndex& index) + { + auto& moldata = group.traits(); if (!moldata.rigid) { if (index.size() == 1) { groupInternal(pair_accumulator, group, index[0]); - } else { + } + else { // TODO investigate overhead of `index_complement` filtering; // TODO perhaps allow different strategies based on the index-size/group-size ratio auto index_complement = indexComplement(group.size(), index); @@ -853,22 +970,23 @@ class GroupPairingPolicy { * * group1 Γ— group2 * - * If the distance between the groups is greater or equal to the group cutoff distance, no calculation is performed. - * The group intersection must be an empty set, i.e., no particle is included in both groups. This is not verified - * for performance reason. + * If the distance between the groups is greater or equal to the group cutoff distance, no + * calculation is performed. The group intersection must be an empty set, i.e., no particle is + * included in both groups. This is not verified for performance reason. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles * @param group1 * @param group2 */ template - void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2) { + void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2) + { if (!cut(group1, group2)) { - for (auto &particle1 : group1) { - for (auto &particle2 : group2) { + for (auto& particle1 : group1) { + for (auto& particle2 : group2) { particle2particle(pair_accumulator, particle1, particle2); } } @@ -876,17 +994,22 @@ class GroupPairingPolicy { } /** - * @brief Cross pairing of particles in two groups. Only a cartesian subset of the complete cartesian product is - * considered as the particles in the first group must be also present in the index. The aim is to capture only + * @brief Cross pairing of particles in two groups. Only a cartesian subset of the complete + cartesian product is + * considered as the particles in the first group must be also present in the index. The aim is + to capture only * interactions that involve changing (indexed) particles. * * βŠ•group1 Γ— group2, where βŠ• denotes a filter by an index * - * If the distance between the groups is greater or equal to the group cutoff distance, no calculation is performed. - * The group intersection must be an empty set, i.e., no particle is included in both groups. This is not verified + * If the distance between the groups is greater or equal to the group cutoff distance, no + calculation is performed. + * The group intersection must be an empty set, i.e., no particle is included in both groups. + This is not verified * for performance reason. - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + as references * {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles @@ -896,31 +1019,34 @@ class GroupPairingPolicy { */ template void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2, - const std::vector& index1) { + const std::vector& index1) + { if (!cut(group1, group2)) { for (auto particle1_ndx : index1) { - for (auto &particle2 : group2) { - particle2particle(pair_accumulator, *(group1.begin() + particle1_ndx), particle2); + for (auto& particle2 : group2) { + particle2particle(pair_accumulator, *(group1.begin() + particle1_ndx), + particle2); } } } } /** - * @brief Cross pairing of particles in two groups. Only a non-cartesian subset of the complete cartesian product - * is considered as at least one particles in the pair must be also present in the respective index. The aim is - * to capture only interactions that involve changing (indexed) particles, i.e., to avoid pairs containing only - * non-indexed particles. + * @brief Cross pairing of particles in two groups. Only a non-cartesian subset of the complete + * cartesian product is considered as at least one particles in the pair must be also present in + * the respective index. The aim is to capture only interactions that involve changing (indexed) + * particles, i.e., to avoid pairs containing only non-indexed particles. * * (βŠ•group1 Γ— βˆβŠ•group2) + (βˆβŠ•group1 Γ— βŠ•group2) + (βŠ•group1 Γ— βŠ•group2) = - * = group1 Γ— group2 βˆ’ (βˆβŠ•group2 Γ— βˆβŠ•group2), where βŠ• denotes a filter by an index and ∁ a complement + * = group1 Γ— group2 βˆ’ (βˆβŠ•group2 Γ— βˆβŠ•group2), where βŠ• denotes a filter by an index and ∁ a + * complement * - * If the distance between the groups is greater or equal to the group cutoff distance, no calculation is performed. - * The group intersection must be an empty set, i.e., no particle is included in both groups. This is not verified - * for performance reason. + * If the distance between the groups is greater or equal to the group cutoff distance, no + * calculation is performed. The group intersection must be an empty set, i.e., no particle is + * included in both groups. This is not verified for performance reason. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles * @param group1 @@ -930,7 +1056,8 @@ class GroupPairingPolicy { */ template void group2group(TAccumulator& pair_accumulator, const TGroup& group1, const TGroup& group2, - const std::vector& index1, const std::vector& index2) { + const std::vector& index1, const std::vector& index2) + { if (!cut(group1, group2)) { if (!index2.empty()) { // (βˆβŠ•group1 Γ— βŠ•group2) + (βŠ•group1 Γ— βŠ•group2) = group1 Γ— βŠ•group2 @@ -939,14 +1066,17 @@ class GroupPairingPolicy { auto index2_complement = indexComplement(group2.size(), index2); for (auto particle1_ndx : index1) { for (auto particle2_ndx : index2_complement) { - particle2particle(pair_accumulator, group2[particle2_ndx], group1[particle1_ndx]); + particle2particle(pair_accumulator, group2[particle2_ndx], + group1[particle1_ndx]); } } - } else if (!index1.empty()) { + } + else if (!index1.empty()) { // (βŠ•group1 Γ— βˆβŠ•group2) + (βŠ•group1 Γ— βŠ•group2) = βŠ•group1 Γ— group2 group2group(pair_accumulator, group1, group2, index1); // + (βˆβŠ•group1 Γ— βŠ•group2) = Ø as βŠ•group2 is empty - } else { + } + else { // both indices empty hence nothing to do } } @@ -957,12 +1087,12 @@ class GroupPairingPolicy { * * group Γ— (βˆͺ groups) * - * If the distance between the groups is greater or equal to the group cutoff distance, the particle pairing between - * them is skipped. The internal energy of the group is not computed even if the group is also present in the union - * of groups. + * If the distance between the groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. The internal energy of the group is not computed + * even if the group is also present in the union of groups. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @tparam TGroups * @param pair_accumulator accumulator of interacting pairs of particles @@ -970,8 +1100,9 @@ class GroupPairingPolicy { * @param groups */ template - void group2groups(TAccumulator& pair_accumulator, const TGroup& group, const TGroups& groups) { - for (auto &other_group : groups) { + void group2groups(TAccumulator& pair_accumulator, const TGroup& group, const TGroups& groups) + { + for (auto& other_group : groups) { if (&other_group != &group) { group2group(pair_accumulator, group, other_group); } @@ -979,18 +1110,19 @@ class GroupPairingPolicy { } /** - * @brief Cross pairing of particles in a group and a union of groups. Only a cartesian subset of the complete - * cartesian product is considered as the particles of the first group must be also present in the index. The aim - * is to capture only interactions that involve changing (indexed) particles. + * @brief Cross pairing of particles in a group and a union of groups. Only a cartesian subset + * of the complete cartesian product is considered as the particles of the first group must be + * also present in the index. The aim is to capture only interactions that involve changing + * (indexed) particles. * * βŠ•group Γ— (βˆͺ groups), where βŠ• denotes a filter by an index * - * If the distance between the groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. The internal energy of the group is not computed even if the group is also present - * in the union of groups. + * If the distance between the groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. The internal energy of the group is not computed + * even if the group is also present in the union of groups. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @tparam TGroups * @param pair_accumulator accumulator of interacting pairs of particles @@ -999,10 +1131,11 @@ class GroupPairingPolicy { * @param index list of particle indices in the group relative to the group beginning */ template - void group2groups(TAccumulator& pair_accumulator, const TGroup& group, const TGroups& group_index, - const std::vector& index) { + void group2groups(TAccumulator& pair_accumulator, const TGroup& group, + const TGroups& group_index, const std::vector& index) + { for (auto other_group_ndx : group_index) { - const auto &other_group = spc.groups[other_group_ndx]; + const auto& other_group = spc.groups[other_group_ndx]; if (&other_group != &group) { group2group(pair_accumulator, group, other_group, index); } @@ -1010,22 +1143,24 @@ class GroupPairingPolicy { } /** - * @brief Complete cartesian pairing between particles in a group and particles in other groups in space. + * @brief Complete cartesian pairing between particles in a group and particles in other groups + * in space. * * group Γ— (space βˆ– group) * - * If the distance between the groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. + * If the distance between the groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles * @param group */ template - void group2all(TAccumulator& pair_accumulator, const Tgroup& group) { - for (auto &other_group : spc.groups) { + void group2all(TAccumulator& pair_accumulator, const Tgroup& group) + { + for (auto& other_group : spc.groups) { if (&other_group != &group) { group2group(pair_accumulator, group, other_group); } @@ -1033,27 +1168,30 @@ class GroupPairingPolicy { } /** - * @brief Complete cartesian pairing between a single particle in a group and particles in other groups in space. + * @brief Complete cartesian pairing between a single particle in a group and particles in other + * groups in space. * * βŠ•group Γ— (space βˆ– group), where βŠ• denotes a filter by an index (here a single particle) * - * If the distance between the groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. This method is performance-optimized version of the multiple indices method. + * If the distance between the groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. This method is performance-optimized version of the + * multiple indices method. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TGroup * @param pair_accumulator accumulator of interacting pairs of particles * @param group * @param index a particle index relative to the group beginning */ template - void group2all(TAccumulator& pair_accumulator, const TGroup& group, const int index) { - const auto &particle = group[index]; - for (auto &other_group : spc.groups) { + void group2all(TAccumulator& pair_accumulator, const TGroup& group, const int index) + { + const auto& particle = group[index]; + for (auto& other_group : spc.groups) { if (&other_group != &group) { // avoid self-interaction if (!cut(other_group, group)) { // check g2g cut-off - for (auto &other_particle : other_group) { // loop over particles in other group + for (auto& other_particle : other_group) { // loop over particles in other group particle2particle(pair_accumulator, particle, other_particle); } } @@ -1062,25 +1200,29 @@ class GroupPairingPolicy { } /** - * @brief Complete cartesian pairing between selected particles in a group and particles in other groups in space. + * @brief Complete cartesian pairing between selected particles in a group and particles in + * other groups in space. * * βŠ•group Γ— (space βˆ– group), where βŠ• denotes a filter by an index * - * If the distance between the groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. + * If the distance between the groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @param pair_accumulator accumulator of interacting pairs of particles * @param group * @param index list of particle indices in the group relative to the group beginning */ template - void group2all(TAccumulator& pair_accumulator, const Tgroup& group, const std::vector& index) { + void group2all(TAccumulator& pair_accumulator, const Tgroup& group, + const std::vector& index) + { if (index.size() == 1) { group2all(pair_accumulator, group, index[0]); - } else { - for (auto &other_group : spc.groups) { + } + else { + for (auto& other_group : spc.groups) { if (&other_group != &group) { group2group(pair_accumulator, group, other_group, index); } @@ -1089,24 +1231,29 @@ class GroupPairingPolicy { } /** - * @brief Cross pairing of particles among a union of groups. No internal pairs within any group are considered. + * @brief Cross pairing of particles among a union of groups. No internal pairs within any group + * are considered. * - * If the distance between any two groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. + * If the distance between any two groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam T * @param pair_accumulator accumulator of interacting pairs of particles * @param group_index list of groups */ template - void groups2self(TAccumulator& pair_accumulator, const T& group_index) { - for (auto group1_ndx_it = group_index.begin(); group1_ndx_it < group_index.end(); ++group1_ndx_it) { - //no such move exists that the internal energy has to be recalculated - //groupInternal(pair_accumulator, spc.groups[*group1_ndx_it]); - for (auto group2_ndx_it = std::next(group1_ndx_it); group2_ndx_it < group_index.end(); group2_ndx_it++) { - group2group(pair_accumulator, spc.groups[*group1_ndx_it], spc.groups[*group2_ndx_it]); + void groups2self(TAccumulator& pair_accumulator, const T& group_index) + { + for (auto group1_ndx_it = group_index.begin(); group1_ndx_it < group_index.end(); + ++group1_ndx_it) { + // no such move exists that the internal energy has to be recalculated + // groupInternal(pair_accumulator, spc.groups[*group1_ndx_it]); + for (auto group2_ndx_it = std::next(group1_ndx_it); group2_ndx_it < group_index.end(); + group2_ndx_it++) { + group2group(pair_accumulator, spc.groups[*group1_ndx_it], + spc.groups[*group2_ndx_it]); } } } @@ -1114,17 +1261,18 @@ class GroupPairingPolicy { /** * @brief Cross pairing of particles between a union of groups and its complement in space. * - * If the distance between any two groups is greater or equal to the group cutoff distance, the particle pairing - * between them is skipped. + * If the distance between any two groups is greater or equal to the group cutoff distance, the + * particle pairing between them is skipped. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam T * @param pair_accumulator accumulator of interacting pairs of particles * @param group_index list of groups */ template - void groups2all(TAccumulator& pair_accumulator, const T& group_index) { + void groups2all(TAccumulator& pair_accumulator, const T& group_index) + { groups2self(pair_accumulator, group_index); auto index_complement = indexComplement(spc.groups.size(), group_index); for (auto group1_ndx : group_index) { @@ -1137,17 +1285,19 @@ class GroupPairingPolicy { /** * @brief Cross pairing between all particles in the space. * - * If the distance between particles' groups is greater or equal to the group cutoff distance, no calculation is - * performed. + * If the distance between particles' groups is greater or equal to the group cutoff distance, + * no calculation is performed. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @param pair_accumulator accumulator of interacting pairs of particles */ - template void all(TAccumulator& pair_accumulator) { + template void all(TAccumulator& pair_accumulator) + { for (auto group_it = spc.groups.begin(); group_it < spc.groups.end(); ++group_it) { groupInternal(pair_accumulator, *group_it); - for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end(); other_group_it++) { + for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end(); + other_group_it++) { group2group(pair_accumulator, *group_it, *other_group_it); } } @@ -1156,22 +1306,24 @@ class GroupPairingPolicy { /** * @brief Cross pairing between all particles in the space. * - * If the distance between particles' groups is greater or equal to the group cutoff distance, no calculation is - * performed. + * If the distance between particles' groups is greater or equal to the group cutoff distance, + * no calculation is performed. * - * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles as references - * {T&, T&} + * @tparam TAccumulator an accumulator with '+=' operator overloaded to add a pair of particles + * as references {T&, T&} * @tparam TCondition a function returning bool and having a group as an argument * @param pair_accumulator accumulator of interacting pairs of particles * @param condition a group filter if internal energy of the group shall be added */ template - void all(TAccumulator& pair_accumulator, TCondition condition) { + void all(TAccumulator& pair_accumulator, TCondition condition) + { for (auto group_it = spc.groups.begin(); group_it < spc.groups.end(); ++group_it) { if (condition(*group_it)) { groupInternal(pair_accumulator, *group_it); } - for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end(); other_group_it++) { + for (auto other_group_it = std::next(group_it); other_group_it < spc.groups.end(); + other_group_it++) { group2group(pair_accumulator, *group_it, *other_group_it); } } @@ -1179,14 +1331,14 @@ class GroupPairingPolicy { }; /** - * @brief Computes pair quantity difference for a systen perturbation. Such quantity can be energy using nonponded - * pair potential + * @brief Computes pair quantity difference for a systen perturbation. Such quantity can be energy + * using nonponded pair potential * . * @tparam TPolicy a pairing policy */ -template -class GroupPairing { - const Space &spc; +template class GroupPairing +{ + const Space& spc; TPolicy pairing; protected: @@ -1198,26 +1350,32 @@ class GroupPairing { * @param change */ template - void accumulateGroup(TAccumulator& pair_accumulator, const Change& change) { - const auto &change_data = change.groups.at(0); + void accumulateGroup(TAccumulator& pair_accumulator, const Change& change) + { + const auto& change_data = change.groups.at(0); const auto& group = spc.groups.at(change_data.group_index); if (change_data.relative_atom_indices.size() == 1) { // faster algorithm if only a single particle moves pairing.group2all(pair_accumulator, group, change_data.relative_atom_indices[0]); if (change_data.internal) { - pairing.groupInternal(pair_accumulator, group, change_data.relative_atom_indices[0]); + pairing.groupInternal(pair_accumulator, group, + change_data.relative_atom_indices[0]); } - } else { - const bool change_all = change_data.relative_atom_indices.empty(); // all particles or only their subset? + } + else { + const bool change_all = + change_data.relative_atom_indices.empty(); // all particles or only their subset? if (change_all) { pairing.group2all(pair_accumulator, group); if (change_data.internal) { pairing.groupInternal(pair_accumulator, group); } - } else { + } + else { pairing.group2all(pair_accumulator, group, change_data.relative_atom_indices); if (change_data.internal) { - pairing.groupInternal(pair_accumulator, group, change_data.relative_atom_indices); + pairing.groupInternal(pair_accumulator, group, + change_data.relative_atom_indices); } } } @@ -1226,38 +1384,46 @@ class GroupPairing { /** * @brief Computes pair quantity difference if the number of particles has changed. * - * Particles have to be explicitly enumerated in the atom indices of the changed group. Implicit addition of atoms - * with a group is not supported yet. Note that we do not have to care about missing (removed) particles at all. - * They are taken into account in the original (old) space where they are present. + * Particles have to be explicitly enumerated in the atom indices of the changed group. Implicit + * addition of atoms with a group is not supported yet. Note that we do not have to care about + * missing (removed) particles at all. They are taken into account in the original (old) space + * where they are present. * * @param pair_accumulator accumulator of interacting pairs of particles * @param change */ template - void accumulateSpeciation(TAccumulator& pair_accumulator, const Change& change) { + void accumulateSpeciation(TAccumulator& pair_accumulator, const Change& change) + { assert(change.matter_change); - const auto &moved = change.touchedGroupIndex(); // index of moved groups - const auto fixed = - indexComplement(spc.groups.size(), moved) | ranges::to; // index of static groups - auto filter_active = [](int size) { return ranges::views::filter([size](const auto i) { return i < size; }); }; + const auto& moved = change.touchedGroupIndex(); // index of moved groups + const auto fixed = indexComplement(spc.groups.size(), moved) | + ranges::to; // index of static groups + auto filter_active = [](int size) { + return ranges::views::filter([size](const auto i) { return i < size; }); + }; // loop over all changed groups - for (auto change_group1_it = change.groups.begin(); change_group1_it < change.groups.end(); ++change_group1_it) { + for (auto change_group1_it = change.groups.begin(); change_group1_it < change.groups.end(); + ++change_group1_it) { const auto& group1 = spc.groups.at(change_group1_it->group_index); // filter only active particles - const auto index1 = - change_group1_it->relative_atom_indices | filter_active(group1.size()) | ranges::to; + const auto index1 = change_group1_it->relative_atom_indices | + filter_active(group1.size()) | ranges::to; if (!index1.empty()) { // particles added into the group: compute (changed group) <-> (static group) pairing.group2groups(pair_accumulator, group1, fixed, index1); } - // loop over successor changed groups (hence avoid double counting group1Γ—group2 and group2Γ—group1) - for (auto change_group2_it = std::next(change_group1_it); change_group2_it < change.groups.end(); ++change_group2_it) { + // loop over successor changed groups (hence avoid double counting group1Γ—group2 and + // group2Γ—group1) + for (auto change_group2_it = std::next(change_group1_it); + change_group2_it < change.groups.end(); ++change_group2_it) { const auto& group2 = spc.groups.at(change_group2_it->group_index); - const auto index2 = - change_group2_it->relative_atom_indices | filter_active(group2.size()) | ranges::to; + const auto index2 = change_group2_it->relative_atom_indices | + filter_active(group2.size()) | ranges::to; if (!index1.empty() || !index2.empty()) { - // particles added into one or other group: compute (changed group) <-> (changed group) + // particles added into one or other group: compute (changed group) <-> (changed + // group) pairing.group2group(pair_accumulator, group1, group2, index1, index2); } } @@ -1265,7 +1431,8 @@ class GroupPairing { // compute internal energy in the changed group if (change_group1_it->all) { pairing.groupInternal(pair_accumulator, group1); - } else { + } + else { pairing.groupInternal(pair_accumulator, group1, index1); }; } @@ -1276,55 +1443,67 @@ class GroupPairing { /** * @brief Computes pair quantity difference from changed particles. * - * The internal energy contribution, i.e., the contribution from the intra group interactions, is added - * only if a single group is changed or if all changed. + * The internal energy contribution, i.e., the contribution from the intra group interactions, + * is added only if a single group is changed or if all changed. * * @param change * @param pair_accumulator accumulator of interacting pairs of particles */ template - void accumulate(TAccumulator& pair_accumulator, const Change& change) { + void accumulate(TAccumulator& pair_accumulator, const Change& change) + { assert(std::is_sorted(change.groups.begin(), change.groups.end())); if (change.everything) { pairing.all(pair_accumulator); - } else if (change.volume_change) { + } + else if (change.volume_change) { // sum all interaction energies except the internal energies of incompressible molecules - pairing.all(pair_accumulator, [](auto& group) { return group.isAtomic() || group.traits().compressible; }); - } else if (!change.matter_change) { + pairing.all(pair_accumulator, [](auto& group) { + return group.isAtomic() || group.traits().compressible; + }); + } + else if (!change.matter_change) { if (change.groups.size() == 1) { - // if only a single group changes use faster algorithm and optionally add the internal energy + // if only a single group changes use faster algorithm and optionally add the + // internal energy accumulateGroup(pair_accumulator, change); - } else { + } + else { // if multiple groups move, no internal energies are computed - const auto &moved = change.touchedGroupIndex(); // index of moved groups + const auto& moved = change.touchedGroupIndex(); // index of moved groups pairing.groups2all(pair_accumulator, moved); } - } else { // change.dN + } + else { // change.dN accumulateSpeciation(pair_accumulator, change); } } - GroupPairing(Space &spc) : spc(spc), pairing(spc) {} - - void from_json(const json &j) { - pairing.from_json(j); + GroupPairing(Space& spc) + : spc(spc) + , pairing(spc) + { } - void to_json(json &j) const { - pairing.to_json(j); - } + void from_json(const json& j) { pairing.from_json(j); } + + void to_json(json& j) const { pairing.to_json(j); } // FIXME a temporal fix for non-refactorized NonbondedCached template - void group2group(Accumulator& pair_accumulator, const Space::GroupType& group1, const Space::GroupType& group2) { - pairing.group2group(std::forward(pair_accumulator), std::forward(group1), + void group2group(Accumulator& pair_accumulator, const Space::GroupType& group1, + const Space::GroupType& group2) + { + pairing.group2group(std::forward(pair_accumulator), + std::forward(group1), std::forward(group2)); } }; -class NonbondedBase : public EnergyTerm { +class NonbondedBase : public EnergyTerm +{ public: - virtual double particleParticleEnergy(const Particle &particle1, const Particle &particle2) = 0; + virtual double particleParticleEnergy(const Particle& particle1, const Particle& particle2) = 0; virtual double groupGroupEnergy(const Group& group1, const Group& group2) = 0; }; @@ -1332,13 +1511,18 @@ class NonbondedBase : public EnergyTerm { * @brief Computes change in the non-bonded energy, assuming pairwise additive energy terms. * * @tparam TPairEnergy a functor to compute non-bonded energy between two particles - * @tparam TPairingPolicy pairing policy to effectively sum up the pairwise additive non-bonded energy + * @tparam TPairingPolicy pairing policy to effectively sum up the pairwise additive non-bonded + * energy */ -template class Nonbonded : public NonbondedBase { +template +class Nonbonded : public NonbondedBase +{ protected: const Space& spc; //!< space to operate on - TPairEnergy pair_energy; //!< a functor to compute non-bonded energy between two particles, see PairEnergy - TPairingPolicy pairing; //!< pairing policy to effectively sum up the pairwise additive non-bonded energy + TPairEnergy pair_energy; //!< a functor to compute non-bonded energy between two particles, see + //!< PairEnergy + TPairingPolicy + pairing; //!< pairing policy to effectively sum up the pairwise additive non-bonded energy std::shared_ptr energy_accumulator; //!< energy accumulator used for storing and summing pair-wise energies @@ -1346,42 +1530,52 @@ template class Nonbonde Nonbonded(const json& j, Space& spc, BasePointerVector& pot) : spc(spc) , pair_energy(spc, pot) - , pairing(spc) { + , pairing(spc) + { name = "nonbonded"; from_json(j); energy_accumulator = createEnergyAccumulator(j, pair_energy, 0.0); energy_accumulator->reserve(spc.numParticles()); // attempt to reduce memory fragmentation } - double particleParticleEnergy(const Particle& particle1, const Particle& particle2) override { + double particleParticleEnergy(const Particle& particle1, const Particle& particle2) override + { return pair_energy(particle1, particle2); } - double groupGroupEnergy(const Group &group1, const Group& group2) override { + double groupGroupEnergy(const Group& group1, const Group& group2) override + { InstantEnergyAccumulator accumulator(pair_energy); pairing.group2group(accumulator, group1, group2); return static_cast(accumulator); } - void from_json(const json &j) { + void from_json(const json& j) + { pair_energy.from_json(j); pairing.from_json(j); } - void to_json(json &j) const override { + void to_json(json& j) const override + { pair_energy.to_json(j); pairing.to_json(j); energy_accumulator->to_json(j); } - double energy(const Change& change) override { + double energy(const Change& change) override + { energy_accumulator->clear(); // down-cast to avoid slow, virtual function calls: - if (auto ptr = std::dynamic_pointer_cast>(energy_accumulator)) { + if (auto ptr = std::dynamic_pointer_cast>( + energy_accumulator)) { pairing.accumulate(*ptr, change); - } else if (auto ptr = std::dynamic_pointer_cast>(energy_accumulator)) { + } + else if (auto ptr = std::dynamic_pointer_cast>( + energy_accumulator)) { pairing.accumulate(*ptr, change); - } else { + } + else { pairing.accumulate(*energy_accumulator, change); } return static_cast(*energy_accumulator); @@ -1392,9 +1586,12 @@ template class Nonbonde * * @todo A stub. Change to reflect only active particle, see Space::activeParticles(). */ - void force(std::vector &forces) override { - // just a temporary hack; perhaps better to allow PairForce instead of the PairEnergy template - assert(forces.size() == spc.particles.size() && "the forces size must match the particle size"); + void force(std::vector& forces) override + { + // just a temporary hack; perhaps better to allow PairForce instead of the PairEnergy + // template + assert(forces.size() == spc.particles.size() && + "the forces size must match the particle size"); for (size_t i = 0; i < spc.particles.size() - 1; ++i) { for (size_t j = i + 1; j < spc.particles.size(); ++j) { const Point f = pair_energy.force(spc.particles[i], spc.particles[j]); @@ -1406,47 +1603,54 @@ template class Nonbonde }; /** - * @brief Computes non-bonded energy contribution from changed particles. Cache group2group energy once calculated, - * until a new trial configuration is provided. Not for general use as only partially implemented! + * @brief Computes non-bonded energy contribution from changed particles. Cache group2group energy + * once calculated, until a new trial configuration is provided. Not for general use as only + * partially implemented! * - * Original implementation, only refurbished. Generally suboptimal as only PairingPolicy::group2group method - * may be called. - * No internal energy is ever computed. Cannot deal with particle count changes. And other unmentioned constrains. + * Original implementation, only refurbished. Generally suboptimal as only + * PairingPolicy::group2group method may be called. No internal energy is ever computed. Cannot deal + * with particle count changes. And other unmentioned constrains. * * @tparam TPairEnergy a functor to compute non-bonded energy between two particles - * @tparam TPairingPolicy pairing policy to effectively sum up the pairwise additive non-bonded energy + * @tparam TPairingPolicy pairing policy to effectively sum up the pairwise additive non-bonded + * energy */ template -class NonbondedCached : public Nonbonded { +class NonbondedCached : public Nonbonded +{ using Base = Nonbonded; using TAccumulator = InstantEnergyAccumulator; Eigen::MatrixXf energy_cache; using Base::spc; - template - double g2g(const TGroup &g1, const TGroup &g2) { + template double g2g(const TGroup& g1, const TGroup& g2) + { int i = &g1 - spc.groups.data(); int j = &g2 - spc.groups.data(); if (j < i) { std::swap(i, j); } - if (EnergyTerm::state == EnergyTerm::MonteCarloState::TRIAL) { // if this is from the trial system + if (EnergyTerm::state == + EnergyTerm::MonteCarloState::TRIAL) { // if this is from the trial system TAccumulator energy_accumulator(Base::pair_energy); Base::pairing.group2group(energy_accumulator, g1, g2); - energy_cache(i, j) = static_cast(energy_accumulator); // update the cache + energy_cache(i, j) = static_cast(energy_accumulator); // update the cache } return energy_cache(i, j); // return (cached) value } template - double g2g(const TGroup& g1, const TGroup& g2, [[maybe_unused]] const std::vector& index) { + double g2g(const TGroup& g1, const TGroup& g2, + [[maybe_unused]] const std::vector& index) + { // index not implemented return g2g(g1, g2); } public: NonbondedCached(const json& j, Space& spc, BasePointerVector& pot) - : Base(j, spc, pot) { + : Base(j, spc, pot) + { Base::name += "EM"; init(); } @@ -1454,7 +1658,8 @@ class NonbondedCached : public Nonbonded { /** * @brief Cache pair interactions in matrix. */ - void init() override { + void init() override + { const auto groups_size = spc.groups.size(); energy_cache.resize(groups_size, groups_size); energy_cache.setZero(); @@ -1468,7 +1673,8 @@ class NonbondedCached : public Nonbonded { } } - double energy(const Change& change) override { + double energy(const Change& change) override + { if (!change) { return 0.0; } @@ -1508,7 +1714,8 @@ class NonbondedCached : public Nonbonded { // moved<->static #if true // classic version - const auto fixed = indexComplement(spc.groups.size(), moved) | ranges::to_vector; // static groups + const auto fixed = + indexComplement(spc.groups.size(), moved) | ranges::to_vector; // static groups for (auto i : moved) { for (auto j : fixed) { energy_sum += g2g(spc.groups[i], spc.groups[j]); @@ -1516,7 +1723,8 @@ class NonbondedCached : public Nonbonded { } #else // OMP-ready version - auto fixed = indexComplement(spc.groups.size(), moved) | ranges::to; // index of static groups + auto fixed = indexComplement(spc.groups.size(), moved) | + ranges::to; // index of static groups const size_t moved_size = moved.size(); const size_t fixed_size = fixed.size(); for (auto i = 0; i < moved_size; ++i) { @@ -1533,13 +1741,15 @@ class NonbondedCached : public Nonbonded { * @param base_ptr * @param change */ - void sync(EnergyTerm* base_ptr, const Change& change) override { + void sync(EnergyTerm* base_ptr, const Change& change) override + { auto other = dynamic_cast(base_ptr); assert(other); if (change.everything || change.volume_change) { energy_cache.triangularView() = (other->energy_cache).template triangularView(); - } else { + } + else { for (const auto& d : change.groups) { for (int i = 0; i < d.group_index; i++) { energy_cache(i, d.group_index) = other->energy_cache(i, d.group_index); @@ -1560,7 +1770,8 @@ class NonbondedCached : public Nonbonded { * @todo - Implement partial evaluation refelcting `change` object * - Average volume currently mixes accepted/rejected states */ -class FreeSASAEnergy : public EnergyTerm { +class FreeSASAEnergy : public EnergyTerm +{ private: std::vector positions; //!< Flattened position buffer for all particles std::vector radii; //!< Radii buffer for all particles @@ -1571,7 +1782,7 @@ class FreeSASAEnergy : public EnergyTerm { std::unique_ptr parameters; //!< Parameters for freesasa Average mean_surface_area; - void to_json(json &j) const override; + void to_json(json& j) const override; void sync(EnergyTerm* energybase_ptr, const Change& change) override; void updateSASA(const Change& change); void init() override; @@ -1583,7 +1794,8 @@ class FreeSASAEnergy : public EnergyTerm { * @param change Change object (currently unused) */ template - void updateRadii(Tfirst begin, Tend end, [[maybe_unused]] const Change& change) { + void updateRadii(Tfirst begin, Tend end, [[maybe_unused]] const Change& change) + { const auto number_of_particles = std::distance(begin, end); radii.clear(); radii.reserve(number_of_particles); @@ -1598,7 +1810,8 @@ class FreeSASAEnergy : public EnergyTerm { * @param change Change object (currently unused) */ template - void updatePositions(Tfirst begin, Tend end, [[maybe_unused]] const Change& change) { + void updatePositions(Tfirst begin, Tend end, [[maybe_unused]] const Change& change) + { const auto number_of_particles = std::distance(begin, end); positions.clear(); positions.reserve(3 * number_of_particles); @@ -1617,6 +1830,7 @@ class FreeSASAEnergy : public EnergyTerm { FreeSASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius); FreeSASAEnergy(const json& j, const Space& spc); double energy(const Change& change) override; + const std::vector& getAreas() const { return sasa; } }; //!< SASA energy from transfer free energies #endif @@ -1628,7 +1842,8 @@ class FreeSASAEnergy : public EnergyTerm { * (and slow) system update is performed on each call to `energy()`. This class is used as * a reference and base class for more clever implementations. */ -class SASAEnergyReference : public EnergyTerm { +class SASAEnergyReference : public EnergyTerm +{ private: void to_json(json& j) const override; void sync(EnergyTerm* energybase_ptr, const Change& change) override; @@ -1636,32 +1851,38 @@ class SASAEnergyReference : public EnergyTerm { protected: void init() override; using index_type = size_t; - const Space& spc; //!< Space to operate on - std::vector areas; //!< Target buffer for calculated surface areas - double cosolute_molarity = 0.0; //!< co-solute concentration (mol/l) - std::unique_ptr sasa; //!< performs neighbour searching and subsequent sasa calculation + const Space& spc; //!< Space to operate on + std::vector areas; //!< Target buffer for calculated surface areas + double cosolute_molarity = 0.0; //!< co-solute concentration (mol/l) + std::unique_ptr + sasa; //!< performs neighbour searching and subsequent sasa calculation /** absolute index of particle in space particle vector */ - inline auto indexOf(const Particle& particle) const { - return static_cast(std::addressof(particle) - std::addressof(spc.particles.at(0))); + inline auto indexOf(const Particle& particle) const + { + return static_cast(std::addressof(particle) - + std::addressof(spc.particles.at(0))); } public: - SASAEnergyReference(const Space& spc, double cosolute_molarity, double probe_radius, int slices_per_atom = 25, - bool dense_container = true); + SASAEnergyReference(const Space& spc, double cosolute_molarity, double probe_radius, + int slices_per_atom = 25, bool dense_container = true); SASAEnergyReference(const json& j, const Space& spc); const std::vector& getAreas() const; double energy(const Change& change) override; }; /** - * @brief class for calculating SASA energies calculating SASA of particles based on change object every step + * @brief class for calculating SASA energies calculating SASA of particles based on change object + * every step */ -class SASAEnergy : public SASAEnergyReference { +class SASAEnergy : public SASAEnergyReference +{ private: std::vector> current_neighbours; //!< holds cached neighbour indices for each particle in ParticleVector - std::vector changed_indices; //!< paritcle indices whose SASA changed based on change object + std::vector + changed_indices; //!< paritcle indices whose SASA changed based on change object void sync(EnergyTerm* energybase_ptr, const Change& change) override; void init() override; @@ -1670,8 +1891,8 @@ class SASAEnergy : public SASAEnergyReference { void insertChangedNeighboursOf(index_type index, std::set& target_indices) const; public: - SASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius, int slices_per_atom = 25, - bool dense_container = true); + SASAEnergy(const Space& spc, double cosolute_molarity, double probe_radius, + int slices_per_atom = 25, bool dense_container = true); SASAEnergy(const json& j, const Space& spc); double energy(const Change& change) override; }; @@ -1683,15 +1904,16 @@ class SASAEnergy : public SASAEnergyReference { * to illustrate parallel tempering in the book * "Understanding Molecular Simulation" by D. Frenkel. */ -class Example2D : public EnergyTerm { +class Example2D : public EnergyTerm +{ private: bool use_2d = true; // Set to false to apply energy only along x (as by the book) double scale_energy = 1.0; // effective temperature - const Point &particle; // reference to 1st particle in the system - void to_json(json &j) const override; + const Point& particle; // reference to 1st particle in the system + void to_json(json& j) const override; public: - Example2D(const json &j, Space &spc); + Example2D(const json& j, Space& spc); double energy(const Change& change) override; }; @@ -1704,7 +1926,8 @@ class Example2D : public EnergyTerm { * - "R" Mass center separation * - "Z1" and "Z2" mean charges of the two molecules */ -class CustomGroupGroup : public EnergyTerm { +class CustomGroupGroup : public EnergyTerm +{ private: const Space& spc; MoleculeData::index_type molid1; @@ -1712,11 +1935,14 @@ class CustomGroupGroup : public EnergyTerm { std::map> mean_charges; std::unique_ptr> expr; json json_input_backup; // initial json input - struct Properties { // storage for particle properties + + struct Properties + { // storage for particle properties double mean_charge1 = 0; double mean_charge2 = 0; double mass_center_separation = 0; }; + Properties properties; void to_json(json& j) const override; @@ -1730,13 +1956,17 @@ class CustomGroupGroup : public EnergyTerm { /** * @brief Aggregate and sum energy terms */ -class Hamiltonian : public EnergyTerm, public BasePointerVector { +class Hamiltonian : public EnergyTerm, public BasePointerVector +{ private: double maximum_allowed_energy = pc::infty; //!< Maximum allowed energy change - std::vector latest_energies; //!< Placeholder for the lastest energies for each energy term - decltype(vec)& energy_terms; //!< Alias for `vec` - void addEwald(const json& j, Space& spc); //!< Adds an instance of reciprocal space Ewald energies (if appropriate) - void checkBondedMolecules() const; //!< Warn if bonded molecules and no bonded energy term + std::vector + latest_energies; //!< Placeholder for the lastest energies for each energy term + decltype(vec)& energy_terms; //!< Alias for `vec` + void + addEwald(const json& j, + Space& spc); //!< Adds an instance of reciprocal space Ewald energies (if appropriate) + void checkBondedMolecules() const; //!< Warn if bonded molecules and no bonded energy term void to_json(json& j) const override; void force(PointVector& forces) override; std::unique_ptr createEnergy(Space& spc, const std::string& name, const json& j); @@ -1746,8 +1976,9 @@ class Hamiltonian : public EnergyTerm, public BasePointerVector { void init() override; void updateState(const Change& change) override; void sync(EnergyTerm* other_hamiltonian, const Change& change) override; - double energy(const Change& change) override; //!< Energy due to changes - const std::vector& latestEnergies() const; //!< Energies for each term from the latest call to `energy()` + double energy(const Change& change) override; //!< Energy due to changes + const std::vector& + latestEnergies() const; //!< Energies for each term from the latest call to `energy()` }; } // namespace Energy } // namespace Faunus diff --git a/src/externalpotential.cpp b/src/externalpotential.cpp index 9dfe14a78..cbc4afeb0 100644 --- a/src/externalpotential.cpp +++ b/src/externalpotential.cpp @@ -18,9 +18,13 @@ void EnergyTerm::to_json(json&) const {} * @param other_energy Other energy instance to copy data from * @param change Describes the difference with the other energy term */ -void EnergyTerm::sync([[maybe_unused]] EnergyTerm* other_energy, [[maybe_unused]] const Change& change) {} +void EnergyTerm::sync([[maybe_unused]] EnergyTerm* other_energy, + [[maybe_unused]] const Change& change) +{ +} void EnergyTerm::init() {} + void EnergyTerm::force([[maybe_unused]] PointVector& forces) {} /** @@ -30,7 +34,8 @@ void EnergyTerm::force([[maybe_unused]] PointVector& forces) {} */ void EnergyTerm::updateState([[maybe_unused]] const Change& change) {} -void to_json(json& j, const EnergyTerm& base) { +void to_json(json& j, const EnergyTerm& base) +{ assert(not base.name.empty()); if (base.timer) j[base.name]["relative time"] = base.timer.result(); @@ -50,12 +55,13 @@ void to_json(json& j, const EnergyTerm& base) { * - If `act_on_mass_center` is true, the external potential is applied on a * fictitious particle placed at the COM and with a net-charge of the group. */ -double ExternalPotential::groupEnergy(const Group& group) const { +double ExternalPotential::groupEnergy(const Group& group) const +{ if (group.empty() || !molecule_ids.contains(group.id)) { return 0.0; } if (act_on_mass_center && group.massCenter().has_value()) { // apply only to center of mass - Particle mass_center; // temp. particle representing molecule + Particle mass_center; // temp. particle representing molecule mass_center.charge = Faunus::monopoleMoment(group.begin(), group.end()); mass_center.pos = group.mass_center; return externalPotentialFunc(mass_center); @@ -71,7 +77,8 @@ double ExternalPotential::groupEnergy(const Group& group) const { } ExternalPotential::ExternalPotential(const json& j, const Space& spc) - : space(spc) { + : space(spc) +{ name = "external"; act_on_mass_center = j.value("com", false); molecule_names = j.at("molecules").get(); // molecule names @@ -81,7 +88,9 @@ ExternalPotential::ExternalPotential(const json& j, const Space& spc) throw std::runtime_error(name + ": molecule list is empty"); } } -double ExternalPotential::energy(const Change& change) { + +double ExternalPotential::energy(const Change& change) +{ assert(externalPotentialFunc != nullptr); double energy = 0.0; if (change.volume_change or change.everything or change.matter_change) { @@ -98,10 +107,12 @@ double ExternalPotential::energy(const Change& change) { if (not molecule_ids.contains(group.id)) { continue; // skip non-registered groups } - if (group_change.all or act_on_mass_center) { // check all atoms in group - energy += groupEnergy(group); // groupEnergy also checks for molecule id - } else { // only specified atoms in group - for (auto index : group_change.relative_atom_indices) { // loop over changed atoms in group + if (group_change.all or act_on_mass_center) { // check all atoms in group + energy += groupEnergy(group); // groupEnergy also checks for molecule id + } + else { // only specified atoms in group + for (auto index : + group_change.relative_atom_indices) { // loop over changed atoms in group energy += externalPotentialFunc(group.at(index)); } } @@ -111,12 +122,15 @@ double ExternalPotential::energy(const Change& change) { } return energy; // in kT } -void ExternalPotential::to_json(json &j) const { + +void ExternalPotential::to_json(json& j) const +{ j["molecules"] = molecule_names; j["com"] = act_on_mass_center; } -TEST_CASE("[Faunus] ExternalPotential") { +TEST_CASE("[Faunus] ExternalPotential") +{ using doctest::Approx; Faunus::atoms = R"([ { "A": { "sigma": 4.0, "tfe": 1.0 } }, @@ -132,9 +146,10 @@ TEST_CASE("[Faunus] ExternalPotential") { "insertmolecules": [ { "M": { "N": 1 } } ] })"_json; - SUBCASE("ParticleSelfEnergy") { + SUBCASE("ParticleSelfEnergy") + { Space spc = j; - ParticleSelfEnergy pot(spc, [](const Particle &) { return 0.5; }); + ParticleSelfEnergy pot(spc, [](const Particle&) { return 0.5; }); Change change; change.everything = true; // if both particles have changed CHECK_EQ(pot.energy(change), Approx(0.5 + 0.5)); @@ -143,7 +158,9 @@ TEST_CASE("[Faunus] ExternalPotential") { // ------------ Confine ------------- -Confine::Confine(const json& j, Space& spc) : ExternalPotential(j, spc) { +Confine::Confine(const json& j, Space& spc) + : ExternalPotential(j, spc) +{ name = "confine"; spring_constant = getValueInfinity(j, "k") * 1.0_kJmol; // get floating point; allow inf/-inf type = m.at(j.at("type")); @@ -156,14 +173,18 @@ Confine::Confine(const json& j, Space& spc) : ExternalPotential(j, spc) { dir = {1, 1, 0}; } externalPotentialFunc = [&](const Particle& particle) { - const auto squared_distance = (origo - particle.pos).cwiseProduct(dir).squaredNorm() - radius * radius; + const auto squared_distance = + (origo - particle.pos).cwiseProduct(dir).squaredNorm() - radius * radius; return (squared_distance > 0.0) ? 0.5 * spring_constant * squared_distance : 0.0; }; // If volume is scaled, also scale the confining radius by adding a trigger // to `Space::scaleVolume()` if (scale) { - spc.scaleVolumeTriggers.emplace_back([&]([[maybe_unused]] Space& spc, double Vold, double Vnew) { radius *= std::cbrt(Vnew / Vold); }); + spc.scaleVolumeTriggers.emplace_back( + [&]([[maybe_unused]] Space& spc, double Vold, double Vnew) { + radius *= std::cbrt(Vnew / Vold); + }); } } @@ -188,7 +209,9 @@ Confine::Confine(const json& j, Space& spc) : ExternalPotential(j, spc) { }; } } -void Confine::to_json(json &j) const { + +void Confine::to_json(json& j) const +{ if (type == cuboid) { j = {{"low", low}, {"high", high}}; } @@ -212,7 +235,8 @@ void Confine::to_json(json &j) const { // ------------ ExternalAkesson ------------- ExternalAkesson::ExternalAkesson(const json& j, const Space& spc) - : ExternalPotential(j, spc) { + : ExternalPotential(j, spc) +{ name = "akesson"; citation_information = "doi:10/dhb9mj"; nstep = j.at("nstep").get(); @@ -230,11 +254,14 @@ ExternalAkesson::ExternalAkesson(const json& j, const Space& spc) filename = j.value("file", "mfcorr.dat"s); loadChargeDensity(); - externalPotentialFunc = [&](const Particle& particle) { return particle.charge * phi(particle.pos.z()); }; + externalPotentialFunc = [&](const Particle& particle) { + return particle.charge * phi(particle.pos.z()); + }; } -double ExternalAkesson::energy(const Change& change) { - if (not fixed_potential) { // phi(z) unconverged, keep sampling +double ExternalAkesson::energy(const Change& change) +{ + if (not fixed_potential) { // phi(z) unconverged, keep sampling if (state == MonteCarloState::ACCEPTED) { // only sample on accepted configs num_density_updates++; if (num_density_updates % nstep == 0) { @@ -248,7 +275,8 @@ double ExternalAkesson::energy(const Change& change) { return ExternalPotential::energy(change); } -ExternalAkesson::~ExternalAkesson() { +ExternalAkesson::~ExternalAkesson() +{ // save only if still updating and if energy type is `ACCEPTED_MONTE_CARLO_STATE`, // that is, accepted configurations (not trial) if (not fixed_potential and state == MonteCarloState::ACCEPTED) { @@ -256,28 +284,39 @@ ExternalAkesson::~ExternalAkesson() { } } -void ExternalAkesson::to_json(json &j) const { - j = {{"lB", bjerrum_length}, {"dz", dz}, {"nphi", phi_update_interval}, {"epsr", dielectric_constant}, - {"file", filename}, {"nstep", nstep}, {"Nupdates", num_rho_updates}, {"fixed", fixed_potential}}; +void ExternalAkesson::to_json(json& j) const +{ + j = {{"lB", bjerrum_length}, + {"dz", dz}, + {"nphi", phi_update_interval}, + {"epsr", dielectric_constant}, + {"file", filename}, + {"nstep", nstep}, + {"Nupdates", num_rho_updates}, + {"fixed", fixed_potential}}; ExternalPotential::to_json(j); roundJSON(j, 5); } -void ExternalAkesson::saveChargeDensity() { +void ExternalAkesson::saveChargeDensity() +{ if (auto stream = std::ofstream(filename); stream) { stream.precision(16); stream << rho; - } else { + } + else { throw std::runtime_error("cannot save file '"s + filename + "'"); } } -void ExternalAkesson::loadChargeDensity() { +void ExternalAkesson::loadChargeDensity() +{ if (auto stream = std::ifstream(filename); stream) { rho << stream; updatePotential(); faunus_logger->info("{}: density file '{}' loaded", name, filename); - } else { + } + else { faunus_logger->warn("{}: density file '{}' not loaded", name, filename); } } @@ -289,14 +328,17 @@ void ExternalAkesson::loadChargeDensity() { * @param z z-position where to evaluate the electric potential * @param a Half box length in x or y direction (x == y assumed) */ -double ExternalAkesson::evalPotential(const double z, const double a) { +double ExternalAkesson::evalPotential(const double z, const double a) +{ const auto a2 = a * a; const auto z2 = z * z; return -2 * pc::pi * z - 8 * a * std::log((std::sqrt(2 * a2 + z2) + a) / std::sqrt(a2 + z2)) + - 2 * z * (0.5 * pc::pi + std::asin((a2 * a2 - z2 * z2 - 2 * a2 * z2) / std::pow(a2 + z2, 2))); + 2 * z * + (0.5 * pc::pi + std::asin((a2 * a2 - z2 * z2 - 2 * a2 * z2) / std::pow(a2 + z2, 2))); } -void ExternalAkesson::sync(EnergyTerm* source, const Change&) { +void ExternalAkesson::sync(EnergyTerm* source, const Change&) +{ if (fixed_potential) { return; } @@ -309,12 +351,14 @@ void ExternalAkesson::sync(EnergyTerm* source, const Change&) { phi = other->phi; } } - } else { + } + else { throw std::runtime_error("akesson sync error"); } } -void ExternalAkesson::updateChargeDensity() { +void ExternalAkesson::updateChargeDensity() +{ num_rho_updates++; const Point box_length = space.geometry.getLength(); if (box_length.x() != box_length.y() || 0.5 * box_length.z() != half_box_length_z) { @@ -330,14 +374,16 @@ void ExternalAkesson::updateChargeDensity() { } } -void ExternalAkesson::updatePotential() { +void ExternalAkesson::updatePotential() +{ auto L = space.geometry.getLength(); double a = 0.5 * L.x(); for (double z = -half_box_length_z; z <= half_box_length_z; z += dz) { double s = 0; for (double zn = -half_box_length_z; zn <= half_box_length_z; zn += dz) { if (!rho(zn).empty()) { - s += rho(zn).avg() * evalPotential(std::fabs(z - zn), a); // Eq. 14 in Greberg's paper + s += rho(zn).avg() * + evalPotential(std::fabs(z - zn), a); // Eq. 14 in Greberg's paper } } phi(z) = bjerrum_length * s; @@ -346,7 +392,9 @@ void ExternalAkesson::updatePotential() { // ------------ createGouyChapman ------------- -std::function createGouyChapmanPotential(const json &j, const Geometry::Chameleon &geo) { +std::function createGouyChapmanPotential(const json& j, + const Geometry::Chameleon& geo) +{ if (geo.boundaryConditions().direction.z() != Geometry::Boundary::FIXED) { throw std::runtime_error("Gouy-Chapman requires non-periodicity in z-direction"); } @@ -358,26 +406,30 @@ std::function createGouyChapmanPotential(const json &j if (std::fabs(phi0) > 0) { rho = std::sqrt(2.0 * molarity / (pc::pi * bjerrum_length)) * std::sinh(0.5 * phi0); // Evans&Wennerstrom,Colloidal Domain p. 138-140 - } else { // phi0 was not provided + } + else { // phi0 was not provided double area_per_charge = j.value("rhoinv", 0.0); if (std::fabs(area_per_charge) > 0) { rho = 1.0 / area_per_charge; - } else { + } + else { rho = j.at("rho").get(); } - phi0 = 2.0 * std::asinh(rho * std::sqrt(0.5 * bjerrum_length * pc::pi / molarity)); // [Evans..] + phi0 = 2.0 * + std::asinh(rho * std::sqrt(0.5 * bjerrum_length * pc::pi / molarity)); // [Evans..] } double gamma0 = std::tanh(phi0 / 4.0); // assuming z=1 [Evans..] faunus_logger->trace("generated Gouy-Chapman potential with {} A^2/charge ", 1.0 / rho); if (j.value("linearise", false)) { - return [=, &geo](const Particle &p) { + return [=, &geo](const Particle& p) { double surface_z_pos = -0.5 * geo.getLength().z(); return p.charge * phi0 * std::exp(-kappa * std::fabs(surface_z_pos - p.pos.z())); }; - } else { - return [=, &geo](const Particle &p) { + } + else { + return [=, &geo](const Particle& p) { double surface_z_pos = -0.5 * geo.getLength().z(); double x = gamma0 * std::exp(-kappa * std::fabs(surface_z_pos - p.pos.z())); return 2.0 * p.charge * std::log((1.0 + x) / (1.0 - x)); @@ -385,7 +437,8 @@ std::function createGouyChapmanPotential(const json &j } } -TEST_CASE("[Faunus] Gouy-Chapman") { +TEST_CASE("[Faunus] Gouy-Chapman") +{ using doctest::Approx; Geometry::Slit slit(50, 50, 50); Geometry::Chameleon geometry(slit, Geometry::Variant::SLIT); @@ -393,7 +446,7 @@ TEST_CASE("[Faunus] Gouy-Chapman") { auto phi = Energy::createGouyChapmanPotential(j, geometry); Particle p; p.charge = 1.0; - p.pos = {0, 0, -25}; // potential at charged surface + p.pos = {0, 0, -25}; // potential at charged surface CHECK_EQ(phi(p), doctest::Approx(0.2087776151)); // = phi_0 p.pos = {0, 0, 0}; // potential at mid-plane @@ -414,14 +467,19 @@ TEST_CASE("[Faunus] Gouy-Chapman") { // ------------ CustomExternal ------------- -CustomExternal::CustomExternal(const json &j, Space &spc) : ExternalPotential(j, spc), json_input_backup(j) { +CustomExternal::CustomExternal(const json& j, Space& spc) + : ExternalPotential(j, spc) + , json_input_backup(j) +{ name = "customexternal"; - auto &constants = json_input_backup["constants"]; + auto& constants = json_input_backup["constants"]; if (std::string function = j.at("function"); function == "gouychapman") { externalPotentialFunc = createGouyChapmanPotential(constants, spc.geometry); - } else if (function == "some-new-potential") { // add new potentials here + } + else if (function == "some-new-potential") { // add new potentials here // func = createSomeNewPotential(...); - } else { // nothing found above; assume `function` is an expression + } + else { // nothing found above; assume `function` is an expression if (constants == nullptr) { constants = json::object(); } @@ -431,10 +489,11 @@ CustomExternal::CustomExternal(const json &j, Space &spc) : ExternalPotential(j, constants["Nav"] = pc::avogadro; constants["T"] = pc::temperature; expr = std::make_unique>(); - expr->set( - j, - {{"q", &particle_data.charge}, {"x", &particle_data.x}, {"y", &particle_data.y}, {"z", &particle_data.z}}); - externalPotentialFunc = [&](const Particle &a) { + expr->set(j, {{"q", &particle_data.charge}, + {"x", &particle_data.x}, + {"y", &particle_data.y}, + {"z", &particle_data.z}}); + externalPotentialFunc = [&](const Particle& a) { particle_data.x = a.pos.x(); particle_data.y = a.pos.y(); particle_data.z = a.pos.z(); @@ -443,7 +502,9 @@ CustomExternal::CustomExternal(const json &j, Space &spc) : ExternalPotential(j, }; } } -void CustomExternal::to_json(json &j) const { + +void CustomExternal::to_json(json& j) const +{ j = json_input_backup; ExternalPotential::to_json(j); } @@ -454,15 +515,17 @@ void CustomExternal::to_json(json &j) const { * Upon construction, make sure the ExternalPotential base class loop * over all groups and particles (com=false) */ -ParticleSelfEnergy::ParticleSelfEnergy(Space &spc, std::function selfEnergy) - : ExternalPotential({{"molecules", {"*"}}, {"com", false}}, spc) { +ParticleSelfEnergy::ParticleSelfEnergy(Space& spc, + std::function selfEnergy) + : ExternalPotential({{"molecules", {"*"}}, {"com", false}}, spc) +{ assert(selfEnergy && "selfEnergy is not callable"); externalPotentialFunc = std::move(selfEnergy); #ifndef NDEBUG // test if self energy can be called assert(not Faunus::atoms.empty()); Particle myparticle; - myparticle.id=0; + myparticle.id = 0; if (this->externalPotentialFunc) { double u = this->externalPotentialFunc(myparticle); assert(std::isfinite(u)); diff --git a/src/externalpotential.h b/src/externalpotential.h index 319669c92..232060ee1 100644 --- a/src/externalpotential.h +++ b/src/externalpotential.h @@ -17,19 +17,27 @@ namespace Energy { /** * All energies inherit from this class */ -class EnergyTerm { +class EnergyTerm +{ public: - enum class MonteCarloState { ACCEPTED, TRIAL, NONE }; + enum class MonteCarloState + { + ACCEPTED, + TRIAL, + NONE + }; MonteCarloState state = MonteCarloState::NONE; std::string name; //!< Meaningful name std::string citation_information; //!< Possible reference; may be left empty TimeRelativeOfTotal timer; //!< Timer for measuring speed virtual double energy(const Change& change) = 0; //!< energy due to change virtual void to_json(json& j) const; //!< json output - virtual void sync(EnergyTerm* other_energy, const Change& change); //!< Sync (copy from) another energy instance - virtual void init(); //!< reset and initialize - virtual void updateState(const Change& change); //!< Update internal state to reflect change in e.g. Space - virtual void force(PointVector& forces); //!< update forces on all particles + virtual void sync(EnergyTerm* other_energy, + const Change& change); //!< Sync (copy from) another energy instance + virtual void init(); //!< reset and initialize + virtual void + updateState(const Change& change); //!< Update internal state to reflect change in e.g. Space + virtual void force(PointVector& forces); //!< update forces on all particles inline virtual ~EnergyTerm() = default; }; @@ -44,7 +52,8 @@ void to_json(json& j, const EnergyTerm& base); //!< Converts any energy class to * * @todo The `dN` check is inefficient as it calculates the external potential on *all* particles. */ -class ExternalPotential : public EnergyTerm { +class ExternalPotential : public EnergyTerm +{ private: bool act_on_mass_center = false; //!< apply only on center-of-mass std::set molecule_ids; //!< ids of molecules to act on @@ -70,23 +79,28 @@ class ExternalPotential : public EnergyTerm { * * @warning: under construction */ -std::function createGouyChapmanPotential(const json &j, const Geometry::Chameleon &); +std::function createGouyChapmanPotential(const json& j, + const Geometry::Chameleon&); /** * @brief Custom external potential on molecules */ -class CustomExternal : public ExternalPotential { +class CustomExternal : public ExternalPotential +{ private: std::unique_ptr> expr; - struct ParticleData { // storage for particle properties + + struct ParticleData + { // storage for particle properties double charge = 0, x = 0, y = 0, z = 0; }; + ParticleData particle_data; json json_input_backup; // initial json input public: - CustomExternal(const json &, Space &); - void to_json(json &) const override; + CustomExternal(const json&, Space&); + void to_json(json&) const override; }; /** @@ -102,28 +116,29 @@ class CustomExternal : public ExternalPotential { * @date Asljunga, December 2010. * @todo Split this into two classes; one with a static rho, and a derived that updates rho */ -class ExternalAkesson : public ExternalPotential { +class ExternalAkesson : public ExternalPotential +{ private: - std::string filename; //!< input/output filename to charge density profile - bool fixed_potential = false; //!< If true, the potential is never updated - unsigned int nstep = 0; //!< Internal between samples - unsigned int phi_update_interval = 0; //!< Distance between phi updating - unsigned int num_rho_updates = 0; //!< Number of time rho has been updated - unsigned int num_density_updates = 0; //!< Number of charge density updates - double dielectric_constant; //!< Relative dielectric constant - double dz; //!< z spacing between slits (A) - double bjerrum_length; //!< Bjerrum length (A) - double half_box_length_z; //!< Half box length in z direction - Equidistant2DTable charge_profile; //!< instantaneous charge as func. of z + std::string filename; //!< input/output filename to charge density profile + bool fixed_potential = false; //!< If true, the potential is never updated + unsigned int nstep = 0; //!< Internal between samples + unsigned int phi_update_interval = 0; //!< Distance between phi updating + unsigned int num_rho_updates = 0; //!< Number of time rho has been updated + unsigned int num_density_updates = 0; //!< Number of charge density updates + double dielectric_constant; //!< Relative dielectric constant + double dz; //!< z spacing between slits (A) + double bjerrum_length; //!< Bjerrum length (A) + double half_box_length_z; //!< Half box length in z direction + Equidistant2DTable charge_profile; //!< instantaneous charge as func. of z Equidistant2DTable> rho; //!< Charge density at z (unit A^-2) Equidistant2DTable phi; //!< External potential at z (unit: beta*e) - static double evalPotential(double z, double a) ; //!< Calculate external potential - void updateChargeDensity(); //!< update average charge density - void updatePotential(); //!< update average external potential - void saveChargeDensity(); //!< save charge density profile to disk - void loadChargeDensity(); //!< load charge density profile from disk - void to_json(json &) const override; + static double evalPotential(double z, double a); //!< Calculate external potential + void updateChargeDensity(); //!< update average charge density + void updatePotential(); //!< update average external potential + void saveChargeDensity(); //!< save charge density profile to disk + void loadChargeDensity(); //!< load charge density profile from disk + void to_json(json&) const override; void sync(EnergyTerm*, const Change&) override; public: @@ -136,9 +151,17 @@ class ExternalAkesson : public ExternalPotential { * @brief Confines molecules inside geometric shapes * @todo enum class; get rid of map; is non-const space needed? */ -class Confine : public ExternalPotential { +class Confine : public ExternalPotential +{ public: - enum Variant { sphere, cylinder, cuboid, none }; + enum Variant + { + sphere, + cylinder, + cuboid, + none + }; + Variant type = none; private: @@ -149,7 +172,8 @@ class Confine : public ExternalPotential { double radius = 0.0; double spring_constant = 0.0; bool scale = false; - std::map m = {{"sphere", sphere}, {"cylinder", cylinder}, {"cuboid", cuboid}}; + std::map m = { + {"sphere", sphere}, {"cylinder", cylinder}, {"cuboid", cuboid}}; public: Confine(const json& j, Space& spc); @@ -166,9 +190,10 @@ class Confine : public ExternalPotential { * The constructor requires a functor that operates on each particle * and returns the resulting energy (kT) */ -class ParticleSelfEnergy : public ExternalPotential { +class ParticleSelfEnergy : public ExternalPotential +{ public: - ParticleSelfEnergy(Space &, std::function); + ParticleSelfEnergy(Space&, std::function); }; } // namespace Energy diff --git a/src/faunus.cpp b/src/faunus.cpp index 779b9093d..4e3b7baf3 100644 --- a/src/faunus.cpp +++ b/src/faunus.cpp @@ -83,8 +83,10 @@ static const char USAGE[] = 3. Input prefixing can be suppressed with --nopfx )"; -int main(int argc, const char** argv) { - if (argc > 1 && std::string(argv[1]) == "test") { // run unittests if the first argument equals "test" +int main(int argc, const char** argv) +{ + if (argc > 1 && + std::string(argv[1]) == "test") { // run unittests if the first argument equals "test" return runUnittests(argc, argv); } bool fun = false; //!< enable utterly unnecessarily stuff? @@ -106,7 +108,8 @@ int main(int argc, const char** argv) { loadState(args, simulation); prefaceActions(input["preface"], simulation.getSpace(), simulation.getHamiltonian()); checkElectroNeutrality(simulation); - analysis::CombinedAnalysis analysis(input.at("analysis"), simulation.getSpace(), simulation.getHamiltonian()); + analysis::CombinedAnalysis analysis(input.at("analysis"), simulation.getSpace(), + simulation.getHamiltonian()); bool show_progress = !quiet && !args["--nobar"].asBool(); #ifdef ENABLE_MPI @@ -119,8 +122,8 @@ int main(int argc, const char** argv) { saveOutput(starting_time, args, simulation, analysis); } return EXIT_SUCCESS; - - } catch (std::exception& e) { + } + catch (std::exception& e) { showErrorMessage(e); if (!quiet && fun) { playRetroMusic(); @@ -128,14 +131,17 @@ int main(int argc, const char** argv) { return EXIT_FAILURE; } } -void setRandomNumberGenerator(const json& input) { + +void setRandomNumberGenerator(const json& input) +{ if (auto it = input.find("random"); it != input.end()) { from_json(*it, move::Move::slump); // static --> shared for all moves from_json(*it, Faunus::random); } } -void showErrorMessage(std::exception& exception) { +void showErrorMessage(std::exception& exception) +{ faunus_logger->error(exception.what()); std::cerr << exception.what() << std::endl; @@ -145,15 +151,16 @@ void showErrorMessage(std::exception& exception) { faunus_logger->debug("json snippet:\n{}", config_error->attachedJson().dump(4)); } if (!usageTip.output_buffer.empty()) { - // Use the srderr stream directly for more elaborated output of usage tip, optionally containing an ASCII - // art. Level info seems appropriate. + // Use the srderr stream directly for more elaborated output of usage tip, optionally + // containing an ASCII art. Level info seems appropriate. if (faunus_logger->should_log(spdlog::level::info)) { std::cerr << usageTip.output_buffer << std::endl; } } } -void playRetroMusic() { +void playRetroMusic() +{ #ifdef ENABLE_MPI if (!Faunus::MPI::mpi.isMaster()) { return; @@ -163,19 +170,21 @@ void playRetroMusic() { using std::chrono_literals::operator""s; using std::chrono_literals::operator""ns; if (auto player = createLoadedSIDplayer()) { // create C64 SID emulation and load a random tune - faunus_logger->info("error message music '{}' by {}, {} (6502/SID emulation)", player->title(), - player->author(), player->info()); + faunus_logger->info("error message music '{}' by {}, {} (6502/SID emulation)", + player->title(), player->author(), player->info()); faunus_logger->info("\033[1mpress ctrl-c to quit\033[0m"); - player->start(); // start music - std::this_thread::sleep_for(10ns); // short delay - std::this_thread::sleep_until(std::chrono::system_clock::now() + 240s); // play for 4 minutes, then exit + player->start(); // start music + std::this_thread::sleep_for(10ns); // short delay + std::this_thread::sleep_until(std::chrono::system_clock::now() + + 240s); // play for 4 minutes, then exit player->stop(); std::cout << std::endl; } #endif } -std::string versionString() { +std::string versionString() +{ std::string version = "Faunus"; #ifdef GIT_LATEST_TAG version += " "s + QUOTE(GIT_LATEST_TAG); @@ -195,7 +204,8 @@ std::string versionString() { return version; } -int runUnittests(int argc, const char* const* argv) { +int runUnittests(int argc, const char* const* argv) +{ #ifdef DOCTEST_CONFIG_DISABLE std::cerr << "this version of faunus does not include unittests" << std::endl; return EXIT_FAILURE; @@ -208,7 +218,8 @@ int runUnittests(int argc, const char* const* argv) { } void mainLoop(bool show_progress, const json& json_in, MetropolisMonteCarlo& simulation, - analysis::CombinedAnalysis& analysis) { + analysis::CombinedAnalysis& analysis) +{ const auto& loop = json_in.at("mcloop"); const auto macro = loop.at("macro").get(); const auto micro = loop.at("micro").get(); @@ -218,24 +229,27 @@ void mainLoop(bool show_progress, const json& json_in, MetropolisMonteCarlo& sim simulation.sweep(); analysis.sample(); showProgress(progress_tracker); - } // end of micro steps + } // end of micro steps analysis.to_disk(); // save analysis to disk - } // end of macro steps + } // end of macro steps if (progress_tracker) { progress_tracker->done(); } const double drift_tolerance = 1e-9; - const auto level = simulation.relativeEnergyDrift() < drift_tolerance ? spdlog::level::info : spdlog::level::warn; + const auto level = simulation.relativeEnergyDrift() < drift_tolerance ? spdlog::level::info + : spdlog::level::warn; faunus_logger->log(level, "relative energy drift = {:.3E}", simulation.relativeEnergyDrift()); } -void showProgress(std::shared_ptr& progress_tracker) { +void showProgress(std::shared_ptr& progress_tracker) +{ if (progress_tracker && (++(*progress_tracker) % 10 == 0)) { progress_tracker->display(); } } -void checkElectroNeutrality(MetropolisMonteCarlo& simulation) { +void checkElectroNeutrality(MetropolisMonteCarlo& simulation) +{ auto particles = simulation.getSpace().activeParticles(); const auto system_charge = Faunus::monopoleMoment(particles.begin(), particles.end()); const double max_allowed_charge = 1e-6; @@ -244,14 +258,16 @@ void checkElectroNeutrality(MetropolisMonteCarlo& simulation) { } } -void setInformationLevelAndLoggers(bool quiet, docopt::Options& args) { +void setInformationLevelAndLoggers(bool quiet, docopt::Options& args) +{ // @todo refactor to use cmd line options for different sinks, etc. faunus_logger = spdlog::stderr_color_mt("faunus"); faunus_logger->set_pattern("[%n %P] %^%L: %v%$"); mcloop_logger = spdlog::stderr_color_mt("mcloop"); mcloop_logger->set_pattern("[%n %P] [%E.%f] %L: %v"); - const long log_level = spdlog::level::off - (quiet ? 0 : args["--verbosity"].asLong()); // reverse: 0 β†’ 6 to 6 β†’ 0 + const long log_level = + spdlog::level::off - (quiet ? 0 : args["--verbosity"].asLong()); // reverse: 0 β†’ 6 to 6 β†’ 0 spdlog::set_level(static_cast(log_level)); if (quiet) { std::cout.setstate(std::ios_base::failbit); // muffle stdout @@ -276,7 +292,8 @@ void setInformationLevelAndLoggers(bool quiet, docopt::Options& args) { * - BINARY_DIR/sids/ * - INSTALL_PREFIX/share/faunus/sids */ -std::pair findSIDsong() { +std::pair findSIDsong() +{ std::string filename; int subsong = -1; try { @@ -291,7 +308,8 @@ std::pair findSIDsong() { pfx = dir + "/"; break; } - } catch (...) { + } + catch (...) { // ignore any error } } @@ -304,23 +322,29 @@ std::pair findSIDsong() { std::discrete_distribution dist(weight.begin(), weight.end()); Faunus::random.seed(); // give global random a hardware seed const auto random_song = - json_music.begin() + dist(Faunus::random.engine); // pick random tune weighted by subsongs - const auto subsongs = random_song->at("subsongs").get>(); // all subsongs - subsong = *(Faunus::random.sample(subsongs.begin(), subsongs.end())) - 1; // random subsong + json_music.begin() + + dist(Faunus::random.engine); // pick random tune weighted by subsongs + const auto subsongs = + random_song->at("subsongs").get>(); // all subsongs + subsong = + *(Faunus::random.sample(subsongs.begin(), subsongs.end())) - 1; // random subsong filename = pfx + random_song->at("file").get(); } - } catch (const std::exception&) { + } + catch (const std::exception&) { // silently ignore if something fails; it's just for fun! } return {filename, subsong}; } -std::shared_ptr createLoadedSIDplayer() { +std::shared_ptr createLoadedSIDplayer() +{ std::shared_ptr player; if (static_cast(isatty(fileno(stdout)))) { // only play music if on console if (!static_cast(std::getenv("SSH_CLIENT"))) { // and not through a ssh connection player = std::make_shared(); // let's emulate a Commodore 64... - const auto [filename, subsong] = findSIDsong(); // pick a song from our pre-defined library + const auto [filename, subsong] = + findSIDsong(); // pick a song from our pre-defined library player->load(filename, subsong); } } @@ -328,7 +352,9 @@ std::shared_ptr createLoadedSIDplayer() { } #endif -std::shared_ptr createProgressTracker(bool show_progress, unsigned int steps) { +std::shared_ptr createProgressTracker(bool show_progress, + unsigned int steps) +{ if (!show_progress) { return nullptr; } @@ -342,19 +368,22 @@ std::shared_ptr createProgressTracker(bool s std::chrono::duration_cast(std::chrono::minutes(10)), 0.005); } -json getUserInput(docopt::Options& args) { +json getUserInput(docopt::Options& args) +{ try { json j; if (auto filename = args["--input"].asString(); filename == "/dev/stdin") { std::cin >> j; - } else { + } + else { if (!args["--nopfx"].asBool()) { filename = Faunus::MPI::prefix + filename; } j = loadJSON(filename); } return j; - } catch (json::parse_error& e) { + } + catch (json::parse_error& e) { faunus_logger->error(e.what()); throw ConfigurationError("invalid input -> {}", e.what()); } @@ -366,14 +395,16 @@ json getUserInput(docopt::Options& args) { * The given file name should be one of the known type (xyz, pqr, gro, ...) and only * the _coordinates_ will be copied. All other properties are unused. */ -void loadCoordinates(std::string_view filename, MetropolisMonteCarlo& simulation) { +void loadCoordinates(std::string_view filename, MetropolisMonteCarlo& simulation) +{ auto& space = simulation.getSpace(); auto& trial_space = simulation.getTrialSpace(); auto source = Faunus::loadStructure(filename, false); if (source.size() > space.particles.size()) { throw ConfigurationError("too many particles in {}", filename); } - faunus_logger->info("overwriting {} of {} positions using {}", source.size(), space.particles.size(), filename); + faunus_logger->info("overwriting {} of {} positions using {}", source.size(), + space.particles.size(), filename); auto copy_f = [](const Particle& source, Particle& destination) { destination.pos = source.pos; if (source.hasExtension()) { @@ -390,10 +421,12 @@ void loadCoordinates(std::string_view filename, MetropolisMonteCarlo& simulation } }; space.updateParticles(source.begin(), source.end(), space.particles.begin(), copy_f); - trial_space.updateParticles(source.begin(), source.end(), trial_space.particles.begin(), copy_f); + trial_space.updateParticles(source.begin(), source.end(), trial_space.particles.begin(), + copy_f); } -void loadState(docopt::Options& args, MetropolisMonteCarlo& simulation) { +void loadState(docopt::Options& args, MetropolisMonteCarlo& simulation) +{ if (args["--state"]) { const auto statefile = Faunus::MPI::prefix + args["--state"].asString(); const auto suffix = statefile.substr(statefile.find_last_of('.') + 1); @@ -408,14 +441,16 @@ void loadState(docopt::Options& args, MetropolisMonteCarlo& simulation) { if (binary) { const auto size = stream.tellg(); // get file size std::vector buffer(size / sizeof(std::uint8_t)); - stream.seekg(0, std::ifstream::beg); // go back to start... + stream.seekg(0, std::ifstream::beg); // go back to start... stream.read((char*)buffer.data(), size); // ...and read into buffer j = json::from_ubjson(buffer); - } else { + } + else { stream >> j; } simulation.restore(j); - } else { + } + else { throw std::runtime_error("state file error -> "s + statefile); } } @@ -425,7 +460,8 @@ void loadState(docopt::Options& args, MetropolisMonteCarlo& simulation) { } } -void prefaceActions(const json& input, Space& spc, Energy::Hamiltonian& hamiltonian) { +void prefaceActions(const json& input, Space& spc, Energy::Hamiltonian& hamiltonian) +{ if (!input.is_array()) { return; } @@ -436,7 +472,8 @@ void prefaceActions(const json& input, Space& spc, Energy::Hamiltonian& hamilton template void saveOutput(TimePoint& starting_time, docopt::Options& args, MetropolisMonteCarlo& simulation, - const analysis::CombinedAnalysis& analysis) { + const analysis::CombinedAnalysis& analysis) +{ if (std::ofstream stream(Faunus::MPI::prefix + args["--output"].asString()); stream) { json j; @@ -453,8 +490,10 @@ void saveOutput(TimePoint& starting_time, docopt::Options& args, MetropolisMonte { // report on total simulation time const auto ending_time = std::chrono::steady_clock::now(); const auto elapsed_seconds = - std::chrono::duration_cast(ending_time - starting_time).count(); - j["simulation time"] = {{"in minutes", elapsed_seconds / 60.0}, {"in seconds", elapsed_seconds}}; + std::chrono::duration_cast(ending_time - starting_time) + .count(); + j["simulation time"] = {{"in minutes", elapsed_seconds / 60.0}, + {"in seconds", elapsed_seconds}}; } stream << std::setw(2) << j << std::endl; diff --git a/src/forcemove.cpp b/src/forcemove.cpp index 9afe7172c..0e6a75ab7 100644 --- a/src/forcemove.cpp +++ b/src/forcemove.cpp @@ -9,16 +9,17 @@ namespace Faunus::move { TEST_SUITE_BEGIN("ForceMove"); /** - * @brief Compute a single dimension contribution to the mean square thermal speed of a particle, i.e., - * compute γ€ˆvΒ²_x〉. + * @brief Compute a single dimension contribution to the mean square thermal speed of a particle, + * i.e., compute γ€ˆvΒ²_x〉. * - * This is equivalent to a one third of a total mean square thermal speed of a particle in three dimensions, - * i.e., γ€ˆv²〉 = 3 Γ— γ€ˆvΒ²_x〉. + * This is equivalent to a one third of a total mean square thermal speed of a particle in three + * dimensions, i.e., γ€ˆv²〉 = 3 Γ— γ€ˆvΒ²_x〉. * * @param mass particle's mass in g/mol * @return mean square thermal speed in (Γ…/ps)Β² */ -static inline double meanSquareSpeedComponent(T mass) { +static inline double meanSquareSpeedComponent(T mass) +{ return (pc::kT() / mass * 1.0_kg) * ((1.0_m * 1.0_m) / (1.0_s * 1.0_s)); } @@ -26,33 +27,52 @@ static inline double meanSquareSpeedComponent(T mass) { IntegratorBase::IntegratorBase(Space& spc, Energy::EnergyTerm& energy) : spc(spc) - , energy(energy) {} + , energy(energy) +{ +} -void from_json(const json &j, IntegratorBase &i) { i.from_json(j); } -void to_json(json &j, const IntegratorBase &i) { i.to_json(j); } +void from_json(const json& j, IntegratorBase& i) +{ + i.from_json(j); +} + +void to_json(json& j, const IntegratorBase& i) +{ + i.to_json(j); +} // =============== LangevinVelocityVerlet =============== LangevinVelocityVerlet::LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy) - : IntegratorBase(spc, energy) {} + : IntegratorBase(spc, energy) +{ +} -LangevinVelocityVerlet::LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, double time_step, - double friction_coefficient) +LangevinVelocityVerlet::LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, + double time_step, double friction_coefficient) : IntegratorBase(spc, energy) , time_step(time_step) - , friction_coefficient(friction_coefficient) {} + , friction_coefficient(friction_coefficient) +{ +} -LangevinVelocityVerlet::LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, const json& j) - : LangevinVelocityVerlet::LangevinVelocityVerlet(spc, energy) { +LangevinVelocityVerlet::LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, + const json& j) + : LangevinVelocityVerlet::LangevinVelocityVerlet(spc, energy) +{ from_json(j); } -inline Point LangevinVelocityVerlet::positionIncrement(const Point &velocity) const { return 0.5 * time_step * velocity; } +inline Point LangevinVelocityVerlet::positionIncrement(const Point& velocity) const +{ + return 0.5 * time_step * velocity; +} -inline Point LangevinVelocityVerlet::velocityIncrement(const Point &force, const double mass) const { - // As forces are in kT per Γ₯ngstrΓΆm units (a hybrid between reduced energy units and absolute units), we use - // the mean square speed to compute acceleration from the force and as a conversion factor. - // Dimension analysis: (ps * 1 / Γ…) * (Γ…^2 / ps^2) = Γ… / ps. +inline Point LangevinVelocityVerlet::velocityIncrement(const Point& force, const double mass) const +{ + // As forces are in kT per Γ₯ngstrΓΆm units (a hybrid between reduced energy units and absolute + // units), we use the mean square speed to compute acceleration from the force and as a + // conversion factor. Dimension analysis: (ps * 1 / Γ…) * (Γ…^2 / ps^2) = Γ… / ps. return 0.5 * time_step * force * meanSquareSpeedComponent(mass); } @@ -61,10 +81,14 @@ inline Point LangevinVelocityVerlet::velocityIncrement(const Point &force, const * @param mass Particle Mass in g/mol * @return Updated velocity */ -inline Point LangevinVelocityVerlet::velocityFluctuationDissipation(const Point &velocity, const double mass) { - const double prefactor = std::exp(-friction_coefficient * time_step); // Ornstein-Uhlenbeck process prefactor +inline Point LangevinVelocityVerlet::velocityFluctuationDissipation(const Point& velocity, + const double mass) +{ + const double prefactor = + std::exp(-friction_coefficient * time_step); // Ornstein-Uhlenbeck process prefactor return (prefactor * velocity) + - random_vector(random.engine) * std::sqrt((1.0 - prefactor * prefactor) * meanSquareSpeedComponent(mass)); + random_vector(random.engine) * + std::sqrt((1.0 - prefactor * prefactor) * meanSquareSpeedComponent(mass)); } /** @@ -81,13 +105,14 @@ inline Point LangevinVelocityVerlet::velocityFluctuationDissipation(const Point * }}; * @todo Splitting scheme still hard-coded to 'BAOAB' */ -void LangevinVelocityVerlet::step(PointVector &velocities, PointVector &forces) { +void LangevinVelocityVerlet::step(PointVector& velocities, PointVector& forces) +{ assert(spc.numParticles(Space::Selection::ACTIVE) == forces.size()); assert(forces.size() == velocities.size()); auto zipped = ranges::views::zip(spc.activeParticles(), forces, velocities); - for (auto &&[particle, force, velocity] : zipped) { + for (auto&& [particle, force, velocity] : zipped) { const auto mass = particle.traits().mw; velocity += velocityIncrement(force, mass); // B step particle.pos += positionIncrement(velocity); // A step @@ -98,24 +123,29 @@ void LangevinVelocityVerlet::step(PointVector &velocities, PointVector &forces) std::fill(forces.begin(), forces.end(), Point::Zero()); // forces must be updated ... energy.force(forces); // ... before each B step - for (auto &&[particle, force, velocity] : zipped) { + for (auto&& [particle, force, velocity] : zipped) { velocity += velocityIncrement(force, particle.traits().mw); // B step } } -void LangevinVelocityVerlet::from_json(const json &j) { +void LangevinVelocityVerlet::from_json(const json& j) +{ time_step = j.at("time_step").get() * 1.0_ps; friction_coefficient = j.at("friction").get() / 1.0_ps; } -void LangevinVelocityVerlet::to_json(json &j) const { +void LangevinVelocityVerlet::to_json(json& j) const +{ j = {{"time_step", time_step / 1.0_ps}, {"friction", friction_coefficient * 1.0_ps}}; } -TEST_CASE("[Faunus] Integrator") { - class DummyEnergy : public Energy::EnergyTerm { +TEST_CASE("[Faunus] Integrator") +{ + class DummyEnergy : public Energy::EnergyTerm + { double energy([[maybe_unused]] const Change& change) override { return 0.0; } }; + Space spc; DummyEnergy energy; @@ -127,11 +157,12 @@ TEST_CASE("[Faunus] Integrator") { // =============== ForceMoveBase =============== -ForceMove::ForceMove(Space& spc, const std::string& name, const std::string& cite, std::shared_ptr integrator, - unsigned int nsteps) +ForceMove::ForceMove(Space& spc, const std::string& name, const std::string& cite, + std::shared_ptr integrator, unsigned int nsteps) : Move(spc, name, cite) , integrator(std::move(integrator)) - , number_of_steps(nsteps) { + , number_of_steps(nsteps) +{ forces.reserve(spc.particles.size()); velocities.reserve(spc.particles.size()); resizeForcesAndVelocities(); @@ -143,14 +174,16 @@ ForceMove::ForceMove(Space& spc, const std::string& name, const std::string& cit * * Upon resizing, new elements in `forces` and `velocities` are zeroed. */ -size_t ForceMove::resizeForcesAndVelocities() { +size_t ForceMove::resizeForcesAndVelocities() +{ const auto num_active_particles = spc.numParticles(Space::Selection::ACTIVE); forces.resize(num_active_particles, Point::Zero()); velocities.resize(num_active_particles, Point::Zero()); return num_active_particles; } -void ForceMove::_move(Change& change) { +void ForceMove::_move(Change& change) +{ change.clear(); change.everything = true; resizeForcesAndVelocities(); @@ -162,69 +195,103 @@ void ForceMove::_move(Change& change) { } } -void ForceMove::_to_json(json& j) const { +void ForceMove::_to_json(json& j) const +{ j = {{"nsteps", number_of_steps}}; j["integrator"] = *integrator; } -void ForceMove::_from_json(const json& j) { +void ForceMove::_from_json(const json& j) +{ number_of_steps = j.at("nsteps").get(); integrator->from_json(j["integrator"]); generateVelocities(); } -double ForceMove::bias(Change&, double, double) { +double ForceMove::bias(Change&, double, double) +{ return pc::neg_infty; // always accept the move } /** - * @note: omitting explicit return type in the std::transform lambda below can in some compiler settings lead to - * a dangling Point& reference being returned. Observed with Clang10/RelWithDebInfo, but not in Debug, or - * with GCC. + * @note: omitting explicit return type in the std::transform lambda below can in some compiler + * settings lead to a dangling Point& reference being returned. Observed with + * Clang10/RelWithDebInfo, but not in Debug, or with GCC. */ -void ForceMove::generateVelocities() { +void ForceMove::generateVelocities() +{ NormalRandomVector random_vector; // generator of random 3d vector from a normal distribution const auto particles = spc.activeParticles(); resizeForcesAndVelocities(); - std::transform(particles.begin(), particles.end(), velocities.begin(), [&](auto& particle) -> Point { - return random_vector(random.engine) * std::sqrt(meanSquareSpeedComponent(particle.traits().mw)); - }); + std::transform(particles.begin(), particles.end(), velocities.begin(), + [&](auto& particle) -> Point { + return random_vector(random.engine) * + std::sqrt(meanSquareSpeedComponent(particle.traits().mw)); + }); std::fill(forces.begin(), forces.end(), Point::Zero()); } -const PointVector& ForceMove::getForces() const { return forces; } -const PointVector& ForceMove::getVelocities() const { return velocities; } +const PointVector& ForceMove::getForces() const +{ + return forces; +} + +const PointVector& ForceMove::getVelocities() const +{ + return velocities; +} // =============== LangevinMove =============== LangevinDynamics::LangevinDynamics(Space& spc, const std::string& name, const std::string& cite, std::shared_ptr integrator, unsigned int nsteps) - : ForceMove(spc, name, cite, integrator, nsteps) {} + : ForceMove(spc, name, cite, integrator, nsteps) +{ +} -LangevinDynamics::LangevinDynamics(Space &spc, std::shared_ptr integrator, unsigned int nsteps) - : LangevinDynamics(spc, "langevin_dynamics", "", std::move(integrator), nsteps) {} +LangevinDynamics::LangevinDynamics(Space& spc, std::shared_ptr integrator, + unsigned int nsteps) + : LangevinDynamics(spc, "langevin_dynamics", "", std::move(integrator), nsteps) +{ +} LangevinDynamics::LangevinDynamics(Space& spc, Energy::EnergyTerm& energy) - : LangevinDynamics::LangevinDynamics(spc, std::make_shared(spc, energy), 0) {} + : LangevinDynamics::LangevinDynamics(spc, std::make_shared(spc, energy), + 0) +{ +} LangevinDynamics::LangevinDynamics(Space& spc, Energy::EnergyTerm& energy, const json& j) - : LangevinDynamics::LangevinDynamics(spc, energy) { + : LangevinDynamics::LangevinDynamics(spc, energy) +{ from_json(j); } -void LangevinDynamics::_to_json(json& j) const { ForceMove::_to_json(j); } -void LangevinDynamics::_from_json(const json& j) { ForceMove::_from_json(j); } +void LangevinDynamics::_to_json(json& j) const +{ + ForceMove::_to_json(j); +} + +void LangevinDynamics::_from_json(const json& j) +{ + ForceMove::_from_json(j); +} -TEST_CASE("[Faunus] LangevinDynamics") { - class DummyEnergy : public Energy::EnergyTerm { +TEST_CASE("[Faunus] LangevinDynamics") +{ + class DummyEnergy : public Energy::EnergyTerm + { double energy([[maybe_unused]] const Change& change) override { return 0.0; } }; + Space spc; DummyEnergy energy; - SUBCASE("Velocity and force initialization") { - spc.particles.resize(10); // 10 particles in total - spc.groups.emplace_back(0, spc.particles.begin(), spc.particles.end() - 1); // 9 active particles + SUBCASE("Velocity and force initialization") + { + spc.particles.resize(10); // 10 particles in total + spc.groups.emplace_back(0, spc.particles.begin(), + spc.particles.end() - 1); // 9 active particles LangevinDynamics ld(spc, energy); CHECK((ld.getForces().capacity() >= 10)); CHECK((ld.getVelocities().capacity() >= 10)); @@ -232,7 +299,8 @@ TEST_CASE("[Faunus] LangevinDynamics") { CHECK_EQ(ld.getVelocities().size(), 9); } - SUBCASE("JSON init") { + SUBCASE("JSON init") + { json j_in = R"({"nsteps": 100, "integrator": {"time_step": 0.001, "friction": 2.0}})"_json; LangevinDynamics ld(spc, energy); ld.from_json(j_in); diff --git a/src/forcemove.h b/src/forcemove.h index b23259f73..624267534 100644 --- a/src/forcemove.h +++ b/src/forcemove.h @@ -6,125 +6,145 @@ namespace Faunus::move { /** @section Force moves * - * Force moves are pseudo-Monte-Carlo moves that run a short trajectory of Langevin or similar dynamics. - * The move is always accepted regardless of the total potential energy of the final system's configuration. + * Force moves are pseudo-Monte-Carlo moves that run a short trajectory of Langevin or similar + * dynamics. The move is always accepted regardless of the total potential energy of the final + * system's configuration. * - * ForceMoveBase provides an interface for all dynamics moves. It orchestrates integrators, thermostats, etc. - * to work in accord. - * IntegratorBase provides an interface for all integrators that are responsible for development - * of the simulation in time steps. The integrators update positions and velocities of particles as needed. + * ForceMoveBase provides an interface for all dynamics moves. It orchestrates integrators, + * thermostats, etc. to work in accord. IntegratorBase provides an interface for all integrators + * that are responsible for development of the simulation in time steps. The integrators update + * positions and velocities of particles as needed. */ /** * @brief Generate a random 3d vector from the normal distribution * @note A helper class only to be used within this module. */ -class NormalRandomVector { +class NormalRandomVector +{ std::normal_distribution normal_distribution; public: - explicit NormalRandomVector(double mean = 0.0, double stddev = 1.0) : normal_distribution(mean, stddev){}; - template Point operator()(random_engine &engine) { - return {normal_distribution(engine), normal_distribution(engine), normal_distribution(engine)}; + explicit NormalRandomVector(double mean = 0.0, double stddev = 1.0) + : normal_distribution(mean, stddev) {}; + + template Point operator()(random_engine& engine) + { + return {normal_distribution(engine), normal_distribution(engine), + normal_distribution(engine)}; } }; /** * @brief Base class for dynamics integrators * - * Integrators progress the simulation in time steps. Positions and velocities of the particles as well as forces - * acting on them are updated in every time step as prescribed by the given integrator scheme. + * Integrators progress the simulation in time steps. Positions and velocities of the particles as + * well as forces acting on them are updated in every time step as prescribed by the given + * integrator scheme. */ -class IntegratorBase { +class IntegratorBase +{ protected: - Space &spc; + Space& spc; Energy::EnergyTerm& energy; IntegratorBase(Space&, Energy::EnergyTerm&); virtual ~IntegratorBase() = default; public: /** - * @brief Move particles by one time step and update their positions, velocities and acting forces + * @brief Move particles by one time step and update their positions, velocities and acting + * forces * @param velocities a vector of particles' velocities * @param forces a vector of particles' forces */ - virtual void step(PointVector &velocities, PointVector &forces) = 0; // todo shall we return real time progress? - virtual void from_json(const json &j) = 0; - virtual void to_json(json &j) const = 0; + virtual void step(PointVector& velocities, + PointVector& forces) = 0; // todo shall we return real time progress? + virtual void from_json(const json& j) = 0; + virtual void to_json(json& j) const = 0; }; -void from_json(const json &j, IntegratorBase &i); -void to_json(json &j, const IntegratorBase &i); +void from_json(const json& j, IntegratorBase& i); +void to_json(json& j, const IntegratorBase& i); /** * @brief Symmetric Langevin velocity-Verlet method (BAOAB) * * The integrator can conduct normal velocity-Verlet, when friction_coefficient = 0. */ -class LangevinVelocityVerlet : public IntegratorBase { - double time_step; //!< integration time step (picoseconds) - double friction_coefficient; //!< friction coefficient (inverse picoseconds) - NormalRandomVector random_vector; //!< generator of random 3d vectors from the normal distribution - [[nodiscard]] inline Point positionIncrement(const Point &velocity) const; //!< increment particle's position in an A semi-step - [[nodiscard]] inline Point velocityIncrement(const Point &force, - double mass) const; //!< increment particle's velocity in a B semi-step - //! apply fluctuation-dissipation to particle's velocity by solving of Ornstein-Uhlenbeck process in an O-step - inline Point velocityFluctuationDissipation(const Point &velocity, double mass); +class LangevinVelocityVerlet : public IntegratorBase +{ + double time_step; //!< integration time step (picoseconds) + double friction_coefficient; //!< friction coefficient (inverse picoseconds) + NormalRandomVector + random_vector; //!< generator of random 3d vectors from the normal distribution + [[nodiscard]] inline Point positionIncrement( + const Point& velocity) const; //!< increment particle's position in an A semi-step + [[nodiscard]] inline Point + velocityIncrement(const Point& force, + double mass) const; //!< increment particle's velocity in a B semi-step + //! apply fluctuation-dissipation to particle's velocity by solving of Ornstein-Uhlenbeck + //! process in an O-step + inline Point velocityFluctuationDissipation(const Point& velocity, double mass); public: LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy); - LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, double time_step, double friction_coefficient); + LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, double time_step, + double friction_coefficient); LangevinVelocityVerlet(Space& spc, Energy::EnergyTerm& energy, const json& j); - void step(PointVector &velocities, PointVector &forces) override; - void from_json(const json &j) override; - void to_json(json &j) const override; + void step(PointVector& velocities, PointVector& forces) override; + void from_json(const json& j) override; + void to_json(json& j) const override; }; -void from_json(const json &j, IntegratorBase &i); -void to_json(json &j, const IntegratorBase &i); +void from_json(const json& j, IntegratorBase& i); +void to_json(json& j, const IntegratorBase& i); /** * @brief Base class for force moves, e.g., molecular dynamics or Langevin dynamics. * - * Orchestrate execution of integrators, thermostats, etc. Store vectors for velocities and forces, which are not part - * of the particle vector in the space. + * Orchestrate execution of integrators, thermostats, etc. Store vectors for velocities and forces, + * which are not part of the particle vector in the space. */ -class ForceMove : public Move { +class ForceMove : public Move +{ protected: std::shared_ptr integrator; - unsigned int number_of_steps; //!< number of integration steps to perform during a single MC move - PointVector velocities; //!< Vector of velocities matching each active particle in Space - PointVector forces; //!< Vector of forces matching each active particle in Space + unsigned int + number_of_steps; //!< number of integration steps to perform during a single MC move + PointVector velocities; //!< Vector of velocities matching each active particle in Space + PointVector forces; //!< Vector of forces matching each active particle in Space - size_t resizeForcesAndVelocities(); //!< Resize velocities and forces to match number of active particles + size_t resizeForcesAndVelocities(); //!< Resize velocities and forces to match number of active + //!< particles void generateVelocities(); //!< Generate initial velocities and zero forces - void _to_json(json &j) const override; - void _from_json(const json &j) override; - void _move(Change &change) override; - ForceMove(Space& spc, const std::string& name, const std::string& cite, std::shared_ptr integrator, - unsigned int nsteps); + void _to_json(json& j) const override; + void _from_json(const json& j) override; + void _move(Change& change) override; + ForceMove(Space& spc, const std::string& name, const std::string& cite, + std::shared_ptr integrator, unsigned int nsteps); ~ForceMove() override = default; public: - double bias(Change &, double, double) override; - [[nodiscard]] const PointVector &getForces() const; - [[nodiscard]] const PointVector &getVelocities() const; + double bias(Change&, double, double) override; + [[nodiscard]] const PointVector& getForces() const; + [[nodiscard]] const PointVector& getVelocities() const; }; /** * @brief Langevin dynamics move using Langevin equation of motion */ -class LangevinDynamics : public ForceMove { +class LangevinDynamics : public ForceMove +{ protected: - LangevinDynamics(Space& spc, const std::string& name, const std::string& cite, std::shared_ptr integrator, - unsigned int nsteps); + LangevinDynamics(Space& spc, const std::string& name, const std::string& cite, + std::shared_ptr integrator, unsigned int nsteps); public: LangevinDynamics(Space& spc, std::shared_ptr integrator, unsigned int nsteps); LangevinDynamics(Space& spc, Energy::EnergyTerm&, const json&); LangevinDynamics(Space& spc, Energy::EnergyTerm&); - void _to_json(json &j) const override; - void _from_json(const json &j) override; + void _to_json(json& j) const override; + void _from_json(const json& j) override; }; -} // namespace Faunus::Move \ No newline at end of file +} // namespace Faunus::move \ No newline at end of file diff --git a/src/functionparser.cpp b/src/functionparser.cpp index c982991b3..c452c826c 100644 --- a/src/functionparser.cpp +++ b/src/functionparser.cpp @@ -3,26 +3,28 @@ #include // https://github.com/ArashPartow/exprtk #include -template -void ExprFunction::set(const std::string &exprstr, const Tvarvec &vars, const Tconstvec &consts) { +template +void ExprFunction::set(const std::string& exprstr, const Tvarvec& vars, const Tconstvec& consts) +{ if (not parser) { parser = std::make_shared>(); symbols = std::make_shared>(); expression = std::make_shared>(); } symbols->clear(); - for (auto &v : vars) + for (auto& v : vars) symbols->add_variable(v.first, *v.second); - for (auto &v : consts) + for (auto& v : consts) symbols->add_constant(v.first, v.second); symbols->add_constants(); expression->register_symbol_table(*symbols); - if (! parser->compile(exprstr, *expression)) + if (!parser->compile(exprstr, *expression)) throw std::runtime_error("error passing function/expression"); } -template -void ExprFunction::set(const nlohmann::json &j, const Tvarvec &vars) { +template +void ExprFunction::set(const nlohmann::json& j, const Tvarvec& vars) +{ Tconstvec consts; auto it = j.find("constants"); if (it != j.end()) @@ -31,17 +33,20 @@ void ExprFunction::set(const nlohmann::json &j, const Tvarvec &vars) { set(j.at("function"), vars, consts); } -template -T ExprFunction::operator()() const { +template T ExprFunction::operator()() const +{ return expression->value(); } -template T ExprFunction::derivative(T& variable) const { + +template T ExprFunction::derivative(T& variable) const +{ return exprtk::derivative(*expression, variable); } template class ExprFunction; -TEST_CASE("[Faunus] ExprFunction") { +TEST_CASE("[Faunus] ExprFunction") +{ double x = 0, y = 0; ExprFunction expr; nlohmann::json j = R"({ "function": "x*x+kappa", "constants": {"kappa": 0.4, "f": 2} })"_json; diff --git a/src/functionparser.h b/src/functionparser.h index 1d59415ce..eac33a947 100644 --- a/src/functionparser.h +++ b/src/functionparser.h @@ -7,14 +7,15 @@ namespace exprtk { // exprtk.hpp template class parser; template class expression; template class symbol_table; -} +} // namespace exprtk /** * Since parser is non-copyable we instantiate it * with a shared pointer, allowing `ExprFunction` * to be directly assigned to `std::function`. */ -template class ExprFunction { +template class ExprFunction +{ std::shared_ptr> parser; std::shared_ptr> expression; std::shared_ptr> symbols; @@ -22,11 +23,10 @@ template class ExprFunction { typedef std::vector> Tconstvec; public: - void set(const std::string &exprstr, const Tvarvec &vars = {}, const Tconstvec &consts = {}); - void set(const nlohmann::json &, const Tvarvec &vars = {}); + void set(const std::string& exprstr, const Tvarvec& vars = {}, const Tconstvec& consts = {}); + void set(const nlohmann::json&, const Tvarvec& vars = {}); T operator()() const; T derivative(T& variable) const; }; extern template class ExprFunction; - diff --git a/src/geometry.cpp b/src/geometry.cpp index d6acdcb63..6bba9aa24 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -17,32 +17,45 @@ GeometryBase::~GeometryBase() = default; /** * @return Boolean matrix with true for each periodic direction */ -Eigen::Matrix BoundaryCondition::isPeriodic() const { +Eigen::Matrix BoundaryCondition::isPeriodic() const +{ return direction.cwiseEqual( BoundaryXYZ(Boundary::PERIODIC, Boundary::PERIODIC, Boundary::PERIODIC)); } GeometryImplementation::~GeometryImplementation() = default; -Cuboid::Cuboid(const Point &side_length) { - boundary_conditions = - BoundaryCondition(Coordinates::ORTHOGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::PERIODIC}); +Cuboid::Cuboid(const Point& side_length) +{ + boundary_conditions = BoundaryCondition( + Coordinates::ORTHOGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::PERIODIC}); setLength(side_length); } -Cuboid::Cuboid() : Cuboid(Point::Zero()) {} +Cuboid::Cuboid() + : Cuboid(Point::Zero()) +{ +} -Point Cuboid::getLength() const { return box; } +Point Cuboid::getLength() const +{ + return box; +} -void Cuboid::setLength(const Point &len) { +void Cuboid::setLength(const Point& len) +{ box = len; box_half = 0.5 * box; box_inv = box.cwiseInverse(); } -double Cuboid::getVolume(int) const { return box.x() * box.y() * box.z(); } +double Cuboid::getVolume(int) const +{ + return box.x() * box.y() * box.z(); +} -Point Cuboid::setVolume(double volume, const VolumeMethod method) { +Point Cuboid::setVolume(double volume, const VolumeMethod method) +{ const double old_volume = getVolume(); double alpha; Point new_box; @@ -75,7 +88,8 @@ Point Cuboid::setVolume(double volume, const VolumeMethod method) { return box_scaling; // this will scale any point to new volume } -void Cuboid::boundary(Point &a) const { +void Cuboid::boundary(Point& a) const +{ if (boundary_conditions.direction.x() == Boundary::PERIODIC) { if (std::fabs(a.x()) > box_half.x()) a.x() -= box.x() * anint(a.x() * box_inv.x()); @@ -90,7 +104,8 @@ void Cuboid::boundary(Point &a) const { } } -Point Cuboid::vdist(const Point &a, const Point &b) const { +Point Cuboid::vdist(const Point& a, const Point& b) const +{ Point distance(a - b); if (boundary_conditions.direction.x() == Boundary::PERIODIC) { if (distance.x() > box_half.x()) @@ -113,23 +128,28 @@ Point Cuboid::vdist(const Point &a, const Point &b) const { return distance; } -void Cuboid::randompos(Point &m, Random &rand) const { +void Cuboid::randompos(Point& m, Random& rand) const +{ m.x() = (rand() - 0.5) * box.x(); m.y() = (rand() - 0.5) * box.y(); m.z() = (rand() - 0.5) * box.z(); } -bool Cuboid::collision(const Point &a) const { - return (std::fabs(a.x()) > box_half.x() || std::fabs(a.y()) > box_half.y() || std::fabs(a.z()) > box_half.z()); +bool Cuboid::collision(const Point& a) const +{ + return (std::fabs(a.x()) > box_half.x() || std::fabs(a.y()) > box_half.y() || + std::fabs(a.z()) > box_half.z()); } -void Cuboid::from_json(const json &j) { +void Cuboid::from_json(const json& j) +{ box.setZero(); if (const auto& length_ = j.at("length"); length_.is_number()) { auto l = length_.get(); setLength({l, l, l}); return; - } else if (length_.is_array()) { + } + else if (length_.is_array()) { if (length_.size() == 3) { setLength(length_.get()); return; @@ -138,35 +158,56 @@ void Cuboid::from_json(const json &j) { throw ConfigurationError("sidelength syntax error"); } -void Cuboid::to_json(json &j) const { j = {{"length", box}}; } +void Cuboid::to_json(json& j) const +{ + j = {{"length", box}}; +} -std::unique_ptr Cuboid::clone() const { +std::unique_ptr Cuboid::clone() const +{ return std::make_unique(*this); } // =============== Slit =============== -Slit::Slit(const Point &p) : Tbase(p) { - boundary_conditions = - BoundaryCondition(Coordinates::ORTHOGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::FIXED}); +Slit::Slit(const Point& p) + : Tbase(p) +{ + boundary_conditions = BoundaryCondition( + Coordinates::ORTHOGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::FIXED}); +} + +Slit::Slit(double x, double y, double z) + : Slit(Point(x, y, z)) +{ } -Slit::Slit(double x, double y, double z) : Slit(Point(x, y, z)) {} -Slit::Slit(double x) : Slit(x, x, x) {} -std::unique_ptr Slit::clone() const { +Slit::Slit(double x) + : Slit(x, x, x) +{ +} + +std::unique_ptr Slit::clone() const +{ return std::make_unique(*this); } // =============== Sphere =============== -Sphere::Sphere(double radius) : radius(radius) { - boundary_conditions = - BoundaryCondition(Coordinates::ORTHOGONAL, {Boundary::FIXED, Boundary::FIXED, Boundary::FIXED}); +Sphere::Sphere(double radius) + : radius(radius) +{ + boundary_conditions = BoundaryCondition(Coordinates::ORTHOGONAL, + {Boundary::FIXED, Boundary::FIXED, Boundary::FIXED}); } -Point Sphere::getLength() const { return {2.0 * radius, 2.0 * radius, 2.0 * radius}; } +Point Sphere::getLength() const +{ + return {2.0 * radius, 2.0 * radius, 2.0 * radius}; +} -double Sphere::getVolume(int dim) const { +double Sphere::getVolume(int dim) const +{ double result; switch (dim) { case 3: @@ -179,35 +220,43 @@ double Sphere::getVolume(int dim) const { result = 2.0 * radius; // diameter break; default: - throw std::invalid_argument("unsupported volume dimension for the sphere: " + std::to_string(dim)); + throw std::invalid_argument("unsupported volume dimension for the sphere: " + + std::to_string(dim)); } return result; } -Point Sphere::setVolume(double volume, const VolumeMethod method) { +Point Sphere::setVolume(double volume, const VolumeMethod method) +{ const double old_radius = radius; Point box_scaling; if (method == VolumeMethod::ISOTROPIC) { radius = std::cbrt(volume / (4.0 / 3.0 * pc::pi)); assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume"); - } else { + } + else { throw std::invalid_argument("unsupported volume scaling method for the spherical geometry"); } box_scaling.setConstant(radius / old_radius); return box_scaling; } -void Sphere::boundary(Point &) const {} // no PBC +void Sphere::boundary(Point&) const {} // no PBC -bool Sphere::collision(const Point &point) const { return point.squaredNorm() > radius * radius; } +bool Sphere::collision(const Point& point) const +{ + return point.squaredNorm() > radius * radius; +} -Point Sphere::vdist(const Point &a, const Point &b) const { +Point Sphere::vdist(const Point& a, const Point& b) const +{ // no pbc; shall we check points coordinates? Point distance(a - b); return distance; } -void Sphere::randompos(Point &m, Random &rand) const { +void Sphere::randompos(Point& m, Random& rand) const +{ double r2 = radius * radius, d = 2 * radius; do { m.x() = (rand() - 0.5) * d; @@ -216,22 +265,36 @@ void Sphere::randompos(Point &m, Random &rand) const { } while (m.squaredNorm() > r2); } -void Sphere::from_json(const json &j) { radius = j.at("radius").get(); } +void Sphere::from_json(const json& j) +{ + radius = j.at("radius").get(); +} + +void Sphere::to_json(json& j) const +{ + j = {{"radius", radius}}; +} -void Sphere::to_json(json &j) const { j = {{"radius", radius}}; } +double Sphere::getRadius() const +{ + return radius; +} -double Sphere::getRadius() const { return radius; } -std::unique_ptr Sphere::clone() const { +std::unique_ptr Sphere::clone() const +{ return std::make_unique(*this); } // =============== Hypersphere 2D =============== -Hypersphere2d::Hypersphere2d(double radius) : Sphere(radius) { +Hypersphere2d::Hypersphere2d(double radius) + : Sphere(radius) +{ boundary_conditions = BoundaryCondition(Coordinates::NON3D); } -Point Hypersphere2d::vdist(const Point &a, const Point &b) const { +Point Hypersphere2d::vdist(const Point& a, const Point& b) const +{ // ugly but works, needs fixing though... Point distance3d(a - b); double angle = std::acos(a.dot(b) / (radius * radius)); @@ -239,43 +302,58 @@ Point Hypersphere2d::vdist(const Point &a, const Point &b) const { return distance3d / distance3d.norm() * distance; } -void Hypersphere2d::randompos(Point &m, Random &rand) const { +void Hypersphere2d::randompos(Point& m, Random& rand) const +{ Sphere::randompos(m, rand); m = m / m.norm() * radius; } -bool Hypersphere2d::collision(const Point &a) const { +bool Hypersphere2d::collision(const Point& a) const +{ bool collision = std::fabs(a.norm() - radius) > 1e-6; return collision; } -std::unique_ptr Hypersphere2d::clone() const { + +std::unique_ptr Hypersphere2d::clone() const +{ return std::make_unique(*this); } // =============== Hexagonal Prism =============== const Eigen::Matrix3d HexagonalPrism::rhombic2cartesian = - (Eigen::Matrix3d() << std::cos(-pc::pi / 6.0), 0.0, 0.0, std::sin(-pc::pi / 6.0), 1.0, 0.0, 0.0, 0.0, 1.0) + (Eigen::Matrix3d() << std::cos(-pc::pi / 6.0), 0.0, 0.0, std::sin(-pc::pi / 6.0), 1.0, 0.0, 0.0, + 0.0, 1.0) .finished(); const Eigen::Matrix3d HexagonalPrism::cartesian2rhombic = rhombic2cartesian.inverse(); -HexagonalPrism::HexagonalPrism(double side, double height) { - // the current implementation is hardcoded as bellow and ignores other periodic_directions settings - boundary_conditions = - BoundaryCondition(Coordinates::ORTHOHEXAGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::FIXED}); +HexagonalPrism::HexagonalPrism(double side, double height) +{ + // the current implementation is hardcoded as bellow and ignores other periodic_directions + // settings + boundary_conditions = BoundaryCondition( + Coordinates::ORTHOHEXAGONAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::FIXED}); set_box(side, height); } -Point HexagonalPrism::getLength() const { return box; } +Point HexagonalPrism::getLength() const +{ + return box; +} -double HexagonalPrism::getVolume(int) const { +double HexagonalPrism::getVolume(int) const +{ return 3.0 / 4.0 * box.x() * box.y() * box.z(); // 3 * inner_radius * outer_radius * height } -void HexagonalPrism::set_box(double side, double height) { box = {std::sqrt(3.0) * side, 2.0 * side, height}; } +void HexagonalPrism::set_box(double side, double height) +{ + box = {std::sqrt(3.0) * side, 2.0 * side, height}; +} -Point HexagonalPrism::setVolume(double volume, const VolumeMethod method) { +Point HexagonalPrism::setVolume(double volume, const VolumeMethod method) +{ const double old_volume = getVolume(); double alpha; Point box_scaling; @@ -300,32 +378,37 @@ Point HexagonalPrism::setVolume(double volume, const VolumeMethod method) { volume = old_volume; break; default: - throw std::invalid_argument("unsupported volume scaling method for the hexagonal-prism geometry"); + throw std::invalid_argument( + "unsupported volume scaling method for the hexagonal-prism geometry"); } box = box.cwiseProduct(box_scaling); assert(fabs(getVolume() - volume) < 1e-6); return box_scaling; } -Point HexagonalPrism::vdist(const Point &a, const Point &b) const { +Point HexagonalPrism::vdist(const Point& a, const Point& b) const +{ Point distance(a - b); boundary(distance); return distance; } -bool HexagonalPrism::collision(const Point &a) const { +bool HexagonalPrism::collision(const Point& a) const +{ const double height = box.z(); const double outer_radius = 0.5 * box.y(); // Hexagon can be divided into three rhombuses. Using natural rhombic coordinates, it can be - // straightforwardly determined if the particular point is inside the rhombus. If the point is mirrored to - // the first quadrant taking the absolute values of coordinates, only one rhombus needs to be evaluated. + // straightforwardly determined if the particular point is inside the rhombus. If the point is + // mirrored to the first quadrant taking the absolute values of coordinates, only one rhombus + // needs to be evaluated. Point b = cartesian2rhombic * a.cwiseAbs(); bool collision = b.z() > 0.5 * height || b.x() > outer_radius || b.y() > outer_radius; return collision; } -void HexagonalPrism::boundary(Point &a) const { +void HexagonalPrism::boundary(Point& a) const +{ // TODO optimise and add documentation const double sqrtThreeByTwo = sqrt(3.0) / 2.0; const Point unitvX = {1.0, 0.0, 0.0}; @@ -354,7 +437,8 @@ void HexagonalPrism::boundary(Point &a) const { a.z() -= box.z() * anint(a.z() / box.z()); } -void HexagonalPrism::randompos(Point &m, Random &rand) const { +void HexagonalPrism::randompos(Point& m, Random& rand) const +{ // Generating random points in hexagonal-prism coordinates is not feasible as the space coverage // will not be homogeneous back in the cartesian coordinates. m.z() = (rand() - 0.5) * box.z(); @@ -365,35 +449,61 @@ void HexagonalPrism::randompos(Point &m, Random &rand) const { } while (collision(m)); } -void HexagonalPrism::from_json(const json &j) { +void HexagonalPrism::from_json(const json& j) +{ auto radius = j.at("radius").get(); // inner radius auto height = j.at("length").get(); auto edge = 2.0 / std::sqrt(3.0) * radius; set_box(edge, height); } -void HexagonalPrism::to_json(json &j) const { j = {{"radius", 0.5 * box.x()}, {"length", box.z()}}; } +void HexagonalPrism::to_json(json& j) const +{ + j = {{"radius", 0.5 * box.x()}, {"length", box.z()}}; +} + +double HexagonalPrism::innerRadius() const +{ + return 0.5 * box.x(); +} -double HexagonalPrism::innerRadius() const { return 0.5 * box.x(); } -double HexagonalPrism::outerRadius() const { return 0.5 * box.y(); } -double HexagonalPrism::height() const { return box.z(); } +double HexagonalPrism::outerRadius() const +{ + return 0.5 * box.y(); +} + +double HexagonalPrism::height() const +{ + return box.z(); +} -std::unique_ptr HexagonalPrism::clone() const { +std::unique_ptr HexagonalPrism::clone() const +{ return std::make_unique(*this); } // =============== Cylinder =============== -Cylinder::Cylinder(double radius, double height) : radius(radius), height(height) { - boundary_conditions = - BoundaryCondition(Coordinates::ORTHOGONAL, {Boundary::FIXED, Boundary::FIXED, Boundary::PERIODIC}); +Cylinder::Cylinder(double radius, double height) + : radius(radius) + , height(height) +{ + boundary_conditions = BoundaryCondition(Coordinates::ORTHOGONAL, + {Boundary::FIXED, Boundary::FIXED, Boundary::PERIODIC}); } -Point Cylinder::getLength() const { return {2.0 * radius, 2.0 * radius, height}; } +Point Cylinder::getLength() const +{ + return {2.0 * radius, 2.0 * radius, height}; +} -double Cylinder::getVolume(int) const { return pc::pi * radius * radius * height; } +double Cylinder::getVolume(int) const +{ + return pc::pi * radius * radius * height; +} -Point Cylinder::setVolume(double volume, const VolumeMethod method) { +Point Cylinder::setVolume(double volume, const VolumeMethod method) +{ const double old_volume = getVolume(); double alpha; Point box_scaling; @@ -424,23 +534,27 @@ Point Cylinder::setVolume(double volume, const VolumeMethod method) { volume = old_volume; break; default: - throw std::invalid_argument("unsupported volume scaling method for the cylindrical geometry"); + throw std::invalid_argument( + "unsupported volume scaling method for the cylindrical geometry"); } assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume"); return box_scaling; } -void Cylinder::boundary(Point &a) const { +void Cylinder::boundary(Point& a) const +{ // z-pbc if (std::fabs(a.z()) > 0.5 * height) a.z() -= height * anint(a.z() / height); } -bool Cylinder::collision(const Point &a) const { +bool Cylinder::collision(const Point& a) const +{ return std::fabs(a.z()) > 0.5 * height || a.x() * a.x() + a.y() * a.y() > radius * radius; } -Point Cylinder::vdist(const Point &a, const Point &b) const { +Point Cylinder::vdist(const Point& a, const Point& b) const +{ Point distance(a - b); if (distance.z() > 0.5 * height) distance.z() -= height; @@ -449,7 +563,8 @@ Point Cylinder::vdist(const Point &a, const Point &b) const { return distance; } -void Cylinder::randompos(Point &m, Random &rand) const { +void Cylinder::randompos(Point& m, Random& rand) const +{ double r2 = radius * radius, d = 2.0 * radius; m.z() = (rand() - 0.5) * height; do { @@ -459,54 +574,72 @@ void Cylinder::randompos(Point &m, Random &rand) const { } while (m.x() * m.x() + m.y() * m.y() > r2); } -void Cylinder::from_json(const json &j) { +void Cylinder::from_json(const json& j) +{ radius = j.at("radius").get(); height = j.at("length").get(); } -void Cylinder::to_json(json &j) const { j = {{"radius", radius}, {"length", height}}; } +void Cylinder::to_json(json& j) const +{ + j = {{"radius", radius}, {"length", height}}; +} -std::unique_ptr Cylinder::clone() const { +std::unique_ptr Cylinder::clone() const +{ return std::make_unique(*this); } // =============== Truncated Octahedron =============== -TruncatedOctahedron::TruncatedOctahedron(double side) : side(side) { - // the current implementation is hardcoded as bellow and ignores other periodic_directions settings +TruncatedOctahedron::TruncatedOctahedron(double side) + : side(side) +{ + // the current implementation is hardcoded as bellow and ignores other periodic_directions + // settings boundary_conditions = - BoundaryCondition(Coordinates::TRUNC_OCTAHEDRAL, {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::PERIODIC}); + BoundaryCondition(Coordinates::TRUNC_OCTAHEDRAL, + {Boundary::PERIODIC, Boundary::PERIODIC, Boundary::PERIODIC}); } -Point TruncatedOctahedron::getLength() const { +Point TruncatedOctahedron::getLength() const +{ // todo check orientation in xyz return Point::Constant(2.0 * std::sqrt(2.0) * side); // distance between opposite square faces } -double TruncatedOctahedron::getVolume(int) const { return std::sqrt(128.0) * side * side * side; } +double TruncatedOctahedron::getVolume(int) const +{ + return std::sqrt(128.0) * side * side * side; +} -Point TruncatedOctahedron::setVolume(double volume, const VolumeMethod method) { +Point TruncatedOctahedron::setVolume(double volume, const VolumeMethod method) +{ const double old_side = side; Point box_scaling; if (method == VolumeMethod::ISOTROPIC) { side = std::cbrt(volume / std::sqrt(128.0)); assert(std::fabs(getVolume() - volume) < 1e-6 && "error setting sphere volume"); - } else { - throw std::invalid_argument("unsupported volume scaling method for the truncated-octahedral geometry"); + } + else { + throw std::invalid_argument( + "unsupported volume scaling method for the truncated-octahedral geometry"); } box_scaling.setConstant(side / old_side); assert(fabs(getVolume() - volume) < 1e-6); return box_scaling; } -Point TruncatedOctahedron::vdist(const Point &a, const Point &b) const { +Point TruncatedOctahedron::vdist(const Point& a, const Point& b) const +{ Point distance(a - b); boundary(distance); return distance; } -bool TruncatedOctahedron::collision(const Point &a) const { +bool TruncatedOctahedron::collision(const Point& a) const +{ const double sqrtThreeI = 1.0 / std::sqrt(3.0); const double origin_to_square_face = std::sqrt(2.0) * side; const double origin_to_hexagonal_face = std::sqrt(1.5) * side; @@ -529,7 +662,8 @@ bool TruncatedOctahedron::collision(const Point &a) const { return false; } -void TruncatedOctahedron::boundary(Point &a) const { +void TruncatedOctahedron::boundary(Point& a) const +{ const double sqrtThreeI = 1.0 / std::sqrt(3.0); const double square_face_distance = std::sqrt(8.0) * side; const double hexagonal_face_distance = std::sqrt(6.0) * side; @@ -574,7 +708,8 @@ void TruncatedOctahedron::boundary(Point &a) const { a.z() -= square_face_distance * anint(a.z() / square_face_distance); } -void TruncatedOctahedron::randompos(Point &pos, Random &rand) const { +void TruncatedOctahedron::randompos(Point& pos, Random& rand) const +{ const double d = std::sqrt(10.0) * side; // use circumdiameter const double r2 = d * d / 4.0; do { @@ -586,37 +721,50 @@ void TruncatedOctahedron::randompos(Point &pos, Random &rand) const { } while (collision(pos)); } -void TruncatedOctahedron::from_json(const json &j) { side = j.at("radius").get(); } +void TruncatedOctahedron::from_json(const json& j) +{ + side = j.at("radius").get(); +} -void TruncatedOctahedron::to_json(json &j) const { j = {{"radius", side}}; } +void TruncatedOctahedron::to_json(json& j) const +{ + j = {{"radius", side}}; +} -std::unique_ptr TruncatedOctahedron::clone() const { +std::unique_ptr TruncatedOctahedron::clone() const +{ return std::make_unique(*this); } // =============== Chameleon============== -const std::map Chameleon::names = {{{"cuboid", Variant::CUBOID}, - {"cylinder", Variant::CYLINDER}, - {"slit", Variant::SLIT}, - {"sphere", Variant::SPHERE}, - {"hexagonal", Variant::HEXAGONAL}, - {"octahedron", Variant::OCTAHEDRON}, - {"hypersphere2d", Variant::HYPERSPHERE2D}}}; - -void from_json(const json &j, Chameleon &g) { +const std::map Chameleon::names = { + {{"cuboid", Variant::CUBOID}, + {"cylinder", Variant::CYLINDER}, + {"slit", Variant::SLIT}, + {"sphere", Variant::SPHERE}, + {"hexagonal", Variant::HEXAGONAL}, + {"octahedron", Variant::OCTAHEDRON}, + {"hypersphere2d", Variant::HYPERSPHERE2D}}}; + +void from_json(const json& j, Chameleon& g) +{ try { g.from_json(j); } - catch (std::exception &e) { + catch (std::exception& e) { usageTip.pick("geometry"); throw ConfigurationError("geometry construction error: {}", e.what()); } } -void to_json(json &j, const Chameleon &g) { g.to_json(j); } +void to_json(json& j, const Chameleon& g) +{ + g.to_json(j); +} -ParticleVector mapParticlesOnSphere(const ParticleVector &source) { +ParticleVector mapParticlesOnSphere(const ParticleVector& source) +{ assert(source.size() > 1); Average radius; // average radial distance ParticleVector destination = source; @@ -625,34 +773,39 @@ ParticleVector mapParticlesOnSphere(const ParticleVector &source) { destination[i].pos = source[i].pos - COM; // make COM origin double r = destination[i].pos.norm(); // radial distance destination[i].pos /= r; // normalize to unit vector - radius += r; // radius is the average r + radius += r; // radius is the average r } destination[0].pos.setZero(); - for (auto &i : destination) // scale positions to surface of sphere + for (auto& i : destination) // scale positions to surface of sphere i.pos = i.pos * radius.avg() + COM; // rmsd, skipping the first particle - double _rmsd = - rootMeanSquareDeviation(destination.begin() + 1, destination.end(), source.begin() + 1, - [](const Particle &a, const Particle &b) { return (a.pos - b.pos).squaredNorm(); }); - - faunus_logger->info("{} particles mapped on sphere of radius {} with RMSD {} {}; the first particle ({}) is a " - "dummy and COM placeholder", - destination.size(), radius.avg(), _rmsd, unicode::angstrom, - Faunus::atoms.at(destination.at(0).id).name); + double _rmsd = rootMeanSquareDeviation( + destination.begin() + 1, destination.end(), source.begin() + 1, + [](const Particle& a, const Particle& b) { return (a.pos - b.pos).squaredNorm(); }); + + faunus_logger->info( + "{} particles mapped on sphere of radius {} with RMSD {} {}; the first particle ({}) is a " + "dummy and COM placeholder", + destination.size(), radius.avg(), _rmsd, unicode::angstrom, + Faunus::atoms.at(destination.at(0).id).name); return destination; } -ShapeDescriptors::ShapeDescriptors(const Tensor &gyration_tensor) { - const auto principal_moment = Eigen::SelfAdjointEigenSolver(gyration_tensor).eigenvalues(); +ShapeDescriptors::ShapeDescriptors(const Tensor& gyration_tensor) +{ + const auto principal_moment = + Eigen::SelfAdjointEigenSolver(gyration_tensor).eigenvalues(); gyration_radius_squared = gyration_tensor.trace(); asphericity = 3.0 / 2.0 * principal_moment.z() - gyration_radius_squared / 2.0; acylindricity = principal_moment.y() - principal_moment.x(); - relative_shape_anisotropy = (asphericity * asphericity + 3.0 / 4.0 * acylindricity * acylindricity) / - (gyration_radius_squared * gyration_radius_squared); + relative_shape_anisotropy = + (asphericity * asphericity + 3.0 / 4.0 * acylindricity * acylindricity) / + (gyration_radius_squared * gyration_radius_squared); } -ShapeDescriptors &ShapeDescriptors::operator+=(const ShapeDescriptors &other) { +ShapeDescriptors& ShapeDescriptors::operator+=(const ShapeDescriptors& other) +{ gyration_radius_squared += other.gyration_radius_squared; asphericity += other.asphericity; acylindricity += other.acylindricity; @@ -660,7 +813,8 @@ ShapeDescriptors &ShapeDescriptors::operator+=(const ShapeDescriptors &other) { return *this; } -ShapeDescriptors ShapeDescriptors::operator*(const double scale) const { +ShapeDescriptors ShapeDescriptors::operator*(const double scale) const +{ auto scaled = *this; scaled.gyration_radius_squared *= scale; scaled.asphericity *= scale; @@ -669,14 +823,16 @@ ShapeDescriptors ShapeDescriptors::operator*(const double scale) const { return scaled; } -void to_json(json &j, const ShapeDescriptors &shape) { +void to_json(json& j, const ShapeDescriptors& shape) +{ j = {{"Rg = √⟨s²⟩", std::sqrt(shape.gyration_radius_squared)}, {"asphericity (b)", shape.asphericity}, {"acylindricity (c)", shape.acylindricity}, {"relative shape anisotropy (πœ…Β²)", shape.relative_shape_anisotropy}}; } -TEST_CASE("[Faunus] ShapeDescriptors") { +TEST_CASE("[Faunus] ShapeDescriptors") +{ using doctest::Approx; std::vector positions = {{0, 0, 0}, {1, 0, 0}}; std::vector masses = {1, 1}; @@ -687,14 +843,16 @@ TEST_CASE("[Faunus] ShapeDescriptors") { auto shape = ShapeDescriptors(gyration_tensor); CHECK_EQ(shape.relative_shape_anisotropy, Approx(1.0)); - positions = {{0, 1, 0}, {1, 0, 0}, {-1, 0, 0}, {0, -1, 0}, {0, 1, 1}, {1, 0, 1}, {-1, 0, 1}, {0, -1, 1}}; + positions = {{0, 1, 0}, {1, 0, 0}, {-1, 0, 0}, {0, -1, 0}, + {0, 1, 1}, {1, 0, 1}, {-1, 0, 1}, {0, -1, 1}}; masses = {1, 1, 1, 1, 1, 1, 1, 1}; gyration_tensor = gyration(positions.begin(), positions.end(), masses.begin(), origin); shape = ShapeDescriptors(gyration_tensor); CHECK_EQ(shape.relative_shape_anisotropy, Approx(0.0)); } -TEST_CASE("[Faunus] hexagonalPrismToCuboid") { +TEST_CASE("[Faunus] hexagonalPrismToCuboid") +{ using doctest::Approx; double radius = 2.0, height = 20.0; double side = 2.0 / std::sqrt(3.0) * radius; @@ -713,11 +871,12 @@ TEST_CASE("[Faunus] hexagonalPrismToCuboid") { CHECK_EQ(cuboid.getLength().z(), Approx(height)); CHECK_EQ(cuboid.getVolume(), Approx(2.0 * hexagonal_prism.getVolume())); - std::vector positions = {{0, 1, 0}, {0.866, 0.5, 0}, {0.866, -0.5, 0}, {0, -1, 0}, - {-0.866, -0.5, 0}, {-0.866, 0.5, 0}, {2, -2.4641, 0}, {-1.134, -2.9641, 0}, - {-1.134, 2.9641, 0}, {2, 2.4641, 0}, {1.134, 2.9641, 0}, {1.134, -2.9641, 0}}; + std::vector positions = {{0, 1, 0}, {0.866, 0.5, 0}, {0.866, -0.5, 0}, + {0, -1, 0}, {-0.866, -0.5, 0}, {-0.866, 0.5, 0}, + {2, -2.4641, 0}, {-1.134, -2.9641, 0}, {-1.134, 2.9641, 0}, + {2, 2.4641, 0}, {1.134, 2.9641, 0}, {1.134, -2.9641, 0}}; size_t i = 0; - for (auto &particle : p_new) { // compared actual positions w. expected positions + for (auto& particle : p_new) { // compared actual positions w. expected positions CHECK_EQ(particle.pos.x(), Approx(positions[i].x())); CHECK_EQ(particle.pos.y(), Approx(positions[i].y())); CHECK_EQ(particle.pos.z(), Approx(positions[i].z())); @@ -725,33 +884,41 @@ TEST_CASE("[Faunus] hexagonalPrismToCuboid") { } } -Chameleon::Chameleon(const Variant type) { +Chameleon::Chameleon(const Variant type) +{ makeGeometry(type); _setLength(geometry->getLength()); } -Chameleon::Chameleon(const GeometryImplementation &geo, const Variant type) : geometry(geo.clone()), _type(type) { +Chameleon::Chameleon(const GeometryImplementation& geo, const Variant type) + : geometry(geo.clone()) + , _type(type) +{ _setLength(geometry->getLength()); } -Point Chameleon::getLength() const { +Point Chameleon::getLength() const +{ assert(geometry); return geometry->getLength(); } -void Chameleon::setLength(const Point &l) { +void Chameleon::setLength(const Point& l) +{ assert(geometry); _setLength(l); // ugly if (type == Variant::CUBOID) { - auto &cuboid = dynamic_cast(*geometry); + auto& cuboid = dynamic_cast(*geometry); cuboid.setLength(l); - } else { + } + else { throw std::runtime_error("setLength allowed only for the Cuboid geometry"); } } -void Chameleon::makeGeometry(const Variant type) { +void Chameleon::makeGeometry(const Variant type) +{ switch (type) { case Variant::CUBOID: geometry = std::make_unique(); @@ -780,20 +947,23 @@ void Chameleon::makeGeometry(const Variant type) { _type = type; } -void Chameleon::from_json(const json &j) { +void Chameleon::from_json(const json& j) +{ std::tie(_name, _type) = variantName(j); makeGeometry(_type); geometry->from_json(j); _setLength(geometry->getLength()); } -void Chameleon::to_json(json &j) const { +void Chameleon::to_json(json& j) const +{ assert(geometry); geometry->to_json(j); j["type"] = name; } -void Chameleon::_setLength(const Point &l) { +void Chameleon::_setLength(const Point& l) +{ len = l; len_half = l * 0.5; len_inv = l.cwiseInverse(); @@ -804,31 +974,40 @@ void Chameleon::_setLength(const Point &l) { // `len` for PBC or zero if not. if (geometry->boundary_conditions.coordinates == Coordinates::ORTHOGONAL) for (int i = 0; i < 3; i++) - len_or_zero[i] = len[i] * (geometry->boundary_conditions.direction[i] == Boundary::PERIODIC); + len_or_zero[i] = + len[i] * (geometry->boundary_conditions.direction[i] == Boundary::PERIODIC); } -double Chameleon::getVolume(int dim) const { +double Chameleon::getVolume(int dim) const +{ assert(geometry); return geometry->getVolume(dim); } -Point Chameleon::setVolume(double V, VolumeMethod method) { +Point Chameleon::setVolume(double V, VolumeMethod method) +{ auto scale = geometry->setVolume(V, method); _setLength(geometry->getLength()); return scale; } -Chameleon::VariantName Chameleon::variantName(const std::string &name) { +Chameleon::VariantName Chameleon::variantName(const std::string& name) +{ if (auto it = names.find(name); it == names.end()) { throw std::runtime_error("unknown geometry: " + name); - } else { + } + else { return *it; } } -Chameleon::VariantName Chameleon::variantName(const json &j) { return variantName(j.at("type").get()); } +Chameleon::VariantName Chameleon::variantName(const json& j) +{ + return variantName(j.at("type").get()); +} -Chameleon &Chameleon::operator=(const Chameleon &geo) { +Chameleon& Chameleon::operator=(const Chameleon& geo) +{ if (&geo != this) { GeometryBase::operator=(geo); len = geo.len; @@ -842,15 +1021,29 @@ Chameleon &Chameleon::operator=(const Chameleon &geo) { return *this; } -const BoundaryCondition &Chameleon::boundaryConditions() const { return geometry->boundary_conditions; } +const BoundaryCondition& Chameleon::boundaryConditions() const +{ + return geometry->boundary_conditions; +} -Chameleon::Chameleon(const Chameleon &geo) - : GeometryBase(geo), len(geo.len), len_half(geo.len_half), len_inv(geo.len_inv), - geometry(geo.geometry != nullptr ? geo.geometry->clone() : nullptr), _type(geo._type), _name(geo._name) {} +Chameleon::Chameleon(const Chameleon& geo) + : GeometryBase(geo) + , len(geo.len) + , len_half(geo.len_half) + , len_inv(geo.len_inv) + , geometry(geo.geometry != nullptr ? geo.geometry->clone() : nullptr) + , _type(geo._type) + , _name(geo._name) +{ +} -std::shared_ptr Chameleon::asSimpleGeometry() const { return geometry->clone(); } +std::shared_ptr Chameleon::asSimpleGeometry() const +{ + return geometry->clone(); +} -TEST_CASE("[Faunus] spherical coordinates") { +TEST_CASE("[Faunus] spherical coordinates") +{ using doctest::Approx; Point sph1 = {2, 0.5, -0.3}; @@ -863,11 +1056,13 @@ TEST_CASE("[Faunus] spherical coordinates") { // CHECK_EQ( sph1.z(), Approx(sph2.z())); } -TEST_CASE("[Faunus] Geometry") { +TEST_CASE("[Faunus] Geometry") +{ using doctest::Approx; Random slump; - SUBCASE("cuboid") { + SUBCASE("cuboid") + { double x = 2, y = 3, z = 4; Cuboid geo({x, y, z}); CHECK_EQ(geo.getVolume(), doctest::Approx(x * y * z)); @@ -880,10 +1075,10 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(a.x(), Approx(-0.9)); // x has been wrapped CHECK_EQ(a.y(), Approx(1.5)); // y is unchanged CHECK_EQ(a.z(), Approx(1.999)); // z has been wrapped - a.y() = 1.51; // move y out of box - geo.getBoundaryFunc()(a); // wrap around boundary + a.y() = 1.51; // move y out of box + geo.getBoundaryFunc()(a); // wrap around boundary CHECK_EQ(a.y(), Approx(-1.49)); // check y-boundary - a.y() = 1.5; // restore + a.y() = 1.5; // restore // check distances Point distance = geo.vdist({0.1, 0.5, -1.001}, a); @@ -924,7 +1119,8 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(geo.getVolume(), doctest::Approx(2.5 * 3.5 * 4.5)); } - SUBCASE("slit") { + SUBCASE("slit") + { double x = 2, y = 4, z = 3; Slit geo(x, y, z); CHECK_EQ(geo.getVolume(), doctest::Approx(x * y * z)); @@ -987,7 +1183,8 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(geo.getVolume(), doctest::Approx(2.5 * 3.5 * 4.5)); } - SUBCASE("sphere") { + SUBCASE("sphere") + { double radius = 5.; Sphere geo(radius); CHECK_EQ(geo.getVolume(), doctest::Approx(4. / 3. * pc::pi * radius * radius * radius)); @@ -1033,7 +1230,8 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(geo.getVolume(), doctest::Approx(4. / 3. * pc::pi * 2.0 * 2.0 * 2.0)); } - SUBCASE("cylinder") { + SUBCASE("cylinder") + { double radius = 1., volume = 1.; double height = volume / (pc::pi * radius * radius); Point box; @@ -1077,7 +1275,8 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(geo.getVolume(), doctest::Approx(8.0)); } - SUBCASE("hexagonal prism") { + SUBCASE("hexagonal prism") + { double side = 1., volume = 1.; double outer_radius = side, inner_radius = side * std::sqrt(3.0) / 2.; double height = volume / (3. * outer_radius * inner_radius); @@ -1089,10 +1288,10 @@ TEST_CASE("[Faunus] Geometry") { CHECK_EQ(geo.collision({0.99 * inner_radius, 0, 0}), false); CHECK_EQ(geo.collision({0.0, -1.01 * outer_radius, 0}), true); CHECK_EQ(geo.collision({0.0, 0.99 * outer_radius, 0}), false); - CHECK((geo.collision({0.99 * std::cos(pc::pi / 3.) * inner_radius, 0.99 * std::sin(pc::pi / 3.) * inner_radius, - 0}) == false)); - CHECK((geo.collision({1.01 * std::cos(pc::pi / 3.) * inner_radius, 1.01 * std::sin(pc::pi / 3.) * inner_radius, - 0}) == true)); + CHECK((geo.collision({0.99 * std::cos(pc::pi / 3.) * inner_radius, + 0.99 * std::sin(pc::pi / 3.) * inner_radius, 0}) == false)); + CHECK((geo.collision({1.01 * std::cos(pc::pi / 3.) * inner_radius, + 1.01 * std::sin(pc::pi / 3.) * inner_radius, 0}) == true)); CHECK_EQ(geo.collision({0, 0, -0.51 * height}), true); CHECK_EQ(geo.collision({0, 0, 0.49 * height}), false); @@ -1118,14 +1317,16 @@ TEST_CASE("[Faunus] Geometry") { } } -TEST_CASE("[Faunus] Chameleon") { +TEST_CASE("[Faunus] Chameleon") +{ using doctest::Approx; Random slump; //! function compares if Chamelon's and Geometry's boundary methods produce the same result //! using n random points - auto compare_boundary = [&slump](Chameleon &chameleon, GeometryImplementation &geo, Cuboid &box, int n = 100) { + auto compare_boundary = [&slump](Chameleon& chameleon, GeometryImplementation& geo, Cuboid& box, + int n = 100) { Point a, b; for (int i = 0; i < n; i++) { box.randompos(a, slump); @@ -1140,7 +1341,8 @@ TEST_CASE("[Faunus] Chameleon") { //! function compares if Chamelon's and Geometry's vdist methods produce the same result //! using n random points - auto compare_vdist = [&slump](Chameleon &chameleon, GeometryImplementation &geo, Cuboid &box, int n = 100) { + auto compare_vdist = [&slump](Chameleon& chameleon, GeometryImplementation& geo, Cuboid& box, + int n = 100) { Point a, b, d_cham, d_geo; for (int i = 0; i < n; i++) { box.randompos(a, slump); @@ -1154,7 +1356,8 @@ TEST_CASE("[Faunus] Chameleon") { } }; - SUBCASE("cuboid") { + SUBCASE("cuboid") + { double x = 2.0, y = 3.0, z = 4.0; Point box_size = std::cbrt(2.0) * Point(x, y, z); Cuboid box(box_size); @@ -1167,7 +1370,8 @@ TEST_CASE("[Faunus] Chameleon") { CHECK_EQ(geo.boundary_conditions.isPeriodic()[2], true); } - SUBCASE("slit") { + SUBCASE("slit") + { double x = 2.0, y = 3.0, z = 4.0; Point box_size = std::cbrt(2.0) * Point(x, y, z); Cuboid box(box_size); @@ -1180,7 +1384,8 @@ TEST_CASE("[Faunus] Chameleon") { CHECK_EQ(geo.boundary_conditions.isPeriodic()[2], false); } - SUBCASE("sphere") { + SUBCASE("sphere") + { double radius = 10.0; Point box_size; box_size.setConstant(std::cbrt(2.0) * 2 * radius); @@ -1194,7 +1399,8 @@ TEST_CASE("[Faunus] Chameleon") { CHECK_EQ(geo.boundary_conditions.isPeriodic()[2], false); } - SUBCASE("cylinder") { + SUBCASE("cylinder") + { double radius = 2.0, height = 10.0; Point box_size = std::cbrt(2.0) * Point(2 * radius, 2 * radius, height); Cuboid box(box_size); @@ -1207,9 +1413,11 @@ TEST_CASE("[Faunus] Chameleon") { CHECK_EQ(geo.boundary_conditions.isPeriodic()[2], true); } - SUBCASE("hexagonal prism") { + SUBCASE("hexagonal prism") + { double edge = 5.0, height = 20.0; - Point box_size = std::cbrt(2.0) * Point(2 * edge, 2 * edge, height); // a bit larger in x-direction + Point box_size = + std::cbrt(2.0) * Point(2 * edge, 2 * edge, height); // a bit larger in x-direction Cuboid box(box_size); HexagonalPrism geo(edge, height); Chameleon chameleon(geo, Variant::HEXAGONAL); @@ -1217,7 +1425,8 @@ TEST_CASE("[Faunus] Chameleon") { compare_vdist(chameleon, geo, box); } - SUBCASE("truncated octahedron") { + SUBCASE("truncated octahedron") + { double edge = 5.0; Point box_size; box_size.setConstant(std::cbrt(2.0) * edge * std::sqrt(5.0 / 2.0)); // enlarged circumradius @@ -1228,7 +1437,8 @@ TEST_CASE("[Faunus] Chameleon") { compare_vdist(chameleon, geo, box); } - SUBCASE("Cereal serialisation") { + SUBCASE("Cereal serialisation") + { double x = 2.0, y = 3.0, z = 4.0; std::ostringstream os(std::stringstream::binary); { // write @@ -1249,7 +1459,8 @@ TEST_CASE("[Faunus] Chameleon") { } } -TEST_CASE("[Faunus] weightedCenter") { +TEST_CASE("[Faunus] weightedCenter") +{ Chameleon cyl = json({{"type", "cuboid"}, {"length", 100}, {"radius", 20}}); std::vector p; @@ -1266,7 +1477,8 @@ TEST_CASE("[Faunus] weightedCenter") { CHECK_EQ(cm.z(), doctest::Approx(0)); } -TEST_CASE("[Faunus] gyration") { +TEST_CASE("[Faunus] gyration") +{ std::vector positions = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}; std::vector weights = {0.6, 1.5, 2.2}; auto boundary = [](auto&) {}; @@ -1285,14 +1497,17 @@ TEST_CASE("[Faunus] gyration") { auto mass_center = Geometry::massCenter(particles.begin(), particles.end(), boundary); - SUBCASE("mass center") { + SUBCASE("mass center") + { CHECK_EQ(mass_center.x(), doctest::Approx(5.1162790698)); CHECK_EQ(mass_center.y(), doctest::Approx(6.1162790698)); CHECK_EQ(mass_center.z(), doctest::Approx(7.1162790698)); } - SUBCASE("position based") { - auto gyration = Geometry::gyration(positions.begin(), positions.end(), weights.begin(), mass_center, boundary); + SUBCASE("position based") + { + auto gyration = Geometry::gyration(positions.begin(), positions.end(), weights.begin(), + mass_center, boundary); CHECK_EQ(gyration.trace(), doctest::Approx(13.843158464)); CHECK_EQ(gyration.diagonal().x(), doctest::Approx(4.6143861547)); CHECK_EQ(gyration.diagonal().y(), doctest::Approx(4.6143861547)); @@ -1314,8 +1529,10 @@ TEST_CASE("[Faunus] gyration") { CHECK_EQ(principle_axis.z(), doctest::Approx(0.4082482905)); } - SUBCASE("particle based") { - auto gyration = Geometry::gyration(particles.begin(), particles.end(), mass_center, boundary); + SUBCASE("particle based") + { + auto gyration = + Geometry::gyration(particles.begin(), particles.end(), mass_center, boundary); CHECK_EQ(gyration.trace(), doctest::Approx(13.843158464)); CHECK_EQ(gyration.diagonal().x(), doctest::Approx(4.6143861547)); CHECK_EQ(gyration.diagonal().y(), doctest::Approx(4.6143861547)); @@ -1328,7 +1545,8 @@ TEST_CASE("[Faunus] gyration") { } } -TEST_CASE("[Faunus] rootMeanSquareDeviation") { +TEST_CASE("[Faunus] rootMeanSquareDeviation") +{ std::vector v1 = {1.3, 4.4, -1.1}; std::vector v2 = {1.1, 4.6, -1.0}; auto f = [](double a, double b) { return std::pow(a - b, 2); }; @@ -1344,13 +1562,14 @@ TEST_CASE("[Faunus] rootMeanSquareDeviation") { * - https://en.wikipedia.org/wiki/Geodesic_polyhedron * - c++: https://github.com/caosdoar/spheres */ -std::vector TwobodyAngles::fibonacciSphere(const size_t samples) { +std::vector TwobodyAngles::fibonacciSphere(const size_t samples) +{ unsigned int cnt = 0; - const auto phi = pc::pi * (3.0 - std::sqrt(5.0)); // golden angle in radians + const auto phi = pc::pi * (3.0 - std::sqrt(5.0)); // golden angle in radians std::vector unit_points_on_sphere; unit_points_on_sphere.resize(samples); - - for (auto &point: unit_points_on_sphere) { + + for (auto& point : unit_points_on_sphere) { point.y() = 1.0 - 2.0 * (cnt / double(samples - 1)); // y goes from 1 to -1 const auto radius = std::sqrt(1.0 - point.y() * point.y()); // radius at y const auto theta = phi * double(cnt); // golden angle increment @@ -1362,26 +1581,27 @@ std::vector TwobodyAngles::fibonacciSphere(const size_t samples) { return unit_points_on_sphere; } -TwobodyAngles::TwobodyAngles(const double angle_resolution) { +TwobodyAngles::TwobodyAngles(const double angle_resolution) +{ namespace rv = ranges::cpp20::views; const auto number_of_samples = size_t(std::round(4.0 * pc::pi / std::pow(angle_resolution, 2))); const auto points_on_sphere = fibonacciSphere(number_of_samples); - quaternions_1 = - points_on_sphere | - rv::transform([](const auto& axis) { return Eigen::Quaterniond::FromTwoVectors(axis, Point::UnitZ()); }) | - ::ranges::to_vector; + quaternions_1 = points_on_sphere | rv::transform([](const auto& axis) { + return Eigen::Quaterniond::FromTwoVectors(axis, Point::UnitZ()); + }) | + ::ranges::to_vector; - quaternions_2 = - points_on_sphere | - rv::transform([](const auto& axis) { return Eigen::Quaterniond::FromTwoVectors(axis, -Point::UnitZ()); }) | - ::ranges::to_vector; + quaternions_2 = points_on_sphere | rv::transform([](const auto& axis) { + return Eigen::Quaterniond::FromTwoVectors(axis, -Point::UnitZ()); + }) | + ::ranges::to_vector; - dihedrals = - arange(0.0, 2.0 * pc::pi, angle_resolution) | - rv::transform([](auto angle) { return Eigen::Quaterniond(Eigen::AngleAxisd(angle, Point::UnitZ())); }) | - ::ranges::to_vector; + dihedrals = arange(0.0, 2.0 * pc::pi, angle_resolution) | rv::transform([](auto angle) { + return Eigen::Quaterniond(Eigen::AngleAxisd(angle, Point::UnitZ())); + }) | + ::ranges::to_vector; const auto n1 = quaternions_1.size(); const auto n2 = quaternions_2.size(); @@ -1398,17 +1618,21 @@ TwobodyAngles::TwobodyAngles(const double angle_resolution) { } } -size_t TwobodyAngles::size() const { +size_t TwobodyAngles::size() const +{ return quaternions_1.size() * quaternions_2.size() * dihedrals.size(); } -TwobodyAnglesState::TwobodyAnglesState(const double angle_resolution) : TwobodyAngles(angle_resolution) { +TwobodyAnglesState::TwobodyAnglesState(const double angle_resolution) + : TwobodyAngles(angle_resolution) +{ q_euler1 = quaternions_1.begin(); q_euler2 = quaternions_1.begin(); q_dihedral = dihedrals.begin(); } -TwobodyAnglesState& TwobodyAnglesState::advance() { +TwobodyAnglesState& TwobodyAnglesState::advance() +{ if (q_euler1 < quaternions_1.end()) { q_dihedral++; if (q_dihedral >= dihedrals.end()) { @@ -1424,9 +1648,11 @@ TwobodyAnglesState& TwobodyAnglesState::advance() { } /** - * @return Pair of quaternions, one for each body, or `std::nullopt` of all angles have been explored. + * @return Pair of quaternions, one for each body, or `std::nullopt` of all angles have been + * explored. */ -std::optional> TwobodyAnglesState::get() { +std::optional> TwobodyAnglesState::get() +{ if (q_euler1 < quaternions_1.end()) { return std::make_pair(*q_euler1, (*q_dihedral) * (*q_euler2)); } diff --git a/src/geometry.h b/src/geometry.h index 6409197d5..187506717 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -23,30 +23,49 @@ class Random; /** * @brief Simulation geometries and related operations. * - * Other parts of Faunus use directly only Chameleon geometry which serves as an interface. Based on the provided - * configuration, Chameleon initializes an appropriate concrete implementation, which it encapsulates. + * Other parts of Faunus use directly only Chameleon geometry which serves as an interface. Based on + * the provided configuration, Chameleon initializes an appropriate concrete implementation, which + * it encapsulates. * - * To add a new geometry implementation, a class derived from GeometryImplementation is created. Geometry::Variant - * enum type is extended and an initialization within Chameleon::makeGeometry() is provided. In order to make - * geometry constructable from a json configuration, the map Chameleon::names is extended. When performance is - * an issue, inlineable implementation of vdist and boundary can be added into respective methods of Chameleon. + * To add a new geometry implementation, a class derived from GeometryImplementation is created. + * Geometry::Variant enum type is extended and an initialization within Chameleon::makeGeometry() is + * provided. In order to make geometry constructable from a json configuration, the map + * Chameleon::names is extended. When performance is an issue, inlineable implementation of vdist + * and boundary can be added into respective methods of Chameleon. * * All geometry implementation shall be covered by unit tests. * */ namespace Geometry { -//! Function to apply PBC to a position, i.e. wrap around the borders if applicable for the given container geometry -typedef std::function BoundaryFunction; +//! Function to apply PBC to a position, i.e. wrap around the borders if applicable for the given +//! container geometry +typedef std::function BoundaryFunction; //! Function to calculate the (minimum) distance between two points depending on contained geometry -typedef std::function DistanceFunction; +typedef std::function DistanceFunction; //! Geometry variant used for Chameleon -enum class Variant { CUBOID = 0, SPHERE, CYLINDER, SLIT, HEXAGONAL, OCTAHEDRON, HYPERSPHERE2D }; +enum class Variant +{ + CUBOID = 0, + SPHERE, + CYLINDER, + SLIT, + HEXAGONAL, + OCTAHEDRON, + HYPERSPHERE2D +}; //! Various methods of volume scaling, @see GeometryBase::setVolume. -enum class VolumeMethod { ISOTROPIC, ISOCHORIC, XY, Z, INVALID }; +enum class VolumeMethod +{ + ISOTROPIC, + ISOCHORIC, + XY, + Z, + INVALID +}; NLOHMANN_JSON_SERIALIZE_ENUM(VolumeMethod, {{VolumeMethod::INVALID, nullptr}, {VolumeMethod::ISOTROPIC, "isotropic"}, @@ -54,15 +73,26 @@ NLOHMANN_JSON_SERIALIZE_ENUM(VolumeMethod, {{VolumeMethod::INVALID, nullptr}, {VolumeMethod::XY, "xy"}, {VolumeMethod::Z, "z"}}) -enum class Coordinates { ORTHOGONAL, ORTHOHEXAGONAL, TRUNC_OCTAHEDRAL, NON3D }; -enum class Boundary : int { FIXED = 0, PERIODIC = 1 }; +enum class Coordinates +{ + ORTHOGONAL, + ORTHOHEXAGONAL, + TRUNC_OCTAHEDRAL, + NON3D +}; +enum class Boundary : int +{ + FIXED = 0, + PERIODIC = 1 +}; /** * @brief A structure containing a type of boundary condition in each direction. * * A stub. It can be extended to fully json-configurable boundary conditions. */ -class BoundaryCondition { +class BoundaryCondition +{ public: using BoundaryXYZ = Eigen::Matrix; // typedef std::pair BoundaryName; @@ -71,42 +101,50 @@ class BoundaryCondition { Coordinates coordinates; BoundaryXYZ direction; - template void serialize(Archive& archive) { + template void serialize(Archive& archive) + { archive(coordinates, direction); } //!< Cereal serialisation [[nodiscard]] Eigen::Matrix isPeriodic() const; explicit BoundaryCondition(Coordinates coordinates = Coordinates::ORTHOGONAL, - BoundaryXYZ boundary = {Boundary::FIXED, Boundary::FIXED, Boundary::FIXED}) - : coordinates(coordinates), direction(std::move(boundary)){}; + BoundaryXYZ boundary = {Boundary::FIXED, Boundary::FIXED, + Boundary::FIXED}) + : coordinates(coordinates) + , direction(std::move(boundary)) {}; }; /** * @brief An interface for all geometries. */ -struct GeometryBase { +struct GeometryBase +{ virtual Point setVolume(double, VolumeMethod = VolumeMethod::ISOTROPIC) = 0; //!< Set volume [[nodiscard]] virtual double getVolume(int = 3) const = 0; //!< Get volume - virtual void boundary(Point &) const = 0; //!< Apply boundary conditions - [[nodiscard]] virtual bool collision(const Point &) const = 0; //!< Overlap with boundaries - virtual void randompos(Point &, Random &) const = 0; //!< Generate random position - [[nodiscard]] virtual Point vdist(const Point& a, const Point& b) const = 0; //!< Minimum distance vector b->a - [[nodiscard]] virtual Point getLength() const = 0; //!< Side lengths + virtual void boundary(Point&) const = 0; //!< Apply boundary conditions + [[nodiscard]] virtual bool collision(const Point&) const = 0; //!< Overlap with boundaries + virtual void randompos(Point&, Random&) const = 0; //!< Generate random position + [[nodiscard]] virtual Point vdist(const Point& a, + const Point& b) const = 0; //!< Minimum distance vector b->a + [[nodiscard]] virtual Point getLength() const = 0; //!< Side lengths virtual ~GeometryBase(); - virtual void to_json(json &j) const = 0; - virtual void from_json(const json &j) = 0; + virtual void to_json(json& j) const = 0; + virtual void from_json(const json& j) = 0; - [[nodiscard]] inline BoundaryFunction getBoundaryFunc() const { - return [this](Point &i) { boundary(i); }; + [[nodiscard]] inline BoundaryFunction getBoundaryFunc() const + { + return [this](Point& i) { boundary(i); }; } //!< Lambda for applying boundary conditions on a point - [[nodiscard]] inline DistanceFunction getDistanceFunc() const { - return [this](const Point &i, const Point &j) { return vdist(i, j); }; + [[nodiscard]] inline DistanceFunction getDistanceFunc() const + { + return [this](const Point& i, const Point& j) { return vdist(i, j); }; } //!< Lambda for calculating the (minimum) distance vector between two positions protected: - template [[nodiscard]] inline int anint(T x) const { + template [[nodiscard]] inline int anint(T x) const + { return int(x > 0.0 ? x + 0.5 : x - 0.5); } //!< Round to int @@ -115,7 +153,8 @@ struct GeometryBase { /** * @brief A base class for various geometries implementations. */ -class GeometryImplementation : public GeometryBase { +class GeometryImplementation : public GeometryBase +{ public: BoundaryCondition boundary_conditions; @@ -125,58 +164,66 @@ class GeometryImplementation : public GeometryBase { [[nodiscard]] virtual std::unique_ptr clone() const = 0; //! Cereal serialisation - template void serialize(Archive &archive) { archive(boundary_conditions); } + template void serialize(Archive& archive) { archive(boundary_conditions); } }; /** - * @brief The cuboid geometry with periodic boundary conditions possibly applied in all three directions. + * @brief The cuboid geometry with periodic boundary conditions possibly applied in all three + * directions. */ -class Cuboid : public GeometryImplementation { +class Cuboid : public GeometryImplementation +{ protected: Point box, box_half, box_inv; public: Point getLength() const override; double getVolume(int dim = 3) const final; // finalized to help the compiler with inlining - void setLength(const Point &len); // todo shall be protected + void setLength(const Point& len); // todo shall be protected Point setVolume(double volume, VolumeMethod method = VolumeMethod::ISOTROPIC) override; Point vdist(const Point& a, const Point& b) const override; //!< Minimum distance vector b->a - void boundary(Point &a) const override; - bool collision(const Point &a) const override; - void randompos(Point &m, Random &rand) const override; - void from_json(const json &j) override; - void to_json(json &j) const override; - explicit Cuboid(const Point &side_length); + void boundary(Point& a) const override; + bool collision(const Point& a) const override; + void randompos(Point& m, Random& rand) const override; + void from_json(const json& j) override; + void to_json(json& j) const override; + explicit Cuboid(const Point& side_length); Cuboid(); - [[nodiscard]] std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + [[nodiscard]] std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. //! Cereal serialisation - template void serialize(Archive &archive) { + template void serialize(Archive& archive) + { archive(cereal::base_class(this), box); } }; /** - * @brief A legacy class for the cuboid geometry with periodic boundary conditions only in xy directions. + * @brief A legacy class for the cuboid geometry with periodic boundary conditions only in xy + * directions. * * @deprecated Shall be replaced by Cuboid with a proper periodic boundary set on initialization. */ -class Slit : public Cuboid { +class Slit : public Cuboid +{ using Tbase = Cuboid; public: - explicit Slit(const Point &p); + explicit Slit(const Point& p); Slit(double x, double y, double z); explicit Slit(double x = 0.0); - [[nodiscard]] std::unique_ptr clone() const override; //!< A unique pointer to a copy of itself. + [[nodiscard]] std::unique_ptr + clone() const override; //!< A unique pointer to a copy of itself. }; /** * @brief The spherical geometry where no periodic boundary condition could be applied. */ -class Sphere : public GeometryImplementation { +class Sphere : public GeometryImplementation +{ protected: double radius; @@ -185,37 +232,45 @@ class Sphere : public GeometryImplementation { double getVolume(int dim = 3) const override; Point setVolume(double volume, VolumeMethod method = VolumeMethod::ISOTROPIC) override; Point vdist(const Point& a, const Point& b) const override; //!< Minimum distance vector b->a - inline static double sqdist(const Point &a, const Point &b) { return (a - b).squaredNorm(); }; - void boundary(Point &a) const override; - bool collision(const Point &point) const override; - void randompos(Point &m, Random &rand) const override; - void from_json(const json &j) override; - void to_json(json &j) const override; + + inline static double sqdist(const Point& a, const Point& b) { return (a - b).squaredNorm(); }; + + void boundary(Point& a) const override; + bool collision(const Point& point) const override; + void randompos(Point& m, Random& rand) const override; + void from_json(const json& j) override; + void to_json(json& j) const override; explicit Sphere(double radius = 0.0); double getRadius() const; - std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. //! Cereal serialisation - template void serialize(Archive &archive) { + template void serialize(Archive& archive) + { archive(cereal::base_class(this), radius); } }; -class Hypersphere2d : public Sphere { +class Hypersphere2d : public Sphere +{ public: - Point vdist(const Point &a, const Point &b) const override; - bool collision(const Point &a) const override; - void randompos(Point &m, Random &rand) const override; + Point vdist(const Point& a, const Point& b) const override; + bool collision(const Point& a) const override; + void randompos(Point& m, Random& rand) const override; explicit Hypersphere2d(double radius = 0.0); - std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. }; /** - * @brief The cylindrical geometry with periodic boundary conditions in z-axis (the height of the cylinder). + * @brief The cylindrical geometry with periodic boundary conditions in z-axis (the height of the + * cylinder). */ -class Cylinder : public GeometryImplementation { +class Cylinder : public GeometryImplementation +{ protected: double radius, height; @@ -223,18 +278,20 @@ class Cylinder : public GeometryImplementation { Point getLength() const override; double getVolume(int dim = 3) const override; Point setVolume(double volume, VolumeMethod method = VolumeMethod::ISOTROPIC) override; - Point vdist(const Point &a, const Point &b) const override; - void boundary(Point &a) const override; - bool collision(const Point &a) const override; - void randompos(Point &m, Random &rand) const override; - void from_json(const json &j) override; - void to_json(json &j) const override; + Point vdist(const Point& a, const Point& b) const override; + void boundary(Point& a) const override; + bool collision(const Point& a) const override; + void randompos(Point& m, Random& rand) const override; + void from_json(const json& j) override; + void to_json(json& j) const override; explicit Cylinder(double radius = 0.0, double height = 0.0); - [[nodiscard]] std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + [[nodiscard]] std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. //! Cereal serialisation - template void serialize(Archive &archive) { + template void serialize(Archive& archive) + { archive(cereal::base_class(this), radius, height); } }; @@ -242,14 +299,15 @@ class Cylinder : public GeometryImplementation { /** * @brief The hexagonal prism geometry with periodic boundary conditions. * - * The prism is oriented in the coordination system as follows: z height, xy base β¬’ with a shorter length - * (the diameter of an inscribed circle d = 2r) in x direction, and a longer length (the diameter of a - * circumscribed circle D = 2R) in y direction. + * The prism is oriented in the coordination system as follows: z height, xy base β¬’ with a shorter + * length (the diameter of an inscribed circle d = 2r) in x direction, and a longer length (the + * diameter of a circumscribed circle D = 2R) in y direction. */ -class HexagonalPrism : public GeometryImplementation { +class HexagonalPrism : public GeometryImplementation +{ //! Change matrices from rhombic to cartesian coordinates and back. - //! The Y (and Z) axis is identical in both coordination systems, while the X-axis is tilted by -30deg - //! (i.e., clockwise), forming a 120deg angle with the Y axis in the rhombic coordinates. + //! The Y (and Z) axis is identical in both coordination systems, while the X-axis is tilted by + //! -30deg (i.e., clockwise), forming a 120deg angle with the Y axis in the rhombic coordinates. static const Eigen::Matrix3d rhombic2cartesian; static const Eigen::Matrix3d cartesian2rhombic; @@ -260,18 +318,20 @@ class HexagonalPrism : public GeometryImplementation { Point getLength() const override; double getVolume(int dim = 3) const override; Point setVolume(double volume, VolumeMethod method = VolumeMethod::ISOTROPIC) override; - Point vdist(const Point &a, const Point &b) const override; - void boundary(Point &a) const override; - bool collision(const Point &a) const override; - void randompos(Point &m, Random &rand) const override; - void from_json(const json &j) override; - void to_json(json &j) const override; + Point vdist(const Point& a, const Point& b) const override; + void boundary(Point& a) const override; + bool collision(const Point& a) const override; + void randompos(Point& m, Random& rand) const override; + void from_json(const json& j) override; + void to_json(json& j) const override; explicit HexagonalPrism(double side = 0.0, double height = 0.0); - [[nodiscard]] std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + [[nodiscard]] std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. //! Cereal serialisation - template void serialize(Archive &archive) { + template void serialize(Archive& archive) + { archive(cereal::base_class(this), box); } @@ -283,37 +343,40 @@ class HexagonalPrism : public GeometryImplementation { /** * @brief The truncated octahedron geoemtry with periodic boundary conditions in all directions. */ -class TruncatedOctahedron : public GeometryImplementation { +class TruncatedOctahedron : public GeometryImplementation +{ double side; public: Point getLength() const override; double getVolume(int dim = 3) const override; Point setVolume(double volume, VolumeMethod method = VolumeMethod::ISOTROPIC) override; - Point vdist(const Point &a, const Point &b) const override; - void boundary(Point &a) const override; - bool collision(const Point &a) const override; - void randompos(Point &pos, Random &rand) const override; - void from_json(const json &j) override; - void to_json(json &j) const override; + Point vdist(const Point& a, const Point& b) const override; + void boundary(Point& a) const override; + bool collision(const Point& a) const override; + void randompos(Point& pos, Random& rand) const override; + void from_json(const json& j) override; + void to_json(json& j) const override; explicit TruncatedOctahedron(double side = 0.0); - [[nodiscard]] std::unique_ptr clone() const override; //!< A unique pointer to a copy of self. + [[nodiscard]] std::unique_ptr + clone() const override; //!< A unique pointer to a copy of self. //! Cereal serialisation - template void serialize(Archive &archive) { + template void serialize(Archive& archive) + { archive(cereal::base_class(this), side); } }; /** - * @brief Geometry class for spheres, cylinders, cuboids, hexagonal prism, truncated octahedron, slits. It is - * a wrapper of a concrete geometry implementation. + * @brief Geometry class for spheres, cylinders, cuboids, hexagonal prism, truncated octahedron, + * slits. It is a wrapper of a concrete geometry implementation. * - * The class re-implements the time-critical functions vdist and boundary for the orthogonal periodic boundary - * conditions. Hence the call can be inlined by the compiler. That would not be possible otherwise due to the - * polymorphism of the concrete implementations. Other functions calls are delegated directly to the concrete - * implementation. + * The class re-implements the time-critical functions vdist and boundary for the orthogonal + * periodic boundary conditions. Hence the call can be inlined by the compiler. That would not be + * possible otherwise due to the polymorphism of the concrete implementations. Other functions calls + * are delegated directly to the concrete implementation. * * Note that the class implements a copy constructor and overloads the assignment operator. * @@ -323,66 +386,74 @@ class TruncatedOctahedron : public GeometryImplementation { * * @todo Implement unit tests */ -class Chameleon : public GeometryBase { +class Chameleon : public GeometryBase +{ private: Point len_or_zero = {0, 0, 0}; //!< Box length (if PBC) or zero (if no PBC) in given direction - Point len, len_half, len_inv; //!< Cached box dimensions, their half-values, and reciprocal values. - std::unique_ptr geometry = nullptr; //!< A concrete geometry implementation. - Variant _type; //!< Type of concrete geometry. - std::string _name; //!< Name of concrete geometry, e.g., for json. + Point len, len_half, + len_inv; //!< Cached box dimensions, their half-values, and reciprocal values. + std::unique_ptr geometry = + nullptr; //!< A concrete geometry implementation. + Variant _type; //!< Type of concrete geometry. + std::string _name; //!< Name of concrete geometry, e.g., for json. void - makeGeometry(const Variant type = Variant::CUBOID); //!< Creates and assigns a concrete geometry implementation. - void _setLength(const Point &l); + makeGeometry(const Variant type = + Variant::CUBOID); //!< Creates and assigns a concrete geometry implementation. + void _setLength(const Point& l); public: - const Variant &type = _type; //!< Type of concrete geometry, read-only. - const std::string &name = _name; //!< Name of concrete geometry, e.g., for json, read-only. + const Variant& type = _type; //!< Type of concrete geometry, read-only. + const std::string& name = _name; //!< Name of concrete geometry, e.g., for json, read-only. double getVolume(int dim = 3) const override; Point setVolume(double, VolumeMethod = VolumeMethod::ISOTROPIC) override; Point getLength() const override; //!< A minimal containing cubic box. // setLength() needed only for Move::ReplayMove (stems from IO::XTCReader). - void setLength(const Point &); //!< Sets the box dimensions. - void boundary(Point &) const override; //!< Apply boundary conditions - Point vdist(const Point&, const Point&) const override; //!< Minimum distance vector b->a - double sqdist(const Point &, const Point &) const; //!< (Minimum) squared distance between two points - void randompos(Point &, Random &) const override; - bool collision(const Point &) const override; - void from_json(const json &) override; - void to_json(json &j) const override; - - const BoundaryCondition &boundaryConditions() const; //!< Get info on boundary conditions + void setLength(const Point&); //!< Sets the box dimensions. + void boundary(Point&) const override; //!< Apply boundary conditions + Point vdist(const Point&, const Point&) const override; //!< Minimum distance vector b->a + double sqdist(const Point&, + const Point&) const; //!< (Minimum) squared distance between two points + void randompos(Point&, Random&) const override; + bool collision(const Point&) const override; + void from_json(const json&) override; + void to_json(json& j) const override; + + const BoundaryCondition& boundaryConditions() const; //!< Get info on boundary conditions static const std::map names; //!< Geometry names. typedef std::pair VariantName; - static VariantName variantName(const std::string &name); + static VariantName variantName(const std::string& name); - static VariantName variantName(const json &j); + static VariantName variantName(const json& j); explicit Chameleon(const Variant type = Variant::CUBOID); - Chameleon(const GeometryImplementation &geo, Variant type); + Chameleon(const GeometryImplementation& geo, Variant type); //! Copy everything, but clone the geometry. - Chameleon(const Chameleon &geo); + Chameleon(const Chameleon& geo); //! During the assignment copy everything, but clone the geometry. - Chameleon &operator=(const Chameleon &geo); + Chameleon& operator=(const Chameleon& geo); [[nodiscard]] std::shared_ptr asSimpleGeometry() const; }; -inline void Chameleon::randompos(Point &m, Random &rand) const { +inline void Chameleon::randompos(Point& m, Random& rand) const +{ assert(geometry); geometry->randompos(m, rand); } -inline bool Chameleon::collision(const Point &a) const { +inline bool Chameleon::collision(const Point& a) const +{ assert(geometry); return geometry->collision(a); } -inline void Chameleon::boundary(Point &a) const { - const auto &boundary_conditions = geometry->boundary_conditions; +inline void Chameleon::boundary(Point& a) const +{ + const auto& boundary_conditions = geometry->boundary_conditions; if (boundary_conditions.coordinates == Coordinates::ORTHOGONAL) { if (boundary_conditions.direction.x() == Boundary::PERIODIC) { if (std::fabs(a.x()) > len_half.x()) @@ -396,14 +467,16 @@ inline void Chameleon::boundary(Point &a) const { if (std::fabs(a.z()) > len_half.z()) a.z() -= len.z() * anint(a.z() * len_inv.z()); } - } else { + } + else { geometry->boundary(a); } } -inline Point Chameleon::vdist(const Point &a, const Point &b) const { +inline Point Chameleon::vdist(const Point& a, const Point& b) const +{ Point distance; - const auto &boundary_conditions = geometry->boundary_conditions; + const auto& boundary_conditions = geometry->boundary_conditions; if (boundary_conditions.coordinates == Coordinates::ORTHOGONAL) { distance = a - b; if (boundary_conditions.direction.x() == Boundary::PERIODIC) { @@ -424,34 +497,49 @@ inline Point Chameleon::vdist(const Point &a, const Point &b) const { else if (distance.z() < -len_half.z()) distance.z() += len.z(); } - } else { + } + else { distance = geometry->vdist(a, b); } return distance; } -inline double Chameleon::sqdist(const Point &a, const Point &b) const { +inline double Chameleon::sqdist(const Point& a, const Point& b) const +{ if (geometry->boundary_conditions.coordinates == Coordinates::ORTHOGONAL) { if constexpr (true) { Point d((a - b).cwiseAbs()); - return (d - (d.array() > len_half.array()).cast().matrix().cwiseProduct(len_or_zero)).squaredNorm(); - } else { // more readable alternative(?), nearly same speed + return (d - (d.array() > len_half.array()) + .cast() + .matrix() + .cwiseProduct(len_or_zero)) + .squaredNorm(); + } + else { // more readable alternative(?), nearly same speed Point d(a - b); for (int i = 0; i < 3; ++i) { d[i] = std::fabs(d[i]); - d[i] = d[i] - len_or_zero[i] * static_cast(d[i] > len_half[i]); // casting faster than branching + d[i] = d[i] - + len_or_zero[i] * + static_cast(d[i] > len_half[i]); // casting faster than branching } return d[0] * d[0] + d[1] * d[1] + d[2] * d[2]; } - } else { + } + else { return geometry->vdist(a, b).squaredNorm(); } } -void to_json(json &, const Chameleon &); -void from_json(const json &, Chameleon &); +void to_json(json&, const Chameleon&); +void from_json(const json&, Chameleon&); -enum class weight { MASS, CHARGE, GEOMETRIC }; +enum class weight +{ + MASS, + CHARGE, + GEOMETRIC +}; /** * @brief Calculates the (weighted) center for a set of positions @@ -462,8 +550,9 @@ enum class weight { MASS, CHARGE, GEOMETRIC }; */ template Point weightedCenter( - const Positions& positions, const Weights& weights, Geometry::BoundaryFunction boundary = [](auto&) {}, - const Point& shift = Point::Zero()) { + const Positions& positions, const Weights& weights, + Geometry::BoundaryFunction boundary = [](auto&) {}, const Point& shift = Point::Zero()) +{ double weight_sum = 0.0; Point center(0.0, 0.0, 0.0); for (const auto& [position, weight] : ranges::views::zip(positions, weights)) { @@ -476,25 +565,30 @@ Point weightedCenter( center = center / weight_sum - shift; // translate back boundary(center); return center; - } else { + } + else { faunus_logger->warn("warning: sum of weights is zero! setting center to (0,0,0)"); return Point::Zero(); } } /** - * @brief Calculate Center of particle range using arbitrary weight functions applied to each particle + * @brief Calculate Center of particle range using arbitrary weight functions applied to each + * particle * @param begin Begin particle iterator * @param end End parti cle iterator * @param boundary Boundary function to apply PBC (default: no PBC) * @param weight_function Functor return weight for a given particle - * @param shift Shift by this vector before calculating center, then add again. For PBC removal; default: 0,0,0 + * @param shift Shift by this vector before calculating center, then add again. For PBC removal; + * default: 0,0,0 * @return Center position; (0,0,0) if the sum of weights is zero * @throw warning if the sum of weights is zero, thereby hampering normalization */ template //, typename weightFunc> Point weightedCenter(iterator begin, iterator end, BoundaryFunction boundary, - std::function weight_function, const Point& shift = Point::Zero()) { + std::function weight_function, + const Point& shift = Point::Zero()) +{ namespace rv = ranges::cpp20::views; auto particles = ranges::make_subrange(begin, end); auto positions = particles | rv::transform(&Particle::pos); @@ -507,14 +601,16 @@ Point weightedCenter(iterator begin, iterator end, BoundaryFunction boundary, * @param begin Begin particle iterator * @param end End particle iterator * @param apply_boundary Boundary function to apply PBC (default: no PBC) - * @param shift Shift by this vector before calculating center, then add again. For PBC removal; default: 0,0,0 + * @param shift Shift by this vector before calculating center, then add again. For PBC removal; + * default: 0,0,0 * @return Mass center position * @throws if the sum of masses is zero, thereby hampering normalization */ template Point massCenter( iterator begin, iterator end, BoundaryFunction apply_boundary = [](Point&) {}, - const Point& shift = {0.0, 0.0, 0.0}) { + const Point& shift = {0.0, 0.0, 0.0}) +{ auto particle_mass = [](const auto& particle) -> double { return particle.traits().mw; }; return weightedCenter(begin, end, apply_boundary, particle_mass, shift); } @@ -527,7 +623,9 @@ Point massCenter( */ template void translate( - iterator begin, iterator end, const Point& displacement, BoundaryFunction apply_boundary = [](auto&) {}) { + iterator begin, iterator end, const Point& displacement, + BoundaryFunction apply_boundary = [](auto&) {}) +{ std::for_each(begin, end, [&](auto& particle) { particle.pos += displacement; apply_boundary(particle.pos); @@ -541,8 +639,8 @@ void translate( * @param apply_boundary Boundary function to apply PBC (default: none) */ template -void translateToOrigin( - iterator begin, iterator end, BoundaryFunction apply_boundary = [](auto&) {}) { +void translateToOrigin(iterator begin, iterator end, BoundaryFunction apply_boundary = [](auto&) {}) +{ Point cm = massCenter(begin, end, apply_boundary); translate(begin, end, -cm, apply_boundary); } @@ -556,12 +654,14 @@ void translateToOrigin( * @param shift This value is added before rotation to aid PBC remove (default: 0,0,0) * @todo Currently both quaternion and rotation matrix are passed, but one of them should be enough * - * This will rotate both positions and internal coordinates in the particle (dipole moments, tensors etc.) + * This will rotate both positions and internal coordinates in the particle (dipole moments, tensors + * etc.) */ template void rotate( - iterator begin, iterator end, const Eigen::Quaterniond& quaternion, BoundaryFunction apply_boundary = [](auto&) {}, - const Point& shift = Point::Zero()) { + iterator begin, iterator end, const Eigen::Quaterniond& quaternion, + BoundaryFunction apply_boundary = [](auto&) {}, const Point& shift = Point::Zero()) +{ const auto rotation_matrix = quaternion.toRotationMatrix(); // rotation matrix std::for_each(begin, end, [&](auto& particle) { particle.rotate(quaternion, rotation_matrix); // rotate internal coordinates @@ -578,13 +678,15 @@ void rotate( * [More info](http://dx.doi.org/10.1080/2151237X.2008.10129266) */ template -Point trigoCom(const Tspace& spc, const GroupIndex& indices, const std::vector& dir = {0, 1, 2}) { +Point trigoCom(const Tspace& spc, const GroupIndex& indices, + const std::vector& dir = {0, 1, 2}) +{ if (dir.empty() || dir.size() > 3) { throw std::out_of_range("invalid directions"); } namespace rv = ranges::cpp20::views; - auto positions = - indices | rv::transform([&](auto i) { return spc.groups.at(i); }) | rv::join | rv::transform(&Particle::pos); + auto positions = indices | rv::transform([&](auto i) { return spc.groups.at(i); }) | rv::join | + rv::transform(&Particle::pos); Point xhi(0, 0, 0); Point zeta(0, 0, 0); Point theta(0, 0, 0); @@ -598,7 +700,9 @@ Point trigoCom(const Tspace& spc, const GroupIndex& indices, const std::vector(cnt), -xhi[k] / static_cast(cnt)) + pc::pi; + theta[k] = + std::atan2(-zeta[k] / static_cast(cnt), -xhi[k] / static_cast(cnt)) + + pc::pi; com[k] = spc.geometry.getLength()[k] * theta[k] / (2.0 * pc::pi); } spc.geometry.boundary(com); // is this really needed? @@ -608,8 +712,8 @@ Point trigoCom(const Tspace& spc, const GroupIndex& indices, const std::vector Tensor gyration( position_iterator begin, position_iterator end, mass_iterator mass, const Point& mass_center, - const BoundaryFunction boundary = [](auto&) {}) { + const BoundaryFunction boundary = [](auto&) {}) +{ Tensor S = Tensor::Zero(); double total_mass = 0.0; std::for_each(begin, end, [&](const Point& position) { @@ -649,10 +754,9 @@ Tensor gyration( /** * @brief Calculates a gyration tensor of a range of particles * - * The gyration tensor is computed from the atomic position vectors with respect to the reference point - * which is always a center of mass, - * \f$ t_{i} = r_{i} - r_\mathrm{cm} \f$: - * \f$ S = (1 / \sum_{i=1}^{N} m_{i}) \sum_{i=1}^{N} m_{i} t_{i} t_{i}^{T} \f$ + * The gyration tensor is computed from the atomic position vectors with respect to the reference + * point which is always a center of mass, \f$ t_{i} = r_{i} - r_\mathrm{cm} \f$: \f$ S = (1 / + * \sum_{i=1}^{N} m_{i}) \sum_{i=1}^{N} m_{i} t_{i} t_{i}^{T} \f$ * * Before the calculation, the molecule is made whole to moving it to the center or the * simulation box (0,0,0), then apply the given boundary function. @@ -667,7 +771,9 @@ Tensor gyration( */ template Tensor gyration( - iterator begin, iterator end, const Point& mass_center, const BoundaryFunction boundary = [](auto&) {}) { + iterator begin, iterator end, const Point& mass_center, + const BoundaryFunction boundary = [](auto&) {}) +{ namespace rv = ranges::cpp20::views; auto particles = ranges::make_subrange(begin, end); auto positions = particles | rv::transform(&Particle::pos); @@ -681,32 +787,37 @@ Tensor gyration( * The class is prepared with operator overloads to work with `AverageObj` * for averaging over multiple tensors */ -struct ShapeDescriptors { +struct ShapeDescriptors +{ double gyration_radius_squared = 0.0; double asphericity = 0.0; double acylindricity = 0.0; - double relative_shape_anisotropy = 0.0; //!< relative shape anisotropy, kappa^2 (0=rod, 1=spherical) + double relative_shape_anisotropy = + 0.0; //!< relative shape anisotropy, kappa^2 (0=rod, 1=spherical) ShapeDescriptors() = default; - ShapeDescriptors(const Tensor &gyration_tensor); //!< Construct using an initial gyration tensor - ShapeDescriptors &operator+=(const ShapeDescriptors &other); //!< Add another gyration tensor; req. for averaging - ShapeDescriptors operator*(const double scale) const; //!< Scale data; req. for averaging + ShapeDescriptors(const Tensor& gyration_tensor); //!< Construct using an initial gyration tensor + ShapeDescriptors& + operator+=(const ShapeDescriptors& other); //!< Add another gyration tensor; req. for averaging + ShapeDescriptors operator*(const double scale) const; //!< Scale data; req. for averaging }; -void to_json(json &j, const ShapeDescriptors &shape); //!< Store Shape as json object +void to_json(json& j, const ShapeDescriptors& shape); //!< Store Shape as json object /** * @brief Calculates an inertia tensor of a molecular group * - * The inertia tensor is computed from the atomic position vectors with respect to a reference point, - * \f$ t_{i} = r_{i} - r_\mathrm{origin} \f$: - * \f$ S = \sum_{i=1}^{N} m_{i} ( t_{i} \cdot t_{i} I - t_{i} t_{i}^{T} ) \f$ + * The inertia tensor is computed from the atomic position vectors with respect to a reference + * point, \f$ t_{i} = r_{i} - r_\mathrm{origin} \f$: \f$ S = \sum_{i=1}^{N} m_{i} ( t_{i} \cdot + * t_{i} I - t_{i} t_{i}^{T} ) \f$ * * @param origin a reference point * @return inertia tensor (a zero tensor for an empty group) */ template Tensor inertia( - iterator begin, iterator end, const Point origin = Point::Zero(), const BoundaryFunction boundary = [](auto&) {}) { + iterator begin, iterator end, const Point origin = Point::Zero(), + const BoundaryFunction boundary = [](auto&) {}) +{ Tensor I = Tensor::Zero(); std::for_each(begin, end, [&](const Particle& particle) { Point t = particle.pos - origin; @@ -723,7 +834,9 @@ Tensor inertia( * in the two sets, for example `[](int a, int b){return a-b;}`. */ template -double rootMeanSquareDeviation(InputIt1 begin, InputIt1 end, InputIt2 d_begin, BinaryOperation diff_squared_func) { +double rootMeanSquareDeviation(InputIt1 begin, InputIt1 end, InputIt2 d_begin, + BinaryOperation diff_squared_func) +{ assert(std::distance(begin, end) > 0); double sq_sum = 0; for (InputIt1 i = begin; i != end; ++i) { @@ -746,7 +859,7 @@ double rootMeanSquareDeviation(InputIt1 begin, InputIt1 end, InputIt2 d_begin, B * are ignored and will be overwritten. * Similar to routine described in doi:10.1021/jp010360o */ -ParticleVector mapParticlesOnSphere(const ParticleVector &); +ParticleVector mapParticlesOnSphere(const ParticleVector&); /** * @brief Convert particles in hexagonal prism to space-filled cuboid @@ -759,48 +872,54 @@ ParticleVector mapParticlesOnSphere(const ParticleVector &); * the number of particles */ std::pair hexagonalPrismToCuboid(const HexagonalPrism& hexagon, - const RequireParticles auto& particles) { + const RequireParticles auto& particles) +{ Cuboid cuboid({2.0 * hexagon.innerRadius(), 3.0 * hexagon.outerRadius(), hexagon.height()}); ParticleVector cuboid_particles; cuboid_particles.reserve(2 * std::distance(particles.begin(), particles.end())); - std::copy(particles.begin(), particles.end(), std::back_inserter(cuboid_particles)); // add central hexagon - - std::transform(particles.begin(), particles.end(), std::back_inserter(cuboid_particles), [&](auto particle) { - particle.pos.x() += hexagon.innerRadius() * (particle.pos.x() > 0.0 ? -1.0 : 1.0); - particle.pos.y() += hexagon.outerRadius() * (particle.pos.y() > 0.0 ? -1.5 : 1.5); - assert(cuboid.collision(particle.pos) == false); - return particle; - }); // add the four corners; i.e. one extra, split hexagon + std::copy(particles.begin(), particles.end(), + std::back_inserter(cuboid_particles)); // add central hexagon + + std::transform(particles.begin(), particles.end(), std::back_inserter(cuboid_particles), + [&](auto particle) { + particle.pos.x() += + hexagon.innerRadius() * (particle.pos.x() > 0.0 ? -1.0 : 1.0); + particle.pos.y() += + hexagon.outerRadius() * (particle.pos.y() > 0.0 ? -1.5 : 1.5); + assert(cuboid.collision(particle.pos) == false); + return particle; + }); // add the four corners; i.e. one extra, split hexagon assert(std::fabs(cuboid.getVolume() - 2.0 * hexagon.getVolume()) <= pc::epsilon_dbl); return {cuboid, cuboid_particles}; } /** * @brief Structure for exploring a discrete, uniform angular space between two rigid bodies - * + * * Quaternions for visiting all poses in angular space can be generated in several ways: * ~~~ cpp * TwobodyAngles angles(0.1); - * + * * // Visit all poses via pairs of iterators * for (const auto& [q1, q2] : angles.quaternionPairs()) { * pos1 = q1 * pos1; // first body * pos2 = q2 * pos2; // second body * } - * + * * // Visit all poses via triplets of iterators. May be useful for parallel execution * // Note that quaternion multiplication is noncommutative so keep the shown order. * for (const auto &q_euler1 : angles.quaternions1) { * for (const auto &q_euler2 : angles.quaternions2) { * for (const auto &q_dihedral : angles.dihedrals) { * pos1 = q_euler1 * pos1; // first body - * pos2 = (q_dihedral * q_euler2) * pos2; // second body + * pos2 = (q_dihedral * q_euler2) * pos2; // second body * } * } * } * ~~~ */ -class TwobodyAngles { +class TwobodyAngles +{ static std::vector fibonacciSphere(size_t); public: @@ -812,29 +931,34 @@ class TwobodyAngles { TwobodyAngles(double angle_resolution); /** - * @brief Iterator to loop over pairs of quaternions to rotate two rigid bodies against each other + * @brief Iterator to loop over pairs of quaternions to rotate two rigid bodies against each + * other * - * The second quaternion in the pair includes dihedral rotations, i.e. the pair is `q1, q_dihedral * q2`. + * The second quaternion in the pair includes dihedral rotations, i.e. the pair is `q1, + * q_dihedral * q2`. */ - auto quaternionPairs() const { + auto quaternionPairs() const + { namespace rv = ranges::views; auto product = [](const auto& pair) -> Eigen::Quaterniond { const auto& [q2, q_dihedral] = pair; return q_dihedral * q2; // noncommutative }; - auto second_body_quaternions = rv::cartesian_product(quaternions_2, dihedrals) | rv::transform(product); + auto second_body_quaternions = + rv::cartesian_product(quaternions_2, dihedrals) | rv::transform(product); return rv::cartesian_product(quaternions_1, second_body_quaternions); } - size_t size() const; //!< Total number of points in angular space (i.e. the number of unique poses) + size_t + size() const; //!< Total number of points in angular space (i.e. the number of unique poses) }; /** * @brief Structure for exploring a discrete, uniform angular space between two rigid bodies - * + * * This version includes an iterator-like _state_ that can be used to step through * angular space. - * + * * Example: * ~~~ cpp * TwobodyAnglesState angles(0.1); @@ -847,17 +971,20 @@ class TwobodyAngles { * } * ~~~ */ -class TwobodyAnglesState : public TwobodyAngles { +class TwobodyAnglesState : public TwobodyAngles +{ private: using QuaternionIter = std::vector::const_iterator; QuaternionIter q_euler1; QuaternionIter q_euler2; QuaternionIter q_dihedral; + public: TwobodyAnglesState() = default; TwobodyAnglesState(double angle_resolution); TwobodyAnglesState& advance(); //!< Advance to next angle - std::optional> get(); //!< Get current pair of quaternions + std::optional> + get(); //!< Get current pair of quaternions }; } // namespace Geometry diff --git a/src/group.cpp b/src/group.cpp index c6b920276..27f0969ae 100644 --- a/src/group.cpp +++ b/src/group.cpp @@ -9,9 +9,17 @@ namespace Faunus { -Group::Group(Group& other) : base(other.begin(), other.trueend()) { *this = operator=(other); } +Group::Group(Group& other) + : base(other.begin(), other.trueend()) +{ + *this = operator=(other); +} -Group::Group(const Group& other) : base(other.begin(), other.trueend()) { *this = operator=(other); } +Group::Group(const Group& other) + : base(other.begin(), other.trueend()) +{ + *this = operator=(other); +} /** * @param molid Molecule id the group points to, i.e. a valid index in global `Faunus::molecules`. @@ -19,7 +27,10 @@ Group::Group(const Group& other) : base(other.begin(), other.trueend()) { *this * @param end Iterator to (beyond) end particle * @throw if molid is out of range w. respect to `Faunus::molecules` */ -Group::Group(MoleculeData::index_type molid, Group::iter begin, Group::iter end) : base(begin, end), id(molid) { +Group::Group(MoleculeData::index_type molid, Group::iter begin, Group::iter end) + : base(begin, end) + , id(molid) +{ if (id >= Faunus::molecules.size()) { throw std::range_error("invalid molecule id"); } @@ -30,7 +41,8 @@ Group::Group(MoleculeData::index_type molid, Group::iter begin, Group::iter end) * * @throw if the capacities of the two groups differ */ -Group& Group::operator=(const Group& other) { +Group& Group::operator=(const Group& other) +{ if (&other != this) { shallowCopy(other); if (other.begin() != begin()) { @@ -46,7 +58,8 @@ Group& Group::operator=(const Group& other) { * * @throw if the capacities of the two groups differ */ -Group& Group::shallowCopy(const Group& other) { +Group& Group::shallowCopy(const Group& other) +{ if (&other != this) { if (capacity() != other.capacity()) { throw std::runtime_error("Group::shallowCopy: capacity mismatch"); @@ -59,7 +72,8 @@ Group& Group::shallowCopy(const Group& other) { return *this; } -bool Group::contains(const Particle& particle, bool include_inactive) const { +bool Group::contains(const Particle& particle, bool include_inactive) const +{ const auto size = (include_inactive ? capacity() : this->size()); if (size > 0) { auto index = std::addressof(particle) - std::addressof(*begin()); @@ -68,7 +82,8 @@ bool Group::contains(const Particle& particle, bool include_inactive) const { return false; } -AtomData::index_type Group::getParticleIndex(const Particle& particle, bool include_inactive) const { +AtomData::index_type Group::getParticleIndex(const Particle& particle, bool include_inactive) const +{ if (!empty()) { const auto index = std::addressof(particle) - std::addressof(*begin()); // std::ptrdiff_t const auto group_size = (include_inactive ? capacity() : size()); @@ -79,22 +94,27 @@ AtomData::index_type Group::getParticleIndex(const Particle& particle, bool incl throw std::out_of_range("invalid particle index or group is empty"); } -double Group::mass() const { - return std::accumulate(begin(), end(), 0.0, [](double sum, auto& particle) { return sum + particle.traits().mw; }); +double Group::mass() const +{ + return std::accumulate(begin(), end(), 0.0, + [](double sum, auto& particle) { return sum + particle.traits().mw; }); } -[[maybe_unused]] void Group::wrap(Geometry::BoundaryFunction boundary) { +[[maybe_unused]] void Group::wrap(Geometry::BoundaryFunction boundary) +{ boundary(mass_center); for (auto& particle : *this) { boundary(particle.pos); } } -void Group::rotate(const Eigen::Quaterniond& quaternion, Geometry::BoundaryFunction boundary) { +void Group::rotate(const Eigen::Quaterniond& quaternion, Geometry::BoundaryFunction boundary) +{ Geometry::rotate(begin(), end(), quaternion, boundary, -mass_center); } -void Group::translate(const Point& displacement, Geometry::BoundaryFunction boundary) { +void Group::translate(const Point& displacement, Geometry::BoundaryFunction boundary) +{ mass_center += displacement; boundary(mass_center); for (auto& particle : *this) { @@ -113,9 +133,12 @@ void Group::translate(const Point& displacement, Geometry::BoundaryFunction boun * The translation is done by subtracting / adding `approximate_mass_center`. * Safely handles empty groups. */ -void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function, const Point& approximate_mass_center) { +void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function, + const Point& approximate_mass_center) +{ if (isMolecular() && !empty()) { - mass_center = Geometry::massCenter(begin(), end(), boundary_function, -approximate_mass_center); + mass_center = + Geometry::massCenter(begin(), end(), boundary_function, -approximate_mass_center); } } @@ -125,7 +148,8 @@ void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function, const * This will approximate the existing mass center by the middle particle which for PBC systems * is generally safer then using the old mass center. */ -void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function) { +void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function) +{ if (empty()) { return; } @@ -139,7 +163,8 @@ void Group::updateMassCenter(Geometry::BoundaryFunction boundary_function) { * @return reference to value at i'th element * @throw if out of interval `[0:capacity[` */ -Particle& Group::at(size_t index) { +Particle& Group::at(size_t index) +{ if (index >= capacity()) { throw std::out_of_range("group index out of range"); } @@ -152,7 +177,8 @@ Particle& Group::at(size_t index) { * @return reference to value at i'th element * @throw if out of interval `[0:capacity[` */ -const Particle& Group::at(size_t index) const { +const Particle& Group::at(size_t index) const +{ if (index >= capacity()) { throw std::out_of_range("group index out of range"); } @@ -169,21 +195,26 @@ const Particle& Group::at(size_t index) const { * * @return Optional reference to stored group mass center */ -std::optional> Group::massCenter() { +std::optional> Group::massCenter() +{ if (isMolecular()) { return std::ref(mass_center); } return std::nullopt; } -std::optional> Group::massCenter() const { +std::optional> Group::massCenter() const +{ if (isMolecular()) { return std::cref(mass_center); } return std::nullopt; } -bool Group::isFull() const { return size() == capacity(); } +bool Group::isFull() const +{ + return size() == capacity(); +} /** * @param mask Bitmask based on enum `Group::Selectors` @@ -198,7 +229,8 @@ bool Group::isFull() const { return size() == capacity(); } // return [mask = mask](const Group &g) { return g.match(); }; //} -void to_json(json& j, const Group& group) { +void to_json(json& j, const Group& group) +{ j = {{"id", group.id}, {"cm", group.mass_center}, {"atomic", group.isAtomic()}, @@ -212,7 +244,8 @@ void to_json(json& j, const Group& group) { } } -void from_json(const json& j, Group& group) { +void from_json(const json& j, Group& group) +{ group.resize(j.at("size").get()); group.trueend() = group.begin() + j.value("capacity", group.size()); group.id = j.at("id").get(); @@ -224,7 +257,8 @@ using doctest::Approx; TEST_SUITE_BEGIN("Group"); -TEST_CASE("[Faunus] swap_to_back") { +TEST_CASE("[Faunus] swap_to_back") +{ using VecInt = std::vector; VecInt v = {1, 2, 3, 4}; @@ -236,7 +270,8 @@ TEST_CASE("[Faunus] swap_to_back") { CHECK_EQ(v, VecInt({1, 4, 3, 2})); } -TEST_CASE("[Faunus] ElasticRange") { +TEST_CASE("[Faunus] ElasticRange") +{ std::vector v = {10, 20, 30, 40, 50, 60}; ElasticRange r(v.begin(), v.end()); CHECK_EQ(r.size(), 6); @@ -280,7 +315,8 @@ TEST_CASE("[Faunus] ElasticRange") { CHECK_EQ(*r.begin(), -7); } -TEST_CASE("[Faunus] Group") { +TEST_CASE("[Faunus] Group") +{ Random rand; std::vector p(3); p.reserve(10); @@ -292,7 +328,8 @@ TEST_CASE("[Faunus] Group") { } Group g(0, p.begin(), p.end()); - SUBCASE("contains()") { + SUBCASE("contains()") + { CHECK(g.contains(p[0])); CHECK(g.contains(p[1])); CHECK(g.contains(p[2])); @@ -305,7 +342,8 @@ TEST_CASE("[Faunus] Group") { CHECK_EQ(g.size(), 3); } - SUBCASE("getParticleIndex()") { + SUBCASE("getParticleIndex()") + { Group gg(0, p.begin(), p.end()); CHECK_EQ(gg.getParticleIndex(p[0]), 0); CHECK_EQ(gg.getParticleIndex(p[1]), 1); @@ -318,7 +356,8 @@ TEST_CASE("[Faunus] Group") { CHECK_THROWS(std::ignore = gg.getParticleIndex(p[0])); } - SUBCASE("getGroupFilter(): complete group") { + SUBCASE("getGroupFilter(): complete group") + { using T = Group; auto filter = getGroupFilter(); CHECK_EQ(filter(g), true); @@ -374,7 +413,8 @@ TEST_CASE("[Faunus] Group") { CHECK_EQ(p[1].pos.y(), doctest::Approx(10)); CHECK_EQ(p[1].pos.z(), doctest::Approx(12)); - SUBCASE("operator[]") { + SUBCASE("operator[]") + { CHECK_EQ(p.begin(), g.begin()); CHECK_EQ(p.end(), g.end()); @@ -391,7 +431,8 @@ TEST_CASE("[Faunus] Group") { CHECK_EQ(p[1].pos.z(), doctest::Approx(24)); } - SUBCASE("deep copy and resizing") { + SUBCASE("deep copy and resizing") + { std::vector p1(5), p2(5); p1.front().id = 1; p2.front().id = -1; @@ -421,7 +462,8 @@ TEST_CASE("[Faunus] Group") { g1.id = 0; - SUBCASE("getGroupFilter(): incomplete group") { + SUBCASE("getGroupFilter(): incomplete group") + { CHECK(!Faunus::molecules.empty()); using Tgroup = Group; auto filter = getGroupFilter(); @@ -454,7 +496,8 @@ TEST_CASE("[Faunus] Group") { CHECK_EQ((*gvec1[0].begin()).id, (*gvec3[0].begin()).id); } - SUBCASE("cerial serialisation") { + SUBCASE("cerial serialisation") + { std::ostringstream out(std::stringstream::binary); { // serialize g2 std::vector p2(5); diff --git a/src/group.h b/src/group.h index 54198c696..75cf1fa5e 100644 --- a/src/group.h +++ b/src/group.h @@ -11,32 +11,46 @@ namespace Faunus { -template void inline swap_to_back(T first, T last, T end) { +template void inline swap_to_back(T first, T last, T end) +{ while (end-- > last) { std::iter_swap(first++, end); } } //!< Move range [first:last] to [end] by swapping elements -template struct IterRange : std::pair { +template struct IterRange : std::pair +{ using std::pair::pair; + T& begin() { return this->first; } + T& end() { return this->second; } + [[nodiscard]] const T& begin() const { return this->first; } + [[nodiscard]] const T& end() const { return this->second; } + [[nodiscard]] size_t size() const { return std::distance(this->first, this->second); } - void resize(size_t n) { + + void resize(size_t n) + { end() += n - size(); assert(size() == n); } + [[nodiscard]] bool empty() const { return this->first == this->second; } - void clear() { + + void clear() + { this->second = this->first; assert(empty()); } - [[nodiscard]] std::pair to_index(T reference) const { + + [[nodiscard]] std::pair to_index(T reference) const + { return {std::distance(reference, begin()), std::distance(reference, end() - 1)}; } //!< Returns particle index pair relative to given reference -}; //!< Turns a pair of iterators into a range +}; //!< Turns a pair of iterators into a range /** * @brief Turns a pair of iterators into an elastic range @@ -48,7 +62,8 @@ template struct IterRange : std::pair { * - Just activated elements are placed at `end()-n`. * - The true size is given by `capacity()` */ -template class ElasticRange : public IterRange::iterator> { +template class ElasticRange : public IterRange::iterator> +{ public: using Titer = typename std::vector::iterator; using const_iterator = typename std::vector::const_iterator; @@ -66,18 +81,24 @@ template class ElasticRange : public IterRange virtual ~ElasticRange() = default; [[nodiscard]] size_t capacity() const; [[nodiscard]] auto inactive() const; //!< Range of inactive elements - void deactivate(Titer first, - Titer last); //!< Deactivate particles by moving to end, reducing the effective size + void + deactivate(Titer first, + Titer last); //!< Deactivate particles by moving to end, reducing the effective size void activate(Titer first, Titer last); //!< Activate previously deactivated elements Titer& trueend(); [[nodiscard]] const Titer& trueend() const; void relocate(const_iterator oldorigin, - Titer neworigin); //!< Shift all iterators to new underlying container; useful when resizing vectors + Titer neworigin); //!< Shift all iterators to new underlying container; useful + //!< when resizing vectors [[nodiscard]] [[nodiscard]] virtual bool isFull() const; [[maybe_unused]] [[nodiscard]] auto numInactive() const; //!< Number of inactive elements - //!< Determines if the given number (positive or negative) of particles can be inserted or deleted - [[maybe_unused]] [[nodiscard]] inline bool resizeIsPossible(int number_to_insert_or_delete) const { + //!< Determines if the given number (positive or negative) of particles can be inserted or + //!< deleted + + [[maybe_unused]] [[nodiscard]] inline bool + resizeIsPossible(int number_to_insert_or_delete) const + { auto new_size = static_cast(size()) + number_to_insert_or_delete; return (new_size >= 0 && new_size <= capacity()); } @@ -87,18 +108,42 @@ template class ElasticRange : public IterRange * `make_subrange()`. With GCC `cpp20::subrange()` can be used. More info here: * https://stackoverflow.com/questions/58316189/in-ranges-v3-how-do-i-create-a-range-from-a-pair-of-iterators */ - inline auto all() { return ranges::make_subrange(begin(), trueend()); } //!< Active and inactive elements - [[nodiscard]] inline auto all() const { return ranges::make_subrange(begin(), trueend()); } //!< Active and inactive elements + inline auto all() + { + return ranges::make_subrange(begin(), trueend()); + } //!< Active and inactive elements + + [[nodiscard]] inline auto all() const + { + return ranges::make_subrange(begin(), trueend()); + } //!< Active and inactive elements }; -template bool ElasticRange::isFull() const { return end() == trueend(); } + +template bool ElasticRange::isFull() const +{ + return end() == trueend(); +} template -ElasticRange::ElasticRange(ElasticRange::Titer begin, ElasticRange::Titer end) : base({begin, end}), _trueend(end) {} +ElasticRange::ElasticRange(ElasticRange::Titer begin, ElasticRange::Titer end) + : base({begin, end}) + , _trueend(end) +{ +} + +template size_t ElasticRange::capacity() const +{ + return std::distance(begin(), _trueend); +} -template size_t ElasticRange::capacity() const { return std::distance(begin(), _trueend); } -template auto ElasticRange::inactive() const { return base({end(), _trueend}); } +template auto ElasticRange::inactive() const +{ + return base({end(), _trueend}); +} -template void ElasticRange::deactivate(ElasticRange::Titer first, ElasticRange::Titer last) { +template +void ElasticRange::deactivate(ElasticRange::Titer first, ElasticRange::Titer last) +{ size_t n = std::distance(first, last); assert(first >= begin() && last <= end()); std::rotate(begin(), last, end()); @@ -106,26 +151,41 @@ template void ElasticRange::deactivate(ElasticRange::Titer first, E assert(size() + inactive().size() == capacity()); } -template void ElasticRange::activate(ElasticRange::Titer first, ElasticRange::Titer last) { +template +void ElasticRange::activate(ElasticRange::Titer first, ElasticRange::Titer last) +{ size_t n = std::distance(first, last); std::rotate(end(), first, _trueend); end() += n; assert(size() + inactive().size() == capacity()); } -template typename ElasticRange::Titer& ElasticRange::trueend() { return _trueend; } -template const typename ElasticRange::Titer& ElasticRange::trueend() const { return _trueend; } +template typename ElasticRange::Titer& ElasticRange::trueend() +{ + return _trueend; +} + +template const typename ElasticRange::Titer& ElasticRange::trueend() const +{ + return _trueend; +} template -void ElasticRange::relocate(ElasticRange::const_iterator oldorigin, ElasticRange::Titer neworigin) { +void ElasticRange::relocate(ElasticRange::const_iterator oldorigin, + ElasticRange::Titer neworigin) +{ begin() = neworigin + std::distance(oldorigin, const_iterator(begin())); end() = neworigin + std::distance(oldorigin, const_iterator(end())); trueend() = neworigin + std::distance(oldorigin, const_iterator(trueend())); } -template [[maybe_unused]] auto ElasticRange::numInactive() const { return inactive().size(); } +template [[maybe_unused]] auto ElasticRange::numInactive() const +{ + return inactive().size(); +} -class Group : public ElasticRange { +class Group : public ElasticRange +{ public: ~Group() override = default; using base = ElasticRange; @@ -134,15 +194,26 @@ class Group : public ElasticRange { int conformation_id = 0; //!< Conformation index / id Point mass_center = {0.0, 0.0, 0.0}; //!< Mass center - [[nodiscard]] inline bool isAtomic() const { return traits().atomic; } //!< Is it an atomic group? - [[nodiscard]] inline bool isMolecular() const { return !traits().atomic; } //!< is it a molecular group? - [[nodiscard]] bool isFull() const override; //!< True of all particles are active + [[nodiscard]] inline bool isAtomic() const + { + return traits().atomic; + } //!< Is it an atomic group? + + [[nodiscard]] inline bool isMolecular() const + { + return !traits().atomic; + } //!< is it a molecular group? - std::optional> massCenter(); //!< Optional reference to mass center - [[nodiscard]] std::optional> massCenter() const; //!< Optional ref. to mass center + [[nodiscard]] bool isFull() const override; //!< True of all particles are active + + std::optional> + massCenter(); //!< Optional reference to mass center + [[nodiscard]] std::optional> + massCenter() const; //!< Optional ref. to mass center //! Selections to filter groups using `getSelectionFilter()` - enum Selectors : unsigned int { + enum Selectors : unsigned int + { ANY = (1U << 1U), //!< Match any group (disregards all other flags) ACTIVE = (1U << 2U), //!< Only active groups (non-zero size) INACTIVE = (1U << 3U), //!< Only inactive groups (zero size) @@ -162,7 +233,9 @@ class Group : public ElasticRange { */ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++11-narrowing" - template [[nodiscard]] bool match() const { + + template [[nodiscard]] bool match() const + { static_assert(mask >= ANY && mask <= FULL); if constexpr (mask & ANY) { static_assert(mask == ANY, "don't mix ANY with other flags"); @@ -173,7 +246,8 @@ class Group : public ElasticRange { if (empty()) { return false; } - } else if constexpr (mask & INACTIVE) { + } + else if constexpr (mask & INACTIVE) { if (!empty()) { return false; } @@ -188,24 +262,28 @@ class Group : public ElasticRange { if (isMolecular()) { return false; } - } else if constexpr (mask & MOLECULAR) { + } + else if constexpr (mask & MOLECULAR) { if (isAtomic()) { return false; } } if constexpr (mask & NEUTRAL) { auto _end = (mask & INACTIVE) ? trueend() : end(); - auto _charge = std::accumulate(begin(), _end, 0.0, - [](auto sum, const auto& particle) { return sum + particle.charge; }); + auto _charge = std::accumulate(begin(), _end, 0.0, [](auto sum, const auto& particle) { + return sum + particle.charge; + }); if (std::fabs(_charge) > pc::epsilon_dbl) { return false; } } return true; } + #pragma clang diagnostic pop - [[nodiscard]] inline const MoleculeData& traits() const { + [[nodiscard]] inline const MoleculeData& traits() const + { assert(id >= 0 && id < Faunus::molecules.size()); return Faunus::molecules[id]; } //!< Convenient access to molecule properties @@ -213,27 +291,34 @@ class Group : public ElasticRange { Group(Group& other); Group(const Group& other); Group(MoleculeData::index_type molid, iter begin, iter end); //!< Constructor - Group& operator=(const Group& other); //!< Deep copy contents from another Group - Group& shallowCopy(const Group& other); //!< copy from `other` but *not* particle data - [[nodiscard]] bool contains(const Particle& particle, bool include_inactive = false) const; //!< Does particle belong? - [[nodiscard]] double mass() const; //!< Sum of all active masses - - auto positions() { + Group& operator=(const Group& other); //!< Deep copy contents from another Group + Group& shallowCopy(const Group& other); //!< copy from `other` but *not* particle data + [[nodiscard]] bool contains(const Particle& particle, + bool include_inactive = false) const; //!< Does particle belong? + [[nodiscard]] double mass() const; //!< Sum of all active masses + + auto positions() + { return ranges::make_subrange(begin(), end()) | - ranges::cpp20::views::transform([&](Particle& particle) -> Point& { return particle.pos; }); + ranges::cpp20::views::transform( + [&](Particle& particle) -> Point& { return particle.pos; }); } //!< Range of positions of active particles - [[nodiscard]] auto positions() const { + [[nodiscard]] auto positions() const + { return ranges::make_subrange(begin(), end()) | - ranges::cpp20::views::transform([&](const Particle& particle) -> const Point& { return particle.pos; }); + ranges::cpp20::views::transform( + [&](const Particle& particle) -> const Point& { return particle.pos; }); } //!< Range of positions of active particles - [[nodiscard]] AtomData::index_type getParticleIndex( - const Particle& particle, - bool include_inactive = false) const; //!< Finds index of particle within group. Throws if not part of group + [[nodiscard]] AtomData::index_type getParticleIndex(const Particle& particle, + bool include_inactive = false) + const; //!< Finds index of particle within group. Throws if not part of group - [[nodiscard]] auto findAtomID(AtomData::index_type atomid) const { - return *this | ranges::cpp20::views::filter([atomid](auto& particle) { return (particle.id == atomid); }); + [[nodiscard]] auto findAtomID(AtomData::index_type atomid) const + { + return *this | ranges::cpp20::views::filter( + [atomid](auto& particle) { return (particle.id == atomid); }); } //!< Range of all (active) elements with matching particle id /** @@ -243,6 +328,7 @@ class Group : public ElasticRange { * @note No range-checking and i must be in interval `[0:size[` */ inline auto& operator[](size_t index) { return *(begin() + index); } + inline const auto& operator[](size_t index) const { return *(begin() + index); } Particle& at(size_t index); @@ -253,13 +339,15 @@ class Group : public ElasticRange { * @param indices Range of indices relative to group * @warning Do not change `indices` to `const&` which would create a dangling reference */ - template auto operator[](std::vector& indices) { + template auto operator[](std::vector& indices) + { #ifndef NDEBUG if (not indices.empty()) { assert(*std::max_element(indices.begin(), indices.end()) < size()); } #endif - return indices | ranges::cpp20::views::transform([this](auto i) -> Particle& { return *(begin() + i); }); + return indices | ranges::cpp20::views::transform( + [this](auto i) -> Particle& { return *(begin() + i); }); } /** @@ -269,7 +357,8 @@ class Group : public ElasticRange { * @remarks Atomic groups are not touched * @warning Is this fit for use? */ - template void unwrap(const TdistanceFunc& vector_distance) { + template void unwrap(const TdistanceFunc& vector_distance) + { if (isMolecular()) { for (auto& particle : *this) { particle.pos = mass_center + vector_distance(particle.pos, mass_center); @@ -277,15 +366,16 @@ class Group : public ElasticRange { } } - [[maybe_unused]] void wrap(Geometry::BoundaryFunction boundary); //!< Apply periodic boundaries (Order N complexity). + [[maybe_unused]] void + wrap(Geometry::BoundaryFunction boundary); //!< Apply periodic boundaries (Order N complexity). void translate( - const Point& displacement, - Geometry::BoundaryFunction boundary = [](Point&) {}); //!< Translate particle positions and mass center + const Point& displacement, Geometry::BoundaryFunction boundary = [](Point&) { + }); //!< Translate particle positions and mass center - void - rotate(const Eigen::Quaterniond& quaternion, - Geometry::BoundaryFunction boundary); //!< Rotate particles incl. internal coordinates (dipole moment etc.) + void rotate(const Eigen::Quaterniond& quaternion, + Geometry::BoundaryFunction + boundary); //!< Rotate particles incl. internal coordinates (dipole moment etc.) void updateMassCenter(Geometry::BoundaryFunction boundary, const Point& approximate_mass_center); //!< Calculates mass center @@ -298,7 +388,8 @@ void to_json(json& j, const Group& group); void from_json(const json& j, Group& group); //! Get lambda function matching given enum Select mask -template std::function getGroupFilter() { +template std::function getGroupFilter() +{ return [](const Group& group) { return group.match(); }; } @@ -309,18 +400,22 @@ template std::function getGroupFilter() * is thrown. */ -template void save(Archive& archive, const Group& group, std::uint32_t const version) { +template +void save(Archive& archive, const Group& group, std::uint32_t const version) +{ switch (version) { case 0: archive(group.id, group.conformation_id, group.mass_center, group.size(), group.capacity()); - std::for_each(group.begin(), group.trueend(), [&archive](auto& particle) { archive(particle); }); + std::for_each(group.begin(), group.trueend(), + [&archive](auto& particle) { archive(particle); }); break; default: throw std::runtime_error("unknown serialisation version"); }; } //!< Cereal serialisation -template void load(Archive& archive, Group& group, std::uint32_t const version) { +template void load(Archive& archive, Group& group, std::uint32_t const version) +{ size_t size = 0; size_t capacity = 0; switch (version) { @@ -330,7 +425,8 @@ template void load(Archive& archive, Group& group, std::uint32_t throw std::runtime_error("capacity mismatch of archived group"); } group.resize(size); - std::for_each(group.begin(), group.trueend(), [&archive](auto& particle) { archive(particle); }); + std::for_each(group.begin(), group.trueend(), + [&archive](auto& particle) { archive(particle); }); break; default: throw std::runtime_error("unknown serialisation version"); @@ -339,6 +435,7 @@ template void load(Archive& archive, Group& group, std::uint32_t /** Concept for a range of groups */ template -concept RequireGroups = ranges::cpp20::range && std::is_convertible_v, Group>; +concept RequireGroups = + ranges::cpp20::range && std::is_convertible_v, Group>; } // namespace Faunus diff --git a/src/io.cpp b/src/io.cpp index 98adaa543..dd432ce06 100644 --- a/src/io.cpp +++ b/src/io.cpp @@ -18,12 +18,15 @@ namespace Faunus { * @param throw_on_error Throw `std::runtime_error` if file cannot be opened (default: false) * @return pointer to stream; nullptr if it could not be created */ -std::unique_ptr IO::openCompressedOutputStream(const std::string& filename, bool throw_on_error) { +std::unique_ptr IO::openCompressedOutputStream(const std::string& filename, + bool throw_on_error) +{ try { // any neater way to check if path is writable? std::ofstream f; f.exceptions(std::ifstream::failbit | std::ifstream::badbit); f.open(filename); - } catch (std::exception& e) { // reaching here, file cannot be created + } + catch (std::exception& e) { // reaching here, file cannot be created if (throw_on_error) { throw std::runtime_error("could not open file "s + filename); } @@ -32,17 +35,21 @@ std::unique_ptr IO::openCompressedOutputStream(const std::string& if (filename.substr(filename.find_last_of('.') + 1) == "gz") { faunus_logger->trace("enabling gzip compression for {}", filename); return std::make_unique(filename); // compressed - } else { + } + else { return std::make_unique(filename); // uncompressed } } -TEST_CASE("[Faunus] openCompressedOutputStream") { +TEST_CASE("[Faunus] openCompressedOutputStream") +{ CHECK_THROWS(IO::openCompressedOutputStream("/../file", true)); CHECK_NOTHROW(IO::openCompressedOutputStream("/../file")); } -bool PQRTrajectoryReader::readAtomRecord(const std::string& record, Particle& particle, double& radius) { +bool PQRTrajectoryReader::readAtomRecord(const std::string& record, Particle& particle, + double& radius) +{ std::istringstream o(record); std::string key; o >> key; @@ -54,8 +61,8 @@ bool PQRTrajectoryReader::readAtomRecord(const std::string& record, Particle& pa o >> atom_index >> atom_name; const auto atom = findAtomByName(atom_name); particle = atom; - o >> res_name >> res_index >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> particle.charge >> - radius; + o >> res_name >> res_index >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> + particle.charge >> radius; return true; } return false; @@ -68,7 +75,9 @@ bool PQRTrajectoryReader::readAtomRecord(const std::string& record, Particle& pa * Each trajectory must be separated by an "END" record. Blank lines are * ignored. */ -void PQRTrajectoryReader::loadTrajectory(const std::string& filename, std::vector& destination) { +void PQRTrajectoryReader::loadTrajectory(const std::string& filename, + std::vector& destination) +{ if (std::ifstream stream(filename); stream) { destination.clear(); destination.resize(1); // prepare first frame @@ -78,9 +87,10 @@ void PQRTrajectoryReader::loadTrajectory(const std::string& filename, std::vecto double radius = 0.0; if (readAtomRecord(record, particle, radius)) { destination.back().push_back(particle); - } else if (record.find("END") == 0) { // if END record, advance to next frame - destination.back().shrink_to_fit(); // attempt to clean up - destination.emplace_back(); // prepare next frame + } + else if (record.find("END") == 0) { // if END record, advance to next frame + destination.back().shrink_to_fit(); // attempt to clean up + destination.emplace_back(); // prepare next frame destination.back().reserve(destination.front().size()); // reserve memory } } @@ -90,21 +100,27 @@ void PQRTrajectoryReader::loadTrajectory(const std::string& filename, std::vecto if (destination.empty()) { faunus_logger->warn("pqr trajectory {} is empty", filename); } - } else { + } + else { throw std::runtime_error("cannot open file"); } } // ========== XTCTrajectoryFrame ========== -XTCTrajectoryFrame::XTCTrajectoryFrame(int number_of_atoms) { initNumberOfAtoms(number_of_atoms); } +XTCTrajectoryFrame::XTCTrajectoryFrame(int number_of_atoms) +{ + initNumberOfAtoms(number_of_atoms); +} -XTCTrajectoryFrame::XTCTrajectoryFrame(const TrajectoryFrame& frame) { +XTCTrajectoryFrame::XTCTrajectoryFrame(const TrajectoryFrame& frame) +{ initNumberOfAtoms(static_cast(frame.coordinates.size())); importFrame(frame); } -XTCTrajectoryFrame& XTCTrajectoryFrame::operator=(const TrajectoryFrame& frame) { +XTCTrajectoryFrame& XTCTrajectoryFrame::operator=(const TrajectoryFrame& frame) +{ if (frame.coordinates.size() != number_of_atoms) { throw std::runtime_error("wrong number of particles to be assign into the XTC frame"); } @@ -112,25 +128,30 @@ XTCTrajectoryFrame& XTCTrajectoryFrame::operator=(const TrajectoryFrame& frame) return *this; } -void XTCTrajectoryFrame::importFrame(const TrajectoryFrame& frame) { +void XTCTrajectoryFrame::importFrame(const TrajectoryFrame& frame) +{ importTimestamp(frame.step, frame.timestamp); importBox(frame.box); importCoordinates(frame.coordinates, 0.5 * frame.box); } -void XTCTrajectoryFrame::importTimestamp(const int step, const float time) { +void XTCTrajectoryFrame::importTimestamp(const int step, const float time) +{ xtc_step = step; xtc_time = time / static_cast(1.0_ps); } -void XTCTrajectoryFrame::importBox(const Point& box) { - XTCMatrix xtc_box_matrix = XTCMatrix::Zero(); // empty box tensor - xtc_box_matrix.diagonal() = (box / 1.0_nm).cast(); // only XYZ dimensions in nm on diagonal, as floats +void XTCTrajectoryFrame::importBox(const Point& box) +{ + XTCMatrix xtc_box_matrix = XTCMatrix::Zero(); // empty box tensor + xtc_box_matrix.diagonal() = + (box / 1.0_nm).cast(); // only XYZ dimensions in nm on diagonal, as floats std::copy(xtc_box_matrix.data(), xtc_box_matrix.data() + DIM * DIM, &(xtc_box[0][0])); // eigen 1D, row-major -> C-style 2D array } -void XTCTrajectoryFrame::importCoordinates(const PointVector& coordinates, const Point& offset) { +void XTCTrajectoryFrame::importCoordinates(const PointVector& coordinates, const Point& offset) +{ if (coordinates.size() != number_of_atoms) { // to avoid mistakes, the number_of_atoms is immutable throw std::runtime_error("wrong number of particles to be saved in the XTC frame"); @@ -143,18 +164,21 @@ void XTCTrajectoryFrame::importCoordinates(const PointVector& coordinates, const } } -void XTCTrajectoryFrame::exportFrame(TrajectoryFrame& frame) const { +void XTCTrajectoryFrame::exportFrame(TrajectoryFrame& frame) const +{ exportTimestamp(frame.step, frame.timestamp); exportBox(frame.box); exportCoordinates(frame.coordinates, 0.5 * frame.box); } -void XTCTrajectoryFrame::exportTimestamp(int& step, float& time) const { +void XTCTrajectoryFrame::exportTimestamp(int& step, float& time) const +{ step = xtc_step; time = xtc_time * static_cast(1.0_ps); } -void XTCTrajectoryFrame::exportBox(Point& box) const { +void XTCTrajectoryFrame::exportBox(Point& box) const +{ XTCMatrix xtc_box_matrix = Eigen::Map(&(xtc_box[0][0])); if (xtc_box_matrix.diagonal().asDiagonal().toDenseMatrix() != xtc_box_matrix) { throw std::runtime_error("cannot load non-orthogonal box"); @@ -162,7 +186,8 @@ void XTCTrajectoryFrame::exportBox(Point& box) const { box = Point(xtc_box_matrix.diagonal().cast() * 1.0_nm); } -void XTCTrajectoryFrame::exportCoordinates(PointVector& coordinates, const Point& offset) const { +void XTCTrajectoryFrame::exportCoordinates(PointVector& coordinates, const Point& offset) const +{ if (coordinates.size() != number_of_atoms) { throw std::runtime_error("wrong number of particles in the loaded XTC frame"); } @@ -172,7 +197,8 @@ void XTCTrajectoryFrame::exportCoordinates(PointVector& coordinates, const Point } } -void XTCTrajectoryFrame::initNumberOfAtoms(int new_number_of_atoms) { +void XTCTrajectoryFrame::initNumberOfAtoms(int new_number_of_atoms) +{ assert(new_number_of_atoms >= 0); if (number_of_atoms != new_number_of_atoms) { number_of_atoms = new_number_of_atoms; @@ -182,21 +208,33 @@ void XTCTrajectoryFrame::initNumberOfAtoms(int new_number_of_atoms) { // ========== TrajectoryFrame ========== -TrajectoryFrame::TrajectoryFrame(const Point& box, const PointVector& coordinates, int step, float timestamp) - : box(box), coordinates(coordinates), step(step), timestamp(timestamp) {} +TrajectoryFrame::TrajectoryFrame(const Point& box, const PointVector& coordinates, int step, + float timestamp) + : box(box) + , coordinates(coordinates) + , step(step) + , timestamp(timestamp) +{ +} -TrajectoryFrame::TrajectoryFrame(const XTCTrajectoryFrame& xtc_frame) { +TrajectoryFrame::TrajectoryFrame(const XTCTrajectoryFrame& xtc_frame) +{ coordinates.resize(xtc_frame.number_of_atoms); xtc_frame.exportFrame(*this); } -void TrajectoryFrame::operator=(const XTCTrajectoryFrame& xtc_frame) { xtc_frame.exportFrame(*this); } +void TrajectoryFrame::operator=(const XTCTrajectoryFrame& xtc_frame) +{ + xtc_frame.exportFrame(*this); +} -TEST_CASE("XTCFrame") { +TEST_CASE("XTCFrame") +{ using doctest::Approx; TrajectoryFrame frame({10.0, 12.0, 8.0}, {{1.0, 2.0, -1.0}, {-4.0, 4.0, 2.0}}, 10, 0.2); - SUBCASE("To XTCFrame") { + SUBCASE("To XTCFrame") + { XTCTrajectoryFrame xtc_frame(frame); CHECK_EQ(xtc_frame.xtc_step, 10); CHECK_EQ(xtc_frame.xtc_time, Approx(0.2 / 1._ps)); @@ -205,7 +243,8 @@ TEST_CASE("XTCFrame") { REQUIRE_EQ(xtc_frame.number_of_atoms, 2); CHECK_EQ(xtc_frame.xtc_coordinates.get()[1][2], Approx((2.0 + 4.0) / 1._nm)); - SUBCASE("From XTCFrame") { + SUBCASE("From XTCFrame") + { TrajectoryFrame new_frame(xtc_frame); CHECK_EQ(new_frame.step, frame.step); CHECK_EQ(new_frame.timestamp, Approx(frame.timestamp)); @@ -214,22 +253,25 @@ TEST_CASE("XTCFrame") { CHECK(new_frame.coordinates[1].isApprox(frame.coordinates[1], 1e-6)); } - SUBCASE("From XTCFrame Iterator") { + SUBCASE("From XTCFrame Iterator") + { TrajectoryFrame new_frame; PointVector coordinates{2}; REQUIRE_EQ(coordinates.size(), 2); - xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, new_frame.box, coordinates.begin(), - coordinates.end()); + xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, new_frame.box, + coordinates.begin(), coordinates.end()); CHECK(coordinates[0].isApprox(frame.coordinates[0], 1e-6)); CHECK(coordinates[1].isApprox(frame.coordinates[1], 1e-6)); PointVector coordinates_too_small{1}, coordinates_too_big{3}; REQUIRE_LT(coordinates_too_small.size(), 2); REQUIRE_GT(coordinates_too_big.size(), 2); - CHECK_THROWS_AS(xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, new_frame.box, - coordinates_too_small.begin(), coordinates_too_small.end()), + CHECK_THROWS_AS(xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, + new_frame.box, coordinates_too_small.begin(), + coordinates_too_small.end()), std::runtime_error); - CHECK_THROWS_AS(xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, new_frame.box, - coordinates_too_big.begin(), coordinates_too_big.end()), + CHECK_THROWS_AS(xtc_frame.exportFrame(new_frame.step, new_frame.timestamp, + new_frame.box, coordinates_too_big.begin(), + coordinates_too_big.end()), std::runtime_error); } } @@ -237,7 +279,9 @@ TEST_CASE("XTCFrame") { // ========== XTCReader ========== -XTCReader::XTCReader(const std::string& filename) : filename(filename) { +XTCReader::XTCReader(const std::string& filename) + : filename(filename) +{ int number_of_atoms; if (XdrFile::read_xtc_natoms(filename.c_str(), &number_of_atoms) == XdrFile::exdrOK) { xtc_frame = std::make_unique(number_of_atoms); @@ -248,19 +292,25 @@ XTCReader::XTCReader(const std::string& filename) : filename(filename) { } } -int XTCReader::getNumberOfCoordinates() { return xtc_frame->number_of_atoms; } +int XTCReader::getNumberOfCoordinates() +{ + return xtc_frame->number_of_atoms; +} -bool XTCReader::readFrame() { - return_code = - XdrFile::read_xtc(xdrfile.get(), xtc_frame->number_of_atoms, &xtc_frame->xtc_step, &xtc_frame->xtc_time, - xtc_frame->xtc_box, xtc_frame->xtc_coordinates.get(), &xtc_frame->precision); +bool XTCReader::readFrame() +{ + return_code = XdrFile::read_xtc(xdrfile.get(), xtc_frame->number_of_atoms, &xtc_frame->xtc_step, + &xtc_frame->xtc_time, xtc_frame->xtc_box, + xtc_frame->xtc_coordinates.get(), &xtc_frame->precision); if (return_code != XdrFile::exdrENDOFFILE && return_code != XdrFile::exdrOK) { - throw std::runtime_error(fmt::format("xtc file {} could not be read (error code {})", filename, return_code)); + throw std::runtime_error( + fmt::format("xtc file {} could not be read (error code {})", filename, return_code)); } return return_code == XdrFile::exdrOK; } -bool XTCReader::read(TrajectoryFrame& frame) { +bool XTCReader::read(TrajectoryFrame& frame) +{ if (readFrame()) { frame = *xtc_frame; return true; @@ -272,32 +322,37 @@ bool XTCReader::read(TrajectoryFrame& frame) { XTCWriter::XTCWriter(const std::string& filename) : xdrfile(XdrFile::xdrfile_open(filename.c_str(), "w")) - , filename(filename) { + , filename(filename) +{ if (!xdrfile) { throw std::runtime_error(fmt::format("xtc file {} could not be opened", filename)); } } -void XTCWriter::writeFrameAt(int step, float time) { - return_code = XdrFile::write_xtc(xdrfile.get(), xtc_frame->number_of_atoms, step, time, xtc_frame->xtc_box, - xtc_frame->xtc_coordinates.get(), xtc_frame->precision); +void XTCWriter::writeFrameAt(int step, float time) +{ + return_code = XdrFile::write_xtc(xdrfile.get(), xtc_frame->number_of_atoms, step, time, + xtc_frame->xtc_box, xtc_frame->xtc_coordinates.get(), + xtc_frame->precision); if (return_code != XdrFile::exdrOK) { throw std::runtime_error( fmt::format("xtc file {} could not be written (error code {})", filename, return_code)); } } -void XTCWriter::writeFrame() { - return_code = - XdrFile::write_xtc(xdrfile.get(), xtc_frame->number_of_atoms, xtc_frame->xtc_step, xtc_frame->xtc_time, - xtc_frame->xtc_box, xtc_frame->xtc_coordinates.get(), xtc_frame->precision); +void XTCWriter::writeFrame() +{ + return_code = XdrFile::write_xtc(xdrfile.get(), xtc_frame->number_of_atoms, xtc_frame->xtc_step, + xtc_frame->xtc_time, xtc_frame->xtc_box, + xtc_frame->xtc_coordinates.get(), xtc_frame->precision); if (return_code != XdrFile::exdrOK) { throw std::runtime_error( fmt::format("xtc file {} could not be written (error code {})", filename, return_code)); } } -void XTCWriter::write(const TrajectoryFrame& frame) { +void XTCWriter::write(const TrajectoryFrame& frame) +{ if (!xtc_frame) { xtc_frame = std::make_unique(frame.coordinates.size()); } @@ -306,7 +361,8 @@ void XTCWriter::write(const TrajectoryFrame& frame) { step_counter = frame.step + 1; } -void XTCWriter::writeNext(const TrajectoryFrame& frame) { +void XTCWriter::writeNext(const TrajectoryFrame& frame) +{ if (!xtc_frame) { xtc_frame = std::make_unique(frame.coordinates.size()); } @@ -315,18 +371,23 @@ void XTCWriter::writeNext(const TrajectoryFrame& frame) { ++step_counter; } -ParticleVector fastaToParticles(std::string_view fasta_sequence, double bond_length, const Point& origin) { +ParticleVector fastaToParticles(std::string_view fasta_sequence, double bond_length, + const Point& origin) +{ ParticleVector particles; // particle vector auto ids = fastaToAtomIds(fasta_sequence); // convert letters to atom ids std::transform(ids.begin(), ids.end(), std::back_inserter(particles), [&](auto& id) { Particle particle = Faunus::atoms.at(id); - particle.pos = particles.empty() ? origin : particles.back().pos + randomUnitVector(random) * bond_length; + particle.pos = particles.empty() + ? origin + : particles.back().pos + randomUnitVector(random) * bond_length; return particle; }); return particles; } -std::unique_ptr createStructureFileWriter(const std::string& suffix) { +std::unique_ptr createStructureFileWriter(const std::string& suffix) +{ if (suffix == "pqr") { return std::make_unique(); } @@ -350,40 +411,50 @@ std::unique_ptr createStructureFileWriter(const std::string // ----------------------------- -ParticleVector loadStructure(std::string_view filename, bool prefer_charges_from_file) { +ParticleVector loadStructure(std::string_view filename, bool prefer_charges_from_file) +{ ParticleVector particles; std::unique_ptr reader; const auto suffix = filename.substr(filename.find_last_of('.') + 1); if (suffix == "aam") { reader = std::make_unique(); - } else if (suffix == "pqr") { + } + else if (suffix == "pqr") { reader = std::make_unique(); - } else if (suffix == "xyz") { + } + else if (suffix == "xyz") { reader = std::make_unique(); - } else if (suffix == "gro") { + } + else if (suffix == "gro") { reader = std::make_unique(); - } else if (suffix == "xyz_psc") { + } + else if (suffix == "xyz_psc") { reader = std::make_unique(); } try { if (reader) { reader->prefer_charges_from_file = prefer_charges_from_file; particles = reader->load(filename); - } else { + } + else { throw std::runtime_error("unknown format"); } if (particles.empty()) { throw std::runtime_error("empty structure"); } return particles; - } catch (std::exception& e) { throw std::runtime_error(fmt::format("{} load error -> {}", filename, e.what())); } + } + catch (std::exception& e) { + throw std::runtime_error(fmt::format("{} load error -> {}", filename, e.what())); + } } /** * @param fasta_sequence FASTA sequence, capital letters. * @return vector of verified and existing atom id's */ -std::vector fastaToAtomIds(std::string_view fasta_sequence) { +std::vector fastaToAtomIds(std::string_view fasta_sequence) +{ const std::map map = {{'A', "ALA"}, {'R', "ARG"}, {'N', "ASN"}, @@ -421,48 +492,69 @@ std::vector fastaToAtomIds(std::string_view fasta_sequence } if (auto it = map.find(letter); it != map.end()) { // is it in map? names.push_back(it->second); - } else { + } + else { throw std::runtime_error("invalid FASTA letter '" + std::string(1, letter) + "'"); } } return Faunus::names2ids(Faunus::atoms, names); } -FormatSpaceTrajectory::FormatSpaceTrajectory(std::ostream& ostream) { +FormatSpaceTrajectory::FormatSpaceTrajectory(std::ostream& ostream) +{ if (ostream) { output_archive = std::make_unique(ostream); } } -FormatSpaceTrajectory::FormatSpaceTrajectory(std::istream& istream) { + +FormatSpaceTrajectory::FormatSpaceTrajectory(std::istream& istream) +{ if (istream) { input_archive = std::make_unique(istream); } } -void FormatSpaceTrajectory::load([[maybe_unused]] Space& spc) { assert(input_archive != nullptr); } -void FormatSpaceTrajectory::save([[maybe_unused]] const Space& spc) { assert(output_archive != nullptr); } + +void FormatSpaceTrajectory::load([[maybe_unused]] Space& spc) +{ + assert(input_archive != nullptr); +} + +void FormatSpaceTrajectory::save([[maybe_unused]] const Space& spc) +{ + assert(output_archive != nullptr); +} // ------------------------ -void StructureFileReader::handleChargeMismatch(Particle& particle, const int atom_index) const { +void StructureFileReader::handleChargeMismatch(Particle& particle, const int atom_index) const +{ if (std::fabs(particle.traits().charge - particle.charge) > pc::epsilon_dbl) { - faunus_logger->warn("charge mismatch on atom {0} {1}: {2} atomlist value", atom_index, particle.traits().name, + faunus_logger->warn("charge mismatch on atom {0} {1}: {2} atomlist value", atom_index, + particle.traits().name, (prefer_charges_from_file) ? "ignoring" : "using"); if (not prefer_charges_from_file) { particle.charge = particle.traits().charge; // let's use atomdata charge } } } -void StructureFileReader::handleRadiusMismatch(const Particle& particle, const double radius, const int atom_index) { + +void StructureFileReader::handleRadiusMismatch(const Particle& particle, const double radius, + const int atom_index) +{ if (std::fabs(particle.traits().sigma - 2.0 * radius) > pc::epsilon_dbl) { faunus_logger->warn("radius mismatch on atom {0} {1}: using atomlist value", atom_index, particle.traits().name); } } -size_t StructureFileReader::getNumberOfAtoms(const std::string& line) { +size_t StructureFileReader::getNumberOfAtoms(const std::string& line) +{ try { return std::stoul(line); - } catch (std::exception& e) { throw std::invalid_argument("invalid number of particles"); } + } + catch (std::exception& e) { + throw std::invalid_argument("invalid number of particles"); + } } /** @@ -471,19 +563,22 @@ size_t StructureFileReader::getNumberOfAtoms(const std::string& line) { * @param comment_identifiers Array of characters used to identify comment lines, e.g. "#", ";" etc. * @throw If line cannot be loaded * - * If the *first* character in the loaded line matches any character in `comment_identifiers`, the line - * will be ignored and the next will be loaded. + * If the *first* character in the loaded line matches any character in `comment_identifiers`, the + * line will be ignored and the next will be loaded. */ void StructureFileReader::getNextLine(std::istream& stream, std::string& destination, - const std::string& comment_identifiers) { + const std::string& comment_identifiers) +{ assert(stream.exceptions() & std::ios::failbit); // check that we throw upon failure while (true) { std::getline(stream, destination); if (!destination.empty()) { - const bool is_comment = comment_identifiers.find_first_of(destination.front()) != std::string::npos; + const bool is_comment = + comment_identifiers.find_first_of(destination.front()) != std::string::npos; if (is_comment) { const auto first_non_whitespace = destination.substr(1).find_first_not_of(' ') + 1; - comments.emplace_back(destination.begin() + first_non_whitespace, destination.end()); + comments.emplace_back(destination.begin() + first_non_whitespace, + destination.end()); continue; } break; @@ -491,7 +586,8 @@ void StructureFileReader::getNextLine(std::istream& stream, std::string& destina } } -ParticleVector& StructureFileReader::load(std::istream& stream) { +ParticleVector& StructureFileReader::load(std::istream& stream) +{ comments.clear(); particles.clear(); stream.exceptions(std::istream::failbit | std::istream::badbit); @@ -504,9 +600,13 @@ ParticleVector& StructureFileReader::load(std::istream& stream) { while (true) { try { particles.emplace_back(loadParticle(stream)); - } catch (std::istream::failure& e) { + } + catch (std::istream::failure& e) { break; // end of stream reached - } catch (std::exception& e) { throw std::runtime_error(e.what()); } + } + catch (std::exception& e) { + throw std::runtime_error(e.what()); + } } particles.shrink_to_fit(); loadFooter(stream); @@ -518,10 +618,11 @@ ParticleVector& StructureFileReader::load(std::istream& stream) { return particles; } -void StructureFileReader::checkLoadedParticles() const { +void StructureFileReader::checkLoadedParticles() const +{ if (expected_number_of_particles > 0 && expected_number_of_particles != particles.size()) { - throw std::out_of_range( - fmt::format("expected {} particle records but found {}", expected_number_of_particles, particles.size())); + throw std::out_of_range(fmt::format("expected {} particle records but found {}", + expected_number_of_particles, particles.size())); } if (particles.empty()) { std::cout << "no particles loaded" << std::endl; @@ -529,13 +630,17 @@ void StructureFileReader::checkLoadedParticles() const { } } -ParticleVector& StructureFileReader::load(std::string_view filename) { +ParticleVector& StructureFileReader::load(std::string_view filename) +{ try { if (auto stream = std::ifstream(std::string(filename)); stream) { return load(stream); } throw std::runtime_error("cannot open file"); - } catch (std::exception& e) { throw std::runtime_error(e.what()); } + } + catch (std::exception& e) { + throw std::runtime_error(e.what()); + } } void StructureFileReader::loadFooter([[maybe_unused]] std::istream& stream) {} @@ -547,7 +652,8 @@ const std::string StructureFileWriter::generated_by_faunus_comment = void StructureFileWriter::saveFooter([[maybe_unused]] std::ostream& stream) const {} -void StructureFileWriter::saveGroup(std::ostream& stream, const Group& group) { +void StructureFileWriter::saveGroup(std::ostream& stream, const Group& group) +{ group_name = group.traits().name; for (auto particle = group.begin(); particle != group.trueend(); particle++) { particle_is_active = particle < group.end(); @@ -557,7 +663,8 @@ void StructureFileWriter::saveGroup(std::ostream& stream, const Group& group) { group_index++; } -TEST_CASE("[Faunus] StructureFileReader and StructureFileWriter") { +TEST_CASE("[Faunus] StructureFileReader and StructureFileWriter") +{ using doctest::Approx; // Space object with two salt pairs, i.e. four particles Space spc; @@ -596,25 +703,29 @@ TEST_CASE("[Faunus] StructureFileReader and StructureFileWriter") { CHECK_EQ(reader.comments.front(), "Generated by Faunus - https://github.com/mlund/faunus"); }; - SUBCASE("PQR roundtrip") { + SUBCASE("PQR roundtrip") + { PQRReader reader; PQRWriter writer; io_roundtrip(reader, writer); } - SUBCASE("XYZ roundtrip") { + SUBCASE("XYZ roundtrip") + { XYZReader reader; XYZWriter writer; io_roundtrip(reader, writer); } - SUBCASE("AAM roundtrip") { + SUBCASE("AAM roundtrip") + { AminoAcidModelReader reader; AminoAcidModelWriter writer; io_roundtrip(reader, writer); } - SUBCASE("Gromacs roundtrip") { + SUBCASE("Gromacs roundtrip") + { GromacsReader reader; GromacsWriter writer; io_roundtrip(reader, writer); @@ -623,26 +734,32 @@ TEST_CASE("[Faunus] StructureFileReader and StructureFileWriter") { // ----------------------------- -void AminoAcidModelWriter::saveHeader(std::ostream& stream, int number_of_particles) const { +void AminoAcidModelWriter::saveHeader(std::ostream& stream, int number_of_particles) const +{ stream << fmt::format("# {}\n{}\n", generated_by_faunus_comment, number_of_particles); } -void AminoAcidModelWriter::saveParticle(std::ostream& stream, const Particle& particle) { + +void AminoAcidModelWriter::saveParticle(std::ostream& stream, const Particle& particle) +{ const auto& traits = particle.traits(); auto scale = static_cast(particle_is_active) / 1.0_angstrom; - stream << fmt::format("{:7} {:7d} {:>13.6E} {:>13.6E} {:>13.6E} {:>13.6E} {:9.3f} {:9.3f}\n", traits.name, - particle_index + 1, scale * particle.pos[0], scale * particle.pos[1], scale * particle.pos[2], - scale * particle.charge, scale * traits.mw, 0.5 * traits.sigma); + stream << fmt::format("{:7} {:7d} {:>13.6E} {:>13.6E} {:>13.6E} {:>13.6E} {:9.3f} {:9.3f}\n", + traits.name, particle_index + 1, scale * particle.pos[0], + scale * particle.pos[1], scale * particle.pos[2], scale * particle.charge, + scale * traits.mw, 0.5 * traits.sigma); } // ----------------------------- -void AminoAcidModelReader::loadHeader(std::istream& stream) { +void AminoAcidModelReader::loadHeader(std::istream& stream) +{ std::string line; getNextLine(stream, line, "#"); // all lines starting w. "#" are skipped expected_number_of_particles = getNumberOfAtoms(line); } -Particle AminoAcidModelReader::loadParticle(std::istream& stream) { +Particle AminoAcidModelReader::loadParticle(std::istream& stream) +{ std::string line; getNextLine(stream, line, "#"); std::istringstream record(line); @@ -655,53 +772,68 @@ Particle AminoAcidModelReader::loadParticle(std::istream& stream) { const auto& atomdata = Faunus::findAtomByName(particle_name); auto particle = Particle(atomdata); try { - record >> particle_index >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> particle.charge >> - molecular_weight >> radius; - } catch (std::exception& e) { throw std::runtime_error("syntax error in: " + line); } + record >> particle_index >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> + particle.charge >> molecular_weight >> radius; + } + catch (std::exception& e) { + throw std::runtime_error("syntax error in: " + line); + } particle.pos *= 1.0_angstrom; handleChargeMismatch(particle, particle_index); handleRadiusMismatch(particle, radius, particle_index); return particle; } -AminoAcidModelReader::AminoAcidModelReader() { +AminoAcidModelReader::AminoAcidModelReader() +{ particle_charge_support = true; particle_radius_support = true; } // ----------------------------- -void XYZWriter::saveHeader(std::ostream& stream, int number_of_particles) const { +void XYZWriter::saveHeader(std::ostream& stream, int number_of_particles) const +{ stream << fmt::format("{}\n{}\n", number_of_particles, generated_by_faunus_comment); } -void XYZWriter::saveParticle(std::ostream& stream, const Particle& particle) { + +void XYZWriter::saveParticle(std::ostream& stream, const Particle& particle) +{ const auto scale = static_cast(particle_is_active); stream << particle.traits().name << " " << scale * particle.pos.transpose() << "\n"; } // ----------------------------- -void SpheroCylinderXYZWriter::saveHeader(std::ostream& stream, [[maybe_unused]] int number_of_particles) const { +void SpheroCylinderXYZWriter::saveHeader(std::ostream& stream, + [[maybe_unused]] int number_of_particles) const +{ // @todo we currently have no access to the "sweep" and is now fixed to "0" stream << fmt::format("sweep {}; box ", 0) << box_dimensions.transpose() << "\n"; } -void SpheroCylinderXYZWriter::saveParticle(std::ostream& stream, const Particle& particle) { + +void SpheroCylinderXYZWriter::saveParticle(std::ostream& stream, const Particle& particle) +{ if (particle.hasExtension()) { const auto scale = static_cast(particle_is_active); stream << particle.traits().name << " " << scale * particle.pos.transpose() << " " - << scale * particle.getExt().scdir.transpose() << " " << scale * particle.getExt().patchdir.transpose() - << "\n"; + << scale * particle.getExt().scdir.transpose() << " " + << scale * particle.getExt().patchdir.transpose() << "\n"; } } // ----------------------------- -void XYZReader::loadHeader(std::istream& stream) { +void XYZReader::loadHeader(std::istream& stream) +{ std::string line; try { std::getline(stream, line); expected_number_of_particles = std::stoi(line); - } catch (std::exception& e) { throw std::invalid_argument("atom count expected on first line"); } + } + catch (std::exception& e) { + throw std::invalid_argument("atom count expected on first line"); + } try { auto flags = stream.exceptions(); // backup state flags stream.exceptions(std::ios::goodbit); // do not throw if empty comment line @@ -710,10 +842,14 @@ void XYZReader::loadHeader(std::istream& stream) { if (!line.empty()) { comments.push_back(line); } - } catch (std::exception& e) { throw std::invalid_argument("cannot load comment"); } + } + catch (std::exception& e) { + throw std::invalid_argument("cannot load comment"); + } } -Particle XYZReader::loadParticle(std::istream& stream) { +Particle XYZReader::loadParticle(std::istream& stream) +{ std::string line; getNextLine(stream, line, ";#"); std::istringstream record(line); @@ -729,14 +865,19 @@ Particle XYZReader::loadParticle(std::istream& stream) { // ----------------------------- -void SpheroCylinderXYZReader::loadHeader(std::istream& stream) { +void SpheroCylinderXYZReader::loadHeader(std::istream& stream) +{ std::string line; try { std::getline(stream, line); - } catch (std::exception& e) { throw std::invalid_argument("cannot load comment"); } + } + catch (std::exception& e) { + throw std::invalid_argument("cannot load comment"); + } } -Particle SpheroCylinderXYZReader::loadParticle(std::istream& stream) { +Particle SpheroCylinderXYZReader::loadParticle(std::istream& stream) +{ std::string line; getNextLine(stream, line, ";#"); std::istringstream record(line); @@ -746,8 +887,9 @@ Particle SpheroCylinderXYZReader::loadParticle(std::istream& stream) { const auto& atom = Faunus::findAtomByName(atom_name); auto particle = Particle(atom); auto& extension = particle.createExtension(); - record >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> extension.scdir.x() >> extension.scdir.y() >> - extension.scdir.z() >> extension.patchdir.x() >> extension.patchdir.y() >> extension.patchdir.z(); + record >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> extension.scdir.x() >> + extension.scdir.y() >> extension.scdir.z() >> extension.patchdir.x() >> + extension.patchdir.y() >> extension.patchdir.z(); particle.pos *= 1.0_angstrom; // xyz files are commonly in Γ…ngstroms return particle; } @@ -758,7 +900,8 @@ Particle SpheroCylinderXYZReader::loadParticle(std::istream& stream) { * Read line by line until ATOM or HETATM entry is found or EOF is reached * @throw if eof is reached or if syntax error in ATOM or HETATM entry */ -Particle PQRReader::loadParticle(std::istream& stream) { +Particle PQRReader::loadParticle(std::istream& stream) +{ std::string line; std::string type; while (true) { @@ -774,8 +917,8 @@ Particle PQRReader::loadParticle(std::istream& stream) { std::string residue_name; record >> atom_index >> atom_name; auto particle = Particle(Faunus::findAtomByName(atom_name)); - record >> residue_name >> residue_index >> particle.pos.x() >> particle.pos.y() >> particle.pos.z() >> - particle.charge >> radius; + record >> residue_name >> residue_index >> particle.pos.x() >> particle.pos.y() >> + particle.pos.z() >> particle.charge >> radius; particle.pos = particle.pos * 1.0_angstrom - 0.5 * box_length; handleChargeMismatch(particle, atom_index); handleRadiusMismatch(particle, radius, atom_index); @@ -788,14 +931,18 @@ Particle PQRReader::loadParticle(std::istream& stream) { * Load line by line until eof or first ATOM/HETATM is reached * @throw if eof */ -void PQRReader::loadHeader(std::istream& stream) { +void PQRReader::loadHeader(std::istream& stream) +{ std::string line; std::string type; while (true) { const auto original_position = stream.tellg(); try { std::getline(stream, line); - } catch (std::istream::failure& e) { return; } + } + catch (std::istream::failure& e) { + return; + } if (!line.empty()) { std::istringstream record(line); record.exceptions(std::ios::failbit); // throws if error @@ -804,20 +951,27 @@ void PQRReader::loadHeader(std::istream& stream) { if (type == "REMARK") { const auto first_non_white_space = line.substr(6).find_first_not_of(' ') + 6; comments.emplace_back(line.begin() + first_non_white_space, line.end()); - } else if (type == "CRYST1") { + } + else if (type == "CRYST1") { record >> box_length.x() >> box_length.y() >> box_length.z(); box_length *= 1.0_angstrom; - faunus_logger->debug("read box dimensions: {} {} {}", box_length.x(), box_length.y(), - box_length.z()); - } else if (type == "ATOM" || type == "HETATM") { + faunus_logger->debug("read box dimensions: {} {} {}", box_length.x(), + box_length.y(), box_length.z()); + } + else if (type == "ATOM" || type == "HETATM") { stream.seekg(original_position); // rewind and stop break; } - } catch (std::exception& e) { throw std::runtime_error("header -> " + line + " ("s + e.what() + ")"s); } + } + catch (std::exception& e) { + throw std::runtime_error("header -> " + line + " ("s + e.what() + ")"s); + } } } } -PQRReader::PQRReader() { + +PQRReader::PQRReader() +{ particle_charge_support = true; particle_radius_support = true; box_dimension_support = true; @@ -825,7 +979,8 @@ PQRReader::PQRReader() { // ----------------------------- -void PQRWriter::saveParticle(std::ostream& stream, const Particle& particle) { +void PQRWriter::saveParticle(std::ostream& stream, const Particle& particle) +{ const auto& atom_name = particle.traits().name; const auto scale = static_cast(particle_is_active) / 1.0_angstrom; const auto position = scale * (particle.pos + 0.5 * box_dimensions); // origin to corner of box @@ -843,71 +998,87 @@ void PQRWriter::saveParticle(std::ostream& stream, const Particle& particle) { case Style::PQR: stream << fmt::format("{:6s}{:5d} {:^4.4s}{:1s}{:3.3s} {:1s}{:4d}{:1s} " "{:8.3f}{:8.3f}{:8.3f}{:6.2f}{:6.2f}\n", - "ATOM", particle_index + 1, atom_name, "A", group_name, chain, group_index + 1, "0", - position.x(), position.y(), position.z(), scale * particle.charge, - scale * particle.traits().sigma * 0.5); + "ATOM", particle_index + 1, atom_name, "A", group_name, chain, + group_index + 1, "0", position.x(), position.y(), position.z(), + scale * particle.charge, scale * particle.traits().sigma * 0.5); break; case Style::PDB: // see https://cupnet.net/pdb-format stream << fmt::format("{:6s}{:5d} {:^4.4s}{:1s}{:3.3s} {:1s}{:4d}{:1s} " "{:8.3f}{:8.3f}{:8.3f}{:6.2f}{:6.2f} {:>2.2s}{:2s}\n", - "ATOM", particle_index + 1, atom_name, "A", group_name, chain, group_index + 1, "0", - position.x(), position.y(), position.z(), occupancy, temperature_factor, element_symbol, - charge); + "ATOM", particle_index + 1, atom_name, "A", group_name, chain, + group_index + 1, "0", position.x(), position.y(), position.z(), + occupancy, temperature_factor, element_symbol, charge); break; case Style::PQR_LEGACY: - stream << fmt::format("ATOM {:5d} {:4.4} {:4.3}{:5d} {:8.3f} {:8.3f} {:8.3f} {:.3f} {:.3f}\n", - particle_index + 1, atom_name, group_name, group_index + 1, position.x(), position.y(), - position.z(), scale * particle.charge, scale * particle.traits().sigma * 0.5); + stream << fmt::format( + "ATOM {:5d} {:4.4} {:4.3}{:5d} {:8.3f} {:8.3f} {:8.3f} {:.3f} {:.3f}\n", + particle_index + 1, atom_name, group_name, group_index + 1, position.x(), position.y(), + position.z(), scale * particle.charge, scale * particle.traits().sigma * 0.5); break; default: throw std::runtime_error("unimplemented style"); } } -void PQRWriter::saveHeader(std::ostream& stream, [[maybe_unused]] int number_of_particles) const { +void PQRWriter::saveHeader(std::ostream& stream, [[maybe_unused]] int number_of_particles) const +{ stream << "REMARK " << generated_by_faunus_comment << "\n"; if (box_dimensions.squaredNorm() > pc::epsilon_dbl) { const Point angle = {90.0, 90.0, 90.0}; const Point box = box_dimensions / 1.0_angstrom; - stream << fmt::format("CRYST1{:9.3f}{:9.3f}{:9.3f}{:7.2f}{:7.2f}{:7.2f} P 1 1\n", box.x(), box.y(), - box.z(), angle.x(), angle.y(), angle.z()); + stream << fmt::format("CRYST1{:9.3f}{:9.3f}{:9.3f}{:7.2f}{:7.2f}{:7.2f} P 1 1\n", + box.x(), box.y(), box.z(), angle.x(), angle.y(), angle.z()); } } -void PQRWriter::saveFooter(std::ostream& stream) const { stream << "END\n"; } +void PQRWriter::saveFooter(std::ostream& stream) const +{ + stream << "END\n"; +} -PQRWriter::PQRWriter(PQRWriter::Style style) : style(style) {} +PQRWriter::PQRWriter(PQRWriter::Style style) + : style(style) +{ +} // ----------------------- -void GromacsWriter::saveHeader(std::ostream& stream, const int number_of_particles) const { +void GromacsWriter::saveHeader(std::ostream& stream, const int number_of_particles) const +{ stream << fmt::format("{}\n{:5d}\n", generated_by_faunus_comment, number_of_particles); } -void GromacsWriter::saveFooter(std::ostream& stream) const { +void GromacsWriter::saveFooter(std::ostream& stream) const +{ if (box_dimensions.squaredNorm() > pc::epsilon_dbl) { - stream << fmt::format("{:10.5f}{:10.5f}{:10.5f}\n", box_dimensions.x() / 1.0_nm, box_dimensions.y() / 1.0_nm, - box_dimensions.z() / 1.0_nm); + stream << fmt::format("{:10.5f}{:10.5f}{:10.5f}\n", box_dimensions.x() / 1.0_nm, + box_dimensions.y() / 1.0_nm, box_dimensions.z() / 1.0_nm); } } -void GromacsWriter::saveParticle(std::ostream& stream, const Particle& particle) { +void GromacsWriter::saveParticle(std::ostream& stream, const Particle& particle) +{ const auto& atom_name = particle.traits().name; const auto scale = static_cast(particle_is_active) / 1.0_nm; // zero if inactive Point position = scale * (particle.pos + 0.5 * box_dimensions); // shift origin - stream << fmt::format("{:5d}{:5.5}{:>5.5}{:5d}{:8.3f}{:8.3f}{:8.3f}\n", group_index + 1, group_name, atom_name, - particle_index + 1, position.x(), position.y(), position.z()); + stream << fmt::format("{:5d}{:5.5}{:>5.5}{:5d}{:8.3f}{:8.3f}{:8.3f}\n", group_index + 1, + group_name, atom_name, particle_index + 1, position.x(), position.y(), + position.z()); } // ----------------------- -GromacsReader::GromacsReader() { box_dimension_support = true; } +GromacsReader::GromacsReader() +{ + box_dimension_support = true; +} /** * This also loads the *footer* as box information is placed after the coordinates, * but we need it to offset while loading particles. */ -void GromacsReader::loadHeader(std::istream& stream) { +void GromacsReader::loadHeader(std::istream& stream) +{ std::string line; std::getline(stream, line); comments.push_back(line); @@ -916,13 +1087,15 @@ void GromacsReader::loadHeader(std::istream& stream) { loadBoxInformation(stream); } -void GromacsReader::loadBoxInformation(std::istream& stream) { +void GromacsReader::loadBoxInformation(std::istream& stream) +{ auto initial_position = stream.tellg(); stream.seekg(-2, std::istream::end); // jump to end and skip possible newline and the very end while (stream.tellg() > 0) { // read file backwards, letter by letter if (stream.peek() != '\n') { stream.seekg(-1, std::ios::cur); // one step backwards - } else { + } + else { stream.get(); // gobble newline std::string line; std::getline(stream, line); @@ -931,14 +1104,18 @@ void GromacsReader::loadBoxInformation(std::istream& stream) { try { o >> box_length.x() >> box_length.y() >> box_length.z(); box_length *= 1.0_nm; - } catch (...) { faunus_logger->debug("no box information found in gro file"); } + } + catch (...) { + faunus_logger->debug("no box information found in gro file"); + } break; } } stream.seekg(initial_position); } -Particle GromacsReader::loadParticle(std::istream& stream) { +Particle GromacsReader::loadParticle(std::istream& stream) +{ if (particles.size() >= expected_number_of_particles) { throw std::istream::failure("cannot read beyond expected number of particles"); } @@ -962,14 +1139,17 @@ Particle GromacsReader::loadParticle(std::istream& stream) { * @brief Advance stream position past all initial lines starting with ";" or ">" * @todo See future literal `z`: https://en.cppreference.com/w/cpp/language/integer_literal */ -void CoarseGrainedFastaFileReader::loadHeader(std::istream& stream) { +void CoarseGrainedFastaFileReader::loadHeader(std::istream& stream) +{ std::string non_comment_line; getNextLine(stream, non_comment_line, ">;"); - const auto position_of_non_comment_line = -static_cast(non_comment_line.size() + 1U); + const auto position_of_non_comment_line = + -static_cast(non_comment_line.size() + 1U); stream.seekg(position_of_non_comment_line, std::ios::cur); // rewind } -char CoarseGrainedFastaFileReader::getFastaLetter(std::istream& stream) { +char CoarseGrainedFastaFileReader::getFastaLetter(std::istream& stream) +{ while (true) { const auto letter = static_cast(stream.get()); switch (letter) { @@ -988,7 +1168,8 @@ char CoarseGrainedFastaFileReader::getFastaLetter(std::istream& stream) { } } -Particle CoarseGrainedFastaFileReader::loadParticle(std::istream& stream) { +Particle CoarseGrainedFastaFileReader::loadParticle(std::istream& stream) +{ assert(stream.exceptions() & std::ios::failbit); // check that we throw upon failure const auto letter = getFastaLetter(stream); const auto atomid = fastaToAtomIds(std::string(1, letter)).at(0); @@ -1000,31 +1181,42 @@ Particle CoarseGrainedFastaFileReader::loadParticle(std::istream& stream) { CoarseGrainedFastaFileReader::CoarseGrainedFastaFileReader(const double bond_length, const Point& initial_particle_position) - : bond_length(bond_length), new_particle_position(initial_particle_position) {} + : bond_length(bond_length) + , new_particle_position(initial_particle_position) +{ +} -void CoarseGrainedFastaFileReader::setBondLength(const double bond_length) { +void CoarseGrainedFastaFileReader::setBondLength(const double bond_length) +{ CoarseGrainedFastaFileReader::bond_length = bond_length; } -std::string CoarseGrainedFastaFileReader::loadSequence(std::istream& stream) { +std::string CoarseGrainedFastaFileReader::loadSequence(std::istream& stream) +{ stream.exceptions(std::istream::failbit | std::istream::badbit); loadHeader(stream); std::string sequence; while (true) { try { sequence.push_back(getFastaLetter(stream)); - } catch (std::istream::failure& e) { + } + catch (std::istream::failure& e) { break; // end of stream reached - } catch (std::exception& e) { throw std::runtime_error(e.what()); } + } + catch (std::exception& e) { + throw std::runtime_error(e.what()); + } } return sequence; } -TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") { +TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") +{ Faunus::atoms = R"([{ "ARG": { } }, { "LYS": { } } ])"_json.get(); const auto bond_length = 7.0; - SUBCASE("single sequence") { + SUBCASE("single sequence") + { CoarseGrainedFastaFileReader reader(bond_length, {10, 20, 30}); std::istringstream stream(">MCHU - Calmodulin\n; another comment\nKR\n"); reader.load(stream); @@ -1040,7 +1232,8 @@ TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") { CHECK_EQ(reader.comments.at(0), "MCHU - Calmodulin"); CHECK_EQ(reader.comments.at(1), "another comment"); } - SUBCASE("single sequence - end by star") { + SUBCASE("single sequence - end by star") + { CoarseGrainedFastaFileReader reader(bond_length); std::istringstream stream(">MCHU - Calmodulin\nR*K\n"); reader.load(stream); @@ -1048,7 +1241,8 @@ TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") { CHECK_EQ(reader.comments.size(), 1); CHECK_EQ(reader.comments.at(0), "MCHU - Calmodulin"); } - SUBCASE("multisequence") { + SUBCASE("multisequence") + { CoarseGrainedFastaFileReader reader(bond_length); std::istringstream stream(">MCHU - Calmodulin\nKKK\n>RK*\n"); reader.load(stream); @@ -1056,17 +1250,20 @@ TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") { CHECK_EQ(reader.comments.size(), 1); CHECK_EQ(reader.comments.at(0), "MCHU - Calmodulin"); } - SUBCASE("unknown particle") { + SUBCASE("unknown particle") + { CoarseGrainedFastaFileReader reader(bond_length); std::istringstream stream(">MCHU - Calmodulin\nRKE*\n"); CHECK_THROWS(reader.load(stream)); } - SUBCASE("unknown fasta letter") { + SUBCASE("unknown fasta letter") + { CoarseGrainedFastaFileReader reader(bond_length); std::istringstream stream(">MCHU - Calmodulin\nRKx*\n"); CHECK_THROWS(reader.load(stream)); } - SUBCASE("no comment") { + SUBCASE("no comment") + { CoarseGrainedFastaFileReader reader(bond_length); std::istringstream stream("RKK*\n"); reader.load(stream); @@ -1077,5 +1274,8 @@ TEST_CASE("[Faunus] CoarseGrainedFastaFileReader") { // ----------------------- -void XdrFile::XdrFileDeleter::operator()(XdrFile::XDRFILE* xdrfile) { XdrFile::xdrfile_close(xdrfile); } +void XdrFile::XdrFileDeleter::operator()(XdrFile::XDRFILE* xdrfile) +{ + XdrFile::xdrfile_close(xdrfile); +} } // namespace Faunus diff --git a/src/io.h b/src/io.h index 7aec50d13..ba9e42074 100644 --- a/src/io.h +++ b/src/io.h @@ -33,10 +33,14 @@ namespace XdrFile { #include #include -struct XdrFileDeleter { - void operator()(XdrFile::XDRFILE* xdrfile); //!< Custom deleter for XDRFILE pointer that closes file +struct XdrFileDeleter +{ + void + operator()(XdrFile::XDRFILE* xdrfile); //!< Custom deleter for XDRFILE pointer that closes file }; -using XDRFILE_unique = std::unique_ptr; //!< Safe XDRFILE pointer with cleanup + +using XDRFILE_unique = + std::unique_ptr; //!< Safe XDRFILE pointer with cleanup } // namespace XdrFile namespace IO { @@ -44,7 +48,8 @@ namespace IO { /** * @brief Open (gzip compressed) output stream */ -std::unique_ptr openCompressedOutputStream(const std::string& filename, bool throw_on_error = false); +std::unique_ptr openCompressedOutputStream(const std::string& filename, + bool throw_on_error = false); /** * Write a map to an output stream as key-value pairs @@ -54,8 +59,9 @@ std::unique_ptr openCompressedOutputStream(const std::string& file * @param data */ template -void writeKeyValuePairs(std::ostream& stream, const std::map& data, const std::string& sep = " ", - const std::string& end = "\n") { +void writeKeyValuePairs(std::ostream& stream, const std::map& data, + const std::string& sep = " ", const std::string& end = "\n") +{ if (stream) { for (const auto& [key, value] : data) { stream << key << sep << value << end; @@ -71,7 +77,8 @@ void writeKeyValuePairs(std::ostream& stream, const std::map& data * @param data */ template -void writeKeyValuePairs(const std::string& filename, const std::map& data) { +void writeKeyValuePairs(const std::string& filename, const std::map& data) +{ if (!data.empty()) { std::ofstream file(filename); writeKeyValuePairs(file, data); @@ -83,12 +90,13 @@ void writeKeyValuePairs(const std::string& filename, const std::map comments; - bool prefer_charges_from_file = true; //!< If applicable, prefer charges from AAM file over `AtomData` + bool prefer_charges_from_file = + true; //!< If applicable, prefer charges from AAM file over `AtomData` ParticleVector& load(std::istream& stream); //!< Load entire stream and populate data ParticleVector& load(std::string_view filename); @@ -121,7 +131,8 @@ class StructureFileReader { * Positions are generated as a random walk beginning from the * given `initial_particle_position`, advancing in steps of `bond_length` */ -class CoarseGrainedFastaFileReader : public StructureFileReader { +class CoarseGrainedFastaFileReader : public StructureFileReader +{ private: void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; @@ -129,13 +140,15 @@ class CoarseGrainedFastaFileReader : public StructureFileReader { Point new_particle_position = Point::Zero(); public: - explicit CoarseGrainedFastaFileReader(double bond_length, const Point& initial_particle_position = Point(0, 0, 0)); + explicit CoarseGrainedFastaFileReader(double bond_length, + const Point& initial_particle_position = Point(0, 0, 0)); void setBondLength(double bond_length); std::string loadSequence(std::istream& stream); static char getFastaLetter(std::istream& stream); }; -class AminoAcidModelReader : public StructureFileReader { +class AminoAcidModelReader : public StructureFileReader +{ private: void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; @@ -144,7 +157,8 @@ class AminoAcidModelReader : public StructureFileReader { AminoAcidModelReader(); }; -class PQRReader : public StructureFileReader { +class PQRReader : public StructureFileReader +{ private: void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; @@ -167,22 +181,25 @@ class PQRReader : public StructureFileReader { * HW 1.37 6.26 1.50 * HW 2.31 5.89 0.21 */ -class XYZReader : public StructureFileReader { +class XYZReader : public StructureFileReader +{ private: void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; }; -class SpheroCylinderXYZReader : public StructureFileReader { +class SpheroCylinderXYZReader : public StructureFileReader +{ private: void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; }; - -class GromacsReader : public StructureFileReader { +class GromacsReader : public StructureFileReader +{ private: - void loadBoxInformation(std::istream& stream); //!< Load box dimensions (stream position is preserved) + void loadBoxInformation( + std::istream& stream); //!< Load box dimensions (stream position is preserved) void loadHeader(std::istream& stream) override; Particle loadParticle(std::istream& stream) override; @@ -193,14 +210,20 @@ class GromacsReader : public StructureFileReader { /** * Base class to writeKeyValuePairs simple structure files such as XYZ, AAM, PQR etc. */ -class StructureFileWriter { +class StructureFileWriter +{ private: - virtual void saveHeader(std::ostream& stream, int number_of_particles) const = 0; //!< Write header - virtual void saveFooter(std::ostream& stream) const; //!< Called when all particles have been written - virtual void saveParticle(std::ostream& stream, const Particle& particle) = 0; //!< Write single particle - void saveGroup(std::ostream& stream, const Group& group); //!< Write entire group - - template void saveParticles(std::ostream& stream, ParticleIter begin, ParticleIter end) { + virtual void saveHeader(std::ostream& stream, + int number_of_particles) const = 0; //!< Write header + virtual void + saveFooter(std::ostream& stream) const; //!< Called when all particles have been written + virtual void saveParticle(std::ostream& stream, + const Particle& particle) = 0; //!< Write single particle + void saveGroup(std::ostream& stream, const Group& group); //!< Write entire group + + template + void saveParticles(std::ostream& stream, ParticleIter begin, ParticleIter end) + { group_index = 0; particle_index = 0; std::for_each(begin, end, [&](const auto& particle) { @@ -219,7 +242,8 @@ class StructureFileWriter { public: template - void save(std::ostream& stream, ParticleIter begin, ParticleIter end, const Point& box_length) { + void save(std::ostream& stream, ParticleIter begin, ParticleIter end, const Point& box_length) + { if (auto number_of_particles = std::distance(begin, end); number_of_particles > 0) { box_dimensions = box_length; saveHeader(stream, number_of_particles); @@ -228,24 +252,28 @@ class StructureFileWriter { } } - void save(std::ostream& stream, const RequireGroups auto& groups, const Point& box_length) { + void save(std::ostream& stream, const RequireGroups auto& groups, const Point& box_length) + { group_index = 0; particle_index = 0; box_dimensions = box_length; auto group_sizes = groups | ranges::cpp20::views::transform(&Group::capacity); - const auto number_of_particles = std::accumulate(group_sizes.begin(), group_sizes.end(), 0u); + const auto number_of_particles = + std::accumulate(group_sizes.begin(), group_sizes.end(), 0u); saveHeader(stream, number_of_particles); ranges::cpp20::for_each(groups, [&](const auto& group) { saveGroup(stream, group); }); saveFooter(stream); } - template void save(const std::string& filename, const Args&... args) { + template void save(const std::string& filename, const Args&... args) + { if (std::ofstream stream(filename); stream) { faunus_logger->debug("writing to {}", filename); save(stream, args...); - } else { + } + else { throw std::runtime_error("writeKeyValuePairs error: "s + filename); } } @@ -253,13 +281,15 @@ class StructureFileWriter { virtual ~StructureFileWriter() = default; }; -class AminoAcidModelWriter : public StructureFileWriter { +class AminoAcidModelWriter : public StructureFileWriter +{ private: void saveHeader(std::ostream& stream, int number_of_particles) const override; void saveParticle(std::ostream& stream, const Particle& particle) override; }; -class XYZWriter : public StructureFileWriter { +class XYZWriter : public StructureFileWriter +{ private: void saveHeader(std::ostream& stream, int number_of_particles) const override; void saveParticle(std::ostream& stream, const Particle& particle) override; @@ -269,26 +299,33 @@ class XYZWriter : public StructureFileWriter { * Modified XYZ format that also saves spherocylinder direction and patch * (ported from Faunus v1) */ -class SpheroCylinderXYZWriter : public StructureFileWriter { +class SpheroCylinderXYZWriter : public StructureFileWriter +{ private: void saveHeader(std::ostream& stream, int number_of_particles) const override; void saveParticle(std::ostream& stream, const Particle& particle) override; }; - -class PQRWriter : public StructureFileWriter { +class PQRWriter : public StructureFileWriter +{ private: void saveHeader(std::ostream& stream, int number_of_particles) const override; void saveFooter(std::ostream& stream) const override; void saveParticle(std::ostream& stream, const Particle& particle) override; public: - enum class Style { PQR_LEGACY, PDB, PQR }; //!< PQR style (for ATOM records) + enum class Style + { + PQR_LEGACY, + PDB, + PQR + }; //!< PQR style (for ATOM records) Style style = Style::PQR_LEGACY; explicit PQRWriter(Style style = Style::PQR_LEGACY); }; -class GromacsWriter : public StructureFileWriter { +class GromacsWriter : public StructureFileWriter +{ private: void saveHeader(std::ostream& stream, int number_of_particles) const override; void saveFooter(std::ostream& stream) const override; @@ -296,32 +333,37 @@ class GromacsWriter : public StructureFileWriter { }; namespace PQRTrajectoryReader { -bool readAtomRecord(const std::string& record, Particle& particle, double& radius); //!< Read ATOM or HETATOM record -void loadTrajectory(const std::string& filename, std::vector& destination); //!< Load trajectory +bool readAtomRecord(const std::string& record, Particle& particle, + double& radius); //!< Read ATOM or HETATOM record +void loadTrajectory(const std::string& filename, + std::vector& destination); //!< Load trajectory } // namespace PQRTrajectoryReader struct TrajectoryFrame; /** - * @brief Base data structure for native XTC format as used in Gromacs and supported by the C library. Import methods - * do the data conversion from the Faunus native format to the XTC format, and export methods do the oposite. + * @brief Base data structure for native XTC format as used in Gromacs and supported by the C + * library. Import methods do the data conversion from the Faunus native format to the XTC format, + * and export methods do the oposite. * - * By convention, XTC has coordinates' origin in a corner (main box's coordinates are always positive), while Faunus - * has coordinates' origin in the center of the simulation box. During conversion the corresponding offset is - * subtracted, or added, respectively. + * By convention, XTC has coordinates' origin in a corner (main box's coordinates are always + * positive), while Faunus has coordinates' origin in the center of the simulation box. During + * conversion the corresponding offset is subtracted, or added, respectively. * - * XTC format uses floats (Faunus doubles) and dimensions are in nanometers (Faunus Γ₯ngstrΓΆms). XTC library requires - * raw 2D C-style arrays for coordinates in row-major format. XTC tensor of the simulation box is converted - * to an XYZ point pressuming orthogonal geometry bacause of current limitation of Faunus. XTC format does not support - * variable number of coordinates (atoms) between frames. + * XTC format uses floats (Faunus doubles) and dimensions are in nanometers (Faunus Γ₯ngstrΓΆms). XTC + * library requires raw 2D C-style arrays for coordinates in row-major format. XTC tensor of the + * simulation box is converted to an XYZ point pressuming orthogonal geometry bacause of current + * limitation of Faunus. XTC format does not support variable number of coordinates (atoms) between + * frames. */ -struct XTCTrajectoryFrame { - XdrFile::matrix xtc_box; //!< box tensor; only diagonal elements are used +struct XTCTrajectoryFrame +{ + XdrFile::matrix xtc_box; //!< box tensor; only diagonal elements are used std::unique_ptr xtc_coordinates; //!< C-style array of particle coordinates int xtc_step = 0; //!< current frame number float xtc_time = 0.0; //!< current time (unit?) - int number_of_atoms = 0; //!< number of coordinates (atoms) in each frame - float precision = 1000.0; //!< output precision + int number_of_atoms = 0; //!< number of coordinates (atoms) in each frame + float precision = 1000.0; //!< output precision /** * @brief Creates an empty XTC trajectory frame for given number of coordinates (atoms). * @param number_of_atoms number of coordinates (atoms) @@ -332,8 +374,10 @@ struct XTCTrajectoryFrame { * @param frame source trajectory frame */ XTCTrajectoryFrame(const TrajectoryFrame& frame); + /** - * @brief Creates an XTC trajectory frame from scalar parameters and input iterator and converts data accordingly. + * @brief Creates an XTC trajectory frame from scalar parameters and input iterator and converts + * data accordingly. * @tparam begin_iterator * @tparam end_iterator * @param[in] step frame step @@ -344,10 +388,12 @@ struct XTCTrajectoryFrame { */ template XTCTrajectoryFrame(int step, float time, const Point& box, begin_iterator coordinates_begin, - end_iterator coordinates_end) { + end_iterator coordinates_end) + { initNumberOfAtoms(std::distance(coordinates_begin, coordinates_end)); importFrame(step, time, box, coordinates_begin, coordinates_end); } + /** * @brief Copies TrajectoryFrame and converts data. Calls importFrame. * @@ -362,6 +408,7 @@ struct XTCTrajectoryFrame { * @throw std::runtime_error when the number of coordinates does not match */ void importFrame(const TrajectoryFrame& frame); + /** * @brief Imports data from scalar parameters and an input iterator over coordinates. * @tparam begin_iterator @@ -374,18 +421,21 @@ struct XTCTrajectoryFrame { * @throw std::runtime_error when the number of coordinates does not match */ template - void importFrame(const int step, const float time, const Point& box, begin_iterator coordinates_begin, - end_iterator coordinates_end) { + void importFrame(const int step, const float time, const Point& box, + begin_iterator coordinates_begin, end_iterator coordinates_end) + { importTimestamp(step, time); importBox(box); importCoordinates(coordinates_begin, coordinates_end, 0.5 * box); } + /** * @brief Exports data from a TrajectoryFrame. * @param frame target frame * @throw std::runtime_error when the number of coordinates does not match */ void exportFrame(TrajectoryFrame& frame) const; + /** * @brief Exports data to scalar paramers and output iterator over atomic coordinates. * @tparam begin_iterator @@ -399,7 +449,8 @@ struct XTCTrajectoryFrame { */ template void exportFrame(int& step, float& time, Point& box, begin_iterator coordinates_begin, - end_iterator coordinates_end) const { + end_iterator coordinates_end) const + { exportTimestamp(step, time); exportBox(box); exportCoordinates(coordinates_begin, coordinates_end, 0.5 * box); @@ -407,8 +458,10 @@ struct XTCTrajectoryFrame { protected: using XTCFloat = float; - using XTCMatrix = Eigen::Matrix; //; //; //; // - void importCoordinates(begin_iterator begin, end_iterator end, const Point& offset) const { + void importCoordinates(begin_iterator begin, end_iterator end, const Point& offset) const + { int i = 0; ranges::cpp20::for_each(begin, end, [&](const auto& position) { if (i >= number_of_atoms) { @@ -463,16 +518,17 @@ struct XTCTrajectoryFrame { */ void exportBox(Point& box) const; /** - * @brief Exports and converts atomic coordinates. Offset is subtracted from all coordinates to account different - * coordinates' origin. + * @brief Exports and converts atomic coordinates. Offset is subtracted from all coordinates to + * account different coordinates' origin. * @param[out] coordinates atomic coordinates in nanometers * @param[in] offset offset in nanometers to subtract from all coordinates upon conversion * @throw std::runtime_error when the source box is not orthogonal */ void exportCoordinates(PointVector& coordinates, const Point& offset) const; + /** - * @brief Exports and converts atomic coordinates. Offset is subtracted to all coordinates to account different - * coordinates' origin. + * @brief Exports and converts atomic coordinates. Offset is subtracted to all coordinates to + * account different coordinates' origin. * @tparam begin_iterator * @tparam end_iterator * @param[out] begin output iterator with coordinates in nanometers @@ -480,7 +536,8 @@ struct XTCTrajectoryFrame { * @param[in] offset offset in nanometers to subtract from all coordinates upon conversion */ template - void exportCoordinates(begin_iterator begin, end_iterator end, const Point& offset) const { + void exportCoordinates(begin_iterator begin, end_iterator end, const Point& offset) const + { // comparison of i is probably faster than prior call of std::distance int i = 0; for (auto coordinates_it = begin; coordinates_it != end; ++coordinates_it) { @@ -505,10 +562,11 @@ struct XTCTrajectoryFrame { /** * @brief Simple data structure to store a trajectory frame in native Faunus format. * - * All coordinates are in Γ₯nstrΓΆm, origin is placed into the geometric center of the simulation box and timestamps are - * in picoseconds. + * All coordinates are in Γ₯nstrΓΆm, origin is placed into the geometric center of the simulation box + * and timestamps are in picoseconds. */ -struct TrajectoryFrame { +struct TrajectoryFrame +{ Point box; //!< simulation box PointVector coordinates; //!< coordinates of particles int step = 0; //!< frame number @@ -517,27 +575,30 @@ struct TrajectoryFrame { TrajectoryFrame() = default; TrajectoryFrame(const Point& box, const PointVector& coordinates, int step, float timestamp); /** - * @brief Creates a new trajectory frame based on an XTC frame. Data are converted as necessary. Convenient wrapper - * around XTCTrajectoryFrame::exportFrame. + * @brief Creates a new trajectory frame based on an XTC frame. Data are converted as necessary. + * Convenient wrapper around XTCTrajectoryFrame::exportFrame. * @param xtc_frame source XTC trajectory frame */ explicit TrajectoryFrame(const XTCTrajectoryFrame& xtc_frame); /** - * @brief Assignes an XTC frame. Data are converted as necessary. However, the number of coordinates has to be - * the same in both (source and target) frames. Convinient wrapper around XTCTrajectoryFrame::exportFrame. + * @brief Assignes an XTC frame. Data are converted as necessary. However, the number of + * coordinates has to be the same in both (source and target) frames. Convinient wrapper around + * XTCTrajectoryFrame::exportFrame. * @param xtc_frame source XTC trajectory frame */ void operator=(const XTCTrajectoryFrame& xtc_frame); }; /** - * @brief Reads frames from an XTC file (GROMACS compressed trajectory file format). It is a wrapper around - * C function calls. + * @brief Reads frames from an XTC file (GROMACS compressed trajectory file format). It is a wrapper + * around C function calls. * - * Frames are stored into a TrajectoryFrame structure or as a list of positions in an output iterator. The class - * is responsible for I/O operations, not data conversion. For details about data conversion XTCTrajectoryFrame. + * Frames are stored into a TrajectoryFrame structure or as a list of positions in an output + * iterator. The class is responsible for I/O operations, not data conversion. For details about + * data conversion XTCTrajectoryFrame. */ -class XTCReader { +class XTCReader +{ int return_code = XdrFile::exdrOK; //!< last return code of a C function XdrFile::XDRFILE_unique xdrfile; //!< file handle //! data structure for C functions; the number of coordinates is immutable @@ -562,6 +623,7 @@ class XTCReader { * @throw std::runtime_error when other I/O error occures */ bool read(TrajectoryFrame& frame); + /** * @brief Reads the next frame in the trajectory. * @tparam begin_iterator @@ -575,7 +637,9 @@ class XTCReader { * @throw std::runtime_error when other I/O error occures */ template - bool read(int& step, float& time, Point& box, begin_iterator coordinates_begin, end_iterator coordinates_end) { + bool read(int& step, float& time, Point& box, begin_iterator coordinates_begin, + end_iterator coordinates_end) + { if (readFrame()) { xtc_frame->exportFrame(step, time, box, coordinates_begin, coordinates_end); return true; @@ -593,19 +657,20 @@ class XTCReader { }; /** - * @brief Writes frames into an XTC file (GROMACS compressed trajectory file format). It is a wrapper around - * C function calls. + * @brief Writes frames into an XTC file (GROMACS compressed trajectory file format). It is a + * wrapper around C function calls. * - * The frames can be provided as a TrajectoryFrame structure or as a list of positions in an input iterator. The class - * is responsible for I/O operations, not data conversion. + * The frames can be provided as a TrajectoryFrame structure or as a list of positions in an input + * iterator. The class is responsible for I/O operations, not data conversion. */ -class XTCWriter { +class XTCWriter +{ int return_code = XdrFile::exdrOK; //!< last return code of a C function XdrFile::XDRFILE_unique xdrfile; //!< file handle std::unique_ptr xtc_frame; //!< data structure for C functions; //!< the number of coordinates is immutable int step_counter = 0; //!< frame counter for automatic increments - float time_delta = 1.0_ps; //!< timestamp of a frame is computed as step * time_delta + float time_delta = 1.0_ps; //!< timestamp of a frame is computed as step * time_delta public: const std::string filename; //!< name of the trajectory file, mainly for error reporting @@ -626,6 +691,7 @@ class XTCWriter { * @throw std::runtime_error when other I/O error occures */ void writeNext(const TrajectoryFrame& frame); + /** * @brief Writes a next frame into the file using own automatic counter for step and timestamp. * @tparam begin_iterator @@ -635,14 +701,15 @@ class XTCWriter { * @param[in] coordinates_end input iterator's end */ template - void writeNext(const Point& box, begin_iterator coordinates_begin, end_iterator coordinates_end) { + void writeNext(const Point& box, begin_iterator coordinates_begin, end_iterator coordinates_end) + { if (!xtc_frame) { auto number_of_atoms = ranges::cpp20::distance(coordinates_begin, coordinates_end); faunus_logger->trace("preparing xtc output for {} particles", number_of_atoms); xtc_frame = std::make_unique(number_of_atoms); } - xtc_frame->importFrame(step_counter, static_cast(step_counter) * time_delta, box, coordinates_begin, - coordinates_end); + xtc_frame->importFrame(step_counter, static_cast(step_counter) * time_delta, box, + coordinates_begin, coordinates_end); writeFrame(); ++step_counter; } @@ -654,8 +721,8 @@ class XTCWriter { */ void writeFrame(); /** - * @brief Actual wrapper around C function that writes the current frame into xtc_frame overriding step - * and timestamp. + * @brief Actual wrapper around C function that writes the current frame into xtc_frame + * overriding step and timestamp. * @throw std::runtime_error when other I/O error occures */ void writeFrameAt(int step, float time); @@ -695,7 +762,8 @@ std::unique_ptr createStructureFileWriter(const std::string * The idea is that the format handles both input and * output streams that may of may not be compressed. */ -class FormatSpaceTrajectory { +class FormatSpaceTrajectory +{ private: std::unique_ptr output_archive; std::unique_ptr input_archive; diff --git a/src/molecule.cpp b/src/molecule.cpp index b644aec4f..8c7ee56de 100644 --- a/src/molecule.cpp +++ b/src/molecule.cpp @@ -26,7 +26,8 @@ std::vector reactions; //!< List of reactions MoleculeData::MoleculeData(const std::string& name, const ParticleVector& particles, const BasePointerVector& bonds) : name(name) - , bonds(bonds) { + , bonds(bonds) +{ for (const auto& particle : particles) { atoms.push_back(particle.id); } @@ -35,26 +36,50 @@ MoleculeData::MoleculeData(const std::string& name, const ParticleVector& partic } } -MoleculeData::index_type& MoleculeData::id() { return _id; } +MoleculeData::index_type& MoleculeData::id() +{ + return _id; +} -const MoleculeData::index_type& MoleculeData::id() const { return _id; } +const MoleculeData::index_type& MoleculeData::id() const +{ + return _id; +} -bool MoleculeData::isImplicit() const { return implicit; } +bool MoleculeData::isImplicit() const +{ + return implicit; +} -bool MoleculeData::isMolecular() const { return !atomic; } +bool MoleculeData::isMolecular() const +{ + return !atomic; +} -bool MoleculeData::isAtomic() const { return atomic; } +bool MoleculeData::isAtomic() const +{ + return atomic; +} -ParticleVector MoleculeData::getRandomConformation(Geometry::GeometryBase& geo, const ParticleVector& otherparticles) { +ParticleVector MoleculeData::getRandomConformation(Geometry::GeometryBase& geo, + const ParticleVector& otherparticles) +{ assert(inserter != nullptr); return (*inserter)(geo, *this, otherparticles); } -void MoleculeData::setInserter(std::shared_ptr ins) { inserter = std::move(ins); } +void MoleculeData::setInserter(std::shared_ptr ins) +{ + inserter = std::move(ins); +} -MoleculeData::MoleculeData() { setInserter(std::make_shared()); } +MoleculeData::MoleculeData() +{ + setInserter(std::make_shared()); +} -void to_json(json &j, const MoleculeData &a) { +void to_json(json& j, const MoleculeData& a) +{ j[a.name] = { {"id", a.id()}, {"atomic", a.atomic}, @@ -65,7 +90,7 @@ void to_json(json &j, const MoleculeData &a) { }; j[a.name].update(a.json_cfg); - if(a.inserter != nullptr) { + if (a.inserter != nullptr) { a.inserter->to_json(j[a.name]); } j[a.name]["atoms"] = json::array(); @@ -80,19 +105,23 @@ void to_json(json &j, const MoleculeData &a) { } } -void MoleculeData::createMolecularConformations(const json &j) { +void MoleculeData::createMolecularConformations(const json& j) +{ assert(j.is_object()); if (auto trajfile = j.value("traj", ""s); not trajfile.empty()) { - conformations.clear(); // remove all previous conformations + conformations.clear(); // remove all previous conformations if (j.contains("structure")) { throw ConfigurationError("`structure` and `traj` are mutually exclusive"); } try { - PQRTrajectoryReader::loadTrajectory(trajfile, conformations.data); // read traj. from disk + PQRTrajectoryReader::loadTrajectory(trajfile, + conformations.data); // read traj. from disk if (bool read_charges = j.value("keepcharges", true); read_charges) { faunus_logger->debug("charges in {} preferred over `atomlist` values", trajfile); - } else { - faunus_logger->debug("replacing all charges from {} with atomlist values", trajfile); + } + else { + faunus_logger->debug("replacing all charges from {} with atomlist values", + trajfile); auto all_particles = conformations.data | ranges::cpp20::views::join; Faunus::applyAtomDataCharges(all_particles.begin(), all_particles.end()); } @@ -101,7 +130,8 @@ void MoleculeData::createMolecularConformations(const json &j) { for (ParticleVector& conformation : conformations.data) { Geometry::translateToOrigin(conformation.begin(), conformation.end()); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("error loading {}: {}", trajfile, e.what()); } if (conformations.empty()) { @@ -127,7 +157,9 @@ void MoleculeData::createMolecularConformations(const json &j) { setConformationWeights(j); } } -void MoleculeData::setConformationWeights(const json& j) { + +void MoleculeData::setConformationWeights(const json& j) +{ std::vector weights(conformations.size(), 1.0); // default uniform weight if (auto filename = j.value("trajweight", ""s); !filename.empty()) { @@ -143,31 +175,35 @@ void MoleculeData::setConformationWeights(const json& j) { } stream.close(); if (weights.size() != conformations.size()) { - throw ConfigurationError("{} conformation weights found while expecting {}", weights.size(), - conformations.size()); + throw ConfigurationError("{} conformation weights found while expecting {}", + weights.size(), conformations.size()); } faunus_logger->info("{} weights loaded from {}", weights.size(), filename); } conformations.setWeight(weights.begin(), weights.end()); } -TEST_CASE("[Faunus] MoleculeData") { +TEST_CASE("[Faunus] MoleculeData") +{ // json j = R"( // { "moleculelist": [ - // { "B": {"activity":0.2, "atomic":true, "insdir": [0.5,0,0], "insoffset": [-1.1, 0.5, 10], - // "atoms":["a"] } }, { "A": { "atomic":false, "structure": [{"a": [0.1, 0.1, 0.1]}] } } + // { "B": {"activity":0.2, "atomic":true, "insdir": [0.5,0,0], "insoffset": + // [-1.1, 0.5, 10], "atoms":["a"] } }, { "A": { "atomic":false, "structure": + // [{"a": [0.1, 0.1, 0.1]}] } } // ]})"_json; json j = R"([ { "B": {"activity": 0.2, "atomic": true, "atoms": ["a"] } }, { "A": { "atomic": false, "structure": [{"a": [0.1, 0.1, 0.1]}] } } ])"_json; - SUBCASE("Unknown atom") { + SUBCASE("Unknown atom") + { atoms.clear(); CHECK_THROWS_AS_MESSAGE(j.get(), std::runtime_error, "JSON->molecule B: unknown atom in atomic molecule"); } - SUBCASE("Construction") { + SUBCASE("Construction") + { using doctest::Approx; json ja = R"([{"a": {}}])"_json; atoms = ja.get(); @@ -194,14 +230,17 @@ TEST_CASE("[Faunus] MoleculeData") { // ============ NeighboursGenerator ============ -NeighboursGenerator::NeighboursGenerator(const BondVector &bonds) { +NeighboursGenerator::NeighboursGenerator(const BondVector& bonds) +{ createBondMap(bonds); } -void NeighboursGenerator::createBondMap(const BondVector &bonds) { +void NeighboursGenerator::createBondMap(const BondVector& bonds) +{ for (auto& bond : bonds.find()) { auto add_bond = [this, &bond](auto i, auto j) { - auto atom_emplaced = bond_map.emplace(bond->indices.at(i), AtomList{}); // find or create empty vector + auto atom_emplaced = + bond_map.emplace(bond->indices.at(i), AtomList{}); // find or create empty vector atom_emplaced.first->second.push_back( bond->indices.at(j)); // atom_emplaced.->.push_back() }; @@ -212,8 +251,9 @@ void NeighboursGenerator::createBondMap(const BondVector &bonds) { } } -void NeighboursGenerator::generatePaths(int bonded_distance) { - if(paths.empty()) { +void NeighboursGenerator::generatePaths(int bonded_distance) +{ + if (paths.empty()) { // starting with a path of length 0 bonds which are just separated atoms paths.emplace_back(); for (const auto& bonded_atoms : bond_map) { @@ -223,7 +263,8 @@ void NeighboursGenerator::generatePaths(int bonded_distance) { // continue wherever the last path generation ended for (int distance = (int)paths.size(); distance <= bonded_distance; ++distance) { paths.emplace_back(); - const auto base_paths = paths.rbegin()[1]; // last but one: a set of paths of length (distance - 1) + const auto base_paths = + paths.rbegin()[1]; // last but one: a set of paths of length (distance - 1) for (const auto& base_path : base_paths) { // try to extend the path with every atom bonded to the tail for (const auto& appending : bond_map.at(base_path.back())) { @@ -238,24 +279,29 @@ void NeighboursGenerator::generatePaths(int bonded_distance) { } } -void NeighboursGenerator::generatePairs(AtomPairList& pairs, int bond_distance) { +void NeighboursGenerator::generatePairs(AtomPairList& pairs, int bond_distance) +{ using namespace ranges::cpp20::views; generatePaths(bond_distance); auto is_valid_pair = [](const auto& pair) { return pair.first != pair.second; }; auto make_ordered_pair = [](const auto& path) { - return path.front() < path.back() ? AtomPair(path.front(), path.back()) : AtomPair(path.back(), path.front()); + return path.front() < path.back() ? AtomPair(path.front(), path.back()) + : AtomPair(path.back(), path.front()); }; auto excluded_pairs = // temp. conversion to std::set is avoid duplicates - paths | join | transform(make_ordered_pair) | filter(is_valid_pair) | ranges::to>; + paths | join | transform(make_ordered_pair) | filter(is_valid_pair) | + ranges::to>; pairs.reserve(pairs.size() + excluded_pairs.size()); std::copy(excluded_pairs.begin(), excluded_pairs.end(), std::back_inserter(pairs)); } -TEST_CASE("NeighboursGenerator") { +TEST_CASE("NeighboursGenerator") +{ BasePointerVector bonds; std::vector> pairs; - SUBCASE("Linear Chain") { + SUBCASE("Linear Chain") + { // decamer connected with harmonic bonds const auto mer = 10; for (auto i = 0; i < mer - 1; ++i) { @@ -271,7 +317,8 @@ TEST_CASE("NeighboursGenerator") { int match_cnt = 0; for (auto dist = 1; dist <= distance; ++dist) { for (auto n = 0; n < mer - dist; ++n) { - if (std::find(pairs.begin(), pairs.end(), std::make_pair(n, n + dist)) != pairs.end()) { + if (std::find(pairs.begin(), pairs.end(), std::make_pair(n, n + dist)) != + pairs.end()) { ++match_cnt; } } @@ -281,7 +328,8 @@ TEST_CASE("NeighboursGenerator") { CHECK_EQ(pairs_matched(), pairs.size()); } - SUBCASE("Cycle") { + SUBCASE("Cycle") + { // cyclic hexamer connected with harmonic bonds const auto mer = 6; for (auto i = 0; i < mer; ++i) { @@ -292,7 +340,8 @@ TEST_CASE("NeighboursGenerator") { const auto distance = 3; // up to dihedrals NeighboursGenerator generator(bonds); generator.generatePairs(pairs, distance); - CHECK_EQ(pairs.size(), mer + mer + mer / 2); // 1-4 pairs in the cyclic hexamer are double degenerated + CHECK_EQ(pairs.size(), + mer + mer + mer / 2); // 1-4 pairs in the cyclic hexamer are double degenerated auto pairs_matched = [pairs]() { int match_cnt = 0; for (auto dist = 1; dist <= distance; ++dist) { @@ -302,10 +351,12 @@ TEST_CASE("NeighboursGenerator") { if (i > j) { std::swap(i, j); if (j - i == 3) { - continue; // skip the pair doubles in the cyclic hexamer, e.g., 1-4 and 4-1 + continue; // skip the pair doubles in the cyclic hexamer, e.g., 1-4 and + // 4-1 } } - if (std::find(pairs.begin(), pairs.end(), std::make_pair(i, j)) != pairs.end()) { + if (std::find(pairs.begin(), pairs.end(), std::make_pair(i, j)) != + pairs.end()) { ++match_cnt; } } @@ -315,7 +366,8 @@ TEST_CASE("NeighboursGenerator") { CHECK_EQ(pairs_matched(), pairs.size()); } - SUBCASE("Branched") { + SUBCASE("Branched") + { // isopentane like structure std::vector> bonds_ndxs = {{0, 1}, {1, 2}, {1, 3}, {3, 4}}; for (const auto& bond_ndxs : bonds_ndxs) { @@ -326,7 +378,8 @@ TEST_CASE("NeighboursGenerator") { CHECK_EQ(pairs.size(), 4 + 4); } - SUBCASE("Harmonic and FENE") { + SUBCASE("Harmonic and FENE") + { bonds.emplace_back(0., 0., std::vector{1, 2}); bonds.emplace_back(0., 0., std::vector{2, 3}); bonds.emplace_back(0., 0., std::vector{4, 5}); @@ -338,7 +391,8 @@ TEST_CASE("NeighboursGenerator") { // ============ MoleculeBuilder ============ -void MoleculeBuilder::from_json(const json &j, MoleculeData &molecule) { +void MoleculeBuilder::from_json(const json& j, MoleculeData& molecule) +{ if (is_used) { throw std::logic_error("MoleculeBuilder cannot be used twice"); } @@ -350,7 +404,8 @@ void MoleculeBuilder::from_json(const json &j, MoleculeData &molecule) { molecule.atomic = j_properties.value("atomic", molecule.atomic); molecule.rigid = j_properties.value("rigid", molecule.rigid); molecule.compressible = j_properties.value("compressible", molecule.compressible); - molecule.activity = j_properties.value("activity", molecule.activity / 1.0_molar) * 1.0_molar; + molecule.activity = + j_properties.value("activity", molecule.activity / 1.0_molar) * 1.0_molar; molecule.implicit = j_properties.value("implicit", false); if (molecule.implicit and molecule.atomic) { throw std::runtime_error("atomic molecules cannot be implicit"); @@ -379,23 +434,28 @@ void MoleculeBuilder::from_json(const json &j, MoleculeData &molecule) { // at this stage all given keys should have been accessed or "spend". If any are // left, an exception will be thrown. // if (! val.empty()) { - // throw std::runtime_error("unused key(s):\n"s + val.dump() + usageTip["moleculelist"]); + // throw std::runtime_error("unused key(s):\n"s + val.dump() + + // usageTip["moleculelist"]); // } - } catch (std::exception &e) { + } + catch (std::exception& e) { usageTip.pick("moleculelist"); throw ConfigurationError("molecule '{}': {}", molecule_name, e.what()); } } -std::shared_ptr MoleculeBuilder::createInserter(const json& j) { +std::shared_ptr MoleculeBuilder::createInserter(const json& j) +{ return std::make_shared(j); } -void MoleculeBuilder::readCompoundValues(const json &j) { +void MoleculeBuilder::readCompoundValues(const json& j) +{ auto is_atomic = j.value("atomic", false); if (is_atomic) { readAtomic(j); - } else { + } + else { readParticles(j); readBonds(j); if (isFasta(j)) { @@ -405,7 +465,8 @@ void MoleculeBuilder::readCompoundValues(const json &j) { } } -void MoleculeBuilder::readAtomic(const json& j) { +void MoleculeBuilder::readAtomic(const json& j) +{ if (const auto atomlist = j.find("atoms"); atomlist != j.end()) { if (!atomlist->is_array()) { throw ConfigurationError("`atoms` must be an array"); @@ -416,13 +477,15 @@ void MoleculeBuilder::readAtomic(const json& j) { const auto& atomdata = findAtomByName(atomname.get()); particles.emplace_back(atomdata); } - } catch (std::exception& e) { + } + catch (std::exception& e) { throw ConfigurationError("molecule '{}': {}", molecule_name, e.what()); } } } -void MoleculeBuilder::readParticles(const json& j) { +void MoleculeBuilder::readParticles(const json& j) +{ auto structure = j.find("structure"); if (structure != j.end()) { bool read_charges = j.value("keepcharges", true); @@ -432,16 +495,19 @@ void MoleculeBuilder::readParticles(const json& j) { particles = Geometry::mapParticlesOnSphere(particles); } if (j.value("to_disk", false)) { - PQRWriter().save(molecule_name + "-initial.pqr", particles.begin(), particles.end(), Point(0, 0, 0)); + PQRWriter().save(molecule_name + "-initial.pqr", particles.begin(), particles.end(), + Point(0, 0, 0)); } - } else { + } + else { // allow virtual molecules :-/ // shall we rather try to fallback on readAtomic()? // throw ConfigurationError("structure of the molecule not given"); } } -void MoleculeBuilder::readBonds(const json& j) { +void MoleculeBuilder::readBonds(const json& j) +{ using namespace ranges::cpp20; bonds = j.value("bondlist", bonds); auto is_invalid_index = [size = particles.size()](auto& i) { return i >= size || i < 0; }; @@ -451,7 +517,8 @@ void MoleculeBuilder::readBonds(const json& j) { } } -void MoleculeBuilder::readFastaBonds(const json& j) { +void MoleculeBuilder::readFastaBonds(const json& j) +{ if (particles.size() < 2) { return; } @@ -464,7 +531,8 @@ void MoleculeBuilder::readFastaBonds(const json& j) { } } -void MoleculeBuilder::readExclusions(const json& j) { +void MoleculeBuilder::readExclusions(const json& j) +{ auto excluded_neighbours_distance = j.value("excluded_neighbours", 0); if (excluded_neighbours_distance > 0) { NeighboursGenerator generator(bonds); @@ -478,12 +546,14 @@ void MoleculeBuilder::readExclusions(const json& j) { } } -bool MoleculeBuilder::isFasta(const json& j) { +bool MoleculeBuilder::isFasta(const json& j) +{ auto structure = j.find("structure"); return structure != j.end() && structure->find("fasta") != structure->end(); } -TEST_CASE("[Faunus] MoleculeBuilder") { +TEST_CASE("[Faunus] MoleculeBuilder") +{ atoms = R"([{"ALA": {}}, {"GLY": {}}, {"NTR": {}}, {"CTR": {}}])"_json.get(); auto j = R"( {"peptide": { "excluded_neighbours": 2, @@ -509,25 +579,31 @@ TEST_CASE("[Faunus] MoleculeBuilder") { // ============ MoleculeStructureReader ============ -void MoleculeStructureReader::readJson(ParticleVector &particles, const json &j) const { +void MoleculeStructureReader::readJson(ParticleVector& particles, const json& j) const +{ if (j.is_string()) { auto filename = j.get(); readFile(particles, filename); - } else if (j.is_array()) { + } + else if (j.is_array()) { readArray(particles, j); - } else if (j.is_object() && j.find("fasta") != j.end()) { + } + else if (j.is_object() && j.find("fasta") != j.end()) { readFasta(particles, j); - } else { + } + else { throw ConfigurationError("unrecognized structure format"); } } -void MoleculeStructureReader::readFile(ParticleVector& particles, const std::string& filename) const { +void MoleculeStructureReader::readFile(ParticleVector& particles, const std::string& filename) const +{ faunus_logger->info("Reading molecule configuration from file: {}", filename); particles = Faunus::loadStructure(filename, read_charges); // throws if nothing is loaded! } -void MoleculeStructureReader::readArray(ParticleVector& particles, const json& j) { +void MoleculeStructureReader::readArray(ParticleVector& particles, const json& j) +{ particles.reserve(j.size()); for (const auto& particle : j) { const auto& [atomname, position] = jsonSingleItem(particle); @@ -535,10 +611,11 @@ void MoleculeStructureReader::readArray(ParticleVector& particles, const json& j } } -void MoleculeStructureReader::readFasta(ParticleVector& particles, const json& j) { +void MoleculeStructureReader::readFasta(ParticleVector& particles, const json& j) +{ if (auto it = j.find("fasta"); it != j.end()) { pairpotential::HarmonicBond bond; // harmonic bond - bond.from_json(j); // read 'k' and 'req' from json + bond.from_json(j); // read 'k' and 'req' from json auto fasta = it->get(); // fasta sequence or filename @@ -547,39 +624,53 @@ void MoleculeStructureReader::readFasta(ParticleVector& particles, const json& j if (auto stream = std::ifstream(fasta)) { fasta = CoarseGrainedFastaFileReader(0.0).loadSequence(stream); stream.close(); - } else { + } + else { throw ConfigurationError("could not open fasta file: {}", fasta); } } particles = Faunus::fastaToParticles(fasta, bond.equilibrium_distance); faunus_logger->debug("fasta sequence parsed with {} letters", particles.size()); - } else { + } + else { throw ConfigurationError("invalid FASTA format"); } } -MoleculeStructureReader::MoleculeStructureReader(bool read_charges) : read_charges(read_charges) {} -void from_json(const json &j, MoleculeData &a) { +MoleculeStructureReader::MoleculeStructureReader(bool read_charges) + : read_charges(read_charges) +{ +} + +void from_json(const json& j, MoleculeData& a) +{ MoleculeBuilder builder; builder.from_json(j, a); } -size_t MoleculeData::numConformations() const { return conformations.data.size(); } +size_t MoleculeData::numConformations() const +{ + return conformations.data.size(); +} -void from_json(const json &j, std::vector &v) { +void from_json(const json& j, std::vector& v) +{ v.reserve(v.size() + j.size()); for (const auto& i : j) { v.push_back(i); - v.back().id() = static_cast(v.size() - 1); // id always match vector index + v.back().id() = + static_cast(v.size() - 1); // id always match vector index } } -TEST_CASE("[Faunus] MoleculeStructureReader") { +TEST_CASE("[Faunus] MoleculeStructureReader") +{ using doctest::Approx; MoleculeStructureReader sf; ParticleVector particles; - SUBCASE("[Faunus] Structure JSON") { + SUBCASE("[Faunus] Structure JSON") + { atoms = R"([{"Na": {}}, {"Cl": {}}, {"M": {}}])"_json.get(); auto j = R"([ {"Na": [0,0,0]}, {"Cl": [1,0,0]}, {"M": [0,4.2,0]} ])"_json; particles.clear(); @@ -589,8 +680,10 @@ TEST_CASE("[Faunus] MoleculeStructureReader") { CHECK_EQ(particles.back().pos, Point{0, 4.2, 0}); } - SUBCASE("[Faunus] Structure FASTA") { - atoms = R"([{"ALA": {}}, {"GLY": {}}, {"NTR": {}}, {"CTR": {}}])"_json.get(); + SUBCASE("[Faunus] Structure FASTA") + { + atoms = + R"([{"ALA": {}}, {"GLY": {}}, {"NTR": {}}, {"CTR": {}}])"_json.get(); auto j = R"({"fasta": "nAAAAGGc", "k": 3, "req": 7})"_json; particles.clear(); sf.readJson(particles, j); @@ -602,37 +695,45 @@ TEST_CASE("[Faunus] MoleculeStructureReader") { // ============ ExclusionsSimple ============ -ExclusionsSimple ExclusionsSimple::create(int atoms_cnt, const std::vector> &pairs) { +ExclusionsSimple ExclusionsSimple::create(int atoms_cnt, + const std::vector>& pairs) +{ ExclusionsSimple exclusions(atoms_cnt); exclusions.add(pairs); return exclusions; } ExclusionsSimple::ExclusionsSimple(int size) - : size(size), excluded_pairs(std::make_shared>()) { + : size(size) + , excluded_pairs(std::make_shared>()) +{ faunus_logger->log(size < 1000 ? spdlog::level::trace : spdlog::level::warn, "creating exclusion matrix {}Γ—{} for {} atoms", size, size, size); excluded_pairs->resize(size * size, false); } -void ExclusionsSimple::add(const std::vector& exclusions) { - for (const auto &pair : exclusions) { +void ExclusionsSimple::add(const std::vector& exclusions) +{ + for (const auto& pair : exclusions) { add(pair.first, pair.second); } } -inline void ExclusionsSimple::add(int i, int j) { - if(i >= size || j >= size) { +inline void ExclusionsSimple::add(int i, int j) +{ + if (i >= size || j >= size) { throw std::range_error("exclusion index out of range"); } -// if (i > j) { -// std::swap(i, j); -// } + // if (i > j) { + // std::swap(i, j); + // } any_exclusions = true; - excluded_pairs->at(i * size + j) = excluded_pairs->at(j * size + i) = static_cast(true); + excluded_pairs->at(i * size + j) = excluded_pairs->at(j * size + i) = + static_cast(true); } -void from_json(const json &j, ExclusionsSimple &exclusions) { +void from_json(const json& j, ExclusionsSimple& exclusions) +{ auto is_invalid_pair = [](auto& pair) { return !pair.is_array() || pair.size() != 2; }; if (!j.is_array() || std::any_of(j.begin(), j.end(), is_invalid_pair)) { throw ConfigurationError("invalid exclusion: {}", j.dump()); @@ -640,7 +741,8 @@ void from_json(const json &j, ExclusionsSimple &exclusions) { std::for_each(j.begin(), j.end(), [&](auto& pair) { exclusions.add(pair[0], pair[1]); }); } -void to_json(json &j, const ExclusionsSimple &exclusions) { +void to_json(json& j, const ExclusionsSimple& exclusions) +{ j = json::array(); for (auto m = 0; m < exclusions.size; ++m) { for (auto n = m + 1; n < exclusions.size; ++n) { @@ -653,9 +755,12 @@ void to_json(json &j, const ExclusionsSimple &exclusions) { // ============ ExclusionsVicinity ============ -ExclusionsVicinity ExclusionsVicinity::create(int atoms_cnt, const std::vector& pairs) { +ExclusionsVicinity ExclusionsVicinity::create(int atoms_cnt, const std::vector& pairs) +{ auto max_neighbours_distance = 0; - auto compare = [](auto &a, auto &b) { return std::abs(a.first - a.second) < std::abs(b.first - b.second); }; + auto compare = [](auto& a, auto& b) { + return std::abs(a.first - a.second) < std::abs(b.first - b.second); + }; if (auto it = std::max_element(pairs.begin(), pairs.end(), compare); it != pairs.end()) { max_neighbours_distance = std::abs(it->first - it->second); } @@ -665,16 +770,20 @@ ExclusionsVicinity ExclusionsVicinity::create(int atoms_cnt, const std::vector>()) { - faunus_logger->log(atoms_cnt * max_difference < 1'000'000 ? spdlog::level::trace : spdlog::level::warn, - "creating exclusion matrix {}Γ—{} for {} atoms within distance {}", atoms_cnt, max_difference, - atoms_cnt, max_difference); + : atoms_cnt(atoms_cnt) + , max_bond_distance(max_difference) + , excluded_pairs(std::make_shared>()) +{ + faunus_logger->log(atoms_cnt * max_difference < 1'000'000 ? spdlog::level::trace + : spdlog::level::warn, + "creating exclusion matrix {}Γ—{} for {} atoms within distance {}", atoms_cnt, + max_difference, atoms_cnt, max_difference); excluded_pairs->resize(atoms_cnt * max_difference, static_cast(false)); } -void ExclusionsVicinity::add(int i, int j) { +void ExclusionsVicinity::add(int i, int j) +{ if (i > j) { std::swap(i, j); } @@ -684,15 +793,18 @@ void ExclusionsVicinity::add(int i, int j) { excluded_pairs->at(toIndex(i, j)) = static_cast(true); } -void ExclusionsVicinity::add(const std::vector& pairs) { +void ExclusionsVicinity::add(const std::vector& pairs) +{ for (const auto& pair : pairs) { add(pair.first, pair.second); } } -void to_json(json &j, const ExclusionsVicinity &exclusions) { +void to_json(json& j, const ExclusionsVicinity& exclusions) +{ j = json::array(); - for (auto it = exclusions.excluded_pairs->begin(); it != exclusions.excluded_pairs->end(); ++it) { + for (auto it = exclusions.excluded_pairs->begin(); it != exclusions.excluded_pairs->end(); + ++it) { if (static_cast(*it)) { const auto n = std::distance(exclusions.excluded_pairs->begin(), it); j.push_back(exclusions.fromIndex(n)); @@ -700,7 +812,8 @@ void to_json(json &j, const ExclusionsVicinity &exclusions) { } } -TEST_CASE("[Faunus] ExclusionsVicinity") { +TEST_CASE("[Faunus] ExclusionsVicinity") +{ std::vector pairs{{0, 1}, {1, 2}, {1, 3}, {6, 7}}; auto exclusions = ExclusionsVicinity::create(10, pairs); @@ -711,8 +824,15 @@ TEST_CASE("[Faunus] ExclusionsVicinity") { CHECK_FALSE(exclusions.isExcluded(8, 9)); } -void from_json(const json &j, MoleculeInserter &inserter) { inserter.from_json(j); } -void to_json(json &j, const MoleculeInserter &inserter) { inserter.to_json(j); } +void from_json(const json& j, MoleculeInserter& inserter) +{ + inserter.from_json(j); +} + +void to_json(json& j, const MoleculeInserter& inserter) +{ + inserter.to_json(j); +} /** * @param geo Geometry to use for PBC and container overlap check @@ -720,8 +840,10 @@ void to_json(json &j, const MoleculeInserter &inserter) { inserter.to_json(j); } * @param ignored_other_particles Other particles in the system (ignored for this inserter!) * @return Inserted particle vector */ -ParticleVector RandomInserter::operator()(const Geometry::GeometryBase &geo, MoleculeData &molecule, - [[maybe_unused]] const ParticleVector &ignored_other_particles) { +ParticleVector +RandomInserter::operator()(const Geometry::GeometryBase& geo, MoleculeData& molecule, + [[maybe_unused]] const ParticleVector& ignored_other_particles) +{ auto particles = molecule.conformations.sample(random.engine); // random, weighted conformation if (particles.empty()) { throw std::runtime_error("nothing to insert for molecule '"s + molecule.name + "'"); @@ -740,7 +862,8 @@ ParticleVector RandomInserter::operator()(const Geometry::GeometryBase &geo, Mol for (auto attempts = 0; attempts < max_trials; attempts++) { if (molecule.atomic) { translateRotateAtomicGroup(geo, rotator, particles); - } else { + } + else { translateRotateMolecularGroup(geo, rotator, particles); } if (allow_overlap || ranges::cpp20::none_of(particles, container_overlap)) { @@ -750,22 +873,29 @@ ParticleVector RandomInserter::operator()(const Geometry::GeometryBase &geo, Mol throw std::runtime_error("Max. # of overlap checks reached upon insertion."); } -void RandomInserter::translateRotateMolecularGroup(const Geometry::GeometryBase& geo, QuaternionRotate& rotator, - ParticleVector& particles) const { +void RandomInserter::translateRotateMolecularGroup(const Geometry::GeometryBase& geo, + QuaternionRotate& rotator, + ParticleVector& particles) const +{ Geometry::translateToOrigin(particles.begin(), particles.end()); // translate to origin if (rotate) { - rotator.set(2.0 * pc::pi * random(), randomUnitVector(random)); // random rot around random vector + rotator.set(2.0 * pc::pi * random(), + randomUnitVector(random)); // random rot around random vector Geometry::rotate(particles.begin(), particles.end(), rotator.getQuaternion()); - assert(Geometry::massCenter(particles.begin(), particles.end()).norm() < 1e-6); // cm shouldn't move + assert(Geometry::massCenter(particles.begin(), particles.end()).norm() < + 1e-6); // cm shouldn't move } Point new_mass_center; - geo.randompos(new_mass_center, random); // random point in container - new_mass_center = new_mass_center.cwiseProduct(dir) + offset; // add defined dirs (default: 1,1,1) + geo.randompos(new_mass_center, random); // random point in container + new_mass_center = + new_mass_center.cwiseProduct(dir) + offset; // add defined dirs (default: 1,1,1) Geometry::translate(particles.begin(), particles.end(), new_mass_center, geo.getBoundaryFunc()); } -void RandomInserter::translateRotateAtomicGroup(const Geometry::GeometryBase& geo, QuaternionRotate& rotator, - ParticleVector& particles) const { +void RandomInserter::translateRotateAtomicGroup(const Geometry::GeometryBase& geo, + QuaternionRotate& rotator, + ParticleVector& particles) const +{ for (auto& particle : particles) { // for each atom type id if (rotate) { // internal rotation of atomic particles rotator.set(2.0 * pc::pi * random(), randomUnitVector(random)); @@ -777,14 +907,16 @@ void RandomInserter::translateRotateAtomicGroup(const Geometry::GeometryBase& ge } } -void RandomInserter::from_json(const json &j) { +void RandomInserter::from_json(const json& j) +{ dir = j.value("insdir", dir); offset = j.value("insoffset", offset); rotate = j.value("rotate", rotate); keep_positions = j.value("keeppos", keep_positions); } -void RandomInserter::to_json(json &j) const { +void RandomInserter::to_json(json& j) const +{ j["insdir"] = dir; j["insoffset"] = offset; j["rotate"] = rotate; @@ -792,7 +924,8 @@ void RandomInserter::to_json(json &j) const { j["allow overlap"] = allow_overlap; } -bool Conformation::empty() const { +bool Conformation::empty() const +{ return positions.empty() && charges.empty(); } @@ -802,7 +935,8 @@ bool Conformation::empty() const { * * Overwrites positions and charges; remaining properties are untouched */ -void Conformation::copyTo(ParticleVector &particles) const { +void Conformation::copyTo(ParticleVector& particles) const +{ if (positions.size() != particles.size() or charges.size() != particles.size()) { throw std::runtime_error("conformation size mismatch for positions and/or charges"); } @@ -817,7 +951,8 @@ void Conformation::copyTo(ParticleVector &particles) const { std::copy(charges.begin(), charges.end(), particle_charges.begin()); } -TEST_CASE("[Faunus] Conformation") { +TEST_CASE("[Faunus] Conformation") +{ Conformation conformation; CHECK(conformation.empty()); @@ -833,27 +968,34 @@ TEST_CASE("[Faunus] Conformation") { CHECK_EQ(particles[0].charge, 0.5); } -ReactionData::Direction ReactionData::getDirection() const { return direction; } +ReactionData::Direction ReactionData::getDirection() const +{ + return direction; +} -void ReactionData::setDirection(ReactionData::Direction new_direction) { +void ReactionData::setDirection(ReactionData::Direction new_direction) +{ if (new_direction != direction) { direction = new_direction; } } -void ReactionData::setRandomDirection(Random& rng) { +void ReactionData::setRandomDirection(Random& rng) +{ auto dir = static_cast((char)rng.range(0, 1)); // random direction setDirection(dir); } -ReactionData::AtomicAndMolecularPair ReactionData::getProducts() const { +ReactionData::AtomicAndMolecularPair ReactionData::getProducts() const +{ if (direction == Direction::RIGHT) { return {right_atoms, right_molecules}; } return {left_atoms, left_molecules}; } -ReactionData::AtomicAndMolecularPair ReactionData::getReactants() const { +ReactionData::AtomicAndMolecularPair ReactionData::getReactants() const +{ if (direction == Direction::RIGHT) { return {left_atoms, left_molecules}; } @@ -861,11 +1003,14 @@ ReactionData::AtomicAndMolecularPair ReactionData::getReactants() const { } /** - * @return Pair of sets with id's for atoms and molecules participating in the reaction (i.e. reactants & products) + * @return Pair of sets with id's for atoms and molecules participating in the reaction (i.e. + * reactants & products) */ -std::pair, std::set> ReactionData::participatingAtomsAndMolecules() const { - auto extract_keys = [](const auto &map, std::set &keys) { // map keys --> set - std::transform(map.begin(), map.end(), std::inserter(keys, keys.end()), [](auto &pair) { return pair.first; }); +std::pair, std::set> ReactionData::participatingAtomsAndMolecules() const +{ + auto extract_keys = [](const auto& map, std::set& keys) { // map keys --> set + std::transform(map.begin(), map.end(), std::inserter(keys, keys.end()), + [](auto& pair) { return pair.first; }); }; std::set atomic; std::set molecular; @@ -878,15 +1023,18 @@ std::pair, std::set> ReactionData::participatingAtomsAndMolec return {atomic, molecular}; } -void ReactionData::reverseDirection() { +void ReactionData::reverseDirection() +{ if (direction == Direction::RIGHT) { setDirection(Direction::LEFT); - } else { + } + else { setDirection(Direction::RIGHT); } } -void from_json(const json &j, ReactionData &a) { +void from_json(const json& j, ReactionData& a) +{ if (!j.is_object() || j.size() != 1) { throw ConfigurationError("Invalid JSON data for ReactionData"); } @@ -906,44 +1054,54 @@ void from_json(const json &j, ReactionData &a) { a.only_neutral_molecules = val.value("neutral", false); if (val.contains("lnK")) { a.lnK_unmodified = val.at("lnK").get(); - } else if (val.contains("pK")) { + } + else if (val.contains("pK")) { a.lnK_unmodified = -std::numbers::ln10 * val.at("pK").get(); - } else { + } + else { a.lnK_unmodified = 0.0; } a.lnK = a.lnK_unmodified; // helper function used to parse and register atom and molecule names; updates lnK auto registerNames = [&](auto& names, auto&& atom_map, auto& mol_map, double sign) { - for (const auto& atom_or_molecule_name : names) { // loop over species on reactant side (left) + for (const auto& atom_or_molecule_name : + names) { // loop over species on reactant side (left) auto [atom, molecule] = a.findAtomOrMolecule(atom_or_molecule_name); if (atom != Faunus::atoms.end()) { // atomic reactants - if (atom->implicit) { // if atom is implicit, multiply K by its activity + if (atom->implicit) { // if atom is implicit, multiply K by its activity if (atom->activity > 0.0) { a.lnK += sign * std::log(atom->activity / 1.0_molar); } - } else { + } + else { assert(std::fabs(atom->activity) <= pc::epsilon_dbl); atom_map[atom->id()]++; // increment stoichiometric number } - } else if (molecule != Faunus::molecules.end()) { // molecular reactants (incl. "atomic" groups) - mol_map[molecule->id()]++; // increment stoichiometric number - if (molecule->activity > 0) { // assume activity not part of K -> divide by activity + } + else if (molecule != + Faunus::molecules.end()) { // molecular reactants (incl. "atomic" groups) + mol_map[molecule->id()]++; // increment stoichiometric number + if (molecule->activity > + 0) { // assume activity not part of K -> divide by activity a.lnK -= sign * std::log(molecule->activity / 1.0_molar); } - } else { + } + else { assert(false); // we should never reach here } } }; // end of lambda function - std::tie(a.left_names, a.right_names) = parseReactionString(a.reaction_str); // lists of species - registerNames(a.left_names, a.left_atoms, a.left_molecules, 1.0); // reactants - registerNames(a.right_names, a.right_atoms, a.right_molecules, -1.0); // products + std::tie(a.left_names, a.right_names) = + parseReactionString(a.reaction_str); // lists of species + registerNames(a.left_names, a.left_atoms, a.left_molecules, 1.0); // reactants + registerNames(a.right_names, a.right_atoms, a.right_molecules, -1.0); // products } } -void to_json(json &j, const ReactionData &reaction) { +void to_json(json& j, const ReactionData& reaction) +{ ReactionData a = reaction; // we want lnK to show for LEFT-->RIGHT direction a.setDirection(ReactionData::Direction::RIGHT); @@ -955,7 +1113,8 @@ void to_json(json &j, const ReactionData &reaction) { } std::pair -ReactionData::findAtomOrMolecule(const std::string& atom_or_molecule_name) { +ReactionData::findAtomOrMolecule(const std::string& atom_or_molecule_name) +{ auto atom_iter = findName(Faunus::atoms, atom_or_molecule_name); auto molecule_iter = findName(Faunus::molecules, atom_or_molecule_name); if (molecule_iter == Faunus::molecules.end() and atom_iter == Faunus::atoms.end()) { @@ -967,11 +1126,18 @@ ReactionData::findAtomOrMolecule(const std::string& atom_or_molecule_name) { /** * @return Reaction free energy in the current direction, i.e. products minus reactants (kT) */ -double ReactionData::freeEnergy() const { return (direction == Direction::RIGHT) ? -lnK : lnK; } +double ReactionData::freeEnergy() const +{ + return (direction == Direction::RIGHT) ? -lnK : lnK; +} -const std::string& ReactionData::getReactionString() const { return reaction_str; } +const std::string& ReactionData::getReactionString() const +{ + return reaction_str; +} -bool ReactionData::containsAtomicSwap() const { +bool ReactionData::containsAtomicSwap() const +{ // If exactly one atomic reactant and one atomic products, it's a swap move! return (left_atoms.size() == 1 && right_atoms.size() == 1); } @@ -996,7 +1162,8 @@ const ReactionData::MapFilter ReactionData::is_molecular_group = [](const auto& return Faunus::molecules.at(pair.first).isMolecular(); }; -TEST_CASE("[Faunus] ReactionData") { +TEST_CASE("[Faunus] ReactionData") +{ using doctest::Approx; json j = R"( { @@ -1014,7 +1181,7 @@ TEST_CASE("[Faunus] ReactionData") { Faunus::atoms = j["atomlist"].get(); molecules = j["moleculelist"].get(); // fill global instance - auto &r = reactions; // reference to global reaction list + auto& r = reactions; // reference to global reaction list r = j["reactionlist"].get(); CHECK_EQ(r.size(), 1); @@ -1022,18 +1189,22 @@ TEST_CASE("[Faunus] ReactionData") { CHECK_EQ(r.front().freeEnergy(), Approx(10.051 + std::log(0.2))); } -void MoleculeInserter::from_json(const json &) {} -void MoleculeInserter::to_json(json &) const {} +void MoleculeInserter::from_json(const json&) {} + +void MoleculeInserter::to_json(json&) const {} TEST_SUITE_END(); UnknownMoleculeError::UnknownMoleculeError(std::string_view molecule_name) - : GenericError("unknown molecule: '{}'", molecule_name) {} + : GenericError("unknown molecule: '{}'", molecule_name) +{ +} /** * @throw if molecule not found */ -MoleculeData& findMoleculeByName(std::string_view name) { +MoleculeData& findMoleculeByName(std::string_view name) +{ const auto result = findName(Faunus::molecules, name); if (result == Faunus::molecules.end()) { throw UnknownMoleculeError(name); @@ -1046,7 +1217,9 @@ MoleculeData& findMoleculeByName(std::string_view name) { * @return Pair with reactants (first) and products (second) as atomic or molecule names * @throws std::runtime_error if unknown atom or molecule, or syntax error */ -std::pair, std::vector> parseReactionString(const std::string& process_string) { +std::pair, std::vector> +parseReactionString(const std::string& process_string) +{ using Tvec = std::vector; Tvec names; // vector of atom/molecule names std::string atom_or_molecule_name; @@ -1069,7 +1242,8 @@ std::pair, std::vector> parseReactionStrin * @return vector of corresponding molecule ids (sorted) * @throw if unknown molecule name or not a list of strings */ -std::vector parseMolecules(const json& j) { +std::vector parseMolecules(const json& j) +{ if (!j.is_array()) { throw ConfigurationError("array of molecules expected"); } diff --git a/src/molecule.h b/src/molecule.h index eb5ba5811..16cd4e15b 100644 --- a/src/molecule.h +++ b/src/molecule.h @@ -30,67 +30,76 @@ class MoleculeData; * All inserters are function objects, expecting a geometry, particle vector, * and molecule data. */ -struct MoleculeInserter { - virtual ParticleVector operator()(const Geometry::GeometryBase &geo, MoleculeData &mol, - const ParticleVector &other_particles) = 0; +struct MoleculeInserter +{ + virtual ParticleVector operator()(const Geometry::GeometryBase& geo, MoleculeData& mol, + const ParticleVector& other_particles) = 0; virtual void from_json(const json& j); virtual void to_json(json& j) const; virtual ~MoleculeInserter() = default; }; -void from_json(const json &j, MoleculeInserter &inserter); -void to_json(json &j, const MoleculeInserter &inserter); +void from_json(const json& j, MoleculeInserter& inserter); +void to_json(json& j, const MoleculeInserter& inserter); /** * @brief Inserts molecules into random positions in the container */ -class RandomInserter : public MoleculeInserter { +class RandomInserter : public MoleculeInserter +{ private: - void translateRotateAtomicGroup(const Geometry::GeometryBase& geo, Faunus::QuaternionRotate& rotator, + void translateRotateAtomicGroup(const Geometry::GeometryBase& geo, + Faunus::QuaternionRotate& rotator, ParticleVector& particles) const; void translateRotateMolecularGroup(const Geometry::GeometryBase& geo, QuaternionRotate& rotator, ParticleVector& particles) const; public: - Point dir = {1, 1, 1}; //!< Scalars for random mass center position. Default (1,1,1) - Point offset = {0, 0, 0}; //!< Added to random position. Default (0,0,0) - bool rotate = true; //!< Set to true to randomly rotate molecule when inserted. Default: true + Point dir = {1, 1, 1}; //!< Scalars for random mass center position. Default (1,1,1) + Point offset = {0, 0, 0}; //!< Added to random position. Default (0,0,0) + bool rotate = true; //!< Set to true to randomly rotate molecule when inserted. Default: true bool keep_positions = false; //!< Set to true to keep original positions (default: false) bool allow_overlap = false; //!< Set to true to skip container overlap check int max_trials = 20'000; //!< Maximum number of container overlap checks - ParticleVector operator()(const Geometry::GeometryBase &geo, MoleculeData &molecule, - const ParticleVector &ignored_other_particles = ParticleVector()) override; - void from_json(const json &j) override; - void to_json(json &j) const override; + ParticleVector + operator()(const Geometry::GeometryBase& geo, MoleculeData& molecule, + const ParticleVector& ignored_other_particles = ParticleVector()) override; + void from_json(const json& j) override; + void to_json(json& j) const override; }; /** * Possible structure for molecular conformations */ -struct Conformation { +struct Conformation +{ std::vector positions; std::vector charges; bool empty() const; - void copyTo(ParticleVector &particles) const; //!< Copy conformation into particle vector + void copyTo(ParticleVector& particles) const; //!< Copy conformation into particle vector }; /** - * @brief Determines if two particles within a group are excluded from mutual nonbonded interactions. + * @brief Determines if two particles within a group are excluded from mutual nonbonded + * interactions. * * Simple and naΓ―ve implementation storing all possible pairs in a matrix. * @internal Currently not used. MoleculeData can use this class internally. */ -class ExclusionsSimple { - bool any_exclusions = false; //!< true if at least one excluded interaction is present - int size; //!< number of particles within the group; matrix = size Γ— size - using char_bool = unsigned char; //!< obs: std::vector uses packing with performance implications - std::shared_ptr> excluded_pairs; //!< 1D exclusion matrix; shared ptr saves copying +class ExclusionsSimple +{ + bool any_exclusions = false; //!< true if at least one excluded interaction is present + int size; //!< number of particles within the group; matrix = size Γ— size + using char_bool = + unsigned char; //!< obs: std::vector uses packing with performance implications + std::shared_ptr> + excluded_pairs; //!< 1D exclusion matrix; shared ptr saves copying public: using AtomPair = std::pair; //! Creates and populates exclusions. - static ExclusionsSimple create(int atoms_cnt, const std::vector> &pairs); + static ExclusionsSimple create(int atoms_cnt, const std::vector>& pairs); explicit ExclusionsSimple(int size = 0); //! @param i, j indices of atoms within molecule with excluded nonbonded interaction void add(int i, int j); @@ -98,11 +107,12 @@ class ExclusionsSimple { //! @param i, j indices of atoms within molecule with excluded nonbonded interaction bool isExcluded(int i, int j) const; bool empty() const; //!< true if no excluded interactions at all - friend void from_json(const json &j, ExclusionsSimple &exclusions); - friend void to_json(json &j, const ExclusionsSimple &exclusions); + friend void from_json(const json& j, ExclusionsSimple& exclusions); + friend void to_json(json& j, const ExclusionsSimple& exclusions); }; -inline bool ExclusionsSimple::isExcluded(int i, int j) const { +inline bool ExclusionsSimple::isExcluded(int i, int j) const +{ if (i > j) { std::swap(i, j); } @@ -110,29 +120,36 @@ inline bool ExclusionsSimple::isExcluded(int i, int j) const { return any_exclusions && static_cast((*excluded_pairs)[i * size + j]); } -inline bool ExclusionsSimple::empty() const { return !any_exclusions; } +inline bool ExclusionsSimple::empty() const +{ + return !any_exclusions; +} -void from_json(const json &j, ExclusionsSimple &exclusions); -void to_json(json &j, const ExclusionsSimple &exclusions); +void from_json(const json& j, ExclusionsSimple& exclusions); +void to_json(json& j, const ExclusionsSimple& exclusions); /** - * @brief Determines if two particles within a group are excluded from mutual nonbonded interactions. + * @brief Determines if two particles within a group are excluded from mutual nonbonded + * interactions. * - * This is a memory optimized implementation especially for linear molecules. The matrix of excluded pairs - * has dimensions of number of atoms Γ— maximal distance between excluded neighbours (in terms of atom indices - * within the molecule). + * This is a memory optimized implementation especially for linear molecules. The matrix of excluded + * pairs has dimensions of number of atoms Γ— maximal distance between excluded neighbours (in terms + * of atom indices within the molecule). * * @internal MoleculeData uses this class internally. */ -class ExclusionsVicinity { +class ExclusionsVicinity +{ public: - using char_bool = unsigned char; //!< obs: std::vector uses packing with performance implications + using char_bool = + unsigned char; //!< obs: std::vector uses packing with performance implications using AtomPair = std::pair; private: int atoms_cnt = 0; //!< count of atoms in the molecule; unmutable int max_bond_distance = 0; //!< max distance (difference) between indices of excluded particles - std::shared_ptr> excluded_pairs; //!< 1D exclusion matrix; shared ptr saves copying + std::shared_ptr> + excluded_pairs; //!< 1D exclusion matrix; shared ptr saves copying int toIndex(int i, int j) const; AtomPair fromIndex(int n) const; @@ -152,14 +169,16 @@ class ExclusionsVicinity { //! @param i, j indices of atoms within molecule with excluded nonbonded interaction void add(int i, int j); - void add(const std::vector> &pairs); + void add(const std::vector>& pairs); bool isExcluded(int i, - int j) const; //!< @param i, j indices of atoms within molecule with excluded nonbonded interaction + int j) + const; //!< @param i, j indices of atoms within molecule with excluded nonbonded interaction bool empty() const; //!< true if no excluded interactions at all - friend void to_json(json &j, const ExclusionsVicinity &exclusions); + friend void to_json(json& j, const ExclusionsVicinity& exclusions); }; -inline bool ExclusionsVicinity::isExcluded(int i, int j) const { +inline bool ExclusionsVicinity::isExcluded(int i, int j) const +{ if (i > j) { std::swap(i, j); } @@ -167,23 +186,31 @@ inline bool ExclusionsVicinity::isExcluded(int i, int j) const { return (j - i <= max_bond_distance && static_cast((*excluded_pairs)[toIndex(i, j)])); } -inline bool ExclusionsVicinity::empty() const { return max_bond_distance == 0; } +inline bool ExclusionsVicinity::empty() const +{ + return max_bond_distance == 0; +} -inline int ExclusionsVicinity::toIndex(int i, int j) const { return i * max_bond_distance + (j - i - 1); } +inline int ExclusionsVicinity::toIndex(int i, int j) const +{ + return i * max_bond_distance + (j - i - 1); +} -inline ExclusionsVicinity::AtomPair ExclusionsVicinity::fromIndex(const int n) const { +inline ExclusionsVicinity::AtomPair ExclusionsVicinity::fromIndex(const int n) const +{ auto i = n / max_bond_distance; auto j = n % max_bond_distance + i + 1; return {i, j}; } // void from_json(const json &j, ExclusionsVicinity &exclusions); // not implemented -void to_json(json &j, const ExclusionsVicinity &exclusions); +void to_json(json& j, const ExclusionsVicinity& exclusions); /** * @brief General properties for molecules */ -class MoleculeData { +class MoleculeData +{ public: using index_type = int; @@ -203,11 +230,12 @@ class MoleculeData { void createMolecularConformations(const json& j); //!< Add conformations if appropriate void setConformationWeights(const json& j); //!< Add weights for conformations - std::string name; //!< Molecule name - bool atomic = false; //!< True if atomic group (salt etc.) - bool compressible = false; //!< True if compressible group (scales internally upon volume change) - bool rigid = false; //!< True if particle should be considered as rigid - double activity = 0.0; //!< Chemical activity (mol/l) + std::string name; //!< Molecule name + bool atomic = false; //!< True if atomic group (salt etc.) + bool compressible = + false; //!< True if compressible group (scales internally upon volume change) + bool rigid = false; //!< True if particle should be considered as rigid + double activity = 0.0; //!< Chemical activity (mol/l) std::vector atoms; //!< Sequence of atoms in molecule (atom id's) BasePointerVector bonds; @@ -225,7 +253,8 @@ class MoleculeData { /** @brief Specify function to be used when inserting into space. * - * By default a random position and orientation is generator and overlap with container is avoided. + * By default a random position and orientation is generator and overlap with container is + * avoided. */ void setInserter(std::shared_ptr ins); @@ -242,23 +271,27 @@ class MoleculeData { const ParticleVector& otherparticles = ParticleVector()); friend class MoleculeBuilder; - friend void to_json(json &j, const MoleculeData &a); - friend void from_json(const json &j, MoleculeData &a); + friend void to_json(json& j, const MoleculeData& a); + friend void from_json(const json& j, MoleculeData& a); }; // end of class -inline bool MoleculeData::isPairExcluded(int i, int j) const { return exclusions.isExcluded(i, j); } -void to_json(json &j, const MoleculeData &a); -void from_json(const json &j, MoleculeData &a); -void from_json(const json &j, std::vector &v); +inline bool MoleculeData::isPairExcluded(int i, int j) const +{ + return exclusions.isExcluded(i, j); +} + +void to_json(json& j, const MoleculeData& a); +void from_json(const json& j, MoleculeData& a); +void from_json(const json& j, std::vector& v); // global instance of molecule vector extern std::vector molecules; - /** * @brief An exception to indicate an unknown molecule name in the input. */ -struct UnknownMoleculeError: public GenericError { +struct UnknownMoleculeError : public GenericError +{ explicit UnknownMoleculeError(std::string_view molecule_name); }; @@ -280,31 +313,37 @@ std::vector parseMolecules(const json& j); * * The instance is disposable: The method from_json() may be called only once. */ -class MoleculeBuilder { - bool is_used = false; //!< from_json may be called only once +class MoleculeBuilder +{ + bool is_used = false; //!< from_json may be called only once std::string molecule_name; //!< human readable name of the molecule ParticleVector particles; //!< vector of particles; it unpacks to atoms and conformations[0] decltype(MoleculeData::bonds) bonds; std::vector> exclusion_pairs; + protected: - static bool isFasta(const json& j); //!< the structure is read in FASTA format with harmonic bonds - void readCompoundValues(const json &j); //!< a director method calling the executive methods in the right order - void readAtomic(const json& j); //!< reads "atoms" : ["Na", "Cl"] - void readParticles(const json& j); //!< reads "structure" in any format; uses MoleculeStructureReader - void readBonds(const json& j); //!< reads "bondlist" - void readFastaBonds(const json& j); //!< makes up harmonic bonds for a FASTA sequence - void readExclusions(const json& j); //!< reads "exclusionlist" and "excluded_neighbours" + static bool + isFasta(const json& j); //!< the structure is read in FASTA format with harmonic bonds + void readCompoundValues( + const json& j); //!< a director method calling the executive methods in the right order + void readAtomic(const json& j); //!< reads "atoms" : ["Na", "Cl"] + void + readParticles(const json& j); //!< reads "structure" in any format; uses MoleculeStructureReader + void readBonds(const json& j); //!< reads "bondlist" + void readFastaBonds(const json& j); //!< makes up harmonic bonds for a FASTA sequence + void readExclusions(const json& j); //!< reads "exclusionlist" and "excluded_neighbours" static std::shared_ptr createInserter(const json& j); public: //! initialize MoleculeData from JSON; shall be called only once during the instance lifetime - void from_json(const json &j, MoleculeData &molecule); + void from_json(const json& j, MoleculeData& molecule); }; /** * @brief Fills the particle vector from various sources, e.g., files or JSON array. */ -class MoleculeStructureReader { +class MoleculeStructureReader +{ bool read_charges; //!< shall we also read charges when available, e.g., in PQR protected: //! reads array with atom types and positions @@ -317,16 +356,17 @@ class MoleculeStructureReader { //! reads atom types, positions and optionally charges from a file void readFile(ParticleVector& particles, const std::string& filename) const; //! a director determining the executive method based on JSON content - void readJson(ParticleVector &particles, const json &j) const; + void readJson(ParticleVector& particles, const json& j) const; }; /** - * @brief Generate all possible atom pairs within a given bond distance. Only 1-2 bonds (e.g., harmonic or FENE) - * are considered. + * @brief Generate all possible atom pairs within a given bond distance. Only 1-2 bonds (e.g., + * harmonic or FENE) are considered. * * @internal Used by MoleculeBuilder only to generate exclusions from excluded neighbours count. */ -class NeighboursGenerator { +class NeighboursGenerator +{ //! a path created from 1-2 bonds (e.g., harmonic or FENE) as an ordered list of atoms involved; //! atoms are addressed by intramolecular indices public: @@ -336,7 +376,8 @@ class NeighboursGenerator { using BondVector = decltype(MoleculeData::bonds); private: - std::map bond_map; //!< atom β†’ list of directly bonded atoms with a 1-2 bond; addressing by indices + std::map + bond_map; //!< atom β†’ list of directly bonded atoms with a 1-2 bond; addressing by indices //! paths indexed by a path length (starting with a zero distance for a single atom) //! a path is a sequence (without loops) of atoms connected by a 1-2 bond, std::vector> paths; @@ -346,13 +387,13 @@ class NeighboursGenerator { public: //! atom pairs addressed by intramolecular indices - NeighboursGenerator(const BondVector &bonds); + NeighboursGenerator(const BondVector& bonds); /** * Append all atom pairs within a given bond distance to the pair list. * @param pairs atom pair list * @param bond_distance maximal number of 1-2 bonds between atoms to consider */ - void generatePairs(AtomPairList &pairs, int bond_distance); + void generatePairs(AtomPairList& pairs, int bond_distance); }; /** @@ -377,13 +418,19 @@ class NeighboursGenerator { * - [x] `reservoir_size` should ideally be associated with an implicit molecule * - [ ] `direction` should be a member of ReactionData due to possible data races */ -class ReactionData { +class ReactionData +{ public: using StoichiometryMap = std::map; //!< key = id; value = stoichiometic coeff. using AtomicAndMolecularPair = std::pair; - using StoichiometryPair = StoichiometryMap::value_type; //!< first = id; second = stoichiometic coeff. + using StoichiometryPair = + StoichiometryMap::value_type; //!< first = id; second = stoichiometic coeff. using MapFilter = std::function; - enum class Direction : char { LEFT = 0, RIGHT = 1 }; + enum class Direction : char + { + LEFT = 0, + RIGHT = 1 + }; const static MapFilter is_implicit_group; const static MapFilter not_implicit_group; @@ -392,8 +439,8 @@ class ReactionData { const static MapFilter is_molecular_group; private: - friend void from_json(const json &, ReactionData &); - friend void to_json(json &, const ReactionData &); + friend void from_json(const json&, ReactionData&); + friend void to_json(json&, const ReactionData&); Direction direction = Direction::RIGHT; //!< Direction of reaction std::vector left_names, right_names; //!< Names of reactants and products @@ -401,7 +448,7 @@ class ReactionData { StoichiometryMap right_molecules; //!< Initial products (molecules) StoichiometryMap left_atoms; //!< Initial reactants (atoms) StoichiometryMap right_atoms; //!< Initial products (atoms) - double lnK = 0.0; //!< Effective, natural logarithm of molar eq. const (left->right) + double lnK = 0.0; //!< Effective, natural logarithm of molar eq. const (left->right) double lnK_unmodified = 0.0; //!< Natural logarithm of molar eq. const. (unmodified as in input) std::string reaction_str; //!< Name of reaction @@ -411,21 +458,23 @@ class ReactionData { * * Note that molecules and atoms *cannot* have the same name */ - static std::pair + static std::pair findAtomOrMolecule(const std::string& atom_or_molecule_name); public: - void setDirection(Direction); //!< Set directions of the process - void setRandomDirection(Random &random); //!< Set random direction (left or right) - Direction getDirection() const; //!< Get direction of the process - void reverseDirection(); //!< Reverse direction of reaction - AtomicAndMolecularPair getProducts() const; //!< Pair with atomic and molecular products - AtomicAndMolecularPair getReactants() const; //!< Pair with atomic and molecular reactants - double freeEnergy() const; //!< Free energy of reaction in current direction (kT) - bool containsAtomicSwap() const; //!< True if a swap move is detected, i.e. atom_a <-> atom_b + void setDirection(Direction); //!< Set directions of the process + void setRandomDirection(Random& random); //!< Set random direction (left or right) + Direction getDirection() const; //!< Get direction of the process + void reverseDirection(); //!< Reverse direction of reaction + AtomicAndMolecularPair getProducts() const; //!< Pair with atomic and molecular products + AtomicAndMolecularPair getReactants() const; //!< Pair with atomic and molecular reactants + double freeEnergy() const; //!< Free energy of reaction in current direction (kT) + bool containsAtomicSwap() const; //!< True if a swap move is detected, i.e. atom_a <-> atom_b const std::string& getReactionString() const; //!< Reaction string - std::pair, std::set> participatingAtomsAndMolecules() const; //!< Atom and molecule id's affected + std::pair, std::set> + participatingAtomsAndMolecules() const; //!< Atom and molecule id's affected bool only_neutral_molecules = false; //!< Only neutral molecules are involved in the reaction }; //!< End of class @@ -436,10 +485,11 @@ class ReactionData { * Reactants and products are split by a `=` sign. All elements in the string * must be separated by a white-space. */ -std::pair, std::vector> parseReactionString(const std::string& process_string); +std::pair, std::vector> +parseReactionString(const std::string& process_string); -void from_json(const json &, ReactionData &); -void to_json(json &, const ReactionData &); +void from_json(const json&, ReactionData&); +void to_json(json&, const ReactionData&); extern std::vector reactions; // global instance diff --git a/src/montecarlo.cpp b/src/montecarlo.cpp index 6a0bd65dd..aaaca8c71 100644 --- a/src/montecarlo.cpp +++ b/src/montecarlo.cpp @@ -13,13 +13,16 @@ namespace Faunus { * @note Regardless of outcome the random number generator should be incremented. This is important * when using some MPI schemes where the simulations must be in sync. */ -bool MetropolisMonteCarlo::metropolisCriterion(const double energy_change) { +bool MetropolisMonteCarlo::metropolisCriterion(const double energy_change) +{ static_assert(std::numeric_limits::is_iec559, "IEEE 754 required"); if (std::isnan(energy_change)) { throw std::runtime_error("Metropolis error: energy cannot be NaN"); } - const auto random_number_between_zero_and_one = move::Move::slump(); // engine *must* be propagated! - if (std::isinf(energy_change) && energy_change < 0.0) { // if negative infinity -> quietly accept + const auto random_number_between_zero_and_one = + move::Move::slump(); // engine *must* be propagated! + if (std::isinf(energy_change) && + energy_change < 0.0) { // if negative infinity -> quietly accept return true; } if (-energy_change > pc::max_exp_argument) { // if large negative value -> accept with warning @@ -36,13 +39,16 @@ bool MetropolisMonteCarlo::metropolisCriterion(const double energy_change) { * - resets the sum of energy changes to zero * - recalculates the initial energy */ -void MetropolisMonteCarlo::init() { +void MetropolisMonteCarlo::init() +{ sum_of_energy_changes = 0.0; Change change; change.everything = true; - state->pot->state = Energy::EnergyTerm::MonteCarloState::ACCEPTED; // this is the old energy (current, accepted) - trial_state->pot->state = Energy::EnergyTerm::MonteCarloState::TRIAL; // this is the new energy (trial) + state->pot->state = + Energy::EnergyTerm::MonteCarloState::ACCEPTED; // this is the old energy (current, accepted) + trial_state->pot->state = + Energy::EnergyTerm::MonteCarloState::TRIAL; // this is the new energy (trial) state->pot->init(); auto energy = state->pot->energy(change); @@ -75,8 +81,9 @@ void MetropolisMonteCarlo::init() { * * @return Relative energy drift */ -double MetropolisMonteCarlo::relativeEnergyDrift() { - Change change; // change object where ... +double MetropolisMonteCarlo::relativeEnergyDrift() +{ + Change change; // change object where ... change.everything = true; // ... we want to calculate the total energy double energy = state->pot->energy(change); double du = energy - initial_energy; @@ -90,13 +97,15 @@ double MetropolisMonteCarlo::relativeEnergyDrift() { return std::numeric_limits::quiet_NaN(); } -MetropolisMonteCarlo::MetropolisMonteCarlo(const json &j) - : original_log_level(faunus_logger->level()) { +MetropolisMonteCarlo::MetropolisMonteCarlo(const json& j) + : original_log_level(faunus_logger->level()) +{ state = std::make_unique(j); faunus_logger->set_level(spdlog::level::off); // do not duplicate log info trial_state = std::make_unique(j); // ...for the trial state faunus_logger->set_level(original_log_level); // restore original log level - moves = std::make_unique(j.at("moves"), *trial_state->spc, *trial_state->pot, *state->spc); + moves = std::make_unique(j.at("moves"), *trial_state->spc, + *trial_state->pot, *state->spc); init(); } @@ -105,7 +114,8 @@ MetropolisMonteCarlo::~MetropolisMonteCarlo() = default; /** * @todo Too many responsibilities; tidy up! */ -void MetropolisMonteCarlo::restore(const json &j) { +void MetropolisMonteCarlo::restore(const json& j) +{ try { from_json(j, *state->spc); // default, accepted state from_json(j, *trial_state->spc); // trial state @@ -119,31 +129,35 @@ void MetropolisMonteCarlo::restore(const json &j) { faunus_logger->warn("'reactionlist' in state file is deprecated and will be ignored"); } init(); - } catch (std::exception &e) { + } + catch (std::exception& e) { throw std::runtime_error("error initialising simulation: "s + e.what()); } } -void MetropolisMonteCarlo::performMove(move::Move& move) { +void MetropolisMonteCarlo::performMove(move::Move& move) +{ Change change; move.move(change); #ifndef NDEBUG try { change.sanityCheck(state->spc->groups); - } catch (std::exception &e) { + } + catch (std::exception& e) { throw std::runtime_error(e.what()); } #endif if (change) { latest_move_name = move.getName(); - trial_state->pot->updateState(change); // update energy terms to reflect change + trial_state->pot->updateState(change); // update energy terms to reflect change const auto new_energy = trial_state->pot->energy(change); // trial potential energy (kT) - const auto old_energy = state->pot->energy(change); // potential energy before move (kT) + const auto old_energy = state->pot->energy(change); // potential energy before move (kT) auto energy_change = getEnergyChange(new_energy, old_energy); - const auto energy_bias = move.bias(change, old_energy, new_energy) + - TranslationalEntropy(*trial_state->spc, *state->spc).energy(change); + const auto energy_bias = + move.bias(change, old_energy, new_energy) + + TranslationalEntropy(*trial_state->spc, *state->spc).energy(change); const auto total_trial_energy = energy_change + energy_bias; if (std::isnan(total_trial_energy)) { @@ -152,16 +166,19 @@ void MetropolisMonteCarlo::performMove(move::Move& move) { if (metropolisCriterion(total_trial_energy)) { // accept move state->sync(*trial_state, change); move.accept(change); - } else { // reject move + } + else { // reject move trial_state->sync(*state, change); move.reject(change); energy_change = 0.0; } sum_of_energy_changes += energy_change; // sum of all energy changes if (std::isfinite(initial_energy)) { - average_energy += initial_energy + sum_of_energy_changes; // update average potential energy + average_energy += + initial_energy + sum_of_energy_changes; // update average potential energy } - } else { + } + else { // The `metropolis()` function propagates the engine and we need to stay in sync // Alternatively, we could use `engine.discard()` move::Move::slump(); @@ -172,7 +189,8 @@ void MetropolisMonteCarlo::performMove(move::Move& move) { * Policies for infinite/nan energy changes * @return modified energy change, new_energy - old_energy */ -double MetropolisMonteCarlo::getEnergyChange(const double new_energy, const double old_energy) const { +double MetropolisMonteCarlo::getEnergyChange(const double new_energy, const double old_energy) const +{ if (std::isnan(old_energy) and !std::isnan(new_energy)) { // if NaN --> finite energy change return pc::neg_infty; // ...always accept } @@ -183,7 +201,7 @@ double MetropolisMonteCarlo::getEnergyChange(const double new_energy, const doub return pc::infty; //...always reject } const auto energy_change = new_energy - old_energy; // potential energy change (kT) - if (std::isnan(energy_change)) { // if difference is NaN, e.g. infinity - infinity, + if (std::isnan(energy_change)) { // if difference is NaN, e.g. infinity - infinity, return 0.0; } return energy_change; @@ -198,7 +216,8 @@ double MetropolisMonteCarlo::getEnergyChange(const double new_energy, const doub * * @todo using `weight` to mark as move as static is ugly */ -void MetropolisMonteCarlo::sweep() { +void MetropolisMonteCarlo::sweep() +{ assert(moves); number_of_sweeps++; auto perform_single_move = [&](auto& move) { performMove(*move); }; @@ -206,23 +225,35 @@ void MetropolisMonteCarlo::sweep() { ranges::cpp20::for_each(moves->constantIntervalMoves(number_of_sweeps), perform_single_move); } -Energy::Hamiltonian &MetropolisMonteCarlo::getHamiltonian() { return *state->pot; } +Energy::Hamiltonian& MetropolisMonteCarlo::getHamiltonian() +{ + return *state->pot; +} -Space &MetropolisMonteCarlo::getSpace() { return *state->spc; } +Space& MetropolisMonteCarlo::getSpace() +{ + return *state->spc; +} -Space& MetropolisMonteCarlo::getTrialSpace() { return *trial_state->spc; } +Space& MetropolisMonteCarlo::getTrialSpace() +{ + return *trial_state->spc; +} -void from_json(const json &j, MetropolisMonteCarlo::State &state) { +void from_json(const json& j, MetropolisMonteCarlo::State& state) +{ state.spc = std::make_unique(j); state.pot = std::make_unique(*state.spc, j.at("energy")); } -void MetropolisMonteCarlo::State::sync(const State& other, const Change& change) { +void MetropolisMonteCarlo::State::sync(const State& other, const Change& change) +{ spc->sync(*other.spc, change); pot->sync(&*other.pot, change); } -void to_json(json& j, const MetropolisMonteCarlo& monte_carlo) { +void to_json(json& j, const MetropolisMonteCarlo& monte_carlo) +{ j = monte_carlo.state->spc->info(); j["temperature"] = pc::temperature / 1.0_K; if (monte_carlo.moves) { @@ -238,21 +269,25 @@ void to_json(json& j, const MetropolisMonteCarlo& monte_carlo) { TranslationalEntropy::TranslationalEntropy(const Space& trial_space, const Space& space) : trial_spc(trial_space) - , spc(space) {} + , spc(space) +{ +} /** * @param trial_count Number of atoms or molecules after move * @param count Number of atoms or molecular before move * @return Energy contribution (kT) to be added to MC trial energy */ -double TranslationalEntropy::bias(int trial_count, int count) const { +double TranslationalEntropy::bias(int trial_count, int count) const +{ double energy = 0.0; if (int dN = trial_count - count; dN > 0) { // atoms or molecules were added double V_trial = trial_spc.geometry.getVolume(); for (int n = 0; n < dN; n++) { energy += std::log((count + 1 + n) / (V_trial * 1.0_molar)); } - } else if (dN < 0) { // atoms or molecules were removed + } + else if (dN < 0) { // atoms or molecules were removed double V = spc.geometry.getVolume(); for (int n = 0; n < (-dN); n++) { energy -= std::log((count - n) / (V * 1.0_molar)); @@ -261,12 +296,16 @@ double TranslationalEntropy::bias(int trial_count, int count) const { return energy; // kT } -double TranslationalEntropy::atomSwapEnergy(const Change::GroupChange& group_change) const { +double TranslationalEntropy::atomSwapEnergy(const Change::GroupChange& group_change) const +{ assert(group_change.dNswap); assert(group_change.relative_atom_indices.size() == 1); double energy = 0.0; - int id1 = trial_spc.groups.at(group_change.group_index).at(group_change.relative_atom_indices.front()).id; - int id2 = spc.groups.at(group_change.group_index).at(group_change.relative_atom_indices.front()).id; + int id1 = trial_spc.groups.at(group_change.group_index) + .at(group_change.relative_atom_indices.front()) + .id; + int id2 = + spc.groups.at(group_change.group_index).at(group_change.relative_atom_indices.front()).id; for (auto atomid : {id1, id2}) { auto atoms_new = trial_spc.findAtoms(atomid); auto atoms_old = spc.findAtoms(atomid); @@ -277,9 +316,12 @@ double TranslationalEntropy::atomSwapEnergy(const Change::GroupChange& group_cha return energy; // kT } -double TranslationalEntropy::atomChangeEnergy(const int molid) const { - auto mollist_new = trial_spc.findMolecules(molid, Space::Selection::ALL); // "ALL" because "ACTIVE" - auto mollist_old = spc.findMolecules(molid, Space::Selection::ALL); // ...returns only full groups +double TranslationalEntropy::atomChangeEnergy(const int molid) const +{ + auto mollist_new = + trial_spc.findMolecules(molid, Space::Selection::ALL); // "ALL" because "ACTIVE" + auto mollist_old = + spc.findMolecules(molid, Space::Selection::ALL); // ...returns only full groups if (range_size(mollist_new) > 1 || range_size(mollist_old) > 1) { throw std::runtime_error("multiple atomic groups of the same type is not allowed"); } @@ -288,7 +330,8 @@ double TranslationalEntropy::atomChangeEnergy(const int molid) const { return bias(N_new, N_old); } -double TranslationalEntropy::moleculeChangeEnergy(const int molid) const { +double TranslationalEntropy::moleculeChangeEnergy(const int molid) const +{ auto mollist_new = trial_spc.findMolecules(molid, Space::Selection::ACTIVE); auto mollist_old = spc.findMolecules(molid, Space::Selection::ACTIVE); int N_new = range_size(mollist_new); // number of molecules after move @@ -300,21 +343,27 @@ double TranslationalEntropy::moleculeChangeEnergy(const int molid) const { * @param change Change due to latest Monte Carlo move * @return Logarithm of the bias for the Metropolis criterion (units of kT) */ -double TranslationalEntropy::energy(const Change& change) { +double TranslationalEntropy::energy(const Change& change) +{ double energy_change = 0.0; if (!change.matter_change || change.disable_translational_entropy) { return energy_change; } - std::set already_processed; // ignore future encounters of these molid's + std::set + already_processed; // ignore future encounters of these molid's for (const Change::GroupChange& data : change.groups) { // loop over each change group - if (data.dNswap) { // number of atoms has changed as a result of a swap move + if (data.dNswap) { // number of atoms has changed as a result of a swap move energy_change += atomSwapEnergy(data); - } else { // it is not a swap move + } + else { // it is not a swap move const auto molid = trial_spc.groups.at(data.group_index).id; assert(molid == spc.groups.at(data.group_index).id); - if (data.dNatomic and Faunus::molecules.at(molid).isAtomic()) { // an atomic group has been changed + if (data.dNatomic and + Faunus::molecules.at(molid).isAtomic()) { // an atomic group has been changed energy_change += atomChangeEnergy(molid); - } else if (!already_processed.contains(molid)) { // a molecule has been inserted or deleted + } + else if (!already_processed.contains( + molid)) { // a molecule has been inserted or deleted energy_change += moleculeChangeEnergy(molid); already_processed.insert(molid); // ignore future encounters of molid } diff --git a/src/montecarlo.h b/src/montecarlo.h index 0b8f10f2f..a0171a627 100644 --- a/src/montecarlo.h +++ b/src/montecarlo.h @@ -14,7 +14,7 @@ class Hamiltonian; namespace move { class Move; class MoveCollection; -} // namespace Move +} // namespace move /** * @brief Class to handle Monte Carlo moves @@ -32,7 +32,8 @@ class MoveCollection; * The class has too many responsibilities, particularly in setting up the * system. */ -class MetropolisMonteCarlo { +class MetropolisMonteCarlo +{ public: /** * @brief Class to describe a system state @@ -48,11 +49,13 @@ class MetropolisMonteCarlo { * After the move, the two states can be synchronized using the * `sync()` function. */ - struct State { - std::unique_ptr spc; //!< Simulation space (positions, geometry, molecules) + struct State + { + std::unique_ptr spc; //!< Simulation space (positions, geometry, molecules) std::unique_ptr pot; //!< Hamiltonian for calc. potential energy - void sync(const State& other, - const Change& change); //!< Sync with another state (the other state is not modified) + void + sync(const State& other, + const Change& change); //!< Sync with another state (the other state is not modified) }; private: @@ -68,22 +71,22 @@ class MetropolisMonteCarlo { void performMove(move::Move& move); //!< Perform move using given move implementation double getEnergyChange(double new_energy, double old_energy) const; friend void to_json(json&, const MetropolisMonteCarlo&); //!< Write information to JSON object - unsigned int number_of_sweeps = 0; //!< Number of MC sweeps, e.g. calls to sweep() + unsigned int number_of_sweeps = 0; //!< Number of MC sweeps, e.g. calls to sweep() public: MetropolisMonteCarlo(const json& j); - Energy::Hamiltonian& getHamiltonian(); //!< Get Hamiltonian of accepted (default) state - Space& getSpace(); //!< Access to space in accepted (default) state - Space& getTrialSpace(); //!< Access to trial space - double relativeEnergyDrift(); //!< Relative energy drift from initial configuration - void sweep(); //!< Perform all moves (stochastic and static) - void restore(const json& j); //!< Restores system from previously store json object + Energy::Hamiltonian& getHamiltonian(); //!< Get Hamiltonian of accepted (default) state + Space& getSpace(); //!< Access to space in accepted (default) state + Space& getTrialSpace(); //!< Access to trial space + double relativeEnergyDrift(); //!< Relative energy drift from initial configuration + void sweep(); //!< Perform all moves (stochastic and static) + void restore(const json& j); //!< Restores system from previously store json object static bool metropolisCriterion(double energy_change); //!< Metropolis criterion - ~MetropolisMonteCarlo(); //!< Required due to unique_ptr to incomplete type + ~MetropolisMonteCarlo(); //!< Required due to unique_ptr to incomplete type }; -void from_json(const json &, MetropolisMonteCarlo::State &); //!< Build state from json object -void to_json(json &, const MetropolisMonteCarlo &); +void from_json(const json&, MetropolisMonteCarlo::State&); //!< Build state from json object +void to_json(json&, const MetropolisMonteCarlo&); /** * @brief Entropy change due to particle fluctuations @@ -106,14 +109,17 @@ void to_json(json &, const MetropolisMonteCarlo &); * - [ ] Move to Energy namespace? * - [ ] Verify with volume fluctuations which would make `Energy::Isobaric` redundant */ -class TranslationalEntropy { +class TranslationalEntropy +{ private: const Space& trial_spc; //!< Space after proposed MC move ("trial") const Space& spc; //!< Space before MC move ("default") double bias(int trial_count, int count) const; //!< Bias due to change in atom/molecule numbers - double atomSwapEnergy(const Change::GroupChange& group_change) const; //!< Contribution from atomic swap move - double atomChangeEnergy(int molid) const; //!< Contribution from size-change of atomic group - double moleculeChangeEnergy(int molid) const; //!< Contribution frin change in number of molecular groups + double atomSwapEnergy( + const Change::GroupChange& group_change) const; //!< Contribution from atomic swap move + double atomChangeEnergy(int molid) const; //!< Contribution from size-change of atomic group + double moleculeChangeEnergy( + int molid) const; //!< Contribution frin change in number of molecular groups public: TranslationalEntropy(const Space& trial_space, const Space& space); diff --git a/src/move.cpp b/src/move.cpp index b9a3c4fda..1276c4c96 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -10,25 +10,29 @@ #include #include #include -#include #include +#include +#include namespace Faunus::move { Random Move::slump; // static instance of Random (shared for all moves) -void Move::from_json(const json& j) { +void Move::from_json(const json& j) +{ if (const auto it = j.find("repeat"); it != j.end()) { if (it->is_number()) { repeat = it->get(); - } else if (it->is_string() && it->get() == "N") { + } + else if (it->is_string() && it->get() == "N") { repeat = -1; - } else { + } + else { throw std::runtime_error("invalid 'repeat'"); } } sweep_interval = j.value("nstep", 1); // Non-stochastic moves are defined with `repeat=0`... - if (sweep_interval > 1) { // ...the move is then instead run at a fixed sweep interval + if (sweep_interval > 1) { // ...the move is then instead run at a fixed sweep interval repeat = 0; } _from_json(j); @@ -37,7 +41,8 @@ void Move::from_json(const json& j) { } } -void Move::to_json(json& j) const { +void Move::to_json(json& j) const +{ _to_json(j); if (timer_move.result() > 0.01) { // only print if more than 1% of the time j["relative time (without energy calc)"] = timer_move.result(); @@ -55,7 +60,8 @@ void Move::to_json(json& j) const { roundJSON(j, 3); } -void Move::move(Change& change) { +void Move::move(Change& change) +{ timer.start(); timer_move.start(); number_of_attempted_moves++; @@ -67,13 +73,15 @@ void Move::move(Change& change) { timer_move.stop(); } -void Move::accept(Change& change) { +void Move::accept(Change& change) +{ number_of_accepted_moves++; _accept(change); timer.stop(); } -void Move::reject(Change& change) { +void Move::reject(Change& change) +{ number_of_rejected_moves++; _reject(change); timer.stop(); @@ -90,7 +98,8 @@ void Move::reject(Change& change) { * @return Energy due to custom bias from the particular move (kT) */ double Move::bias([[maybe_unused]] Change& change, [[maybe_unused]] double old_energy, - [[maybe_unused]] double new_energy) { + [[maybe_unused]] double new_energy) +{ return 0.0; } @@ -101,16 +110,32 @@ void Move::_reject([[maybe_unused]] Change& change) {} Move::Move(Space& spc, std::string_view name, std::string_view cite) : cite(cite) , name(name) - , spc(spc) {} + , spc(spc) +{ +} -void Move::setRepeat(const int new_repeat) { repeat = new_repeat; } -bool Move::isStochastic() const { return repeat != 0; } +void Move::setRepeat(const int new_repeat) +{ + repeat = new_repeat; +} -void from_json(const json& j, Move& move) { move.from_json(j); } +bool Move::isStochastic() const +{ + return repeat != 0; +} -void to_json(json& j, const Move& move) { move.to_json(j[move.getName()]); } +void from_json(const json& j, Move& move) +{ + move.from_json(j); +} + +void to_json(json& j, const Move& move) +{ + move.to_json(j[move.getName()]); +} -const std::string& Move::getName() const { +const std::string& Move::getName() const +{ assert(!name.empty()); return name; } @@ -118,47 +143,67 @@ const std::string& Move::getName() const { // ----------------------------------- ReplayMove::ReplayMove(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) {} + : Move(spc, name, cite) +{ +} -ReplayMove::ReplayMove(Space& spc) : ReplayMove(spc, "replay", "") {} +ReplayMove::ReplayMove(Space& spc) + : ReplayMove(spc, "replay", "") +{ +} -void ReplayMove::_to_json(json& j) const { j["file"] = reader->filename; } +void ReplayMove::_to_json(json& j) const +{ + j["file"] = reader->filename; +} -void ReplayMove::_from_json(const json& j) { reader = std::make_unique(j.at("file").get()); } +void ReplayMove::_from_json(const json& j) +{ + reader = std::make_unique(j.at("file").get()); +} -void ReplayMove::_move(Change &change) { +void ReplayMove::_move(Change& change) +{ assert(reader); if (!end_of_trajectory) { - if (reader->read(frame.step, frame.timestamp, frame.box, spc.positions().begin(), spc.positions().end())) { + if (reader->read(frame.step, frame.timestamp, frame.box, spc.positions().begin(), + spc.positions().end())) { spc.geometry.setLength(frame.box); change.everything = true; - } else { + } + else { // nothing to do, simulation shall stop end_of_trajectory = true; - mcloop_logger->warn("No more frames to read from {}. Running on empty.", reader->filename); + mcloop_logger->warn("No more frames to read from {}. Running on empty.", + reader->filename); } } } -double ReplayMove::bias(Change&, double, double) { +double ReplayMove::bias(Change&, double, double) +{ return force_accept; // always accept } -void AtomicTranslateRotate::_to_json(json& j) const { +void AtomicTranslateRotate::_to_json(json& j) const +{ j = {{"dir", directions}, {"molid", molid}, - {unicode::rootof + unicode::bracket("r" + unicode::squared), std::sqrt(mean_square_displacement.avg())}, + {unicode::rootof + unicode::bracket("r" + unicode::squared), + std::sqrt(mean_square_displacement.avg())}, {"molecule", molecule_name}}; roundJSON(j, 3); } -void AtomicTranslateRotate::_from_json(const json& j) { +void AtomicTranslateRotate::_from_json(const json& j) +{ assert(!molecules.empty()); molecule_name = j.at("molecule"); const auto molecule = findMoleculeByName(molecule_name); molid = molecule.id(); if (molecule.rigid) { - faunus_logger->warn("structure of rigid molecule {} may be disturbed by {}", molecule_name, name); + faunus_logger->warn("structure of rigid molecule {} may be disturbed by {}", molecule_name, + name); } directions = j.value("dir", Point(1, 1, 1)); if (repeat < 0) { @@ -171,34 +216,41 @@ void AtomicTranslateRotate::_from_json(const json& j) { energy_resolution = j.value("energy_resolution", 0.0); } -void AtomicTranslateRotate::translateParticle(ParticleVector::iterator particle, double displacement) { +void AtomicTranslateRotate::translateParticle(ParticleVector::iterator particle, + double displacement) +{ const auto old_position = particle->pos; // backup old position particle->pos += randomUnitVector(slump, directions) * displacement * slump(); spc.geometry.boundary(particle->pos); - latest_displacement_squared = spc.geometry.sqdist(old_position, particle->pos); // square displacement + latest_displacement_squared = + spc.geometry.sqdist(old_position, particle->pos); // square displacement auto& group = spc.groups.at(cdata.group_index); if (group.isMolecular()) { - group.mass_center = - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); + group.mass_center = Geometry::massCenter( + group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); checkMassCenter(group); } } -void AtomicTranslateRotate::checkMassCenter(Space::GroupType& group) const { +void AtomicTranslateRotate::checkMassCenter(Space::GroupType& group) const +{ const auto allowed_threshold = 1e-6; const auto old_mass_center = group.mass_center; group.translate(-old_mass_center, spc.geometry.getBoundaryFunc()); // translate to origin - const auto should_be_zero = spc.geometry.sqdist({0, 0, 0}, Geometry::massCenter(group.begin(), group.end())); + const auto should_be_zero = + spc.geometry.sqdist({0, 0, 0}, Geometry::massCenter(group.begin(), group.end())); if (should_be_zero > allowed_threshold) { faunus_logger->error("{}: error calculating mass center for {}", name, group.traits().name); groupToDisk(group); - throw std::runtime_error("molecule likely too large for periodic boundaries; increase box size?"); + throw std::runtime_error( + "molecule likely too large for periodic boundaries; increase box size?"); } group.translate(old_mass_center, spc.geometry.getBoundaryFunc()); } -void AtomicTranslateRotate::groupToDisk(const Space::GroupType& group) const { +void AtomicTranslateRotate::groupToDisk(const Space::GroupType& group) const +{ if (auto stream = std::ofstream("mass-center-failure.pqr"); stream) { const auto group_iter = spc.groups.cbegin() + spc.getGroupIndex(group); auto groups = ranges::cpp20::views::counted(group_iter, 1); // slice out single group @@ -206,7 +258,8 @@ void AtomicTranslateRotate::groupToDisk(const Space::GroupType& group) const { } } -void AtomicTranslateRotate::_move(Change& change) { +void AtomicTranslateRotate::_move(Change& change) +{ if (auto particle = randomAtom(); particle != spc.particles.end()) { latest_particle = particle; const auto translational_displacement = particle->traits().dp; @@ -226,30 +279,38 @@ void AtomicTranslateRotate::_move(Change& change) { if (translational_displacement > 0.0 or rotational_displacement > 0.0) { change.groups.push_back(cdata); // add to list of moved groups } - } else { + } + else { latest_particle = spc.particles.end(); latest_displacement_squared = 0.0; // no particle found --> no movement } } -void AtomicTranslateRotate::_accept(Change&) { +void AtomicTranslateRotate::_accept(Change&) +{ mean_square_displacement += latest_displacement_squared; sampleEnergyHistogram(); } -void AtomicTranslateRotate::_reject(Change&) { mean_square_displacement += 0; } +void AtomicTranslateRotate::_reject(Change&) +{ + mean_square_displacement += 0; +} -AtomicTranslateRotate::AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian, const std::string& name, - const std::string& cite) +AtomicTranslateRotate::AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian, + const std::string& name, const std::string& cite) : Move(spc, name, cite) - , hamiltonian(hamiltonian) { + , hamiltonian(hamiltonian) +{ repeat = -1; // meaning repeat N times cdata.relative_atom_indices.resize(1); cdata.internal = true; } AtomicTranslateRotate::AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian) - : AtomicTranslateRotate(spc, hamiltonian, "transrot", "") {} + : AtomicTranslateRotate(spc, hamiltonian, "transrot", "") +{ +} /** * For atomic groups, select `ALL` since these may be partially filled and thereby @@ -258,17 +319,21 @@ AtomicTranslateRotate::AtomicTranslateRotate(Space& spc, const Energy::Hamiltoni * * @return Iterator to particle to move; `end()` if nothing selected */ -ParticleVector::iterator AtomicTranslateRotate::randomAtom() { +ParticleVector::iterator AtomicTranslateRotate::randomAtom() +{ assert(molid >= 0); auto particle = spc.particles.end(); // particle iterator - auto selection = (Faunus::molecules[molid].atomic) ? Space::Selection::ALL : Space::Selection::ACTIVE; + auto selection = + (Faunus::molecules[molid].atomic) ? Space::Selection::ALL : Space::Selection::ACTIVE; auto mollist = spc.findMolecules(molid, selection); - if (auto group = slump.sample(mollist.begin(), mollist.end()); group != mollist.end()) { // random molecule + if (auto group = slump.sample(mollist.begin(), mollist.end()); + group != mollist.end()) { // random molecule if (not group->empty()) { - particle = slump.sample(group->begin(), group->end()); // random particle - cdata.group_index = Faunus::distance(spc.groups.begin(), group); // index of touched group - cdata.relative_atom_indices[0] = - std::distance(group->begin(), particle); // index of moved particle relative to group + particle = slump.sample(group->begin(), group->end()); // random particle + cdata.group_index = + Faunus::distance(spc.groups.begin(), group); // index of touched group + cdata.relative_atom_indices[0] = std::distance( + group->begin(), particle); // index of moved particle relative to group } } return particle; @@ -278,21 +343,25 @@ ParticleVector::iterator AtomicTranslateRotate::randomAtom() { * Here we access the Hamiltonian and sum all energy terms from the just performed MC move. * This is used to updated the histogram of energies, sampled for each individual particle type. */ -void AtomicTranslateRotate::sampleEnergyHistogram() { +void AtomicTranslateRotate::sampleEnergyHistogram() +{ if (energy_resolution > 0.0) { assert(latest_particle != spc.particles.end()); - const auto particle_energy = - std::accumulate(hamiltonian.latestEnergies().begin(), hamiltonian.latestEnergies().end(), 0.0); + const auto particle_energy = std::accumulate(hamiltonian.latestEnergies().begin(), + hamiltonian.latestEnergies().end(), 0.0); auto& particle_histogram = - energy_histogram.try_emplace(latest_particle->id, SparseHistogram(energy_resolution)).first->second; + energy_histogram.try_emplace(latest_particle->id, SparseHistogram(energy_resolution)) + .first->second; particle_histogram.add(particle_energy); } } -void AtomicTranslateRotate::saveHistograms() { +void AtomicTranslateRotate::saveHistograms() +{ if (energy_resolution) { for (const auto& [atom_id, histogram] : energy_histogram) { - const auto filename = fmt::format("energy-histogram-{}.dat", Faunus::atoms[atom_id].name); + const auto filename = + fmt::format("energy-histogram-{}.dat", Faunus::atoms[atom_id].name); if (auto stream = std::ofstream(filename); stream) { stream << "# energy/kT observations\n" << histogram; } @@ -300,7 +369,10 @@ void AtomicTranslateRotate::saveHistograms() { } } -AtomicTranslateRotate::~AtomicTranslateRotate() { saveHistograms(); } +AtomicTranslateRotate::~AtomicTranslateRotate() +{ + saveHistograms(); +} /** * @param name Name of move to create @@ -310,7 +382,8 @@ AtomicTranslateRotate::~AtomicTranslateRotate() { saveHistograms(); } * @param old_spc Reference to "old" space (rarely used by any move, except Speciation) */ std::unique_ptr createMove(const std::string& name, const json& properties, Space& spc, - Energy::Hamiltonian& hamiltonian, Space& old_spc) { + Energy::Hamiltonian& hamiltonian, Space& old_spc) +{ try { std::unique_ptr move; if (name == "moltransrot") { @@ -318,48 +391,64 @@ std::unique_ptr createMove(const std::string& name, const json& properties return std::make_unique(spc, properties); } move = std::make_unique(spc); - } else if (name == "conformationswap") { + } + else if (name == "conformationswap") { move = std::make_unique(spc); - } else if (name == "transrot") { + } + else if (name == "transrot") { move = std::make_unique(spc, hamiltonian); - } else if (name == "pivot") { + } + else if (name == "pivot") { move = std::make_unique(spc); - } else if (name == "crankshaft") { + } + else if (name == "crankshaft") { move = std::make_unique(spc); - } else if (name == "volume") { + } + else if (name == "volume") { move = std::make_unique(spc); - } else if (name == "charge") { + } + else if (name == "charge") { if (properties.value("quadratic", true)) { move = std::make_unique(spc); - } else { + } + else { move = std::make_unique(spc); } - } else if (name == "chargetransfer") { + } + else if (name == "chargetransfer") { move = std::make_unique(spc); - } else if (name == "rcmc") { + } + else if (name == "rcmc") { move = std::make_unique(spc, old_spc); - } else if (name == "quadrantjump") { + } + else if (name == "quadrantjump") { move = std::make_unique(spc); - } else if (name == "cluster") { + } + else if (name == "cluster") { move = std::make_unique(spc); - } else if (name == "replay") { + } + else if (name == "replay") { move = std::make_unique(spc); - } else if (name == "langevin_dynamics") { + } + else if (name == "langevin_dynamics") { move = std::make_unique(spc, hamiltonian); - } else if (name == "temper") { + } + else if (name == "temper") { #ifdef ENABLE_MPI move = std::make_unique(spc, MPI::mpi); move->setRepeat(0); // zero weight moves are run at the end of each sweep #else throw ConfigurationError("{} requires that Faunus is compiled with MPI", name); #endif - } else if (name == "gibbs_volume") { + } + else if (name == "gibbs_volume") { #ifdef ENABLE_MPI move = std::make_unique(spc, MPI::mpi); #else throw ConfigurationError("{} requires that Faunus is compiled with MPI", name); #endif - } else if (name == "gibbs_matter") { + } + else if (name == "gibbs_matter") { #ifdef ENABLE_MPI move = std::make_unique(spc, MPI::mpi); #else @@ -371,37 +460,52 @@ std::unique_ptr createMove(const std::string& name, const json& properties } move->from_json(properties); return move; - } catch (std::exception& e) { throw ConfigurationError("error creating move -> {}", e.what()); } + } + catch (std::exception& e) { + throw ConfigurationError("error creating move -> {}", e.what()); + } } -void MoveCollection::addMove(std::shared_ptr&& move) { +void MoveCollection::addMove(std::shared_ptr&& move) +{ if (!move) { throw std::runtime_error("invalid move"); } moves.vec.emplace_back(move); repeats.push_back(static_cast(move->repeat)); distribution = std::discrete_distribution(repeats.begin(), repeats.end()); - number_of_moves_per_sweep = static_cast(std::accumulate(repeats.begin(), repeats.end(), 0.0)); + number_of_moves_per_sweep = + static_cast(std::accumulate(repeats.begin(), repeats.end(), 0.0)); } -MoveCollection::MoveCollection(const json& list_of_moves, Space& spc, Energy::Hamiltonian& hamiltonian, Space &old_spc) { +MoveCollection::MoveCollection(const json& list_of_moves, Space& spc, + Energy::Hamiltonian& hamiltonian, Space& old_spc) +{ assert(list_of_moves.is_array()); for (const auto& j : list_of_moves) { // loop over move list const auto& [name, parameters] = jsonSingleItem(j); try { addMove(createMove(name, parameters, spc, hamiltonian, old_spc)); - } catch (std::exception& e) { + } + catch (std::exception& e) { usageTip.pick(name); throw ConfigurationError("{}", e.what()).attachJson(j); } } } -void to_json(json& j, const MoveCollection& propagator) { j = propagator.moves; } +void to_json(json& j, const MoveCollection& propagator) +{ + j = propagator.moves; +} -[[maybe_unused]] const BasePointerVector& MoveCollection::getMoves() const { return moves; } +[[maybe_unused]] const BasePointerVector& MoveCollection::getMoves() const +{ + return moves; +} -MoveCollection::move_iterator MoveCollection::sample() { +MoveCollection::move_iterator MoveCollection::sample() +{ #ifdef ENABLE_MPI auto& random_engine = MPI::mpi.random.engine; // parallel processes (tempering) must be in sync #else @@ -415,20 +519,24 @@ MoveCollection::move_iterator MoveCollection::sample() { #ifdef ENABLE_MPI -GibbsEnsembleHelper::GibbsEnsembleHelper(const Space& spc, const MPI::Controller& mpi, const VectorOfMolIds& molids) +GibbsEnsembleHelper::GibbsEnsembleHelper(const Space& spc, const MPI::Controller& mpi, + const VectorOfMolIds& molids) : mpi(mpi) - , molids(molids) { + , molids(molids) +{ if (molids.empty()) { faunus_logger->error("Gibbs ensemble: At least one molecule type required"); mpi.world.abort(1); } if (mpi.world.size() != 2) { - faunus_logger->error("Gibbs ensemble: Exactly two MPI processes required; use e.g. `mpirun -np 2`"); + faunus_logger->error( + "Gibbs ensemble: Exactly two MPI processes required; use e.g. `mpirun -np 2`"); mpi.world.abort(1); } if (mpi.world.rank() == 0) { partner_rank = 1; - } else { + } + else { partner_rank = 0; } // exchange and sum up total volume @@ -441,11 +549,13 @@ GibbsEnsembleHelper::GibbsEnsembleHelper(const Space& spc, const MPI::Controller for (const auto id : molids) { total_num_particles += spc.numMolecules(id); } - const int total_num_particles_partner = static_cast(exchange(static_cast(total_num_particles))); + const int total_num_particles_partner = + static_cast(exchange(static_cast(total_num_particles))); total_num_particles += total_num_particles_partner; } -double GibbsEnsembleHelper::exchange(const double value) const { +double GibbsEnsembleHelper::exchange(const double value) const +{ const auto tag = mpl::tag_t(0); double partner_value = 0.0; mpi.world.sendrecv(value, partner_rank, tag, partner_value, partner_rank, tag); @@ -456,9 +566,12 @@ double GibbsEnsembleHelper::exchange(const double value) const { * @return Pair with total particle counts in cell 1 and 2 * @note No MPI exchange needed */ -std::pair GibbsEnsembleHelper::currentNumParticles(const Space& spc) const { +std::pair GibbsEnsembleHelper::currentNumParticles(const Space& spc) const +{ namespace rv = ranges::cpp20::views; - auto to_num_molecules = [&](auto molid) { return spc.numMolecules(molid); }; + auto to_num_molecules = [&](auto molid) { + return spc.numMolecules(molid); + }; const int n1 = ranges::fold_left(molids | rv::transform(to_num_molecules), 0, std::plus<>()); const int n2 = total_num_particles - n1; return {n1, n2}; @@ -468,7 +581,8 @@ std::pair GibbsEnsembleHelper::currentNumParticles(const Space& spc) c * @return Pair with volumes of cell 1 and 2 * @note No MPI exchange needed */ -std::pair GibbsEnsembleHelper::currentVolumes(const Space& spc) const { +std::pair GibbsEnsembleHelper::currentVolumes(const Space& spc) const +{ const double v1 = spc.geometry.getVolume(); const double v2 = total_volume - v1; return {v1, v2}; @@ -478,17 +592,21 @@ std::pair GibbsEnsembleHelper::currentVolumes(const Space& spc) GibbsVolumeMove::GibbsVolumeMove(Space& spc, MPI::Controller& mpi) : VolumeMove(spc, "gibbs_volume"s) - , mpi(mpi) { + , mpi(mpi) +{ if (mpi.isMaster()) { - faunus_logger->warn("{}: This move is marked UNSTABLE - carefully check your output ⚠️", name); + faunus_logger->warn("{}: This move is marked UNSTABLE - carefully check your output ⚠️", + name); } } /** - * Here `1` is self and `2` is the partner. Note that the `du1` is captured by the normal trial energy - * and is excluded from the bias. + * Here `1` is self and `2` is the partner. Note that the `du1` is captured by the normal trial + * energy and is excluded from the bias. */ -double GibbsVolumeMove::bias([[maybe_unused]] Change& change, const double old_energy, const double new_energy) { +double GibbsVolumeMove::bias([[maybe_unused]] Change& change, const double old_energy, + const double new_energy) +{ if (volumeTooExtreme()) { return pc::infty; // reject move attempt } @@ -504,13 +622,15 @@ double GibbsVolumeMove::bias([[maybe_unused]] Change& change, const double old_e if (direct_volume_displacement) { // Panagiotopoulos et al., Eq. 5 gibbs_bias = -n1 * std::log((v1_old + dv) / v1_old) - n2 * std::log((v2_old - dv) / v2_old); - } else { + } + else { // @todo implement for ln V displacement - see Frenkel & Smith Eq. 8.3.3 faunus_logger->error("{}: lnV displacement is unimplemented", name); mpi.world.abort(1); } - faunus_logger->trace("{}: n1={} n2={} v1={:.1f} v2={:.1f} du1={:.2E} du2={:.2E} dv={:.1f} bias={:.2E}", name, n1, - n2, v1_old, v2_old, du1, du2, dv, gibbs_bias); + faunus_logger->trace( + "{}: n1={} n2={} v1={:.1f} v2={:.1f} du1={:.2E} du2={:.2E} dv={:.1f} bias={:.2E}", name, n1, + n2, v1_old, v2_old, du1, du2, dv, gibbs_bias); return du2 + gibbs_bias; // du1 is automatically added in `MetropolisMonteCarlo::performMove()` } @@ -519,7 +639,8 @@ double GibbsVolumeMove::bias([[maybe_unused]] Change& change, const double old_e * * @warning Hard-coded volume threshold */ -bool GibbsVolumeMove::volumeTooExtreme() const { +bool GibbsVolumeMove::volumeTooExtreme() const +{ const double min_volume = 1.0 * 1.0 * 1.0; // Γ…3 const double max_volume = gibbs->total_volume - min_volume; return ((new_volume < min_volume) || (new_volume > max_volume)); @@ -533,7 +654,8 @@ bool GibbsVolumeMove::volumeTooExtreme() const { * v1(n) / v2(n) = exp{ ln( v1_o / v2_o ) + 𝝳 } = f * => v1(n) = v_tot / ( 1 / f + 1) */ -void GibbsVolumeMove::setNewVolume() { +void GibbsVolumeMove::setNewVolume() +{ auto& random_engine = mpi.random; // either expand or contract volume; do the opposite in the other cell @@ -546,11 +668,14 @@ void GibbsVolumeMove::setNewVolume() { const auto partner_old_volume = gibbs->total_volume - old_volume; if (direct_volume_displacement) { - new_volume = old_volume + sign * (random_engine() - 0.5) * logarithmic_volume_displacement_factor; - } else { + new_volume = + old_volume + sign * (random_engine() - 0.5) * logarithmic_volume_displacement_factor; + } + else { // ln(V) displacement - see Frenkel and Smith, Section 8.3.2 - const auto f = std::exp(std::log(old_volume / partner_old_volume) + - sign * (random_engine() - 0.5) * logarithmic_volume_displacement_factor); + const auto f = + std::exp(std::log(old_volume / partner_old_volume) + + sign * (random_engine() - 0.5) * logarithmic_volume_displacement_factor); new_volume = gibbs->total_volume / (1.0 / f + 1.0); } @@ -571,12 +696,14 @@ void GibbsVolumeMove::setNewVolume() { * Gibbs ensemble we handle this specially via the `bias()` function, and * we therefore want to skip any calls to `TranslationalEnergy::energy()`. */ -void GibbsVolumeMove::_move(Change& change) { +void GibbsVolumeMove::_move(Change& change) +{ change.disable_translational_entropy = true; VolumeMove::_move(change); } -void GibbsVolumeMove::_from_json(const json& j) { +void GibbsVolumeMove::_from_json(const json& j) +{ VolumeMove::_from_json(j); if (volume_scaling_method != Geometry::VolumeMethod::ISOTROPIC) { throw ConfigurationError("Gibbs ensemble currently requires isotropic volume scaling"); @@ -595,16 +722,19 @@ void GibbsVolumeMove::_from_json(const json& j) { GibbsMatterMove::GibbsMatterMove(Space& spc, MPI::Controller& mpi) : Move(spc, "gibbs_matter"s, "doi:10/cvzgw9") - , mpi(mpi) { + , mpi(mpi) +{ molecule_bouncer = std::make_unique(spc, random, true); if (mpi.isMaster()) { - faunus_logger->warn("{}: This move is marked UNSTABLE - carefully check your output ⚠️", name); + faunus_logger->warn("{}: This move is marked UNSTABLE - carefully check your output ⚠️", + name); } } void GibbsMatterMove::_to_json([[maybe_unused]] json& j) const {} -void GibbsMatterMove::_from_json(const json& j) { +void GibbsMatterMove::_from_json(const json& j) +{ const auto molname = j.at("molecule").get(); const auto molids = Faunus::names2ids(Faunus::molecules, {molname}); gibbs = std::make_unique(spc, mpi, molids); @@ -619,18 +749,19 @@ void GibbsMatterMove::_from_json(const json& j) { const int capacity1 = spc.numMolecules(molid); const int capacity2 = static_cast(gibbs->exchange(static_cast(capacity1))); if ((capacity1 < gibbs->total_num_particles) || (capacity2 < gibbs->total_num_particles)) { - faunus_logger->error("{}: '{}' must have a capacity of at least {} molecules", name, molname, - gibbs->total_num_particles); + faunus_logger->error("{}: '{}' must have a capacity of at least {} molecules", name, + molname, gibbs->total_num_particles); mpi.world.abort(1); } } -void GibbsMatterMove::_move(Change& change) { +void GibbsMatterMove::_move(Change& change) +{ auto& random_engine = mpi.random; // insert or delete; do the opposite in the other cell insert = static_cast(random_engine.range(0, 1)); // note internal random generator! if (gibbs->mpi.isMaster()) { - insert = !insert; // delete in one cell; remove from the other + insert = !insert; // delete in one cell; remove from the other } // find a molecule to insert or delete @@ -639,7 +770,8 @@ void GibbsMatterMove::_move(Change& change) { // boundary check const bool no_group = (group == spc.groups.end()); - const bool cell_is_full = (spc.numMolecules(molid) == gibbs->total_num_particles); + const bool cell_is_full = + (spc.numMolecules(molid) == gibbs->total_num_particles); if (no_group || (insert && cell_is_full)) { change.clear(); return; @@ -652,7 +784,8 @@ void GibbsMatterMove::_move(Change& change) { auto& group_change = change.groups.emplace_back(); if (insert) { std::tie(group_change, _bias) = molecule_bouncer->activate(*group); - } else { + } + else { std::tie(group_change, _bias) = molecule_bouncer->deactivate(*group); } } @@ -662,20 +795,23 @@ void GibbsMatterMove::_move(Change& change) { * * See Eq. 8 of Panagiotopoulus, Quirke, Stapleton, Tildesley, Mol. Phys. 1988:63:527 */ -double GibbsMatterMove::bias([[maybe_unused]] Change& change, const double old_energy, const double new_energy) { +double GibbsMatterMove::bias([[maybe_unused]] Change& change, const double old_energy, + const double new_energy) +{ const auto [n1_new, n2_new] = gibbs->currentNumParticles(spc); const auto [v1, v2] = gibbs->currentVolumes(spc); const auto du1 = new_energy - old_energy; const auto du2 = gibbs->exchange(du1); // MPI call // Eq. 8 in https://dx.doi.org/10/cvzgw9 - const double gibbs_bias = - (insert) ? std::log(v2 * n1_new / (v1 * (n2_new - 1.0))) : std::log(v1 * n2_new / (v2 * (n1_new - 1.0))); + const double gibbs_bias = (insert) ? std::log(v2 * n1_new / (v1 * (n2_new - 1.0))) + : std::log(v1 * n2_new / (v2 * (n1_new - 1.0))); if (faunus_logger->level() <= spdlog::level::trace) { const auto gibbs_bias_other = gibbs->exchange(gibbs_bias); if (mpi.isMaster()) { - faunus_logger->trace("{}: bias={:.2E} other_bias={:.2E}", name, gibbs_bias, gibbs_bias_other); + faunus_logger->trace("{}: bias={:.2E} other_bias={:.2E}", name, gibbs_bias, + gibbs_bias_other); } } @@ -684,7 +820,8 @@ double GibbsMatterMove::bias([[maybe_unused]] Change& change, const double old_e // ----------------------------------- -void ParallelTempering::_to_json(json& j) const { +void ParallelTempering::_to_json(json& j) const +{ j = {{"replicas", mpi.world.size()}, {"format", exchange_particles.getFormat()}, {"partner_policy", partner->policy}, @@ -699,9 +836,12 @@ void ParallelTempering::_to_json(json& j) const { /** * Exchange groups sizes with partner MPI rank and Resize all groups to the exchanged values */ -void ParallelTempering::exchangeGroupSizes(Space::GroupVector& groups, int partner_rank) { - std::vector sizes = groups | ranges::cpp20::views::transform(&Group::size) | ranges::to_vector; - mpi.world.sendrecv_replace(sizes.begin(), sizes.end(), partner_rank, mpl::tag_t(0), partner_rank, mpl::tag_t(0)); +void ParallelTempering::exchangeGroupSizes(Space::GroupVector& groups, int partner_rank) +{ + std::vector sizes = + groups | ranges::cpp20::views::transform(&Group::size) | ranges::to_vector; + mpi.world.sendrecv_replace(sizes.begin(), sizes.end(), partner_rank, mpl::tag_t(0), + partner_rank, mpl::tag_t(0)); auto it = sizes.begin(); ranges::cpp20::for_each(groups, [&it](Group& group) { group.resize(*it++); }); } @@ -709,7 +849,8 @@ void ParallelTempering::exchangeGroupSizes(Space::GroupVector& groups, int partn /** * This will exchange the states between two partner replicas and set the change object accordingy */ -void ParallelTempering::exchangeState(Change& change) { +void ParallelTempering::exchangeState(Change& change) +{ if (MPI::exchangeVolume(mpi, *partner->rank, spc.geometry, volume_scaling_method)) { change.volume_change = true; } @@ -719,10 +860,12 @@ void ParallelTempering::exchangeState(Change& change) { change.everything = true; } -void ParallelTempering::_move(Change& change) { +void ParallelTempering::_move(Change& change) +{ mpi.world.barrier(); // wait until all ranks reach here if (!MPI::checkRandomEngineState(mpi.world, slump)) { - faunus_logger->error("Random numbers out of sync across MPI nodes. Do not use 'hardware' seed."); + faunus_logger->error( + "Random numbers out of sync across MPI nodes. Do not use 'hardware' seed."); mpi.world.abort(1); // neighbor search *requires* that random engines are in sync } partner->generate(mpi.world, slump); @@ -739,10 +882,12 @@ void ParallelTempering::_move(Change& change) { * the bias (== energy change of the partner) are added together * to form the final trial energy for the tempering move. */ -double ParallelTempering::exchangeEnergy(const double energy_change) { +double ParallelTempering::exchangeEnergy(const double energy_change) +{ double partner_energy_change = 0.0; const auto tag = mpl::tag_t(0); - mpi.world.sendrecv(energy_change, *partner->rank, tag, partner_energy_change, *partner->rank, tag); + mpi.world.sendrecv(energy_change, *partner->rank, tag, partner_energy_change, *partner->rank, + tag); return partner_energy_change; // return partner energy change } @@ -752,18 +897,23 @@ double ParallelTempering::exchangeEnergy(const double energy_change) { * replica is returned and will be added to the total trial energy in * the MetropolicMonteCarlo class. */ -double ParallelTempering::bias([[maybe_unused]] Change& change, double uold, double unew) { +double ParallelTempering::bias([[maybe_unused]] Change& change, double uold, double unew) +{ return exchangeEnergy(unew - uold); // energy change in partner replica } -void ParallelTempering::_accept([[maybe_unused]] Change& change) { +void ParallelTempering::_accept([[maybe_unused]] Change& change) +{ acceptance_map[partner->getPair(mpi.world)] += 1.0; } -void ParallelTempering::_reject([[maybe_unused]] Change& change) { + +void ParallelTempering::_reject([[maybe_unused]] Change& change) +{ acceptance_map[partner->getPair(mpi.world)] += 0.0; } -void ParallelTempering::_from_json(const json& j) { +void ParallelTempering::_from_json(const json& j) +{ exchange_particles.setFormat(j.value("format", MPI::ParticleBuffer::Format::XYZQI)); partner = createMPIPartnerPolicy(j.value("partner_policy", MPI::PartnerPolicy::ODDEVEN)); volume_scaling_method = j.value("volume_scale", Geometry::VolumeMethod::ISOTROPIC); @@ -771,7 +921,8 @@ void ParallelTempering::_from_json(const json& j) { ParallelTempering::ParallelTempering(Space& spc, const MPI::Controller& mpi) : Move(spc, "temper", "doi:10/b3vcw7") - , mpi(mpi) { + , mpi(mpi) +{ if (mpi.world.size() < 2) { throw std::runtime_error(name + " requires two or more MPI processes"); } @@ -780,7 +931,8 @@ ParallelTempering::ParallelTempering(Space& spc, const MPI::Controller& mpi) #endif -void VolumeMove::_to_json(json& j) const { +void VolumeMove::_to_json(json& j) const +{ if (number_of_attempted_moves > 0) { j = {{"dV", logarithmic_volume_displacement_factor}, {"method", volume_scaling_method}, @@ -790,7 +942,9 @@ void VolumeMove::_to_json(json& j) const { roundJSON(j, 3); } } -void VolumeMove::_from_json(const json& j) { + +void VolumeMove::_from_json(const json& j) +{ logarithmic_volume_displacement_factor = j.at("dV").get(); volume_scaling_method = j.value("method", Geometry::VolumeMethod::ISOTROPIC); if (volume_scaling_method == Geometry::VolumeMethod::INVALID) { @@ -798,12 +952,15 @@ void VolumeMove::_from_json(const json& j) { } } -void VolumeMove::setNewVolume() { +void VolumeMove::setNewVolume() +{ old_volume = spc.geometry.getVolume(); - new_volume = std::exp(std::log(old_volume) + (slump() - 0.5) * logarithmic_volume_displacement_factor); + new_volume = + std::exp(std::log(old_volume) + (slump() - 0.5) * logarithmic_volume_displacement_factor); } -void VolumeMove::_move(Change& change) { +void VolumeMove::_move(Change& change) +{ if (logarithmic_volume_displacement_factor > 0.0) { change.volume_change = true; change.everything = true; @@ -811,21 +968,27 @@ void VolumeMove::_move(Change& change) { spc.scaleVolume(new_volume, volume_scaling_method); } } -void VolumeMove::_accept([[maybe_unused]] Change& change) { + +void VolumeMove::_accept([[maybe_unused]] Change& change) +{ mean_square_volume_change += std::pow(new_volume - old_volume, 2); mean_volume += new_volume; assert(std::fabs(spc.geometry.getVolume() - new_volume) < 1.0e-9); } VolumeMove::VolumeMove(Space& spc, std::string_view name) - : Move(spc, name, ""s) { + : Move(spc, name, ""s) +{ repeat = 1; } VolumeMove::VolumeMove(Space& spc) - : VolumeMove(spc, "volume"s) {} + : VolumeMove(spc, "volume"s) +{ +} -void VolumeMove::_reject([[maybe_unused]] Change& change) { +void VolumeMove::_reject([[maybe_unused]] Change& change) +{ mean_square_volume_change += 0.0; mean_volume += old_volume; assert(std::fabs(spc.geometry.getVolume() - old_volume) < 1.0e-9); @@ -833,14 +996,17 @@ void VolumeMove::_reject([[maybe_unused]] Change& change) { // ------------------------------------------------ -void ChargeMove::_to_json(json& j) const { +void ChargeMove::_to_json(json& j) const +{ j = {{"index", particle_index}, {"dq", max_charge_displacement}}; if (!mean_squared_charge_displacement.empty()) { j["βˆšβŸ¨Ξ”q²⟩"] = std::sqrt(mean_squared_charge_displacement.avg()); } roundJSON(j, 3); } -void ChargeMove::_from_json(const json& j) { + +void ChargeMove::_from_json(const json& j) +{ max_charge_displacement = j.at("dq").get(); particle_index = j.at("index").get(); auto group_it = spc.findGroupContaining(spc.particles.at(particle_index)); @@ -849,10 +1015,12 @@ void ChargeMove::_from_json(const json& j) { } group_change.group_index = spc.getGroupIndex(*group_it); group_change.relative_atom_indices.at(0) = - std::distance(group_it->begin(), spc.particles.begin() + particle_index); // index of particle rel. to group + std::distance(group_it->begin(), + spc.particles.begin() + particle_index); // index of particle rel. to group } -void ChargeMove::_move(Change& change) { +void ChargeMove::_move(Change& change) +{ if (std::fabs(max_charge_displacement) > pc::epsilon_dbl) { auto& particle = spc.particles.at(particle_index); // refence to particle charge_displacement = getChargeDisplacement(particle); @@ -861,21 +1029,33 @@ void ChargeMove::_move(Change& change) { } } -double ChargeMove::getChargeDisplacement([[maybe_unused]] const Particle& particle) const { +double ChargeMove::getChargeDisplacement([[maybe_unused]] const Particle& particle) const +{ return max_charge_displacement * (slump() - 0.5); } -void ChargeMove::_accept(Change&) { mean_squared_charge_displacement += charge_displacement * charge_displacement; } -void ChargeMove::_reject(Change&) { mean_squared_charge_displacement += 0.0; } +void ChargeMove::_accept(Change&) +{ + mean_squared_charge_displacement += charge_displacement * charge_displacement; +} + +void ChargeMove::_reject(Change&) +{ + mean_squared_charge_displacement += 0.0; +} ChargeMove::ChargeMove(Space& spc, std::string_view name, std::string_view cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = 1; group_change.internal = true; // the group is internally changed group_change.relative_atom_indices.resize(1); // we change exactly one atom } -ChargeMove::ChargeMove(Space& spc) : ChargeMove(spc, "charge", "") {} +ChargeMove::ChargeMove(Space& spc) + : ChargeMove(spc, "charge", "") +{ +} // ----------------------------------- @@ -884,7 +1064,8 @@ ChargeMove::ChargeMove(Space& spc) : ChargeMove(spc, "charge", "") {} * * @returns dq = q' - q */ -double QuadraticChargeMove::getChargeDisplacement(const Particle& particle) const { +double QuadraticChargeMove::getChargeDisplacement(const Particle& particle) const +{ const auto old_charge = particle.charge; const auto sign = (old_charge < 0.0) ? -1.0 : 1.0; auto new_charge = sign * old_charge * old_charge + max_charge_displacement * (slump() - 0.5); @@ -896,7 +1077,8 @@ double QuadraticChargeMove::getChargeDisplacement(const Particle& particle) cons * @return Bias energy/kT = ln( |q'/q| ) */ double QuadraticChargeMove::bias(Change& change, [[maybe_unused]] double old_energy, - [[maybe_unused]] double new_energy) { + [[maybe_unused]] double new_energy) +{ if (change.empty()) { return 0.0; } @@ -907,7 +1089,8 @@ double QuadraticChargeMove::bias(Change& change, [[maybe_unused]] double old_ene return bias_energy; } -void QuadraticChargeMove::_to_json(json& j) const { +void QuadraticChargeMove::_to_json(json& j) const +{ ChargeMove::_to_json(j); j["quadratic"] = true; if (!mean_bias.empty()) { @@ -915,35 +1098,43 @@ void QuadraticChargeMove::_to_json(json& j) const { } } -QuadraticChargeMove::QuadraticChargeMove(Space& spc) : ChargeMove(spc) {} +QuadraticChargeMove::QuadraticChargeMove(Space& spc) + : ChargeMove(spc) +{ +} // ----------------------------------- -void ChargeTransfer::_to_json(json& j) const { +void ChargeTransfer::_to_json(json& j) const +{ using namespace unicode; j = {{"dq", dq}, {rootof + bracket(Delta + "q" + squared), std::sqrt(msqd.avg())}, {cuberoot + rootof + bracket(Delta + "q" + squared), std::cbrt(std::sqrt(msqd.avg()))}}; roundJSON(j, 3); } -void ChargeTransfer::_from_json(const json& j) { + +void ChargeTransfer::_from_json(const json& j) +{ dq = j.at("dq").get(); - mol1.molname = j.at("mol1"); // string containing name of molecule 1 - mol2.molname = j.at("mol2"); // string containing name of molecule 2 - mol1.id = findMoleculeByName(mol1.molname).id(); // group containing mol1.molname - mol2.id = findMoleculeByName(mol2.molname).id(); // group containing mol2.molname - mol1.molrange = j.at("molrange1").get>(); // vector containing lower and upper limit of - // total charge of molecule 1 - mol2.molrange = j.at("molrange2").get>(); // vector containing lower and upper limit of - // total charge of molecule 2 - mol1.min = j.at("min1").get>(); // vector containing lower limits of atomic charges in - // molecule 1 - mol1.max = j.at("max1").get>(); // vector containing upper limits of atomic charges in - // molecule 1 - mol2.min = j.at("min2").get>(); // vector containing lower limits of atomic charges in - // molecule 2 - mol2.max = j.at("max2").get>(); // vector containing upper limits of atomic charges in - // molecule 2 + mol1.molname = j.at("mol1"); // string containing name of molecule 1 + mol2.molname = j.at("mol2"); // string containing name of molecule 2 + mol1.id = findMoleculeByName(mol1.molname).id(); // group containing mol1.molname + mol2.id = findMoleculeByName(mol2.molname).id(); // group containing mol2.molname + mol1.molrange = + j.at("molrange1").get>(); // vector containing lower and upper limit of + // total charge of molecule 1 + mol2.molrange = + j.at("molrange2").get>(); // vector containing lower and upper limit of + // total charge of molecule 2 + mol1.min = j.at("min1").get>(); // vector containing lower limits of atomic + // charges in molecule 1 + mol1.max = j.at("max1").get>(); // vector containing upper limits of atomic + // charges in molecule 1 + mol2.min = j.at("min2").get>(); // vector containing lower limits of atomic + // charges in molecule 2 + mol2.max = j.at("max2").get>(); // vector containing upper limits of atomic + // charges in molecule 2 if (repeat < 0) { auto v = spc.findMolecules(mol1.id); @@ -963,9 +1154,10 @@ void ChargeTransfer::_from_json(const json& j) { if (mol1.min.empty() || mol1.max.empty()) { // checking so that mol1.min and mol1.max are not empty - throw ConfigurationError("mol1.min and mol1.max both need to have nonzero number of entries. " - "mol1.min has {} and mol1.max has {} entries.", - mol1.min.size(), mol1.max.size()); + throw ConfigurationError( + "mol1.min and mol1.max both need to have nonzero number of entries. " + "mol1.min has {} and mol1.max has {} entries.", + mol1.min.size(), mol1.max.size()); } if (mol2.min.size() != mol2.max.size()) { @@ -977,61 +1169,72 @@ void ChargeTransfer::_from_json(const json& j) { if (mol2.min.empty() || mol2.max.empty()) { // checking so that mol2.min and mol2.max are not empty - throw ConfigurationError("mol2.min and mol2.max both need to have nonzero number of entries. " - "mol2.min has {} and mol2.max has {} entries.", - mol2.min.size(), mol2.max.size()); + throw ConfigurationError( + "mol2.min and mol2.max both need to have nonzero number of entries. " + "mol2.min has {} and mol2.max has {} entries.", + mol2.min.size(), mol2.max.size()); } } -void ChargeTransfer::_move(Change& change) { +void ChargeTransfer::_move(Change& change) +{ auto mollist1 = spc.findMolecules(mol1.id, Space::Selection::ACTIVE); auto mollist2 = spc.findMolecules(mol2.id, Space::Selection::ACTIVE); if ((not ranges::cpp20::empty(mollist1)) and (not ranges::cpp20::empty(mollist2))) { - auto git1 = slump.sample(mollist1.begin(), mollist1.end()); // selecting a random molecule of type molecule1 - auto git2 = slump.sample(mollist2.begin(), mollist2.end()); // selecting a random molecule of type molecule2 + auto git1 = slump.sample(mollist1.begin(), + mollist1.end()); // selecting a random molecule of type molecule1 + auto git2 = slump.sample(mollist2.begin(), + mollist2.end()); // selecting a random molecule of type molecule2 if (!git1->empty() && !git2->empty()) { // check that both molecule1 and molecule 2 exist if (dq > 0) { // change.chargeMove = - // true; // setting to true makes the self-energy being computed and added to the total energy + // true; // setting to true makes the self-energy being computed and added to the + // total energy mol1.numOfAtoms = Faunus::distance(git1->begin(), git1->end()); mol2.numOfAtoms = Faunus::distance(git2->begin(), git2->end()); - mol1.ratio.clear(); // clearing vector containing ratio of atomic charge ranges and the charge range of - // the whole molecule1 - mol2.ratio.clear(); // clearing vector containing ratio of atomic charge ranges and the charge range of - // the whole molecule2 + mol1.ratio.clear(); // clearing vector containing ratio of atomic charge ranges and + // the charge range of the whole molecule1 + mol2.ratio.clear(); // clearing vector containing ratio of atomic charge ranges and + // the charge range of the whole molecule2 for (i = 0; i < mol1.numOfAtoms; i++) { mol1.ratio.push_back( (mol1.max[i] - mol1.min[i]) / - (mol1.molrange[1] - mol1.molrange[0])); // calculating ratio of atom i in molecule 1 + (mol1.molrange[1] - + mol1.molrange[0])); // calculating ratio of atom i in molecule 1 } for (i = 0; i < mol2.numOfAtoms; i++) { mol2.ratio.push_back( (mol2.max[i] - mol2.min[i]) / - (mol2.molrange[1] - mol2.molrange[0])); // calculating ratio of atom i in molecule 2 + (mol2.molrange[1] - + mol2.molrange[0])); // calculating ratio of atom i in molecule 2 } mol1.charges = 0; // setting sum of all atomic charges in molecule1 to zero mol2.charges = 0; // setting sum of all atomic charges in molecule2 to zero deltaq = dq * (slump() - 0.5); - mol1.changeQ.clear(); // clearing vector containing attempted charge moves on all atoms in molecule1 - mol2.changeQ.clear(); // clearing vector containing attempted charge moves on all atoms in molecule2 + mol1.changeQ.clear(); // clearing vector containing attempted charge moves on all + // atoms in molecule1 + mol2.changeQ.clear(); // clearing vector containing attempted charge moves on all + // atoms in molecule2 mol1.cdata.group_index = Faunus::distance(spc.groups.begin(), git1); mol2.cdata.group_index = Faunus::distance(spc.groups.begin(), git2); for (i = 0; i < mol1.numOfAtoms; i++) { auto p = git1->begin() + i; // object containing atom i in molecule1 - mol1.changeQ.push_back( - deltaq * mol1.ratio[i]); // assigning attempted charge move of atom i in molecule1 to vector + mol1.changeQ.push_back(deltaq * + mol1.ratio[i]); // assigning attempted charge move of + // atom i in molecule1 to vector // sumChanges1 += changeQ1[i]; - mol1.charges += - p->charge + mol1.changeQ[i]; // adding new attempted charge of atom i in molecule1 to sum + mol1.charges += p->charge + mol1.changeQ[i]; // adding new attempted charge of + // atom i in molecule1 to sum } - for (i = 0; i < mol2.numOfAtoms; i++) { // Doing the same as above loop but for molecule2 + for (i = 0; i < mol2.numOfAtoms; + i++) { // Doing the same as above loop but for molecule2 auto p = git2->begin() + i; mol2.changeQ.push_back(-deltaq * mol2.ratio[i]); // sumMoves2 += changeQ2[i]; @@ -1039,26 +1242,29 @@ void ChargeTransfer::_move(Change& change) { } // Torodial boundary conditions - if (mol1.charges < mol1.molrange[0]) { // Checking if sum of new attempted atomic charges in molecule1 - // will fall below lower limit in molrange1 - sumTemp = 0; // resetting temporary sum of atomic charges + if (mol1.charges < + mol1.molrange[0]) { // Checking if sum of new attempted atomic charges in + // molecule1 will fall below lower limit in molrange1 + sumTemp = 0; // resetting temporary sum of atomic charges for (i = 0; i < mol1.numOfAtoms; i++) { auto p = git1->begin() + i; // temporary sum of charge moves attempted on all atoms in molecule1 sumTemp += p->charge - (2 * mol1.min[i] - (p->charge + mol1.changeQ[i])); - // new attempted charge of atom i in molecule1, obeying torodial boundary conditions + // new attempted charge of atom i in molecule1, obeying torodial boundary + // conditions p->charge = 2 * mol1.min[i] - (p->charge + mol1.changeQ[i]); } for (i = 0; i < mol2.numOfAtoms; i++) { auto p = git2->begin() + i; - // new attempted charge of atom i in molecule2, obeying torodial boundary conditions + // new attempted charge of atom i in molecule2, obeying torodial boundary + // conditions p->charge += sumTemp * mol2.ratio[i]; } } else if (mol1.charges > mol1.molrange[1]) { - // same procedure as above if statement, but if sum of new atempted charges in molecule1 falls above - // upper limit in molrange1 + // same procedure as above if statement, but if sum of new atempted charges in + // molecule1 falls above upper limit in molrange1 sumTemp = 0; for (i = 0; i < mol1.numOfAtoms; i++) { auto p = git1->begin() + i; @@ -1086,8 +1292,8 @@ void ChargeTransfer::_move(Change& change) { } else if (mol2.charges > mol2.molrange[1]) { - // same as previous if statement, but if sum of new attempted charges in molecule2 falls above upper - // limit in molrange2 + // same as previous if statement, but if sum of new attempted charges in + // molecule2 falls above upper limit in molrange2 sumTemp = 0; for (i = 0; i < mol2.numOfAtoms; i++) { auto p = git2->begin() + i; @@ -1101,7 +1307,8 @@ void ChargeTransfer::_move(Change& change) { } else { - // in case no boundaries were crossed, i.e. all new charges lies within their respective ranges + // in case no boundaries were crossed, i.e. all new charges lies within their + // respective ranges for (i = 0; i < mol1.numOfAtoms; i++) { auto p = git1->begin() + i; p->charge += mol1.changeQ[i]; @@ -1115,18 +1322,26 @@ void ChargeTransfer::_move(Change& change) { mol2.cdata.all = true; // change all atoms in molecule2 change.groups.push_back(mol1.cdata); // add to list of moved groups change.groups.push_back(mol2.cdata); // add to list of moved groups - - } else + } + else deltaq = 0; } } } -void ChargeTransfer::_accept(Change&) { msqd += deltaq * deltaq; } -void ChargeTransfer::_reject(Change&) { msqd += 0; } +void ChargeTransfer::_accept(Change&) +{ + msqd += deltaq * deltaq; +} + +void ChargeTransfer::_reject(Change&) +{ + msqd += 0; +} ChargeTransfer::ChargeTransfer(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = -1; // meaning repeat N times mol1.cdata.internal = true; mol2.cdata.internal = true; @@ -1134,16 +1349,22 @@ ChargeTransfer::ChargeTransfer(Space& spc, const std::string& name, const std::s // cdata2.atoms.resize(numOfAtoms2); } -ChargeTransfer::ChargeTransfer(Space& spc) : ChargeTransfer(spc, "chargetransfer", "") {} +ChargeTransfer::ChargeTransfer(Space& spc) + : ChargeTransfer(spc, "chargetransfer", "") +{ +} -void QuadrantJump::_to_json(json& j) const { +void QuadrantJump::_to_json(json& j) const +{ j = {{"dir", dir}, {"molid", molid}, {unicode::rootof + unicode::bracket("r" + unicode::squared), std::sqrt(msqd.avg())}, {"molecule", molecules[molid].name}}; roundJSON(j, 3); } -void QuadrantJump::_from_json(const json& j) { + +void QuadrantJump::_from_json(const json& j) +{ assert(!molecules.empty()); const std::string molname = j.at("molecule"); molid = findMoleculeByName(molname).id(); @@ -1154,7 +1375,9 @@ void QuadrantJump::_from_json(const json& j) { repeat = std::distance(v.begin(), v.end()); } } -void QuadrantJump::_move(Change& change) { + +void QuadrantJump::_move(Change& change) +{ assert(molid >= 0); assert(!spc.groups.empty()); assert(spc.geometry.getVolume() > 0); @@ -1163,42 +1386,54 @@ void QuadrantJump::_move(Change& change) { // pick random group from the system matching molecule type // TODO: This can be slow -- implement look-up-table in Space - auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); // list of molecules w. 'molid' + auto mollist = + spc.findMolecules(molid, Space::Selection::ACTIVE); // list of molecules w. 'molid' if (not ranges::cpp20::empty(mollist)) { auto it = slump.sample(mollist.begin(), mollist.end()); if (not it->empty()) { assert(it->id == molid); Point oldcm = it->mass_center; if (index.size() == 2) { - auto cm_O = Geometry::massCenter(spc.particles.begin() + index[0], spc.particles.begin() + index[1] + 1, + auto cm_O = Geometry::massCenter(spc.particles.begin() + index[0], + spc.particles.begin() + index[1] + 1, spc.geometry.getBoundaryFunc()); it->translate(-2 * spc.geometry.vdist(oldcm, cm_O).cwiseProduct(dir.cast()), spc.geometry.getBoundaryFunc()); - } else { - it->translate(-2 * oldcm.cwiseProduct(dir.cast()), spc.geometry.getBoundaryFunc()); + } + else { + it->translate(-2 * oldcm.cwiseProduct(dir.cast()), + spc.geometry.getBoundaryFunc()); } _sqd = spc.geometry.sqdist(oldcm, it->mass_center); // squared displacement Change::GroupChange d; - d.group_index = Faunus::distance(spc.groups.begin(), it); // integer *index* of moved group - d.all = true; // *all* atoms in group were moved - change.groups.push_back(d); // add to list of moved groups + d.group_index = + Faunus::distance(spc.groups.begin(), it); // integer *index* of moved group + d.all = true; // *all* atoms in group were moved + change.groups.push_back(d); // add to list of moved groups assert(spc.geometry.sqdist(it->mass_center, - Geometry::massCenter(it->begin(), it->end(), spc.geometry.getBoundaryFunc(), + Geometry::massCenter(it->begin(), it->end(), + spc.geometry.getBoundaryFunc(), -it->mass_center)) < 1e-9); } - } else + } + else faunus_logger->warn("{0}: no molecules found", name); } QuadrantJump::QuadrantJump(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = -1; // meaning repeat N times } -QuadrantJump::QuadrantJump(Space& spc) : QuadrantJump(spc, "quadrantjump", "") {} +QuadrantJump::QuadrantJump(Space& spc) + : QuadrantJump(spc, "quadrantjump", "") +{ +} -void AtomicSwapCharge::_to_json(json& j) const { +void AtomicSwapCharge::_to_json(json& j) const +{ j = {{"pH", pH}, {"pka", pKa}, {"molid", molid}, @@ -1206,7 +1441,9 @@ void AtomicSwapCharge::_to_json(json& j) const { {"molecule", molname}}; roundJSON(j, 3); } -void AtomicSwapCharge::_from_json(const json& j) { + +void AtomicSwapCharge::_from_json(const json& j) +{ assert(!molecules.empty()); molname = j.at("molecule"); molid = findMoleculeByName(molname).id(); @@ -1220,21 +1457,27 @@ void AtomicSwapCharge::_from_json(const json& j) { } } } -ParticleVector::iterator AtomicSwapCharge::randomAtom() { + +ParticleVector::iterator AtomicSwapCharge::randomAtom() +{ assert(molid >= 0); auto mollist = spc.findMolecules(molid); // all `molid` groups if (not ranges::cpp20::empty(mollist)) { auto git = slump.sample(mollist.begin(), mollist.end()); // random molecule iterator if (!git->empty()) { - auto p = slump.sample(git->begin(), git->end()); // random particle iterator - cdata.group_index = Faunus::distance(spc.groups.begin(), git); // integer *index* of moved group - cdata.relative_atom_indices[0] = std::distance(git->begin(), p); // index of particle rel. to group + auto p = slump.sample(git->begin(), git->end()); // random particle iterator + cdata.group_index = + Faunus::distance(spc.groups.begin(), git); // integer *index* of moved group + cdata.relative_atom_indices[0] = + std::distance(git->begin(), p); // index of particle rel. to group return p; } } return spc.particles.end(); } -void AtomicSwapCharge::_move(Change& change) { + +void AtomicSwapCharge::_move(Change& change) +{ _sqd = 0.0; auto p = randomAtom(); if (p != spc.particles.end()) { @@ -1242,24 +1485,41 @@ void AtomicSwapCharge::_move(Change& change) { double oldcharge = p->charge; p->charge = fabs(oldcharge - 1); _sqd = fabs(oldcharge - 1) - oldcharge; - change.groups.push_back(cdata); // add to list of moved groups + change.groups.push_back(cdata); // add to list of moved groups _bias = _sqd * (pH - pKa) * std::numbers::ln10; // one may add bias here... } } -double AtomicSwapCharge::bias(Change&, double, double) { return _bias; } -void AtomicSwapCharge::_accept(Change&) { msqd += _sqd; } -void AtomicSwapCharge::_reject(Change&) { msqd += 0; } + +double AtomicSwapCharge::bias(Change&, double, double) +{ + return _bias; +} + +void AtomicSwapCharge::_accept(Change&) +{ + msqd += _sqd; +} + +void AtomicSwapCharge::_reject(Change&) +{ + msqd += 0; +} AtomicSwapCharge::AtomicSwapCharge(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = -1; // meaning repeat N times cdata.relative_atom_indices.resize(1); cdata.internal = true; } -AtomicSwapCharge::AtomicSwapCharge(Space& spc) : AtomicSwapCharge(spc, "swapcharge", "") {} +AtomicSwapCharge::AtomicSwapCharge(Space& spc) + : AtomicSwapCharge(spc, "swapcharge", "") +{ +} -void TranslateRotate::_to_json(json& j) const { +void TranslateRotate::_to_json(json& j) const +{ // For a spherical sphere of radius R, the ratio between mean squared translation (t) // and mean squared rotation (r) is approximately MSD_r * 4R^2 = MSD_t. const auto should_approach_radius = @@ -1270,14 +1530,17 @@ void TranslateRotate::_to_json(json& j) const { {"dprot", rotational_displacement / 1.0_rad}, {"dirrot", fixed_rotation_axis}, {"molid", molid}, - {unicode::rootof + unicode::bracket("r" + unicode::squared), std::sqrt(mean_squared_displacement.avg())}, + {unicode::rootof + unicode::bracket("r" + unicode::squared), + std::sqrt(mean_squared_displacement.avg())}, {"√⟨θ²⟩/Β°", std::sqrt(mean_squared_rotation_angle.avg()) / 1.0_deg}, {"√⟨θ²⟩", std::sqrt(mean_squared_rotation_angle.avg()) / 1.0_rad}, {"molecule", Faunus::molecules.at(molid).name}, {"R/Γ… β‰ˆ √(⟨r²⟩/4⟨θ²⟩)", should_approach_radius}}; roundJSON(j, 3); } -void TranslateRotate::_from_json(const json& j) { + +void TranslateRotate::_from_json(const json& j) +{ const std::string molname = j.at("molecule"); const auto molecule = findMoleculeByName(molname); if (molecule.atomic) { @@ -1295,15 +1558,17 @@ void TranslateRotate::_from_json(const json& j) { fixed_rotation_axis = j.value("dirrot", Point(0.0, 0.0, 0.0)); // predefined axis of rotation if (fixed_rotation_axis.count() > 0) { fixed_rotation_axis.normalize(); - faunus_logger->debug("{}: fixed rotation axis [{:.3f},{:.3f},{:.3f}]", name, fixed_rotation_axis.x(), - fixed_rotation_axis.y(), fixed_rotation_axis.z()); + faunus_logger->debug("{}: fixed rotation axis [{:.3f},{:.3f},{:.3f}]", name, + fixed_rotation_axis.x(), fixed_rotation_axis.y(), + fixed_rotation_axis.z()); } if (repeat < 0) { repeat = spc.numMolecules(molid); if (repeat == 0) { faunus_logger->warn("no initial '{}' molecules found; setting repeat to 1", molname); repeat = 1; - } else { + } + else { faunus_logger->debug("repeat = {} for molecule '{}'", repeat, molname); } } @@ -1312,8 +1577,10 @@ void TranslateRotate::_from_json(const json& j) { /** * @todo `mollist` scales linearly w. system size -- implement look-up-table in Space? */ -TranslateRotate::OptionalGroup TranslateRotate::findRandomMolecule() { - if (auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); not ranges::cpp20::empty(mollist)) { +TranslateRotate::OptionalGroup TranslateRotate::findRandomMolecule() +{ + if (auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); + not ranges::cpp20::empty(mollist)) { if (auto group_it = random.sample(mollist.begin(), mollist.end()); not group_it->empty()) { return *group_it; } @@ -1325,15 +1592,17 @@ TranslateRotate::OptionalGroup TranslateRotate::findRandomMolecule() { * @param group Group to translate * @return Squared translation distance of mass center */ -double TranslateRotate::translateMolecule(Space::GroupType& group) { +double TranslateRotate::translateMolecule(Space::GroupType& group) +{ if (translational_displacement > 0.0) { // translate const auto old_mass_center = group.mass_center; - const Point displacement_vector = - Faunus::randomUnitVector(slump, translational_direction) * translational_displacement * slump(); + const Point displacement_vector = Faunus::randomUnitVector(slump, translational_direction) * + translational_displacement * slump(); group.translate(displacement_vector, spc.geometry.getBoundaryFunc()); return spc.geometry.sqdist(old_mass_center, group.mass_center); - } else { + } + else { return 0.0; } } @@ -1342,14 +1611,16 @@ double TranslateRotate::translateMolecule(Space::GroupType& group) { * @param group Group to rotate * @return Squared rotation angle around mass-center */ -double TranslateRotate::rotateMolecule(Space::GroupType& group) { +double TranslateRotate::rotateMolecule(Space::GroupType& group) +{ if (rotational_displacement <= pc::epsilon_dbl) { return 0.0; } Point rotation_axis; if (fixed_rotation_axis.count() > 0) { // fixed user-defined axis rotation_axis = fixed_rotation_axis; - } else { + } + else { rotation_axis = Faunus::randomUnitVector(slump); } const auto angle = rotational_displacement * (slump() - 0.5); @@ -1358,57 +1629,70 @@ double TranslateRotate::rotateMolecule(Space::GroupType& group) { return angle * angle; } -void TranslateRotate::_move(Change& change) { +void TranslateRotate::_move(Change& change) +{ if (auto group = findRandomMolecule()) { // note that group is of type std::optional latest_displacement_squared = translateMolecule(group->get()); latest_rotation_angle_squared = rotateMolecule(group->get()); - if (latest_displacement_squared > 0.0 || latest_rotation_angle_squared > 0.0) { // report changes + if (latest_displacement_squared > 0.0 || + latest_rotation_angle_squared > 0.0) { // report changes auto& change_data = change.groups.emplace_back(); - change_data.group_index = spc.getGroupIndex(group->get()); // integer *index* of moved group - change_data.all = true; // *all* atoms in group were moved - change_data.internal = false; // internal energy is unchanged + change_data.group_index = + spc.getGroupIndex(group->get()); // integer *index* of moved group + change_data.all = true; // *all* atoms in group were moved + change_data.internal = false; // internal energy is unchanged } checkMassCenter(group->get()); - } else { + } + else { latest_displacement_squared = 0.0; // these are used to track mean squared latest_rotation_angle_squared = 0.0; // translational and rotational displacements } } -void TranslateRotate::checkMassCenter(const Space::GroupType& group) const { +void TranslateRotate::checkMassCenter(const Space::GroupType& group) const +{ const auto allowed_threshold = 1e-6; - const auto cm_recalculated = - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); + const auto cm_recalculated = Geometry::massCenter( + group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center); const auto should_be_small = spc.geometry.sqdist(group.mass_center, cm_recalculated); if (should_be_small > allowed_threshold) { faunus_logger->error("{}: error calculating mass center for {}", name, group.traits().name); PQRWriter().save("mass-center-failure.pqr", spc.groups, spc.geometry.getLength()); - throw std::runtime_error("molecule likely too large for periodic boundaries; increase box size?"); + throw std::runtime_error( + "molecule likely too large for periodic boundaries; increase box size?"); } } -void TranslateRotate::_accept(Change&) { +void TranslateRotate::_accept(Change&) +{ mean_squared_displacement += latest_displacement_squared; mean_squared_rotation_angle += latest_rotation_angle_squared; } -void TranslateRotate::_reject(Change&) { +void TranslateRotate::_reject(Change&) +{ mean_squared_displacement += 0.0; mean_squared_rotation_angle += 0.0; } TranslateRotate::TranslateRotate(Space& spc, std::string name, std::string cite) - : Move(spc, name, cite) { + : Move(spc, name, cite) +{ repeat = -1; // meaning repeat N times } -TranslateRotate::TranslateRotate(Space& spc) : TranslateRotate(spc, "moltransrot", "") {} +TranslateRotate::TranslateRotate(Space& spc) + : TranslateRotate(spc, "moltransrot", "") +{ +} /** * This is called *after* the move and the `bias()` function will determine if the move * resulted in a flux over the region boundary and return the appropriate bias. */ -double SmarterTranslateRotate::bias(Change& change, double old_energy, double new_energy) { +double SmarterTranslateRotate::bias(Change& change, double old_energy, double new_energy) +{ return TranslateRotate::bias(change, old_energy, new_energy) + smartmc.bias(); } @@ -1416,26 +1700,30 @@ double SmarterTranslateRotate::bias(Change& change, double old_energy, double ne * Upon calling `select()`, the `outside_rejection_probability` is used to exclude particles * outside and may thus often return `std::nullopt`. */ -TranslateRotate::OptionalGroup SmarterTranslateRotate::findRandomMolecule() { +TranslateRotate::OptionalGroup SmarterTranslateRotate::findRandomMolecule() +{ auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); return smartmc.select(mollist, slump); } -void SmarterTranslateRotate::_to_json(json& j) const { +void SmarterTranslateRotate::_to_json(json& j) const +{ TranslateRotate::_to_json(j); smartmc.to_json(j["smartmc"]); } SmarterTranslateRotate::SmarterTranslateRotate(Space& spc, const json& j) : TranslateRotate(spc, "moltransrot", "doi:10/frvx8j") - , smartmc(spc, j.at("region")) { + , smartmc(spc, j.at("region")) +{ this->from_json(j); } } // namespace Faunus::move #ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("[Faunus] TranslateRotate") { +TEST_CASE("[Faunus] TranslateRotate") +{ using namespace Faunus; CHECK(!atoms.empty()); // set in a previous test CHECK(!molecules.empty()); // set in a previous test @@ -1456,7 +1744,8 @@ TEST_CASE("[Faunus] TranslateRotate") { namespace Faunus::move { -void ConformationSwap::_to_json(json& j) const { +void ConformationSwap::_to_json(json& j) const +{ j = {{"molid", molid}, {"molecule", Faunus::molecules.at(molid).name}, {"keeppos", inserter.keep_positions}, @@ -1464,7 +1753,8 @@ void ConformationSwap::_to_json(json& j) const { roundJSON(j, 3); } -void ConformationSwap::_from_json(const json& j) { +void ConformationSwap::_from_json(const json& j) +{ const auto molecule_name = j.at("molecule").get(); const auto molecule = Faunus::findMoleculeByName(molecule_name); molid = molecule.id(); @@ -1480,7 +1770,8 @@ void ConformationSwap::_from_json(const json& j) { setRepeat(); } -void ConformationSwap::setRepeat() { +void ConformationSwap::setRepeat() +{ assert(molid >= 0); if (repeat < 0) { // negative value signals repeat = N number of molecules auto groups = spc.findMolecules(molid, Space::Selection::ALL); @@ -1490,17 +1781,22 @@ void ConformationSwap::setRepeat() { } } } -void ConformationSwap::_move(Change& change) { + +void ConformationSwap::_move(Change& change) +{ auto groups = spc.findMolecules(molid, Space::Selection::ACTIVE); if (auto group = slump.sample(groups.begin(), groups.end()); group != groups.end()) { inserter.offset = group->mass_center; // insert on top of mass center - auto particles = inserter(spc.geometry, Faunus::molecules[molid], spc.particles); // new conformation + auto particles = + inserter(spc.geometry, Faunus::molecules[molid], spc.particles); // new conformation if (particles.size() == group->size()) { checkMassCenterDrift(group->mass_center, particles); // throws if not OK copyConformation(particles, group->begin()); - group->conformation_id = Faunus::molecules[molid].conformations.getLastIndex(); // store conformation id - registerChanges(change, *group); // update change object - } else { + group->conformation_id = + Faunus::molecules[molid].conformations.getLastIndex(); // store conformation id + registerChanges(change, *group); // update change object + } + else { throw std::out_of_range(name + ": conformation atom count mismatch"); } } @@ -1513,8 +1809,11 @@ void ConformationSwap::_move(Change& change) { * @param particles Source particles * @param destination Iterator to first particle in destination */ -void ConformationSwap::copyConformation(ParticleVector& particles, ParticleVector::iterator destination) const { - std::function copy_function; // how to copy particle information +void ConformationSwap::copyConformation(ParticleVector& particles, + ParticleVector::iterator destination) const +{ + std::function + copy_function; // how to copy particle information switch (copy_policy) { case CopyPolicy::ALL: copy_function = [](const Particle& src, Particle& dst) { dst = src; }; @@ -1523,9 +1822,10 @@ void ConformationSwap::copyConformation(ParticleVector& particles, ParticleVecto // Copy only PSC patch and length information but keep directions and position copy_function = [](const Particle& src, Particle& dst) { if (src.hasExtension() && src.getExt().isCylindrical()) { - auto &psc_dst = dst.getExt(); + auto& psc_dst = dst.getExt(); psc_dst.half_length = src.getExt().half_length; - psc_dst.setDirections(src.traits().sphero_cylinder, psc_dst.scdir, psc_dst.patchdir); + psc_dst.setDirections(src.traits().sphero_cylinder, psc_dst.scdir, + psc_dst.patchdir); } }; break; @@ -1545,25 +1845,29 @@ void ConformationSwap::copyConformation(ParticleVector& particles, ParticleVecto }); } -void ConformationSwap::registerChanges(Change& change, const Space::GroupType& group) const { +void ConformationSwap::registerChanges(Change& change, const Space::GroupType& group) const +{ auto& group_change = change.groups.emplace_back(); group_change.group_index = spc.getGroupIndex(group); // index of moved group group_change.all = true; // all atoms in group were moved group_change.internal = false; // skip internal energy calculation } + /** * @throw if there's a mass-center drift * * Move shouldn't move mass centers, so let's check if this is true */ -void ConformationSwap::checkMassCenterDrift(const Point& old_mass_center, const ParticleVector& particles) { +void ConformationSwap::checkMassCenterDrift(const Point& old_mass_center, + const ParticleVector& particles) +{ switch (copy_policy) { case CopyPolicy::CHARGES: // positions untouched; no check needed return; default: const auto max_allowed_distance = 1.0e-6; - const auto new_mass_center = - Geometry::massCenter(particles.begin(), particles.end(), spc.geometry.getBoundaryFunc(), -old_mass_center); + const auto new_mass_center = Geometry::massCenter( + particles.begin(), particles.end(), spc.geometry.getBoundaryFunc(), -old_mass_center); if ((new_mass_center - old_mass_center).norm() > max_allowed_distance) { throw std::runtime_error(name + ": unexpected mass center movement"); } @@ -1571,9 +1875,13 @@ void ConformationSwap::checkMassCenterDrift(const Point& old_mass_center, const } ConformationSwap::ConformationSwap(Space& spc, const std::string& name, const std::string& cite) - : Move(spc, name, cite) {} + : Move(spc, name, cite) +{ +} -ConformationSwap::ConformationSwap(Space& spc) : ConformationSwap(spc, "conformationswap", "doi:10/dmc3") { +ConformationSwap::ConformationSwap(Space& spc) + : ConformationSwap(spc, "conformationswap", "doi:10/dmc3") +{ repeat = -1; // meaning repeat n times inserter.dir = Point::Zero(); inserter.rotate = true; @@ -1583,7 +1891,8 @@ ConformationSwap::ConformationSwap(Space& spc) : ConformationSwap(spc, "conforma /** * Checks if any two particles in any conformation is father away than half the shortest cell length */ -void ConformationSwap::checkConformationSize() const { +void ConformationSwap::checkConformationSize() const +{ assert(molid >= 0); const auto is_periodic = spc.geometry.boundaryConditions().isPeriodic(); @@ -1611,9 +1920,10 @@ void ConformationSwap::checkConformationSize() const { const auto positions = conformation | ranges::cpp20::views::transform(&Particle::pos); const auto max_separation = find_max_distance(positions); if (max_separation > max_allowed_separation) { - faunus_logger->warn("particles in conformation {} separated by {:.3f} Γ… which *may* break periodic " - "boundaries. If so, you'll know.", - conformation_id, max_separation); + faunus_logger->warn( + "particles in conformation {} separated by {:.3f} Γ… which *may* break periodic " + "boundaries. If so, you'll know.", + conformation_id, max_separation); } conformation_id++; } diff --git a/src/move.h b/src/move.h index 838b05f64..8cb8607e1 100644 --- a/src/move.h +++ b/src/move.h @@ -38,14 +38,15 @@ class MoveCollection; * * @todo Privatize and rename members */ -class Move { +class Move +{ private: - virtual void _move(Change& change) = 0; //!< Perform move and modify change object - virtual void _accept(Change& change); //!< Call after move is accepted - virtual void _reject(Change& change); //!< Call after move is rejected - virtual void _to_json(json& j) const = 0; //!< Extra info for report if needed - virtual void _from_json(const json& j) = 0; //!< Extra info for report if needed - TimeRelativeOfTotal timer; //!< Timer for whole move + virtual void _move(Change& change) = 0; //!< Perform move and modify change object + virtual void _accept(Change& change); //!< Call after move is accepted + virtual void _reject(Change& change); //!< Call after move is rejected + virtual void _to_json(json& j) const = 0; //!< Extra info for report if needed + virtual void _from_json(const json& j) = 0; //!< Extra info for report if needed + TimeRelativeOfTotal timer; //!< Timer for whole move TimeRelativeOfTotal timer_move; //!< Timer for _move() only friend MoveCollection; @@ -83,15 +84,17 @@ void to_json(json&, const Move&); /** * @brief Replay simulation from a trajectory * - * Particles' positions are updated in every step based on coordinates read from the trajectory. Currently only - * XTCReader is supported. + * Particles' positions are updated in every step based on coordinates read from the trajectory. + * Currently only XTCReader is supported. */ -class ReplayMove : public Move { +class ReplayMove : public Move +{ std::unique_ptr reader = nullptr; //!< trajectory reader TrajectoryFrame frame; //!< recently read frame (w/o coordinates) bool end_of_trajectory = false; //!< flag raised when end of trajectory was reached // FIXME resolve always accept / always reject on the Faunus level - const double force_accept = -1e12; //!< a very negative value of energy difference to force-accept the move + const double force_accept = + -1e12; //!< a very negative value of energy difference to force-accept the move void _move(Change& change) override; void _to_json(json&) const override; @@ -109,7 +112,8 @@ class ReplayMove : public Move { /** * @brief Swap the charge of a single atom */ -class [[maybe_unused]] AtomicSwapCharge : public Move { +class [[maybe_unused]] AtomicSwapCharge : public Move +{ int molid = -1; double pKa, pH; Average msqd; // mean squared displacement @@ -121,7 +125,8 @@ class [[maybe_unused]] AtomicSwapCharge : public Move { void _from_json(const json&) override; //!< Configure via json object typename ParticleVector::iterator randomAtom(); void _move(Change& change) override; - double bias(Change&, double, double) override; //!< adds extra energy change not captured by the Hamiltonian + double bias(Change&, double, + double) override; //!< adds extra energy change not captured by the Hamiltonian void _accept(Change&) override; void _reject(Change&) override; @@ -136,16 +141,20 @@ class [[maybe_unused]] AtomicSwapCharge : public Move { /** * @brief Translate and rotate a molecular group */ -class AtomicTranslateRotate : public Move { - ParticleVector::const_iterator latest_particle; //!< Iterator to last moved particle - const Energy::Hamiltonian& hamiltonian; //!< Reference to Hamiltonian - std::map> energy_histogram; //!< Energy histogram (value) for each particle type (key) - double energy_resolution = 0.0; //!< Resolution of sampled energy histogram - double latest_displacement_squared; //!< temporary squared displacement - void sampleEnergyHistogram(); //!< Update energy histogram based on latest move - void saveHistograms(); //!< Write histograms for file - void checkMassCenter(Space::GroupType& group) const; //!< Perform test to see if the move violates PBC - void groupToDisk(const Space::GroupType& group) const; //!< Save structure to disk in case of failure +class AtomicTranslateRotate : public Move +{ + ParticleVector::const_iterator latest_particle; //!< Iterator to last moved particle + const Energy::Hamiltonian& hamiltonian; //!< Reference to Hamiltonian + std::map> + energy_histogram; //!< Energy histogram (value) for each particle type (key) + double energy_resolution = 0.0; //!< Resolution of sampled energy histogram + double latest_displacement_squared; //!< temporary squared displacement + void sampleEnergyHistogram(); //!< Update energy histogram based on latest move + void saveHistograms(); //!< Write histograms for file + void checkMassCenter( + Space::GroupType& group) const; //!< Perform test to see if the move violates PBC + void + groupToDisk(const Space::GroupType& group) const; //!< Save structure to disk in case of failure protected: int molid = -1; //!< Molecule id to move @@ -163,7 +172,8 @@ class AtomicTranslateRotate : public Move { void _accept(Change&) override; void _reject(Change&) override; - AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian, const std::string& name, const std::string& cite); + AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian, + const std::string& name, const std::string& cite); public: AtomicTranslateRotate(Space& spc, const Energy::Hamiltonian& hamiltonian); @@ -189,39 +199,42 @@ template double slump_theta = dp * (base::slump() - 0.5); // Get random theta-move double slump_phi = dp * (base::slump() - 0.5); // Get random phi-move - double scalefactor_theta = spc.geo.getRadius() * sin(rtp.z()); // Scale-factor for theta - double scalefactor_phi = spc.geo.getRadius(); // Scale-factor for phi + double scalefactor_theta = spc.geo.getRadius() * sin(rtp.z()); // Scale-factor for +theta double scalefactor_phi = spc.geo.getRadius(); // Scale-factor for phi - Point theta_dir = Point(-sin(rtp.y()), cos(rtp.y()), 0); // Unit-vector in theta-direction - Point phi_dir = Point(cos(rtp.y()) * cos(rtp.z()), sin(rtp.y()) * cos(rtp.z()), + Point theta_dir = Point(-sin(rtp.y()), cos(rtp.y()), 0); // Unit-vector in +theta-direction Point phi_dir = Point(cos(rtp.y()) * cos(rtp.z()), sin(rtp.y()) * cos(rtp.z()), -sin(rtp.z())); // Unit-vector in phi-direction Point xyz = oldpos + scalefactor_theta * theta_dir * slump_theta + scalefactor_phi * phi_dir * slump_phi; // New position - p->pos = spc.geo.getRadius() * xyz / xyz.norm(); // Convert to cartesian coordinates + p->pos = spc.geo.getRadius() * xyz / xyz.norm(); // Convert to cartesian +coordinates spc.geo.boundary(p->pos); base::_sqd = spc.geo.sqdist(oldpos, p->pos); // squared displacement - if (not g.atomic) { // recalc mass-center for non-molecular groups - g.cm = Geometry::massCenter(g.begin(), g.end(), spc.geo.getBoundaryFunc(), -g.cm); + if (not g.atomic) { // recalc mass-center for non-molecular +groups g.cm = Geometry::massCenter(g.begin(), g.end(), spc.geo.getBoundaryFunc(), -g.cm); #ifndef NDEBUG Point cmbak = g.cm; // backup mass center g.translate(-cmbak, spc.geo.getBoundaryFunc()); // translate to {0,0,0} double should_be_zero = spc.geo.sqdist({0, 0, 0}, Geometry::massCenter(g.begin(), -g.end())); if (should_be_zero > 1e-6) throw std::runtime_error("atomic move too large"); else g.translate(cmbak, -spc.geo.getBoundaryFunc()); #endif +g.end())); if (should_be_zero > 1e-6) throw std::runtime_error("atomic move too large"); else +g.translate(cmbak, spc.geo.getBoundaryFunc()); #endif } } public: - Atomic2dTranslateRotate(Tspace &spc, std::string name, std::string cite) : base(spc, name, cite) {} - explicit Atomic2dTranslateRotate(Tspace &spc) : Atomic2dTranslateRotate(spc, "transrot 2d", "") {} + Atomic2dTranslateRotate(Tspace &spc, std::string name, std::string cite) : base(spc, +name, cite) {} explicit Atomic2dTranslateRotate(Tspace &spc) : Atomic2dTranslateRotate(spc, +"transrot 2d", "") {} };*/ /** * @brief Translate and rotate a molecular group */ -class TranslateRotate : public Move { +class TranslateRotate : public Move +{ protected: using OptionalGroup = std::optional>; int molid = -1; //!< Molecule ID of the molecule(s) to move @@ -262,7 +275,8 @@ class TranslateRotate : public Move { * 1. `findRandomMolecule()` which performs the group selection * 2. `bias()` as we need to correct for the asymmetric sampling */ -class SmarterTranslateRotate : public TranslateRotate { +class SmarterTranslateRotate : public TranslateRotate +{ private: SmarterMonteCarlo::MoveSupport smartmc; void _to_json(json& j) const override; @@ -286,34 +300,47 @@ class SmarterTranslateRotate : public TranslateRotate { * * @todo Add feature to align molecule on top of an exiting one */ -class ConformationSwap : public Move { +class ConformationSwap : public Move +{ public: - enum class CopyPolicy { ALL, POSITIONS, CHARGES, PATCHES, INVALID }; //!< What to copy from conformation library + enum class CopyPolicy + { + ALL, + POSITIONS, + CHARGES, + PATCHES, + INVALID + }; //!< What to copy from conformation library private: CopyPolicy copy_policy; RandomInserter inserter; int molid = -1; //!< Molecule ID to operate on - void copyConformation(ParticleVector& source_particle, ParticleVector::iterator destination) const; + void copyConformation(ParticleVector& source_particle, + ParticleVector::iterator destination) const; void _to_json(json& j) const override; void _from_json(const json& j) override; void _move(Change& change) override; void setRepeat(); //!< Set move repeat void checkConformationSize() const; //!< Do conformations fit simulation cell? - void checkMassCenterDrift(const Point& old_mass_center, const ParticleVector& particles); //!< Check for CM drift - void registerChanges(Change& change, const Space::GroupType& group) const; //!< Update change object + void checkMassCenterDrift(const Point& old_mass_center, + const ParticleVector& particles); //!< Check for CM drift + void registerChanges(Change& change, + const Space::GroupType& group) const; //!< Update change object ConformationSwap(Space& spc, const std::string& name, const std::string& cite); public: explicit ConformationSwap(Space& spc); }; // end of conformation swap move -NLOHMANN_JSON_SERIALIZE_ENUM(ConformationSwap::CopyPolicy, {{ConformationSwap::CopyPolicy::INVALID, nullptr}, - {ConformationSwap::CopyPolicy::ALL, "all"}, - {ConformationSwap::CopyPolicy::PATCHES, "patches"}, - {ConformationSwap::CopyPolicy::POSITIONS, "positions"}, - {ConformationSwap::CopyPolicy::CHARGES, "charges"}}) +NLOHMANN_JSON_SERIALIZE_ENUM(ConformationSwap::CopyPolicy, + {{ConformationSwap::CopyPolicy::INVALID, nullptr}, + {ConformationSwap::CopyPolicy::ALL, "all"}, + {ConformationSwap::CopyPolicy::PATCHES, "patches"}, + {ConformationSwap::CopyPolicy::POSITIONS, "positions"}, + {ConformationSwap::CopyPolicy::CHARGES, "charges"}}) -class VolumeMove : public Move { +class VolumeMove : public Move +{ protected: Geometry::VolumeMethod volume_scaling_method = Geometry::VolumeMethod::ISOTROPIC; double logarithmic_volume_displacement_factor = 0.0; @@ -339,7 +366,8 @@ class VolumeMove : public Move { /** * @brief Displaces charge on a single atom */ -class ChargeMove : public Move { +class ChargeMove : public Move +{ private: Average mean_squared_charge_displacement; Change::GroupChange group_change; @@ -367,7 +395,8 @@ class ChargeMove : public Move { * The displacement is q' = sqrt(q^2 + dq^2) and the following bias * must be added to enforce symmetry: u/kT = ln( |q'/q| ) */ -class QuadraticChargeMove : public ChargeMove { +class QuadraticChargeMove : public ChargeMove +{ private: Average mean_bias; void _to_json(json& j) const override; @@ -381,12 +410,14 @@ class QuadraticChargeMove : public ChargeMove { /** * @brief Transfers charge between two molecules */ -class ChargeTransfer : public Move { +class ChargeTransfer : public Move +{ private: Average msqd; // mean squared displacement double dq = 0, deltaq = 0; - struct moldata { + struct moldata + { double charges = 0; double moves = 0; size_t numOfAtoms = 0; @@ -423,7 +454,8 @@ class ChargeTransfer : public Move { * considering as the origin the center of the box or the center of mass * of a range of atomic indexes specified by "index": [start:stop]. */ -class QuadrantJump : public Move { +class QuadrantJump : public Move +{ private: int molid = -1; Point dir = {1, 1, 1}; @@ -434,7 +466,9 @@ class QuadrantJump : public Move { void _to_json(json& j) const override; void _from_json(const json& j) override; //!< Configure via json object void _move(Change& change) override; + void _accept(Change&) override { msqd += _sqd; } + void _reject(Change&) override { msqd += 0; } protected: @@ -456,28 +490,33 @@ class QuadrantJump : public Move { * - [_Phase equilibria by simulation in the Gibbs ensemble_](https://dx.doi.org/10/cvzgw9) * - Frenkel and Smith, 2nd Ed., Chapter 8 */ -class GibbsEnsembleHelper { +class GibbsEnsembleHelper +{ public: using VectorOfMolIds = std::vector; const MPI::Controller& mpi; - int partner_rank = -1; //!< Either rank 0 or 1 - double total_volume = 0; //!< Total volume of both cells - int total_num_particles = 0; //! Total number of particles in both cells - VectorOfMolIds molids; //!< Molecule id's to exchange. Must be molecular. - std::pair currentNumParticles(const Space& spc) const; //!< Current number of particles in cell 1 and 2 - std::pair currentVolumes(const Space& spc) const; //!< Current volumes in cell 1 and 2 - double exchange(double value) const; //!< MPI exchange a double with partner + int partner_rank = -1; //!< Either rank 0 or 1 + double total_volume = 0; //!< Total volume of both cells + int total_num_particles = 0; //! Total number of particles in both cells + VectorOfMolIds molids; //!< Molecule id's to exchange. Must be molecular. + std::pair + currentNumParticles(const Space& spc) const; //!< Current number of particles in cell 1 and 2 + std::pair + currentVolumes(const Space& spc) const; //!< Current volumes in cell 1 and 2 + double exchange(double value) const; //!< MPI exchange a double with partner GibbsEnsembleHelper(const Space& spc, const MPI::Controller& mpi, const VectorOfMolIds& molids); }; /** * @brief Volume exchange move for the Gibbs ensemble (doi:10/cvzgw9) */ -class GibbsVolumeMove : public VolumeMove { +class GibbsVolumeMove : public VolumeMove +{ private: MPI::Controller& mpi; std::unique_ptr gibbs; - bool direct_volume_displacement = true; //!< True if direct displacement in V; false if lnV displacement + bool direct_volume_displacement = + true; //!< True if direct displacement in V; false if lnV displacement void setNewVolume() override; void _from_json(const json& j) override; bool volumeTooExtreme() const; //!< Check if volume is too small or too large @@ -496,7 +535,8 @@ class GibbsVolumeMove : public VolumeMove { * - Each of the two cells runs as a separate MPI process * - Multi-component systems are supported; just add a move instance for each species */ -class GibbsMatterMove : public Move { +class GibbsMatterMove : public Move +{ private: bool insert; //!< Insert or delete particle? MoleculeData::index_type molid; //!< Molid to insert or delete @@ -525,22 +565,25 @@ class GibbsMatterMove : public Move { * * @date Lund 2012, 2018 */ -class ParallelTempering : public Move { +class ParallelTempering : public Move +{ private: const MPI::Controller& mpi; MPI::ExchangeParticles exchange_particles; //!< Helper class to exchange particles std::unique_ptr partner; //!< Policy for finding MPI partners - Geometry::VolumeMethod volume_scaling_method = Geometry::VolumeMethod::ISOTROPIC; //!< How to scale volumes - std::map> acceptance_map; //!< Exchange statistics + Geometry::VolumeMethod volume_scaling_method = + Geometry::VolumeMethod::ISOTROPIC; //!< How to scale volumes + std::map> acceptance_map; //!< Exchange statistics Random slump; // static instance of Random (shared for all in ParallelTempering) void _to_json(json& j) const override; void _from_json(const json& j) override; void _move(Change& change) override; void _accept(Change& change) override; void _reject(Change& change) override; - double bias(Change& change, double uold, double unew) override; //!< Energy change in partner replica - double exchangeEnergy(double energy_change); //!< Exchange energy with partner - void exchangeState(Change& change); //!< Exchange positions, charges, volume etc. + double bias(Change& change, double uold, + double unew) override; //!< Energy change in partner replica + double exchangeEnergy(double energy_change); //!< Exchange energy with partner + void exchangeState(Change& change); //!< Exchange positions, charges, volume etc. void exchangeGroupSizes(Space::GroupVector& groups, int partner_rank); public: @@ -555,41 +598,50 @@ class ParallelTempering : public Move { * @returns unique pointer to move * @throw if invalid name or input parameters */ -[[maybe_unused]] std::unique_ptr createMove(const std::string& name, const json& properties, Space& spc, - Energy::Hamiltonian& hamiltonian); +[[maybe_unused]] std::unique_ptr createMove(const std::string& name, const json& properties, + Space& spc, Energy::Hamiltonian& hamiltonian); /** * @brief Class storing a list of MC moves with their probability weights and * randomly selecting one. */ -class MoveCollection { +class MoveCollection +{ private: - unsigned int number_of_moves_per_sweep; //!< Sum of all weights - BasePointerVector moves; //!< list of moves - std::vector repeats; //!< list of repeats (weights) for `moves` + unsigned int number_of_moves_per_sweep; //!< Sum of all weights + BasePointerVector moves; //!< list of moves + std::vector repeats; //!< list of repeats (weights) for `moves` std::discrete_distribution distribution; //!< Probability distribution for `moves` using move_iterator = decltype(moves.vec)::iterator; //!< Iterator to move pointer - move_iterator sample(); //!< Pick move from a weighted, random distribution + move_iterator sample(); //!< Pick move from a weighted, random distribution public: - MoveCollection(const json& list_of_moves, Space& spc, Energy::Hamiltonian& hamiltonian, Space &old_spc); - void addMove(std::shared_ptr&& move); //!< Register new move with correct weight - [[maybe_unused]] [[nodiscard]] const BasePointerVector& getMoves() const; //!< Get list of moves + MoveCollection(const json& list_of_moves, Space& spc, Energy::Hamiltonian& hamiltonian, + Space& old_spc); + void addMove(std::shared_ptr&& move); //!< Register new move with correct weight + [[maybe_unused]] [[nodiscard]] const BasePointerVector& + getMoves() const; //!< Get list of moves friend void to_json(json& j, const MoveCollection& propagator); //!< Generate json output /** * Generates a range of repeated, randomized move pointers guaranteed to be valid. * All moves with `repeat=0` are excluded. Effectively each registered move is called - * with a probability proportional to it's `repeat` value. The random picking of moves is repeated + * with a probability proportional to it's `repeat` value. The random picking of moves is + * repeated * $\sum repeat_i$ times so that running over the range constitutes a complete MC "sweep". * * @returns Range of valid move pointers */ - auto repeatedStochasticMoves() { - auto is_valid_and_stochastic = [&](auto move) { return move < moves.end() && (*move)->isStochastic(); }; + auto repeatedStochasticMoves() + { + auto is_valid_and_stochastic = [&](auto move) { + return move < moves.end() && (*move)->isStochastic(); + }; return ranges::cpp20::views::iota(0U, number_of_moves_per_sweep) | - ranges::cpp20::views::transform([&]([[maybe_unused]] auto count) { return sample(); }) | - ranges::cpp20::views::filter(is_valid_and_stochastic) | ranges::views::indirect; // dereference iterator + ranges::cpp20::views::transform( + [&]([[maybe_unused]] auto count) { return sample(); }) | + ranges::cpp20::views::filter(is_valid_and_stochastic) | + ranges::views::indirect; // dereference iterator } /** @@ -600,10 +652,13 @@ class MoveCollection { * Monte Carlo sweep frequency. Used by e.g. parallel tempering that in the current * implementation must be run at fixed intervals due to MPI concurrency. * - * @param sweep_number Current sweep count to decide if move should be included based on `sweep_interval` - * @returns Range of valid move pointers to be run at given sweep number, i.e. non-stochastically + * @param sweep_number Current sweep count to decide if move should be included based on + * `sweep_interval` + * @returns Range of valid move pointers to be run at given sweep number, i.e. + * non-stochastically */ - auto constantIntervalMoves(const unsigned int sweep_number = 1) { + auto constantIntervalMoves(const unsigned int sweep_number = 1) + { auto is_static_and_time_to_sample = [&, sweep_number](const auto& move) { return (!move->isStochastic()) && (sweep_number % move->sweep_interval == 0); }; diff --git a/src/mpicontroller.cpp b/src/mpicontroller.cpp index f243b5675..a4af97c74 100644 --- a/src/mpicontroller.cpp +++ b/src/mpicontroller.cpp @@ -9,47 +9,71 @@ std::string prefix; #ifdef ENABLE_MPI -bool Controller::isMaster() const { return world.rank() == master_rank; } +bool Controller::isMaster() const +{ + return world.rank() == master_rank; +} -Controller::Controller() : world(mpl::environment::comm_world()) { +Controller::Controller() + : world(mpl::environment::comm_world()) +{ if (world.size() > 1) { prefix = fmt::format("mpi{}.", world.rank()); stream.open((prefix + "stdout")); - } else { + } + else { prefix.clear(); } } -void Controller::to_json(json& j) const { - j = {{"rank", world.rank()}, {"nproc", world.size()}, {"prefix", prefix}, {"master", master_rank}}; +void Controller::to_json(json& j) const +{ + j = {{"rank", world.rank()}, + {"nproc", world.size()}, + {"prefix", prefix}, + {"master", master_rank}}; } -std::ostream& Controller::cout() { return stream.is_open() ? stream : std::cout; } +std::ostream& Controller::cout() +{ + return stream.is_open() ? stream : std::cout; +} -Partner::Partner(PartnerPolicy policy) : policy(policy) {} +Partner::Partner(PartnerPolicy policy) + : policy(policy) +{ +} -bool Partner::isValid(const mpl::communicator& mpi, int partner) { +bool Partner::isValid(const mpl::communicator& mpi, int partner) +{ return (partner >= 0 && partner < mpi.size() && partner != mpi.rank()); } -Partner::PartnerPair Partner::getPair(const mpl::communicator& mpi) const { +Partner::PartnerPair Partner::getPair(const mpl::communicator& mpi) const +{ if (rank.has_value()) { - // note `std::minmax(a,b)` takes _references_; the initializer list (used here) takes a _copy_ + // note `std::minmax(a,b)` takes _references_; the initializer list (used here) takes a + // _copy_ return std::minmax({mpi.rank(), rank.value()}); } throw std::runtime_error("bad partner"); } -OddEvenPartner::OddEvenPartner() : Partner(PartnerPolicy::ODDEVEN) {} +OddEvenPartner::OddEvenPartner() + : Partner(PartnerPolicy::ODDEVEN) +{ +} /** * If true is returned, a valid partner was found */ -bool OddEvenPartner::generate(const mpl::communicator& mpi, Random& random) { +bool OddEvenPartner::generate(const mpl::communicator& mpi, Random& random) +{ int rank_increment = static_cast(random.range(0, 1)) ? 1 : -1; if (mpi.rank() % 2 == 0) { // even replica rank = mpi.rank() + rank_increment; - } else { // odd replica + } + else { // odd replica rank = mpi.rank() - rank_increment; } if (!isValid(mpi, rank.value())) { @@ -58,7 +82,8 @@ bool OddEvenPartner::generate(const mpl::communicator& mpi, Random& random) { return rank.has_value(); } -std::unique_ptr createMPIPartnerPolicy(PartnerPolicy policy) { +std::unique_ptr createMPIPartnerPolicy(PartnerPolicy policy) +{ switch (policy) { case PartnerPolicy::ODDEVEN: return std::make_unique(); @@ -67,7 +92,8 @@ std::unique_ptr createMPIPartnerPolicy(PartnerPolicy policy) { } } -void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) { +void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) +{ format = data_format; switch (format) { case Format::XYZ: @@ -114,28 +140,47 @@ void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) { } } -void ParticleBuffer::copyToBuffer(const ParticleVector& particles) { +void ParticleBuffer::copyToBuffer(const ParticleVector& particles) +{ buffer.resize(packet_size * particles.size()); auto destination = buffer.begin(); // set *after* buffer resize - ranges::cpp20::for_each(particles, std::bind(from_particle, std::placeholders::_1, std::ref(destination))); + ranges::cpp20::for_each(particles, + std::bind(from_particle, std::placeholders::_1, std::ref(destination))); if (destination != buffer.end()) { throw std::runtime_error("buffer mismatch"); } } -void ParticleBuffer::copyFromBuffer(ParticleVector& particles) { +void ParticleBuffer::copyFromBuffer(ParticleVector& particles) +{ if (buffer.size() != particles.size() * packet_size) { throw std::out_of_range("particles out of range"); } auto source = buffer.begin(); - ranges::cpp20::for_each(particles, std::bind(to_particle, std::ref(source), std::placeholders::_1)); + ranges::cpp20::for_each(particles, + std::bind(to_particle, std::ref(source), std::placeholders::_1)); assert(source == buffer.end()); } -ParticleBuffer::Format ParticleBuffer::getFormat() const { return format; } -ParticleBuffer::ParticleBuffer() { setFormat(Format::XYZQI); } -ParticleBuffer::buffer_iterator ParticleBuffer::begin() { return buffer.begin(); } -ParticleBuffer::buffer_iterator ParticleBuffer::end() { return buffer.end(); } +ParticleBuffer::Format ParticleBuffer::getFormat() const +{ + return format; +} + +ParticleBuffer::ParticleBuffer() +{ + setFormat(Format::XYZQI); +} + +ParticleBuffer::buffer_iterator ParticleBuffer::begin() +{ + return buffer.begin(); +} + +ParticleBuffer::buffer_iterator ParticleBuffer::end() +{ + return buffer.end(); +} /** * @brief Perform the exchange @@ -146,15 +191,16 @@ ParticleBuffer::buffer_iterator ParticleBuffer::end() { return buffer.end(); } * @todo This involves a lot of copying... */ const ParticleVector& ExchangeParticles::operator()(const Controller& mpi, int partner_rank, - const ParticleVector& particles) { + const ParticleVector& particles) +{ if (!partner_particles) { partner_particles = std::make_unique(); } partner_particles->resize(particles.size()); particle_buffer.copyToBuffer(particles); // particle data -> vector of doubles - mpi.world.sendrecv_replace(particle_buffer.begin(), particle_buffer.end(), partner_rank, mpl::tag_t(0), - partner_rank, mpl::tag_t(0)); + mpi.world.sendrecv_replace(particle_buffer.begin(), particle_buffer.end(), partner_rank, + mpl::tag_t(0), partner_rank, mpl::tag_t(0)); particle_buffer.copyFromBuffer(*partner_particles); return *partner_particles; } @@ -164,22 +210,32 @@ const ParticleVector& ExchangeParticles::operator()(const Controller& mpi, int p * @param partner_rank Destination and source * @param particles Particle vector to send/recieve */ -void ExchangeParticles::replace(const mpl::communicator& comm, int partner_rank, ParticleVector& particles) { +void ExchangeParticles::replace(const mpl::communicator& comm, int partner_rank, + ParticleVector& particles) +{ particle_buffer.copyToBuffer(particles); // particle data -> vector of doubles - comm.sendrecv_replace(particle_buffer.begin(), particle_buffer.end(), partner_rank, mpl::tag_t(0), - partner_rank, mpl::tag_t(0)); + comm.sendrecv_replace(particle_buffer.begin(), particle_buffer.end(), partner_rank, + mpl::tag_t(0), partner_rank, mpl::tag_t(0)); particle_buffer.copyFromBuffer(particles); } -ParticleBuffer::Format ExchangeParticles::getFormat() const { return particle_buffer.getFormat(); } +ParticleBuffer::Format ExchangeParticles::getFormat() const +{ + return particle_buffer.getFormat(); +} -void ExchangeParticles::setFormat(ParticleBuffer::Format format) { particle_buffer.setFormat(format); } +void ExchangeParticles::setFormat(ParticleBuffer::Format format) +{ + particle_buffer.setFormat(format); +} bool exchangeVolume(const Controller& mpi, int partner_rank, Geometry::GeometryBase& geometry, - Geometry::VolumeMethod& volume_scaling_method) { + Geometry::VolumeMethod& volume_scaling_method) +{ const auto old_volume = geometry.getVolume(); auto new_volume = 0.0; - mpi.world.sendrecv(old_volume, partner_rank, mpl::tag_t(0), new_volume, partner_rank, mpl::tag_t(0)); + mpi.world.sendrecv(old_volume, partner_rank, mpl::tag_t(0), new_volume, partner_rank, + mpl::tag_t(0)); if (new_volume <= pc::epsilon_dbl) { mpi.world.abort(1); } @@ -195,7 +251,8 @@ bool exchangeVolume(const Controller& mpi, int partner_rank, Geometry::GeometryB * @param random Random number object to test. Will be propagated. * @return True if all random number engines are in sync, i.e. returns the same values */ -bool checkRandomEngineState(const mpl::communicator& comm, Random& random) { +bool checkRandomEngineState(const mpl::communicator& comm, Random& random) +{ double random_number = random(); std::vector buffer(comm.size(), 0.0); comm.gather(0, random_number, buffer.data()); diff --git a/src/mpicontroller.h b/src/mpicontroller.h index 83dc7ccaf..376c1e265 100644 --- a/src/mpicontroller.h +++ b/src/mpicontroller.h @@ -39,7 +39,8 @@ extern std::string prefix; * * A global instance is available. */ -class Controller { +class Controller +{ private: std::ofstream stream; //!< Redirect stdout to here for rank-based file output public: @@ -57,8 +58,13 @@ extern Controller mpi; /** @brief Check if all random number generators are in sync */ bool checkRandomEngineState(const mpl::communicator& comm, Random& random); -enum class PartnerPolicy { ODDEVEN, INVALID }; //!< Policies for MPI partner search -NLOHMANN_JSON_SERIALIZE_ENUM(PartnerPolicy, {{PartnerPolicy::INVALID, nullptr}, {PartnerPolicy::ODDEVEN, "oddeven"}}) +enum class PartnerPolicy +{ + ODDEVEN, + INVALID +}; //!< Policies for MPI partner search +NLOHMANN_JSON_SERIALIZE_ENUM(PartnerPolicy, {{PartnerPolicy::INVALID, nullptr}, + {PartnerPolicy::ODDEVEN, "oddeven"}}) /** * Base class for finding MPI partners @@ -66,15 +72,18 @@ NLOHMANN_JSON_SERIALIZE_ENUM(PartnerPolicy, {{PartnerPolicy::INVALID, nullptr}, * Finds pairs of MPI ranks for use with e.g. parallel tempering moves. * The partner rank is contained in `rank` */ -class Partner { +class Partner +{ protected: static bool isValid(const mpl::communicator& mpi, int partner); //!< Is current partner valid? public: using PartnerPair = std::pair; //!< Pair of partner MPI ranks const PartnerPolicy policy; //!< Partner generation policy std::optional rank = std::nullopt; //!< Rank of partner MPI process if available - virtual bool generate(const mpl::communicator& mpi, Random& random) = 0; //!< Generate partner according to policy - PartnerPair getPair(const mpl::communicator& mpi) const; //!< Get ordered pair of current partners + virtual bool generate(const mpl::communicator& mpi, + Random& random) = 0; //!< Generate partner according to policy + PartnerPair + getPair(const mpl::communicator& mpi) const; //!< Get ordered pair of current partners explicit Partner(PartnerPolicy policy); virtual ~Partner() = default; }; @@ -82,7 +91,8 @@ class Partner { /** * @brief Odd ranks pairs with neighboring even rank (left or right) */ -class OddEvenPartner : public Partner { +class OddEvenPartner : public Partner +{ public: OddEvenPartner(); bool generate(const mpl::communicator& mpi, Random& random) override; @@ -101,7 +111,8 @@ std::unique_ptr createMPIPartnerPolicy(PartnerPolicy policy); * This returns a pair with the first and last * item for the current rank. */ -template std::pair splitEven(const mpl::communicator& communicator, T N) { +template std::pair splitEven(const mpl::communicator& communicator, T N) +{ static_assert(std::is_integral_v()); auto M = static_cast(communicator.size()); auto i = static_cast(communicator.rank()); @@ -125,7 +136,8 @@ double reduceDouble(const mpl::communicator& communicator, double local); * continuous block of memory (std::vector) for use with MPI * communication. */ -class ParticleBuffer { +class ParticleBuffer +{ public: /** * @brief Particle information to be copied @@ -134,24 +146,33 @@ class ParticleBuffer { * XYZQ -> positions, charge. * XYZQI -> positions, charge, atom id. */ - enum class Format { XYZ, XYZQ, XYZQI, INVALID }; + enum class Format + { + XYZ, + XYZQ, + XYZQI, + INVALID + }; private: std::vector buffer; using buffer_iterator = decltype(buffer)::iterator; Format format; - int packet_size = 0; //!< Number of doubles per particle - std::function from_particle; //!< Copy particle -> buffer - std::function to_particle; //!< Copy buffer -> particle + int packet_size = 0; //!< Number of doubles per particle + std::function + from_particle; //!< Copy particle -> buffer + std::function to_particle; //!< Copy buffer -> particle public: ParticleBuffer(); - void setFormat(Format data_format); //!< Set format from case-insensitive string - Format getFormat() const; //!< Data format to send/receive - default is XYZQ - void copyToBuffer(const ParticleVector& particles); //!< Copy source particle vector to send buffer - void copyFromBuffer(ParticleVector& particles); //!< Copy receive buffer to target particle vector - buffer_iterator begin(); //!< Begin iterator to `buffer` - buffer_iterator end(); //!< End iterator to `buffer` + void setFormat(Format data_format); //!< Set format from case-insensitive string + Format getFormat() const; //!< Data format to send/receive - default is XYZQ + void + copyToBuffer(const ParticleVector& particles); //!< Copy source particle vector to send buffer + void + copyFromBuffer(ParticleVector& particles); //!< Copy receive buffer to target particle vector + buffer_iterator begin(); //!< Begin iterator to `buffer` + buffer_iterator end(); //!< End iterator to `buffer` }; NLOHMANN_JSON_SERIALIZE_ENUM(ParticleBuffer::Format, { @@ -175,12 +196,14 @@ bool exchangeVolume(const Controller& mpi, int partner_rank, Geometry::GeometryB /** * Helper class to exchange a range of particles between two MPI nodes */ -class ExchangeParticles { +class ExchangeParticles +{ private: std::unique_ptr partner_particles; //!< Storage for recieved particles ParticleBuffer particle_buffer; //!< Class for serializing particles public: - const ParticleVector& operator()(const Controller& mpi, int partner_rank, const ParticleVector& particles); + const ParticleVector& operator()(const Controller& mpi, int partner_rank, + const ParticleVector& particles); void replace(const mpl::communicator& comm, int partner_rank, ParticleVector& particles); ParticleBuffer::Format getFormat() const; void setFormat(ParticleBuffer::Format format); @@ -192,7 +215,9 @@ class ExchangeParticles { * @details Slave processes send histograms to the master. The master computes the * average and sends it back to the slaves. Ttable can be Table, Table2D or Table3D in auxiliary.h. */ -template void avgTables(const mpl::communicator& communicator, Ttable& table, int& size) { +template +void avgTables(const mpl::communicator& communicator, Ttable& table, int& size) +{ std::vector send_buffer; // data to be sent std::vector recv_buffer; // buffer for recieving data if (!mpi.isMaster()) { @@ -200,14 +225,16 @@ template void avgTables(const mpl::communicator& communicator, Tt recv_buffer.resize(send_buffer.size()); const auto tag = mpl::tag_t(0); const auto layout = mpl::contiguous_layout(send_buffer.size()); - communicator.sendrecv(send_buffer.data(), layout, mpi.master_rank, tag, recv_buffer.data(), layout, - mpi.master_rank, tag); + communicator.sendrecv(send_buffer.data(), layout, mpi.master_rank, tag, recv_buffer.data(), + layout, mpi.master_rank, tag); table.buf2hist(recv_buffer); - } else { + } + else { send_buffer = table.hist2buf(size); recv_buffer.resize(size); - auto slaves = ranges::cpp20::views::iota(0, communicator.size()) | - ranges::cpp20::views::filter([&](auto rank) { return rank != mpi.master_rank; }); + auto slaves = + ranges::cpp20::views::iota(0, communicator.size()) | + ranges::cpp20::views::filter([&](auto rank) { return rank != mpi.master_rank; }); ranges::cpp20::for_each(slaves, [&](auto rank) { communicator.recv(recv_buffer, rank); diff --git a/src/multipole.h b/src/multipole.h index 26c984885..240d322e3 100644 --- a/src/multipole.h +++ b/src/multipole.h @@ -14,7 +14,9 @@ namespace Faunus { * @param muB Unit dipole moment vector of particel B * @param r Direction \f$ r_A - r_B \f$ */ -template double q2mu(double QBxMuA, const Tvec &muA, double QAxMuB, const Tvec &muB, const Tvec &r) { +template +double q2mu(double QBxMuA, const Tvec& muA, double QAxMuB, const Tvec& muB, const Tvec& r) +{ double r2i = 1 / r.squaredNorm(); // B = sol(dip), A = ch(charge) double r1i = sqrt(r2i); double r3i = r1i * r2i; @@ -32,7 +34,9 @@ template double q2mu(double QBxMuA, const Tvec &muA, double QAxMuB, * @param r Direction \f$ r_A - r_B \f$ */ template -double mu2mu(const Tvec &muA, const Tvec &muB, double muAxmuB, const Tvec &r, double a = 1.0, double b = 0.0) { +double mu2mu(const Tvec& muA, const Tvec& muB, double muAxmuB, const Tvec& r, double a = 1.0, + double b = 0.0) +{ #ifdef FAU_APPROXMATH double r1i = invsqrtQuake(r.squaredNorm()); double r2i = r1i * r1i; @@ -54,7 +58,8 @@ double mu2mu(const Tvec &muA, const Tvec &muB, double muAxmuB, const Tvec &r, do * @param r Direction \f$ r_A - r_B \f$ */ template -double q2quad(double qA, const Tmat &quadB, double qB, const Tmat &quadA, const Tvec &r) { +double q2quad(double qA, const Tmat& quadB, double qB, const Tmat& quadA, const Tvec& r) +{ double r2i = 1 / r.squaredNorm(); double r1i = sqrt(r2i); double r3i = r1i * r2i; @@ -72,9 +77,13 @@ namespace pairpotential { * @brief Returns the factorial of 'n'. Note that 'n' must be positive semidefinite. * @note Calculated at compile time and thus have no run-time overhead. */ -constexpr unsigned int factorial(unsigned int n) { return n <= 1 ? 1 : n * factorial(n - 1); } +constexpr unsigned int factorial(unsigned int n) +{ + return n <= 1 ? 1 : n * factorial(n - 1); +} -TEST_CASE("[Faunus] Factorial") { +TEST_CASE("[Faunus] Factorial") +{ CHECK(factorial(0) == 1); CHECK(factorial(1) == 1); CHECK(factorial(2) == 2); @@ -84,9 +93,10 @@ TEST_CASE("[Faunus] Factorial") { /** * @brief Help-function for `sfQpotential` using order 3. -*/ -inline void dipoleDipoleQ2Help_3(double &a3, double &b3, double q) { - double q2 = q*q; + */ +inline void dipoleDipoleQ2Help_3(double& a3, double& b3, double q) +{ + double q2 = q * q; constexpr double one_third = 1.0 / 3.0, two_third = 2.0 / 3.0, eight_third = 8.0 / 3.0; a3 = (((-5.0 * q2 + eight_third * q) * q + q) * q + one_third) * q2 + 1.0; b3 = -two_third * ((15.0 * q2 - 10.0 * q - 5.0) * q2 + 1.0) * q2; @@ -94,43 +104,45 @@ inline void dipoleDipoleQ2Help_3(double &a3, double &b3, double q) { /** * @brief Help-function for `sfQpotential` using order 4. -*/ -inline void dipoleDipoleQ2Help_4(double &a4, double &b4, double q) { - double q2 = q*q; - double q3 = q2*q; - a4 = ( ( (10.0 * q2 - 9.0 * q - 8.0 )*q3 + 10.0 )*q3 - 2.0 )*q - 1.0; - b4 = ( ( ( 21.0 * q2 - 16.0 * q - 35.0 / 3.0 )*q3 + 16.0 / 3.0 )*q3 + 1.0 / 3.0 )*q2 + 1.0; + */ +inline void dipoleDipoleQ2Help_4(double& a4, double& b4, double q) +{ + double q2 = q * q; + double q3 = q2 * q; + a4 = (((10.0 * q2 - 9.0 * q - 8.0) * q3 + 10.0) * q3 - 2.0) * q - 1.0; + b4 = (((21.0 * q2 - 16.0 * q - 35.0 / 3.0) * q3 + 16.0 / 3.0) * q3 + 1.0 / 3.0) * q2 + 1.0; } /** -* @brief Help-function for `sfQ0potential`. -*/ -inline double dipoleDipoleQ2Help(double q, int l=0, int P=300, bool a=true) { - if(q >= 1.0 - (1.0/2400.0)) + * @brief Help-function for `sfQ0potential`. + */ +inline double dipoleDipoleQ2Help(double q, int l = 0, int P = 300, bool a = true) +{ + if (q >= 1.0 - (1.0 / 2400.0)) return 0.0; - if(q <= (1.0/2400.0) && a) + if (q <= (1.0 / 2400.0) && a) return 1.0; - if(q <= (1.0/2400.0) && !a) + if (q <= (1.0 / 2400.0) && !a) return 0.0; double qP = 1.0; // Will end as q-Pochhammer Symbol, (q^l;q)_P double fac = Faunus::powi(q, l); double sum1 = 0.0, sum2 = 0.0, sum4 = 0.0; - for( int n = 1; n <= P; n++) { + for (int n = 1; n <= P; n++) { fac *= q; // q^(l+n) qP *= (1.0 - fac); - double tmp0 = (l+n)*fac/(1.0 - fac); + double tmp0 = (l + n) * fac / (1.0 - fac); sum2 += tmp0; - sum1 += tmp0*(fac + l + n - 1.0)/(1.0 - fac); - sum4 += tmp0*(4.0*fac + l + n - 4.0)/(1.0 - fac); + sum1 += tmp0 * (fac + l + n - 1.0) / (1.0 - fac); + sum4 += tmp0 * (4.0 * fac + l + n - 4.0) / (1.0 - fac); } - sum2 = sum2*sum2; - double ac = qP/3.0*(3.0 - sum4 + sum2); - double bc = qP/3.0*(sum2 - sum1); + sum2 = sum2 * sum2; + double ac = qP / 3.0 * (3.0 - sum4 + sum2); + double bc = qP / 3.0 * (sum2 - sum1); - if(a) + if (a) return ac; return bc; } @@ -141,7 +153,8 @@ inline double dipoleDipoleQ2Help(double q, int l=0, int P=300, bool a=true) { * More information here: http://mathworld.wolfram.com/q-PochhammerSymbol.html * P = 300 gives an error of about 10^-17 for k < 4 */ -inline double qPochhammerSymbol(double q, int k = 1, int P = 300) { +inline double qPochhammerSymbol(double q, int k = 1, int P = 300) +{ double value = 1.0; double temp = Faunus::powi(q, k); for (int i = 0; i < P; i++) { @@ -151,7 +164,8 @@ inline double qPochhammerSymbol(double q, int k = 1, int P = 300) { return value; } -TEST_CASE("[Faunus] qPochhammerSymbol") { +TEST_CASE("[Faunus] qPochhammerSymbol") +{ double q = 0.5; CHECK(qPochhammerSymbol(q, 0, 0) == 1); CHECK(qPochhammerSymbol(0, 0, 1) == 0); @@ -167,8 +181,10 @@ TEST_CASE("[Faunus] qPochhammerSymbol") { * @param begin First particle * @param end Last particle */ -template double monopoleMoment(Titer begin, Titer end) { - return std::accumulate(begin, end, 0.0, [](auto sum, auto& particle) { return sum + particle.charge; }); +template double monopoleMoment(Titer begin, Titer end) +{ + return std::accumulate(begin, end, 0.0, + [](auto sum, auto& particle) { return sum + particle.charge; }); } //!< Calculates dipole moment vector for a set of particles /** @@ -183,16 +199,17 @@ template double monopoleMoment(Titer begin, Titer */ template > Point dipoleMoment( - Titer begin, Titer end, BoundaryFunction boundary = [](Point&) {}, const Point origin = {0, 0, 0}, - double cutoff = pc::infty) { + Titer begin, Titer end, BoundaryFunction boundary = [](Point&) {}, + const Point origin = {0, 0, 0}, double cutoff = pc::infty) +{ Point dipole_moment(0, 0, 0); - std::for_each(begin, end, [&](const Particle &particle) { + std::for_each(begin, end, [&](const Particle& particle) { Point position = particle.pos - origin; boundary(position); // at this stage, positions should be unwrapped if (position.squaredNorm() < cutoff * cutoff) { dipole_moment += position * particle.charge; if (particle.hasExtension()) { - const auto &extended_properties = particle.getExt(); + const auto& extended_properties = particle.getExt(); dipole_moment += extended_properties.mu * extended_properties.mulen; } } @@ -200,7 +217,8 @@ Point dipoleMoment( return dipole_moment; } //!< Calculates dipole moment vector -TEST_CASE("[Faunus] dipoleMoment") { +TEST_CASE("[Faunus] dipoleMoment") +{ using doctest::Approx; ParticleVector p(2); p[0].pos = {10, 20, 30}; @@ -208,16 +226,18 @@ TEST_CASE("[Faunus] dipoleMoment") { p[0].charge = -0.5; p[1].charge = 0.5; - SUBCASE("Neutral molecule") { - auto mu = dipoleMoment(p.begin(), p.end(), [](auto &) {}, {2, 3, 4}); // some origin + SUBCASE("Neutral molecule") + { + auto mu = dipoleMoment(p.begin(), p.end(), [](auto&) {}, {2, 3, 4}); // some origin CHECK(mu.squaredNorm() == Approx(10 * 10 + 10 * 10 + 30 * 30)); - mu = dipoleMoment(p.begin(), p.end(), [](auto &) {}, {20, 30, 40}); // another origin + mu = dipoleMoment(p.begin(), p.end(), [](auto&) {}, {20, 30, 40}); // another origin CHECK(mu.squaredNorm() == Approx(10 * 10 + 10 * 10 + 30 * 30)); } - SUBCASE("Charged molecule") { + SUBCASE("Charged molecule") + { p[0].charge *= -1.0; // give molecule a net charge - auto mu = dipoleMoment(p.begin(), p.end(), [](auto &) {}, {2, 3, 4}); + auto mu = dipoleMoment(p.begin(), p.end(), [](auto&) {}, {2, 3, 4}); CHECK(mu.x() == Approx(-2)); CHECK(mu.y() == Approx(7)); CHECK(mu.z() == Approx(-4)); @@ -234,8 +254,9 @@ TEST_CASE("[Faunus] dipoleMoment") { */ template Tensor quadrupoleMoment( - Titer begin, Titer end, BoundaryFunction boundary = [](const Point&) {}, Point origin = {0, 0, 0}, - double cutoff = pc::infty) { + Titer begin, Titer end, BoundaryFunction boundary = [](const Point&) {}, + Point origin = {0, 0, 0}, double cutoff = pc::infty) +{ Tensor theta; theta.setZero(); for (auto it = begin; it != end; ++it) { @@ -248,17 +269,18 @@ Tensor quadrupoleMoment( return 0.5 * theta; } //!< Calculates quadrupole moment tensor (with trace) -TEST_CASE("[Faunus] quadrupoleMoment") { +TEST_CASE("[Faunus] quadrupoleMoment") +{ using doctest::Approx; ParticleVector p(2); p[0].pos = {10, 20, 30}; p[1].pos = {-10, 0, -30}; p[0].charge = -0.5; p[1].charge = 0.3; - auto mu = quadrupoleMoment(p.begin(), p.end(), [](auto &) {}, {2, 3, 4}); + auto mu = quadrupoleMoment(p.begin(), p.end(), [](auto&) {}, {2, 3, 4}); CHECK(mu.trace() == Approx(-60.9)); p[0].charge *= -1.0; - mu = quadrupoleMoment(p.begin(), p.end(), [](auto &) {}, {2, 3, 4}); + mu = quadrupoleMoment(p.begin(), p.end(), [](auto&) {}, {2, 3, 4}); CHECK(mu.trace() == Approx(453.6)); } @@ -269,12 +291,15 @@ TEST_CASE("[Faunus] quadrupoleMoment") { * @param cutoff Cut-off for included particles with regard to origin, default value is infinite */ template -auto toMultipole(const Tgroup &g, BoundaryFunction boundary = [](const Point &) {}, double cutoff = pc::infty) { +auto toMultipole( + const Tgroup& g, BoundaryFunction boundary = [](const Point&) {}, double cutoff = pc::infty) +{ Particle m; m.pos = g.mass_center; - m.charge = Faunus::monopoleMoment(g.begin(), g.end()); // monopole - m.getExt().mu = Faunus::dipoleMoment(g.begin(), g.end(), boundary, m.pos, cutoff); // dipole - m.getExt().Q = Faunus::quadrupoleMoment(g.begin(), g.end(), boundary, m.pos, cutoff); // quadrupole + m.charge = Faunus::monopoleMoment(g.begin(), g.end()); // monopole + m.getExt().mu = Faunus::dipoleMoment(g.begin(), g.end(), boundary, m.pos, cutoff); // dipole + m.getExt().Q = + Faunus::quadrupoleMoment(g.begin(), g.end(), boundary, m.pos, cutoff); // quadrupole m.getExt().mulen = m.getExt().mu.norm(); if (m.getExt().mulen > 1e-9) m.getExt().mu.normalize(); diff --git a/src/particle.cpp b/src/particle.cpp index 99c6767cc..a51f439cc 100644 --- a/src/particle.cpp +++ b/src/particle.cpp @@ -15,56 +15,98 @@ namespace Faunus { * done since tensors are rotated using the latter. Could these be rotated using * quaternions? */ -void ParticlePropertyBase::rotate(const Eigen::Quaterniond &, const Eigen::Matrix3d &) {} -void Radius::to_json(json &j) const { j["r"] = radius; } -void Radius::from_json(const json &j) { radius = j.value("r", 0.0); } -void Charge::to_json(json &j) const { j["q"] = charge; } -void Charge::from_json(const json &j) { charge = j.value("q", 0.0); } -void Dipole::rotate(const Eigen::Quaterniond &q, const Eigen::Matrix3d &) { mu = q * mu; } - -void Dipole::to_json(json &j) const { +void ParticlePropertyBase::rotate(const Eigen::Quaterniond&, const Eigen::Matrix3d&) {} + +void Radius::to_json(json& j) const +{ + j["r"] = radius; +} + +void Radius::from_json(const json& j) +{ + radius = j.value("r", 0.0); +} + +void Charge::to_json(json& j) const +{ + j["q"] = charge; +} + +void Charge::from_json(const json& j) +{ + charge = j.value("q", 0.0); +} + +void Dipole::rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d&) +{ + mu = q * mu; +} + +void Dipole::to_json(json& j) const +{ j["mu"] = mu; j["mulen"] = mulen; } -void Dipole::from_json(const json &j) { +void Dipole::from_json(const json& j) +{ mu = j.value("mu", Point::Zero().eval()); mulen = j.value("mulen", 0.0); } -bool Dipole::isDipolar() const { + +bool Dipole::isDipolar() const +{ return mulen > pc::epsilon_dbl; } -void Polarizable::rotate(const Eigen::Quaterniond &q, const Eigen::Matrix3d &m) { +void Polarizable::rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m) +{ mui = q * mui; alpha.rotate(m); } -void Polarizable::to_json(json &j) const { +void Polarizable::to_json(json& j) const +{ j["alpha"] = alpha; j["mui"] = mui; j["muilen"] = muilen; } -void Polarizable::from_json(const json &j) { +void Polarizable::from_json(const json& j) +{ alpha = j.value("alpha", alpha); mui = j.value("mui", Point(1, 0, 0)); muilen = j.value("mulen", muilen); } -bool Polarizable::isPolarizable() const { + +bool Polarizable::isPolarizable() const +{ return alpha.cwiseAbs().sum() > pc::epsilon_dbl; } -void Quadrupole::rotate(const Eigen::Quaterniond &, const Eigen::Matrix3d &m) { Q.rotate(m); } +void Quadrupole::rotate(const Eigen::Quaterniond&, const Eigen::Matrix3d& m) +{ + Q.rotate(m); +} -void Quadrupole::to_json(json &j) const { j["Q"] = Q; } +void Quadrupole::to_json(json& j) const +{ + j["Q"] = Q; +} + +void Quadrupole::from_json(const json& j) +{ + Q = j.value("Q", Tensor(Tensor::Zero())); +} -void Quadrupole::from_json(const json& j) { Q = j.value("Q", Tensor(Tensor::Zero()));} -bool Quadrupole::isQuadrupolar() const { +bool Quadrupole::isQuadrupolar() const +{ return Q.cwiseAbs().sum() > pc::epsilon_dbl; } -void Cigar::rotate(const Eigen::Quaterniond& quaternion, [[maybe_unused]] const Eigen::Matrix3d& rotation_matrix) { +void Cigar::rotate(const Eigen::Quaterniond& quaternion, + [[maybe_unused]] const Eigen::Matrix3d& rotation_matrix) +{ scdir = quaternion * scdir; patchdir = quaternion * patchdir; patchsides.at(0) = quaternion * patchsides.at(0); @@ -73,15 +115,18 @@ void Cigar::rotate(const Eigen::Quaterniond& quaternion, [[maybe_unused]] const // after many rotations...will require access to AtomData. } -void Cigar::to_json(json& j) const { +void Cigar::to_json(json& j) const +{ j["scdir"] = scdir; j["pdir"] = patchdir; } -void Cigar::from_json(const json& j) { +void Cigar::from_json(const json& j) +{ assert(j.contains("id")); const auto& psc = Faunus::atoms.at(j.at("id").get()).sphero_cylinder; - setDirections(psc, j.value("scdir", Point(1.0, 0.0, 0.0)), j.value("pdir", Point(0.0, 1.0, 0.0))); + setDirections(psc, j.value("scdir", Point(1.0, 0.0, 0.0)), + j.value("pdir", Point(0.0, 1.0, 0.0))); } /** @@ -92,7 +137,9 @@ void Cigar::from_json(const json& j) { * of patch properties. * It shall be also after a lot of moves to remove accumulated errors */ -void Cigar::setDirections(const SpheroCylinderData& psc_data, const Point& new_direction, const Point &new_patch_direction) { +void Cigar::setDirections(const SpheroCylinderData& psc_data, const Point& new_direction, + const Point& new_patch_direction) +{ constexpr auto very_small_number = 1e-9; half_length = 0.5 * psc_data.length; if (half_length < very_small_number) { @@ -103,7 +150,8 @@ void Cigar::setDirections(const SpheroCylinderData& psc_data, const Point& new_d pcanglsw = std::cos(0.5 * psc_data.patch_angle + psc_data.patch_angle_switch); scdir = scdir.squaredNorm() < very_small_number ? Point(1, 0, 0) : new_direction.normalized(); - patchdir = patchdir.squaredNorm() < very_small_number ? Point(0, 1, 0) : new_patch_direction.normalized(); + patchdir = patchdir.squaredNorm() < very_small_number ? Point(0, 1, 0) + : new_patch_direction.normalized(); if (fabs(patchdir.dot(scdir)) > very_small_number) { // must be perpendicular faunus_logger->trace("straigthening patch direction"); patchdir = (patchdir - scdir * patchdir.dot(scdir)).normalized(); // perp. project @@ -128,24 +176,43 @@ void Cigar::setDirections(const SpheroCylinderData& psc_data, const Point& new_d throw std::runtime_error("patch side vector has zero size"); } } -bool Cigar::isCylindrical() const { + +bool Cigar::isCylindrical() const +{ return half_length > pc::epsilon_dbl; } -const AtomData &Particle::traits() const { return atoms[id]; } +const AtomData& Particle::traits() const +{ + return atoms[id]; +} /** * @warning Performance is sub-optimal as conversion is done through a json object */ -Particle::Particle(const AtomData &atomdata) {*this = static_cast(atomdata).front(); } -Particle::Particle(const AtomData &atomdata, const Point &pos) : Particle(atomdata) { this->pos = pos; } -Particle::Particle(const Particle &other) : id(other.id), charge(other.charge), pos(other.pos) { +Particle::Particle(const AtomData& atomdata) +{ + *this = static_cast(atomdata).front(); +} + +Particle::Particle(const AtomData& atomdata, const Point& pos) + : Particle(atomdata) +{ + this->pos = pos; +} + +Particle::Particle(const Particle& other) + : id(other.id) + , charge(other.charge) + , pos(other.pos) +{ if (other.hasExtension()) { ext = std::make_unique(other.getExt()); // deep copy } } -Particle &Particle::operator=(const Particle &other) { +Particle& Particle::operator=(const Particle& other) +{ if (&other != this) { charge = other.charge; pos = other.pos; @@ -153,10 +220,13 @@ Particle &Particle::operator=(const Particle &other) { if (other.hasExtension()) { // if particle has if (ext) { // extension, then *ext = other.getExt(); // deep copy - } else { // else if *this is empty, create new based on p - ext = std::make_unique(other.getExt()); // create and deep copy } - } else { // other doesn't have extended properties + else { // else if *this is empty, create new based on p + ext = std::make_unique( + other.getExt()); // create and deep copy + } + } + else { // other doesn't have extended properties ext = nullptr; } } @@ -171,22 +241,28 @@ Particle &Particle::operator=(const Particle &other) { * This has effect only on anisotropic particles and does no * positional rotation, only internal rotation */ -void Particle::rotate(const Eigen::Quaterniond &quaternion, const Eigen::Matrix3d &rotation_matrix) { +void Particle::rotate(const Eigen::Quaterniond& quaternion, const Eigen::Matrix3d& rotation_matrix) +{ if (hasExtension()) { ext->rotate(quaternion, rotation_matrix); } } -bool Particle::hasExtension() const { return ext != nullptr; } +bool Particle::hasExtension() const +{ + return ext != nullptr; +} -Particle::ParticleExtension& Particle::createExtension() { +Particle::ParticleExtension& Particle::createExtension() +{ if (!ext) { ext = std::make_unique(); } return *ext; } -void from_json(const json &j, Particle &particle) { +void from_json(const json& j, Particle& particle) +{ assert(j.contains("id")); particle.id = j.at("id").get(); particle.pos = j.value("pos", Point::Zero().eval()); @@ -194,13 +270,15 @@ void from_json(const json &j, Particle &particle) { if (j.contains("psc") || j.contains("mu") || j.contains("Q") || j.contains("scdir")) { particle.ext = std::make_unique(j); // delete extension if not in use: - if (!particle.ext->isCylindrical() && !particle.ext->isDipolar() && !particle.ext->isQuadrupolar() && - !particle.ext->isQuadrupolar()) { + if (!particle.ext->isCylindrical() && !particle.ext->isDipolar() && + !particle.ext->isQuadrupolar() && !particle.ext->isQuadrupolar()) { particle.ext = nullptr; } } } -void to_json(json &j, const Particle &particle) { + +void to_json(json& j, const Particle& particle) +{ if (particle.hasExtension()) { to_json(j, particle.getExt()); } @@ -211,7 +289,8 @@ void to_json(json &j, const Particle &particle) { TEST_SUITE_BEGIN("Particle"); -TEST_CASE("[Faunus] Particle") { +TEST_CASE("[Faunus] Particle") +{ using doctest::Approx; json j = R"({ "atomlist" : [ @@ -241,7 +320,8 @@ TEST_CASE("[Faunus] Particle") { p1.getExt().scdir = Point(-0.1, 0.3, 1.9).normalized(); p1.getExt().patchdir = Point::Ones().normalized(); p1.getExt().patchdir = - (p1.getExt().patchdir - p1.getExt().scdir * p1.getExt().patchdir.dot(p1.getExt().scdir)).normalized(); + (p1.getExt().patchdir - p1.getExt().scdir * p1.getExt().patchdir.dot(p1.getExt().scdir)) + .normalized(); CHECK(p1.getExt().patchdir.dot(p1.getExt().scdir) < 1e-9); // must be perpendicular p1.getExt().Q = Tensor(1, 2, 3, 4, 5, 6); @@ -258,7 +338,8 @@ TEST_CASE("[Faunus] Particle") { CHECK_EQ(p2.getExt().mulen, 2.8); CHECK_EQ(p2.getExt().Q, Tensor(1, 2, 3, 4, 5, 6)); - SUBCASE("Cigar") { + SUBCASE("Cigar") + { const auto& cigar = p2.getExt(); CHECK_EQ(cigar.scdir, Point(-0.1, 0.3, 1.9).normalized()); CHECK_EQ(cigar.scdir.x(), Approx(-0.0519174)); @@ -279,7 +360,8 @@ TEST_CASE("[Faunus] Particle") { CHECK_EQ(p2.traits().sphero_cylinder.chiral_angle, 5.0 * 1.0_deg); } - SUBCASE("rotate") { + SUBCASE("rotate") + { // check if all properties are rotated QuaternionRotate qrot(pc::pi / 2, {0, 1, 0}); p1.getExt().mu = p1.getExt().scdir = {1, 0, 0}; @@ -298,7 +380,8 @@ TEST_CASE("[Faunus] Particle") { CHECK_EQ(p1.getExt().Q(2, 2), Approx(1)); } - SUBCASE("Cereal serialisation") { + SUBCASE("Cereal serialisation") + { Particle p; p.pos = {10, 20, 30}; p.charge = -1; diff --git a/src/particle.h b/src/particle.h index 4988c8333..200a55bfa 100644 --- a/src/particle.h +++ b/src/particle.h @@ -17,84 +17,114 @@ namespace Faunus { * @brief Base class for particle properties * @todo Is this really needed? */ -struct ParticlePropertyBase { +struct ParticlePropertyBase +{ virtual void to_json(json& j) const = 0; //!< Convert to JSON object virtual void from_json(const json& j) = 0; //!< Convert from JSON object void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d&); //!< Internal rotation virtual ~ParticlePropertyBase() = default; + template void serialize(Archive&) {} //!< Cereal serialisation }; -template -auto to_json(json&) -> typename std::enable_if::type {} //!< Particle to JSON -template void to_json(json& j, const ParticlePropertyBase& a, const Ts&... rest) { +template auto to_json(json&) -> typename std::enable_if::type +{ +} //!< Particle to JSON + +template +void to_json(json& j, const ParticlePropertyBase& a, const Ts&... rest) +{ a.to_json(j); to_json(j, rest...); } //!< Particle to JSON // JSON --> Particle -template auto from_json(const json&) -> typename std::enable_if::type {} -template void from_json(const json& j, ParticlePropertyBase& a, Ts&... rest) { +template +auto from_json(const json&) -> typename std::enable_if::type +{ +} + +template +void from_json(const json& j, ParticlePropertyBase& a, Ts&... rest) +{ a.from_json(j); from_json(j, rest...); } -struct Radius : public ParticlePropertyBase { +struct Radius : public ParticlePropertyBase +{ double radius = 0.0; //!< Particle radius void to_json(json& j) const override; void from_json(const json& j) override; + template void serialize(Archive& archive) { archive(radius); } + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; //!< Radius property -struct Charge : public ParticlePropertyBase { +struct Charge : public ParticlePropertyBase +{ double charge = 0.0; //!< Particle radius void to_json(json& j) const override; void from_json(const json& j) override; + template void serialize(Archive& archive) { archive(charge); } + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; //!< Charge (monopole) property /** * @brief Dipole properties */ -struct Dipole : public ParticlePropertyBase { +struct Dipole : public ParticlePropertyBase +{ Point mu = {0.0, 0.0, 0.0}; //!< dipole moment unit vector double mulen = 0.0; //!< dipole moment scalar void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d&); //!< Rotate dipole moment void to_json(json& j) const override; void from_json(const json& j) override; bool isDipolar() const; + template void serialize(Archive& archive) { archive(mu, mulen); } + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; -struct Polarizable : public ParticlePropertyBase { - Point mui = {1.0, 0.0, 0.0}; //!< induced dipole moment unit vector - double muilen = 0.0; //!< induced dipole moment scalar - Tensor alpha = Tensor::Zero(); //!< polarizability tensor - void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m); //!< Rotate polarizability tensor +struct Polarizable : public ParticlePropertyBase +{ + Point mui = {1.0, 0.0, 0.0}; //!< induced dipole moment unit vector + double muilen = 0.0; //!< induced dipole moment scalar + Tensor alpha = Tensor::Zero(); //!< polarizability tensor + void rotate(const Eigen::Quaterniond& q, + const Eigen::Matrix3d& m); //!< Rotate polarizability tensor void to_json(json& j) const override; void from_json(const json& j) override; bool isPolarizable() const; //!< True if non-zero polarizability + template void serialize(Archive& archive) { archive(mui, muilen, alpha); } + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; -struct Quadrupole : public ParticlePropertyBase { - Tensor Q = Tensor::Zero(); //!< Quadrupole - void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m); //!< Rotate quadrupole moment +struct Quadrupole : public ParticlePropertyBase +{ + Tensor Q = Tensor::Zero(); //!< Quadrupole + void rotate(const Eigen::Quaterniond& q, + const Eigen::Matrix3d& m); //!< Rotate quadrupole moment void to_json(json& j) const override; void from_json(const json& j) override; bool isQuadrupolar() const; //!< True if non-zero quadrupolar moment + template void serialize(Archive& archive) { archive(Q); } + EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; // Quadrupole property /** * @brief Patchy sphero cylinder a.k.a. Cigar particles */ -class Cigar : public ParticlePropertyBase { +class Cigar : public ParticlePropertyBase +{ private: public: Point scdir = {1.0, 0.0, 0.0}; //!< Sphero-cylinder direction unit vector @@ -103,14 +133,19 @@ class Cigar : public ParticlePropertyBase { double half_length = 0.0; //!< Half end-to-end distace double pcanglsw = 0.0; //!< Cosine of switch angle from AtomData (speed optimization) double pcangl = 0.0; //!< Cosine of AtomData::patch_angle (speed optimization) - void rotate(const Eigen::Quaterniond& quaternion, const Eigen::Matrix3d& rotation_matrix); //!< Rotate sphero-cylinder + void rotate(const Eigen::Quaterniond& quaternion, + const Eigen::Matrix3d& rotation_matrix); //!< Rotate sphero-cylinder void to_json(json& j) const override; void from_json(const json& j) override; - void setDirections(const SpheroCylinderData& psc_data, const Point& new_direction, - const Point& new_patch_direction); // initialize; run at start and after patch changes - template void serialize(Archive& archive) { + void setDirections( + const SpheroCylinderData& psc_data, const Point& new_direction, + const Point& new_patch_direction); // initialize; run at start and after patch changes + + template void serialize(Archive& archive) + { archive(scdir, patchdir, patchsides.at(0), patchsides.at(1)); } + bool isCylindrical() const; //!< True of non-zero length EIGEN_MAKE_ALIGNED_OPERATOR_NEW @@ -125,14 +160,19 @@ class Cigar : public ParticlePropertyBase { * can is typically anisotropic properties which in this way * is stored in an alternative memory location. * */ -template class ParticleTemplate : public Properties... { +template class ParticleTemplate : public Properties... +{ private: // rotate internal coordinates template auto _rotate(const Eigen::Quaterniond&, const Eigen::Matrix3d&) -> - typename std::enable_if::type {} + typename std::enable_if::type + { + } + template - void _rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m, T& a, Ts&... rest) { + void _rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m, T& a, Ts&... rest) + { a.rotate(q, m); _rotate(q, m, rest...); } @@ -140,34 +180,47 @@ template class ParticleTemplate : public Properties... // Cereal serialisation template - auto __serialize(Archive&) -> typename std::enable_if::type {} + auto __serialize(Archive&) -> typename std::enable_if::type + { + } - template void __serialize(Archive& archive, T& a, Ts&... rest) { + template + void __serialize(Archive& archive, T& a, Ts&... rest) + { a.serialize(archive); __serialize(archive, rest...); } public: - ParticleTemplate() : Properties()... {}; + ParticleTemplate() + : Properties()... {}; - explicit ParticleTemplate(const AtomData& a) : Properties()... { *this = json(a).front(); } + explicit ParticleTemplate(const AtomData& a) + : Properties()... + { + *this = json(a).front(); + } - void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m) { + void rotate(const Eigen::Quaterniond& q, const Eigen::Matrix3d& m) + { _rotate(q, m, dynamic_cast(*this)...); } //!< Rotate all internal coordinates if needed - template void serialize(Archive& archive) { + template void serialize(Archive& archive) + { __serialize(archive, dynamic_cast(*this)...); } EIGEN_MAKE_ALIGNED_OPERATOR_NEW }; -template void to_json(json& j, const ParticleTemplate& a) { +template void to_json(json& j, const ParticleTemplate& a) +{ to_json(j, Properties(a)...); } -template void from_json(const json& j, ParticleTemplate& a) { +template void from_json(const json& j, ParticleTemplate& a) +{ from_json(j, dynamic_cast(a)...); } @@ -181,7 +234,8 @@ template void from_json(const json& j, ParticleTemplate * * @todo: memory model for extended properties not optimal */ -class Particle { +class Particle +{ public: using ParticleExtension = ParticleTemplate; std::unique_ptr ext; //!< Point to extended properties @@ -195,11 +249,18 @@ class Particle { Particle(const Particle&); //!< copy constructor Particle& operator=(const Particle&); //!< assignment operator const AtomData& traits() const; //!< get properties from AtomData - void rotate(const Eigen::Quaterniond& quaternion, const Eigen::Matrix3d& rotation_matrix); //!< internal rotation + void rotate(const Eigen::Quaterniond& quaternion, + const Eigen::Matrix3d& rotation_matrix); //!< internal rotation bool hasExtension() const; //!< check if particle has extensions (dipole etc.) ParticleExtension& createExtension(); //!< Create extension - inline ParticleExtension& getExt() { return ext ? *ext : createExtension(); } //!< get/create extension - inline const ParticleExtension& getExt() const { + + inline ParticleExtension& getExt() + { + return ext ? *ext : createExtension(); + } //!< get/create extension + + inline const ParticleExtension& getExt() const + { assert(ext); return *ext; } //!< Get extended particle properties; @@ -209,7 +270,8 @@ class Particle { * @param archive Archive to serialize to/from * @warning Still under construction */ - template void serialize(Archive& archive) { + template void serialize(Archive& archive) + { archive(ext, id, charge, pos); // if (ext != nullptr) // ext->serialize(archive); @@ -221,7 +283,8 @@ using ParticleVector = std::vector; /** Concept for a range of particles */ template -concept RequireParticles = ranges::cpp20::range && std::is_convertible_v, Particle>; +concept RequireParticles = + ranges::cpp20::range && std::is_convertible_v, Particle>; template concept RequireParticleIterator = std::is_convertible_v, Particle>; @@ -236,7 +299,8 @@ void to_json(json&, const Particle&); * @param max_difference Apply only if charge mismatch is larger than this, then log */ template -void applyAtomDataCharges(Iterator first, Iterator last, const double max_difference = 1e-9) { +void applyAtomDataCharges(Iterator first, Iterator last, const double max_difference = 1e-9) +{ size_t mismatch_counter = 0; std::for_each(first, last, [&](Particle& particle) { const auto topology_charge = Faunus::atoms.at(particle.id).charge; @@ -246,7 +310,8 @@ void applyAtomDataCharges(Iterator first, Iterator last, const double max_differ } }); if (mismatch_counter > 0) { - faunus_logger->debug("{} charge(s) reset with topology values (from atomlist)", mismatch_counter); + faunus_logger->debug("{} charge(s) reset with topology values (from atomlist)", + mismatch_counter); } } diff --git a/src/penalty.cpp b/src/penalty.cpp index 4c26d1153..018243af9 100644 --- a/src/penalty.cpp +++ b/src/penalty.cpp @@ -4,7 +4,9 @@ namespace Faunus::Energy { -Penalty::Penalty(const json& j, const Space& spc) : spc(spc) { +Penalty::Penalty(const json& j, const Space& spc) + : spc(spc) +{ name = "penalty"; overwrite_penalty = j.value("overwrite", true); energy_increment = j.at("f0").get(); @@ -23,17 +25,20 @@ Penalty::Penalty(const json& j, const Space& spc) : spc(spc) { initializePenaltyFunction(j.at("coords")); loadPenaltyFunction(MPI::prefix + penalty_function_filename); } -void Penalty::initializePenaltyFunction(const json& j) { + +void Penalty::initializePenaltyFunction(const json& j) +{ if (!j.is_array()) { throw ConfigurationError("array of reaction coordinates required"); } std::vector resolutions, minimum_values, maximum_values; for (const auto& i : j) { - auto& reaction_coordinate = - reaction_coordinates_functions.emplace_back(ReactionCoordinate::createReactionCoordinate(i, spc)); + auto& reaction_coordinate = reaction_coordinates_functions.emplace_back( + ReactionCoordinate::createReactionCoordinate(i, spc)); if (reaction_coordinate->minimum_value >= reaction_coordinate->maximum_value || reaction_coordinate->resolution <= 0) { - throw ConfigurationError("min0 required for penalty reaction coordinate"); + throw ConfigurationError( + "min0 required for penalty reaction coordinate"); } resolutions.push_back(reaction_coordinate->resolution); minimum_values.push_back(reaction_coordinate->minimum_value); @@ -49,11 +54,13 @@ void Penalty::initializePenaltyFunction(const json& j) { penalty_energy.reInitializer(resolutions, minimum_values, maximum_values); } -void Penalty::loadPenaltyFunction(const std::string& filename) { +void Penalty::loadPenaltyFunction(const std::string& filename) +{ if (std::ifstream stream(filename); stream) { faunus_logger->info("Loading penalty function {}", filename); std::string ignore; - stream >> ignore >> energy_increment >> samplings >> penalty_function_exchange_counter; // header line + stream >> ignore >> energy_increment >> samplings >> + penalty_function_exchange_counter; // header line for (int row = 0; row < penalty_energy.rows(); row++) { for (int col = 0; col < penalty_energy.cols(); col++) { if (stream.eof()) { @@ -68,22 +75,29 @@ void Penalty::loadPenaltyFunction(const std::string& filename) { /** * @todo Foul to let constructor be responsible for i/o... */ -Penalty::~Penalty() { toDisk(); } +Penalty::~Penalty() +{ + toDisk(); +} /** * @brief Stream penalty function, offset with the minimum observed energy */ -void Penalty::streamPenaltyFunction(std::ostream& stream) const { +void Penalty::streamPenaltyFunction(std::ostream& stream) const +{ stream.precision(16); - stream << fmt::format("# {} {} {}\n", energy_increment, samplings, penalty_function_exchange_counter) + stream << fmt::format("# {} {} {}\n", energy_increment, samplings, + penalty_function_exchange_counter) << penalty_energy.array() - penalty_energy.minCoeff() << "\n"; } -void Penalty::streamHistogram(std::ostream& stream) const { +void Penalty::streamHistogram(std::ostream& stream) const +{ stream << histogram; } -void Penalty::toDisk() { +void Penalty::toDisk() +{ if (overwrite_penalty) { if (std::ofstream stream(MPI::prefix + penalty_function_filename); stream) { streamPenaltyFunction(stream); @@ -94,7 +108,8 @@ void Penalty::toDisk() { } } -void Penalty::to_json(json& j) const { +void Penalty::to_json(json& j) const +{ j["file"] = penalty_function_filename; j["scale"] = energy_increment_scaling_factor; j["update"] = number_of_steps_between_updates; @@ -110,7 +125,9 @@ void Penalty::to_json(json& j) const { coordinates_j.emplace_back(*reaction_coordinate); // `ReactionCoordinateBase` --> `json` } } -double Penalty::energy(const Change& change) { + +double Penalty::energy(const Change& change) +{ double energy = 0.0; if (change) { for (size_t i = 0; i < number_of_reaction_coordinates; i++) { @@ -133,11 +150,13 @@ double Penalty::energy(const Change& change) { * @todo: If this is called before `energy()`, the latest_coordinate * is never calculated and causes undefined behavior */ -void Penalty::updatePenalty(const std::vector& coordinate) { +void Penalty::updatePenalty(const std::vector& coordinate) +{ update_counter++; if (update_counter % number_of_steps_between_updates == 0 and energy_increment > 0.0) { if (histogram.minCoeff() >= (int)samplings) { - const auto minimum_penalty_energy = penalty_energy.minCoeff(); // define min. penalty energy... + const auto minimum_penalty_energy = + penalty_energy.minCoeff(); // define min. penalty energy... penalty_energy = penalty_energy.array() - minimum_penalty_energy; // ...to zero sum_of_energy_increments -= minimum_penalty_energy; if (verbose) { @@ -153,8 +172,11 @@ void Penalty::updatePenalty(const std::vector& coordinate) { penalty_energy[coordinate] += energy_increment; sum_of_energy_increments += energy_increment; } -void Penalty::logBarrierInformation() const { - faunus_logger->info("energy barriers: penalty function = {} kT, histogram = {} kT", penalty_energy.maxCoeff(), + +void Penalty::logBarrierInformation() const +{ + faunus_logger->info("energy barriers: penalty function = {} kT, histogram = {} kT", + penalty_energy.maxCoeff(), std::log(double(histogram.maxCoeff()) / histogram.minCoeff())); } @@ -162,13 +184,15 @@ void Penalty::logBarrierInformation() const { * Called when a move is accepted or rejected, as well as when initializing the system * @todo: this doubles the MPI communication */ -void Penalty::sync(EnergyTerm* other, [[maybe_unused]] const Change& change) { +void Penalty::sync(EnergyTerm* other, [[maybe_unused]] const Change& change) +{ auto* other_penalty = dynamic_cast(other); if (other_penalty == nullptr) { throw std::runtime_error("error in Penalty::sync - please report"); } updatePenalty(other_penalty->latest_coordinate); - other_penalty->updatePenalty(other_penalty->latest_coordinate); // keep update_counter and samplings in sync + other_penalty->updatePenalty( + other_penalty->latest_coordinate); // keep update_counter and samplings in sync assert(samplings == other_penalty->samplings); assert(latest_coordinate == other_penalty->latest_coordinate); assert(update_counter == other_penalty->update_counter); @@ -177,7 +201,10 @@ void Penalty::sync(EnergyTerm* other, [[maybe_unused]] const Change& change) { #ifdef ENABLE_MPI -PenaltyMPI::PenaltyMPI(const json& j, Space& spc, const MPI::Controller& mpi) : Penalty(j, spc), mpi(mpi) { +PenaltyMPI::PenaltyMPI(const json& j, Space& spc, const MPI::Controller& mpi) + : Penalty(j, spc) + , mpi(mpi) +{ weights.resize(mpi.world.size()); } @@ -186,7 +213,8 @@ PenaltyMPI::PenaltyMPI(const json& j, Space& spc, const MPI::Controller& mpi) : * * MPI_Allgather(&least_sampled_in_histogram, 1, MPI_INT, weights.data(), 1, MPI_INT, mpi.comm); */ -void PenaltyMPI::updatePenalty(const std::vector& coordinate) { +void PenaltyMPI::updatePenalty(const std::vector& coordinate) +{ const auto old_penalty_energy = penalty_energy[coordinate]; update_counter++; if (update_counter % number_of_steps_between_updates == 0 and energy_increment > 0.0) { @@ -216,7 +244,8 @@ void PenaltyMPI::updatePenalty(const std::vector& coordinate) { * 3. Master broadcasts average to all nodes * 4. When done, all penalty functions shall be identical */ -void PenaltyMPI::averagePenaltyFunctions() { +void PenaltyMPI::averagePenaltyFunctions() +{ penalty_function_exchange_counter += 1; const auto layout = mpl::contiguous_layout(penalty_energy.size()); buffer.resize(penalty_energy.size() * mpi.world.size()); @@ -225,10 +254,11 @@ void PenaltyMPI::averagePenaltyFunctions() { penalty_energy.setZero(); for (int i = 0; i < mpi.world.size(); i++) { const auto offset = i * penalty_energy.size(); - penalty_energy += - Eigen::Map(buffer.data() + offset, penalty_energy.rows(), penalty_energy.cols()); + penalty_energy += Eigen::Map( + buffer.data() + offset, penalty_energy.rows(), penalty_energy.cols()); } - penalty_energy = (penalty_energy.array() - penalty_energy.minCoeff()) / static_cast(mpi.world.size()); + penalty_energy = (penalty_energy.array() - penalty_energy.minCoeff()) / + static_cast(mpi.world.size()); } mpi.world.bcast(mpi.master_rank, penalty_energy.data(), layout); } diff --git a/src/penalty.h b/src/penalty.h index 7365298ff..9c6f65baa 100644 --- a/src/penalty.h +++ b/src/penalty.h @@ -14,7 +14,8 @@ namespace Faunus::Energy { * same in both the trial and old state energies it will not affect * MC move acceptance. */ -class Penalty : public EnergyTerm { +class Penalty : public EnergyTerm +{ private: const Space& spc; std::string penalty_function_filename; @@ -28,18 +29,19 @@ class Penalty : public EnergyTerm { protected: typedef typename std::shared_ptr Tcoord; - bool verbose = false; //!< kΓ¦ft op? - bool avoid_energy_drift = true; //!< avoid energy drift when upgrading penalty function + bool verbose = false; //!< kΓ¦ft op? + bool avoid_energy_drift = true; //!< avoid energy drift when upgrading penalty function size_t number_of_reaction_coordinates = 0; //!< number of reaction coordinate size_t update_counter = 0; //!< number of calls to `sync()` size_t number_of_steps_between_updates; //!< update frequency [steps] size_t penalty_function_exchange_counter = 0; size_t samplings; - double sum_of_energy_increments = 0; //!< total energy change of updating penalty function + double sum_of_energy_increments = 0; //!< total energy change of updating penalty function double energy_increment_scaling_factor = 1.0; //!< scaling factor for f0 double energy_increment = 0.0; //!< penalty increment - std::vector reaction_coordinates_functions; //!< vector of reaction coordinate functions (length = 1 or 2) - std::vector latest_coordinate; //!< latest reaction coordinate (length = 1 or 2) + std::vector reaction_coordinates_functions; //!< vector of reaction coordinate functions + //!< (length = 1 or 2) + std::vector latest_coordinate; //!< latest reaction coordinate (length = 1 or 2) Table histogram; //!< count how often a reaction coordinate is visited Table penalty_energy; //!< penalty energy as a function of coordinates @@ -50,21 +52,23 @@ class Penalty : public EnergyTerm { ~Penalty() override; //!< destruct and save to disk (!) double energy(const Change& change) override; void sync(EnergyTerm* other, const Change& change) override; - void streamPenaltyFunction(std::ostream &stream) const; - void streamHistogram(std::ostream &stream) const; + void streamPenaltyFunction(std::ostream& stream) const; + void streamHistogram(std::ostream& stream) const; }; #ifdef ENABLE_MPI /** * @brief Penalty function with MPI exchange */ -class PenaltyMPI : public Penalty { +class PenaltyMPI : public Penalty +{ private: const MPI::Controller& mpi; - Eigen::VectorXi weights; //!< array w. minimum histogram counts - Eigen::VectorXd buffer; //!< receive buffer for penalty functions - void updatePenalty(const std::vector& coordinate) override; //!< Average penalty function across all nodes - void averagePenaltyFunctions(); //!< Average penalty functions over all MPI nodes + Eigen::VectorXi weights; //!< array w. minimum histogram counts + Eigen::VectorXd buffer; //!< receive buffer for penalty functions + void updatePenalty(const std::vector& coordinate) + override; //!< Average penalty function across all nodes + void averagePenaltyFunctions(); //!< Average penalty functions over all MPI nodes public: PenaltyMPI(const json& j, Space& spc, const MPI::Controller& mpi); }; diff --git a/src/potentials.cpp b/src/potentials.cpp index e9bc1ff7c..fa68ffd69 100644 --- a/src/potentials.cpp +++ b/src/potentials.cpp @@ -13,7 +13,9 @@ namespace Faunus::pairpotential { // =============== PairMixer =============== -TCombinatorFunc PairMixer::getCombinator(CombinationRuleType combination_rule, CoefficientType coefficient) { +TCombinatorFunc PairMixer::getCombinator(CombinationRuleType combination_rule, + CoefficientType coefficient) +{ TCombinatorFunc combinator; switch (combination_rule) { case CombinationRuleType::UNDEFINED: @@ -43,71 +45,94 @@ TCombinatorFunc PairMixer::getCombinator(CombinationRuleType combination_rule, C return combinator; } -TPairMatrixPtr PairMixer::createPairMatrix(const std::vector& atoms) { +TPairMatrixPtr PairMixer::createPairMatrix(const std::vector& atoms) +{ size_t n = atoms.size(); // number of atom types TPairMatrixPtr matrix = std::make_shared(n, n); for (const auto& i : atoms) { for (const auto& j : atoms) { if (i.implicit || j.implicit) { - // implicit atoms are ignored as the missing properties, e.g., sigma and epsilon, might raise errors + // implicit atoms are ignored as the missing properties, e.g., sigma and epsilon, + // might raise errors (*matrix)(i.id(), j.id()) = combUndefined(); - } else if (i.id() == j.id()) { - // if the combinator is "undefined" the homogeneous interaction is still well-defined + } + else if (i.id() == j.id()) { + // if the combinator is "undefined" the homogeneous interaction is still + // well-defined (*matrix)(i.id(), j.id()) = modifier(extractor(i.interaction)); - } else { - (*matrix)(i.id(), j.id()) = modifier(combinator(extractor(i.interaction), extractor(j.interaction))); + } + else { + (*matrix)(i.id(), j.id()) = + modifier(combinator(extractor(i.interaction), extractor(j.interaction))); } } } return matrix; } -TPairMatrixPtr PairMixer::createPairMatrix(const std::vector &atoms, - const std::vector &interactions) { +TPairMatrixPtr PairMixer::createPairMatrix(const std::vector& atoms, + const std::vector& interactions) +{ TPairMatrixPtr matrix = PairMixer::createPairMatrix(atoms); auto dimension = std::min(matrix->rows(), matrix->cols()); - for (const auto &i : interactions) { + for (const auto& i : interactions) { if (i.atom_id[0] < dimension && i.atom_id[1] < dimension) { // interaction is always symmetric (*matrix)(i.atom_id[0], i.atom_id[1]) = (*matrix)(i.atom_id[1], i.atom_id[0]) = modifier(extractor(i.interaction)); - } else { + } + else { throw std::range_error("atomtype index out of range"); } } return matrix; } + PairMixer::PairMixer(TExtractorFunc extractor, TCombinatorFunc combinator, TModifierFunc modifier) - : extractor(std::move(extractor)), combinator(std::move(combinator)), modifier(std::move(modifier)){} + : extractor(std::move(extractor)) + , combinator(std::move(combinator)) + , modifier(std::move(modifier)) +{ +} -TEST_CASE("[Faunus] PairMixer") { +TEST_CASE("[Faunus] PairMixer") +{ using namespace std::string_literals; using doctest::Approx; - SUBCASE("Enumerated potential") { + SUBCASE("Enumerated potential") + { REQUIRE((PairMixer::combArithmetic(2.0, 8.0) == Approx(5.0))); REQUIRE((PairMixer::combGeometric(2.0, 8.0) == Approx(4.0))); - CHECK_EQ(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT, PairMixer::CoefficientType::SIGMA)( - 2.0, 8.0), PairMixer::combArithmetic(2.0, 8.0)); - CHECK_EQ(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT, PairMixer::CoefficientType::EPSILON)( - 2.0, 8.0), PairMixer::combGeometric(2.0, 8.0)); - CHECK_THROWS_AS(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT), std::logic_error); - - SUBCASE("") { - atoms = - R"([{"A": {"sigma":2.0}}, {"B": {"sigma":8.0}}, {"C": {"sigma":18.0}}])"_json.get(); + CHECK_EQ(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT, + PairMixer::CoefficientType::SIGMA)(2.0, 8.0), + PairMixer::combArithmetic(2.0, 8.0)); + CHECK_EQ(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT, + PairMixer::CoefficientType::EPSILON)(2.0, 8.0), + PairMixer::combGeometric(2.0, 8.0)); + CHECK_THROWS_AS(PairMixer::getCombinator(CombinationRuleType::LORENTZ_BERTHELOT), + std::logic_error); + + SUBCASE("") + { + atoms = R"([{"A": {"sigma":2.0}}, {"B": {"sigma":8.0}}, {"C": {"sigma":18.0}}])"_json + .get(); REQUIRE((atoms.front().interaction.at("sigma") == Approx(2.0))); - std::vector pairs = R"([{"A C": {"sigma": 9.5}}, {"C B": {"sigma": 12.5}}])"_json; + std::vector pairs = + R"([{"A C": {"sigma": 9.5}}, {"C B": {"sigma": 12.5}}])"_json; TExtractorFunc sigma = [](InteractionData a) -> double { return a.at("sigma"); }; - SUBCASE("") { + SUBCASE("") + { PairMixer mixer(sigma, &PairMixer::combArithmetic); - SUBCASE("Atom pairs") { + SUBCASE("Atom pairs") + { auto matrix = mixer.createPairMatrix(atoms); CHECK(matrix->isApprox(matrix->transpose())); // symmetric CHECK_EQ((*matrix)(0, 0), Approx(2.0)); CHECK_EQ((*matrix)(0, 1), Approx(5.0)); } - SUBCASE("Custom pairs") { + SUBCASE("Custom pairs") + { auto matrix = mixer.createPairMatrix(atoms, pairs); CHECK(matrix->isApprox(matrix->transpose())); // symmetric CHECK_EQ((*matrix)(0, 0), Approx(2.0)); @@ -116,7 +141,8 @@ TEST_CASE("[Faunus] PairMixer") { CHECK_EQ((*matrix)(2, 1), Approx(12.5)); } } - SUBCASE("Modifier") { + SUBCASE("Modifier") + { PairMixer mixer(sigma, &PairMixer::combArithmetic, [](double x) { return 10 * x; }); auto matrix = mixer.createPairMatrix(atoms, pairs); CHECK(matrix->isApprox(matrix->transpose())); // symmetric @@ -125,8 +151,10 @@ TEST_CASE("[Faunus] PairMixer") { CHECK_EQ((*matrix)(2, 0), Approx(95.0)); CHECK_EQ((*matrix)(2, 1), Approx(125.0)); } - SUBCASE("Alternative JSON") { - CHECK_NOTHROW(R"({"A C": {"sigma": 9.5}})"_json.get>()); + SUBCASE("Alternative JSON") + { + CHECK_NOTHROW( + R"({"A C": {"sigma": 9.5}})"_json.get>()); std::vector alt_pairs = R"({"A C": {"sigma": 9.5}, "C B": {"sigma": 12.5}})"_json; CHECK_EQ(alt_pairs.size(), pairs.size()); @@ -137,7 +165,8 @@ TEST_CASE("[Faunus] PairMixer") { // =============== CustomInteractionData =============== -void from_json(const json &j, CustomInteractionData &c) { +void from_json(const json& j, CustomInteractionData& c) +{ if (!j.is_object() || j.size() != 1) { throw ConfigurationError("invalid JSON for custom interaction parameters"); } @@ -146,27 +175,32 @@ void from_json(const json &j, CustomInteractionData &c) { const auto atom_names = splitConvert(j_item.key()); const auto atom_ids = names2ids(atoms, atom_names); if (atom_ids.size() != c.atom_id.size()) { - faunus_logger->error("Custom interaction parameters require exactly {} space-separated atoms: {}.", - c.atom_id.size(), joinToString(atom_names)); + faunus_logger->error( + "Custom interaction parameters require exactly {} space-separated atoms: {}.", + c.atom_id.size(), joinToString(atom_names)); throw ConfigurationError("wrong number of atoms in custom interaction parameters"); } std::copy(atom_ids.begin(), atom_ids.end(), c.atom_id.begin()); - } catch (std::out_of_range &e) { - faunus_logger->error("Unknown atom pair [{}] in custom interaction parameters.", j_item.key()); + } + catch (std::out_of_range& e) { + faunus_logger->error("Unknown atom pair [{}] in custom interaction parameters.", + j_item.key()); throw ConfigurationError("unknown atom pair in custom interaction parameters"); } c.interaction = j_item.value(); } -void to_json(json &j, const CustomInteractionData &interaction) { +void to_json(json& j, const CustomInteractionData& interaction) +{ std::vector atom_names; - for(auto atom_id : interaction.atom_id) { + for (auto atom_id : interaction.atom_id) { atom_names.push_back(atoms[atom_id].name); } j = {{joinToString(atom_names), interaction.interaction}}; } -void from_json(const json& j, std::vector& interactions) { +void from_json(const json& j, std::vector& interactions) +{ auto append = [&](const auto& key, const auto& value) { interactions.push_back(json{{key, value}}); faunus_logger->debug("Custom interaction for particle pair {}: {}", key, value.dump(-1)); @@ -179,11 +213,13 @@ void from_json(const json& j, std::vector& interactions) const auto& [key, value] = j_pair.items().begin(); append(key, value); } - } else if (j.is_object()) { + } + else if (j.is_object()) { for (const auto& [key, value] : j.items()) { append(key, value); } - } else { + } + else { throw ConfigurationError("invalid JSON for custom interaction parameters"); } } @@ -193,7 +229,9 @@ void from_json(const json& j, std::vector& interactions) PairPotential::PairPotential(std::string name, std::string cite, bool isotropic) : name(std::move(name)) , cite(std::move(cite)) - , isotropic(isotropic) {} + , isotropic(isotropic) +{ +} /** * @brief Calculates force on particle a due to another particle, b @@ -204,13 +242,19 @@ PairPotential::PairPotential(std::string name, std::string cite, bool isotropic) * @return Force on particle a due to particle b */ Point PairPotential::force([[maybe_unused]] const Particle& a, [[maybe_unused]] const Particle& b, - [[maybe_unused]] double squared_distance, [[maybe_unused]] const Point& b_towards_a) const { + [[maybe_unused]] double squared_distance, + [[maybe_unused]] const Point& b_towards_a) const +{ throw(std::logic_error("Force computation not implemented for this setup!")); } -void to_json(json& j, const PairPotential& base) { base.name.empty() ? base.to_json(j) : base.to_json(j[base.name]); } +void to_json(json& j, const PairPotential& base) +{ + base.name.empty() ? base.to_json(j) : base.to_json(j[base.name]); +} -void from_json(const json& j, PairPotential& base) { +void from_json(const json& j, PairPotential& base) +{ try { if (not base.name.empty()) { if (j.count(base.name) == 1) { @@ -219,7 +263,8 @@ void from_json(const json& j, PairPotential& base) { } } base.from_json(j); - } catch (std::exception &e) { + } + catch (std::exception& e) { usageTip.pick(base.name); throw ConfigurationError(e); } @@ -227,28 +272,34 @@ void from_json(const json& j, PairPotential& base) { // =============== MixerPairPotentialBase =============== -void MixerPairPotentialBase::init() { +void MixerPairPotentialBase::init() +{ json j_combination_rule = combination_rule; - faunus_logger->debug("Combination rule {} in effect for the {} potential.", j_combination_rule.get(), - name); + faunus_logger->debug("Combination rule {} in effect for the {} potential.", + j_combination_rule.get(), name); initPairMatrices(); } -void MixerPairPotentialBase::from_json(const json &j) { +void MixerPairPotentialBase::from_json(const json& j) +{ try { if (j.contains("mixing")) { const json& mixing = j.at("mixing"); combination_rule = mixing.get(); if (combination_rule == CombinationRuleType::UNDEFINED && mixing != "undefined") { - // an ugly hack because the first pair in the json ↔ enum mapping is silently selected by default - throw PairPotentialException("unknown combination rule " + mixing.get()); + // an ugly hack because the first pair in the json ↔ enum mapping is silently + // selected by default + throw PairPotentialException("unknown combination rule " + + mixing.get()); } } if (j.contains("custom")) { - // *custom_pairs = j["custom"]; // does not work, perhaps as from_json is also a method (a namespace conflict) + // *custom_pairs = j["custom"]; // does not work, perhaps as from_json is also a method + // (a namespace conflict) pairpotential::from_json(j["custom"], *custom_pairs); } - } catch (const ConfigurationError &e) { + } + catch (const ConfigurationError& e) { faunus_logger->error(std::string(e.what()) + " in potential " + name); throw std::runtime_error("error reading potential " + name + " from json"); } @@ -256,36 +307,47 @@ void MixerPairPotentialBase::from_json(const json &j) { init(); } -void MixerPairPotentialBase::to_json(json &j) const { +void MixerPairPotentialBase::to_json(json& j) const +{ j["mixing"] = combination_rule; j.update(json_extra_params); if (!custom_pairs->empty()) { j["custom"] = *custom_pairs; } } + void MixerPairPotentialBase::extractorsFromJson(const json&) {} + MixerPairPotentialBase::MixerPairPotentialBase(const std::string& name, const std::string& cite, CombinationRuleType combination_rule, bool isotropic) : PairPotential(name, cite, isotropic) - , combination_rule(combination_rule) {} + , combination_rule(combination_rule) +{ +} // =============== RepulsionR3 =============== -void RepulsionR3::from_json(const json &j) { +void RepulsionR3::from_json(const json& j) +{ f = j.value("prefactor", 1.0); e = j.value("lj-prefactor", 1.0); s = j.value("sigma", 1.0); } -void RepulsionR3::to_json(json &j) const { j = {{"prefactor", f}, {"lj-prefactor", e}, {"sigma", s}}; } +void RepulsionR3::to_json(json& j) const +{ + j = {{"prefactor", f}, {"lj-prefactor", e}, {"sigma", s}}; +} // =============== CosAttract =============== -void CosAttract::to_json(json &j) const { +void CosAttract::to_json(json& j) const +{ j = {{"eps", eps / 1.0_kJmol}, {"rc", rc / 1.0_angstrom}, {"wc", wc / 1.0_angstrom}}; } -void CosAttract::from_json(const json &j) { +void CosAttract::from_json(const json& j) +{ eps = j.at("eps").get() * 1.0_kJmol; rc = j.at("rc").get() * 1.0_angstrom; wc = j.at("wc").get() * 1.0_angstrom; @@ -293,13 +355,19 @@ void CosAttract::from_json(const json &j) { c = pc::pi / 2 / wc; rcwc2 = pow((rc + wc), 2); } -double CosAttract::cutOffSquared() const { + +double CosAttract::cutOffSquared() const +{ return rcwc2; } + CosAttract::CosAttract(const std::string& name) - : PairPotential(name) {} + : PairPotential(name) +{ +} -TEST_CASE("[Faunus] CosAttract") { +TEST_CASE("[Faunus] CosAttract") +{ Particle a, b; Point r1 = {0.5 - 0.001, 0.0, 0.0}; // r < r_c (-epsilon) Point r2 = {2.1 + 0.5 + 0.001, 0.0, 0.0}; // r > r_c + w_c (zero) @@ -307,7 +375,8 @@ TEST_CASE("[Faunus] CosAttract") { a.id = 0; b.id = 1; - SUBCASE("basic") { + SUBCASE("basic") + { auto j = R"({ "atomlist" : [ { "A": { "eps": 1.0, "rc": 0.5, "wc": 2.1 } }, { "B": { "eps": 1.0, "rc": 0.5, "wc": 2.1 } }]})"_json; @@ -330,7 +399,8 @@ TEST_CASE("[Faunus] CosAttract") { CHECK_EQ(pairpot.force(a, b, r3.squaredNorm(), r3).z(), doctest::Approx(0)); } - SUBCASE("mixed - symmetric") { + SUBCASE("mixed - symmetric") + { auto j = R"({ "atomlist" : [ { "A": { "eps": 1.0, "rc": 0.5, "wc": 2.1 } }, { "B": { "eps": 1.0, "rc": 0.5, "wc": 2.1 } }]})"_json; @@ -353,7 +423,8 @@ TEST_CASE("[Faunus] CosAttract") { CHECK_EQ(pairpot.force(a, b, r3.squaredNorm(), r3).z(), doctest::Approx(0)); } - SUBCASE("mixed - asymmetric") { + SUBCASE("mixed - asymmetric") + { auto j = R"({ "atomlist" : [ { "A": { "eps": 1.0, "rc": 0.5, "wc": 2.1 } }, { "B": { "eps": 0.5, "rc": 0.6, "wc": 1.9 } }]})"_json; @@ -366,49 +437,67 @@ TEST_CASE("[Faunus] CosAttract") { CHECK_EQ(pairpot(a, b, std::pow(0.55 + 2.0 + 0.001, 2), Point::Zero()), doctest::Approx(0)); CHECK_EQ(pairpot.cutOffSquared(a.id, b.id), doctest::Approx(6.5025)); } - } // =============== Coulomb (old) =============== -void Coulomb::to_json(json &j) const { +void Coulomb::to_json(json& j) const +{ j["epsr"] = pc::relativeDielectricFromBjerrumLength(bjerrum_length); j["lB"] = bjerrum_length; } -void Coulomb::from_json(const json &j) { +void Coulomb::from_json(const json& j) +{ if (j.size() == 1 && j.is_object()) { bjerrum_length = pc::bjerrumLength(j.at("epsr").get()); - } else { + } + else { throw ConfigurationError("Plain Coulomb potential expects 'epsr' key (only)"); } } + Coulomb::Coulomb(const std::string& name) - : PairPotential(name) {} + : PairPotential(name) +{ +} // =============== DipoleDipole (old) =============== -void DipoleDipole::to_json(json &j) const { +void DipoleDipole::to_json(json& j) const +{ j["epsr"] = pc::relativeDielectricFromBjerrumLength(bjerrum_length); j["lB"] = bjerrum_length; } -void DipoleDipole::from_json(const json& j) { bjerrum_length = pc::bjerrumLength(j.at("epsr")); } +void DipoleDipole::from_json(const json& j) +{ + bjerrum_length = pc::bjerrumLength(j.at("epsr")); +} DipoleDipole::DipoleDipole(const std::string& name, const std::string& cite) - : PairPotential(name, cite, false) {} + : PairPotential(name, cite, false) +{ +} // =============== FENE =============== -void FENE::from_json(const json &j) { +void FENE::from_json(const json& j) +{ k = j.at("stiffness"); r02 = std::pow(double(j.at("maxsep")), 2); r02inv = 1 / r02; } -void FENE::to_json(json &j) const { j = {{"stiffness", k}, {"maxsep", std::sqrt(r02)}}; } +void FENE::to_json(json& j) const +{ + j = {{"stiffness", k}, {"maxsep", std::sqrt(r02)}}; +} + FENE::FENE(const std::string& name) - : PairPotential(name) {} + : PairPotential(name) +{ +} // =============== SASApotential =============== @@ -426,7 +515,8 @@ FENE::FENE(const std::string& name) * - https://mathworld.wolfram.com/Sphere-SphereIntersection.html * - https://mathworld.wolfram.com/SphericalCap.html */ -double SASApotential::area(double R, double r, double center_center_distance_squared) const { +double SASApotential::area(double R, double r, double center_center_distance_squared) const +{ R += proberadius; r += proberadius; const auto spheres_area = 4.0 * pc::pi * (R * R + r * r); // full surface area of both spheres @@ -447,21 +537,27 @@ double SASApotential::area(double R, double r, double center_center_distance_squ return spheres_area - lens_area - offset; } -void SASApotential::from_json(const json &j) { +void SASApotential::from_json(const json& j) +{ shift = j.value("shift", true); conc = j.at("molarity").get() * 1.0_molar; proberadius = j.value("radius", 1.4) * 1.0_angstrom; } -void SASApotential::to_json(json &j) const { +void SASApotential::to_json(json& j) const +{ j["molarity"] = conc / 1.0_molar; j["radius"] = proberadius / 1.0_angstrom; j["shift"] = shift; } + SASApotential::SASApotential(const std::string& name, const std::string& cite) - : PairPotential(name, cite) {} + : PairPotential(name, cite) +{ +} -TEST_CASE("[Faunus] SASApotential") { +TEST_CASE("[Faunus] SASApotential") +{ using doctest::Approx; json j = R"({ "atomlist" : [ { "A": { "r": 1.5, "tension": 0.023} }, @@ -483,14 +579,16 @@ TEST_CASE("[Faunus] SASApotential") { CHECK((tfe > 0.0)); CHECK((f > 0.0)); CHECK_EQ(in, json(pot)); - CHECK_EQ(pot(a, b, 0, {0, 0, 0}), Approx(f * 4 * pc::pi * 2.1 * 2.1)); // complete overlap - CHECK_EQ(pot(a, b, 10 * 10, {10, 0, 0}), Approx(f * 4 * pc::pi * (2.1 * 2.1 + 1.5 * 1.5))); // far apart - CHECK_EQ(pot(a, b, 2.5 * 2.5, {2.5, 0, 0}), Approx(f * 71.74894965974514)); // partial overlap + CHECK_EQ(pot(a, b, 0, {0, 0, 0}), Approx(f * 4 * pc::pi * 2.1 * 2.1)); // complete overlap + CHECK_EQ(pot(a, b, 10 * 10, {10, 0, 0}), + Approx(f * 4 * pc::pi * (2.1 * 2.1 + 1.5 * 1.5))); // far apart + CHECK_EQ(pot(a, b, 2.5 * 2.5, {2.5, 0, 0}), Approx(f * 71.74894965974514)); // partial overlap } // =============== CustomPairPotential =============== -void CustomPairPotential::from_json(const json& j) { +void CustomPairPotential::from_json(const json& j) +{ squared_cutoff_distance = std::pow(j.value("cutoff", pc::infty), 2); original_input = j; auto& constants = original_input["constants"]; @@ -510,17 +608,22 @@ void CustomPairPotential::from_json(const json& j) { {"s2", &symbols->sigma2}}); } -void CustomPairPotential::to_json(json& j) const { +void CustomPairPotential::to_json(json& j) const +{ j = original_input; if (std::isfinite(squared_cutoff_distance)) { j["cutoff"] = std::sqrt(squared_cutoff_distance); } } + CustomPairPotential::CustomPairPotential(const std::string& name) : PairPotential(name) - , symbols(std::make_shared()) {} + , symbols(std::make_shared()) +{ +} -TEST_CASE("[Faunus] CustomPairPotential") { +TEST_CASE("[Faunus] CustomPairPotential") +{ using doctest::Approx; json j = R"({ "atomlist" : [ {"A": { "q":1.0, "r":3, "eps":0.1 }}, @@ -530,19 +633,24 @@ TEST_CASE("[Faunus] CustomPairPotential") { a = Faunus::atoms[0]; b = Faunus::atoms[1]; - SUBCASE("energy") { + SUBCASE("energy") + { CustomPairPotential pot; pairpotential::from_json(R"({"constants": { "kappa": 30, "lB": 7}, "function": "lB * charge1 * charge2 / (s1+s2) * exp(-kappa/r) * kT + pi"})"_json, pot); - CHECK_EQ(pot(a, b, 2 * 2, {0, 0, 2}), Approx(-7.0 / (3.0 + 4.0) * std::exp(-30.0 / 2.0) * pc::kT() + pc::pi)); + CHECK_EQ(pot(a, b, 2 * 2, {0, 0, 2}), + Approx(-7.0 / (3.0 + 4.0) * std::exp(-30.0 / 2.0) * pc::kT() + pc::pi)); } - SUBCASE("force") { + SUBCASE("force") + { CustomPairPotential pot; pairpotential::from_json( - R"({"constants": { "lB": 7.0056973292 }, "function": "lB * charge1 * charge2 / r"})"_json, pot); + R"({"constants": { "lB": 7.0056973292 }, "function": "lB * charge1 * charge2 / r"})"_json, + pot); NewCoulombGalore coulomb; - pairpotential::from_json(R"({ "coulomb": {"epsr": 80.0, "type": "plain"} } )"_json, coulomb); + pairpotential::from_json(R"({ "coulomb": {"epsr": 80.0, "type": "plain"} } )"_json, + coulomb); Point r = {coulomb.bjerrum_length, 0.2, -0.1}; auto r2 = r.squaredNorm(); auto force_ref = coulomb.force(a, b, r2, r); @@ -559,33 +667,47 @@ TEST_CASE("[Faunus] CustomPairPotential") { // =============== LennardJones =============== -void LennardJones::initPairMatrices() { - const TCombinatorFunc comb_sigma = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); +void LennardJones::initPairMatrices() +{ + const TCombinatorFunc comb_sigma = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); const TCombinatorFunc comb_epsilon = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); - sigma_squared = PairMixer(extract_sigma, comb_sigma, &PairMixer::modSquared).createPairMatrix(atoms, *custom_pairs); + sigma_squared = PairMixer(extract_sigma, comb_sigma, &PairMixer::modSquared) + .createPairMatrix(atoms, *custom_pairs); epsilon_quadruple = PairMixer(extract_epsilon, comb_epsilon, [](double x) -> double { return 4 * x; }).createPairMatrix(atoms, *custom_pairs); - faunus_logger->trace("Pair matrices for {} sigma ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", name, - sigma_squared->rows(), sigma_squared->cols(), epsilon_quadruple->rows(), - epsilon_quadruple->cols(), custom_pairs->size()); + faunus_logger->trace( + "Pair matrices for {} sigma ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", + name, sigma_squared->rows(), sigma_squared->cols(), epsilon_quadruple->rows(), + epsilon_quadruple->cols(), custom_pairs->size()); } -void LennardJones::extractorsFromJson(const json &j) { +void LennardJones::extractorsFromJson(const json& j) +{ auto sigma_name = j.value("sigma", "sigma"); json_extra_params["sigma"] = sigma_name; - extract_sigma = [sigma_name](const InteractionData &a) -> double { return a.at(sigma_name) * 1.0_angstrom; }; + extract_sigma = [sigma_name](const InteractionData& a) -> double { + return a.at(sigma_name) * 1.0_angstrom; + }; auto epsilon_name = j.value("eps", "eps"); json_extra_params["eps"] = epsilon_name; - extract_epsilon = [epsilon_name](const InteractionData &a) -> double { return a.at(epsilon_name) * 1.0_kJmol; }; + extract_epsilon = [epsilon_name](const InteractionData& a) -> double { + return a.at(epsilon_name) * 1.0_kJmol; + }; +} + +LennardJones::LennardJones(const std::string& name, const std::string& cite, + CombinationRuleType combination_rule) + : MixerPairPotentialBase(name, cite, combination_rule) +{ } -LennardJones::LennardJones(const std::string& name, const std::string& cite, CombinationRuleType combination_rule) - : MixerPairPotentialBase(name, cite, combination_rule) {} -TEST_CASE("[Faunus] LennardJones") { +TEST_CASE("[Faunus] LennardJones") +{ atoms = R"([{"A": {"sigma":2.0, "eps":0.9}}, {"B": {"sigma":8.0, "eps":0.1}}, {"C": {"sigma":5.0, "eps":1.1}}])"_json.get(); @@ -597,14 +719,17 @@ TEST_CASE("[Faunus] LennardJones") { return 4 * eps * (std::pow(sigma / d, 12) - std::pow(sigma / d, 6)); }; - SUBCASE("JSON initilization") { - CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), std::runtime_error); + SUBCASE("JSON initilization") + { + CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), + std::runtime_error); CHECK_NOTHROW(makePairPotential(R"({})"_json)); // alternative notation for custom as an array: custom: [] - CHECK_NOTHROW( - makePairPotential(R"({"mixing": "LB", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json)); + CHECK_NOTHROW(makePairPotential( + R"({"mixing": "LB", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json)); } - SUBCASE("JSON output custom") { + SUBCASE("JSON output custom") + { json j_in = R"({"mixing": "LB", "sigma": "sigma", "eps": "eps", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}, {"A C": {"eps": 1.0, "sigma": 5}}]})"_json; auto lj = makePairPotential(j_in); @@ -616,30 +741,34 @@ TEST_CASE("[Faunus] LennardJones") { j_custom[1].merge_patch(j_in["custom"][1]); CHECK_EQ(j_out["lennardjones"]["custom"], j_custom); } - SUBCASE("Lorentz-Berthelot mixing") { + SUBCASE("Lorentz-Berthelot mixing") + { using doctest::Approx; auto lj = makePairPotential(R"({"mixing": "LB"})"_json); CHECK_EQ(lj(a, a, d * d, {0, 0, d}), Approx(lj_func(0.2_nm, 0.9_kJmol))); CHECK_EQ(lj(a, b, d * d, {0, 0, d}), Approx(lj_func(0.5_nm, 0.3_kJmol))); } - SUBCASE("Geometric mixing") { + SUBCASE("Geometric mixing") + { using doctest::Approx; auto lj = makePairPotential(R"({"mixing": "geometric"})"_json); CHECK_EQ(lj(a, a, d * d, {0, 0, d}), Approx(lj_func(0.2_nm, 0.9_kJmol))); CHECK_EQ(lj(a, b, d * d, {0, 0, d}), Approx(lj_func(0.4_nm, 0.3_kJmol))); } - SUBCASE("Custom pairs") { + SUBCASE("Custom pairs") + { using doctest::Approx; - auto lj = - makePairPotential(R"({"mixing": "LB", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json); + auto lj = makePairPotential( + R"({"mixing": "LB", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json); CHECK_EQ(lj(a, b, d * d, {0, 0, d}), Approx(lj_func(0.8_nm, 0.5_kJmol))); CHECK_EQ(lj(a, a, d * d, {0, 0, d}), Approx(lj_func(0.2_nm, 0.9_kJmol))); } - SUBCASE("Force") { + SUBCASE("Force") + { using doctest::Approx; - auto lj = - makePairPotential(R"({"mixing": "LB", "custom": [{"A B": {"eps": 2.0, "sigma": 8}}]})"_json); + auto lj = makePairPotential( + R"({"mixing": "LB", "custom": [{"A B": {"eps": 2.0, "sigma": 8}}]})"_json); a.pos = {0, 0, 0}; b.pos = {9, 0, 0}; Point b_towards_a = a.pos - b.pos; @@ -650,44 +779,58 @@ TEST_CASE("[Faunus] LennardJones") { // =============== HardSphere =============== -void HardSphere::initPairMatrices() { - sigma_squared = PairMixer(extract_sigma, PairMixer::getCombinator(combination_rule), &PairMixer::modSquared) - .createPairMatrix(atoms, *custom_pairs); - faunus_logger->trace("Pair matrix for {} sigma ({}Γ—{}) created using {} custom pairs.", name, sigma_squared->rows(), - sigma_squared->cols(), custom_pairs->size()); +void HardSphere::initPairMatrices() +{ + sigma_squared = + PairMixer(extract_sigma, PairMixer::getCombinator(combination_rule), &PairMixer::modSquared) + .createPairMatrix(atoms, *custom_pairs); + faunus_logger->trace("Pair matrix for {} sigma ({}Γ—{}) created using {} custom pairs.", name, + sigma_squared->rows(), sigma_squared->cols(), custom_pairs->size()); } -void HardSphere::extractorsFromJson(const json &j) { +void HardSphere::extractorsFromJson(const json& j) +{ auto sigma_name = j.value("sigma", "sigma"); json_extra_params["sigma"] = sigma_name; - extract_sigma = [sigma_name](const InteractionData &a) -> double { return a.at(sigma_name) * 1.0_angstrom; }; + extract_sigma = [sigma_name](const InteractionData& a) -> double { + return a.at(sigma_name) * 1.0_angstrom; + }; } + HardSphere::HardSphere(const std::string& name) - : MixerPairPotentialBase(name, std::string(), CombinationRuleType::ARITHMETIC){} + : MixerPairPotentialBase(name, std::string(), CombinationRuleType::ARITHMETIC) +{ +} -TEST_CASE("[Faunus] HardSphere") { +TEST_CASE("[Faunus] HardSphere") +{ atoms = R"([{"A": {"sigma": 2}}, {"B": {"sigma": 8}}])"_json.get(); Particle a = atoms[0], b = atoms[1]; - SUBCASE("JSON initialization") { + SUBCASE("JSON initialization") + { CHECK_NOTHROW(makePairPotential(R"({})"_json)); - CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), std::runtime_error); + CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), + std::runtime_error); } - SUBCASE("Undefined mixing") { + SUBCASE("Undefined mixing") + { auto hs = makePairPotential(R"({"mixing": "undefined"})"_json); CHECK_EQ(hs(a, a, 1.99 * 1.99, {0, 0, 1.99}), pc::infty); CHECK_EQ(hs(a, a, 2.01 * 2.01, {0, 0, 2.01}), 0.0); // CHECK(std::isnan(hs(a, b, {0, 0, 4.99}))); // fails // CHECK(std::isnan(hs(a, b, {0, 0, 5.01}))); // fails } - SUBCASE("Arithmetic mixing") { + SUBCASE("Arithmetic mixing") + { auto hs = makePairPotential(R"({"mixing": "arithmetic"})"_json); CHECK_EQ(hs(a, a, 2.01_angstrom * 2.01_angstrom, {0, 0, 2.01_angstrom}), 0); CHECK_EQ(hs(a, a, 1.99_angstrom * 1.99_angstrom, {0, 0, 1.99_angstrom}), pc::infty); CHECK_EQ(hs(a, b, 5.01_angstrom * 5.01_angstrom, {0, 0, 5.01_angstrom}), 0); CHECK_EQ(hs(a, b, 4.99_angstrom * 4.99_angstrom, {0, 0, 4.99_angstrom}), pc::infty); } - SUBCASE("Custom pairs with implicit mixing") { + SUBCASE("Custom pairs with implicit mixing") + { auto hs = makePairPotential(R"({"custom": [{"A B": {"sigma": 6}}]})"_json); CHECK_EQ(hs(a, a, 2.01_angstrom * 2.01_angstrom, {0, 0, 2.01_angstrom}), 0); CHECK_EQ(hs(a, a, 1.99_angstrom * 1.99_angstrom, {0, 0, 1.99_angstrom}), pc::infty); @@ -698,32 +841,44 @@ TEST_CASE("[Faunus] HardSphere") { // =============== Hertz =============== -void Hertz::initPairMatrices() { - const TCombinatorFunc comb_diameter = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); +void Hertz::initPairMatrices() +{ + const TCombinatorFunc comb_diameter = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); const TCombinatorFunc comb_epsilon = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); - sigma_squared = - PairMixer(extract_sigma, comb_diameter, &PairMixer::modSquared).createPairMatrix(atoms, *custom_pairs); + sigma_squared = PairMixer(extract_sigma, comb_diameter, &PairMixer::modSquared) + .createPairMatrix(atoms, *custom_pairs); epsilon = PairMixer(extract_epsilon, comb_epsilon).createPairMatrix(atoms, *custom_pairs); - faunus_logger->trace("Pair matrix for {} radius ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", name, - sigma_squared->rows(), sigma_squared->cols(), epsilon->rows(), epsilon->cols(), - custom_pairs->size()); + faunus_logger->trace( + "Pair matrix for {} radius ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", + name, sigma_squared->rows(), sigma_squared->cols(), epsilon->rows(), epsilon->cols(), + custom_pairs->size()); } -void Hertz::extractorsFromJson(const json &j) { +void Hertz::extractorsFromJson(const json& j) +{ auto sigma_name = j.value("sigma", "sigma"); json_extra_params["sigma"] = sigma_name; - extract_sigma = [sigma_name](const InteractionData &a) -> double { return a.at(sigma_name) * 1.0_angstrom; }; + extract_sigma = [sigma_name](const InteractionData& a) -> double { + return a.at(sigma_name) * 1.0_angstrom; + }; auto epsilon_name = j.value("eps", "eps"); json_extra_params["eps"] = epsilon_name; - extract_epsilon = [epsilon_name](const InteractionData &a) -> double { return a.at(epsilon_name) * 1.0_kJmol; }; + extract_epsilon = [epsilon_name](const InteractionData& a) -> double { + return a.at(epsilon_name) * 1.0_kJmol; + }; } + Hertz::Hertz(const std::string& name) - : MixerPairPotentialBase(name) {} + : MixerPairPotentialBase(name) +{ +} -TEST_CASE("[Faunus] Hertz") { +TEST_CASE("[Faunus] Hertz") +{ json j = R"({ "atomlist" : [ { "A": { "eps": 1.0, "sigma": 1.3} }, { "B": { "eps": 2.0, "sigma": 1.0 } }]})"_json; @@ -731,12 +886,15 @@ TEST_CASE("[Faunus] Hertz") { atoms = j["atomlist"].get(); Particle a = atoms[0], b = atoms[1]; - SUBCASE("JSON serialization") { - json hertz_json = R"({ "hertz": {"mixing": "lorentz_berthelot", "eps": "eps", "sigma": "sigma"}})"_json; + SUBCASE("JSON serialization") + { + json hertz_json = + R"({ "hertz": {"mixing": "lorentz_berthelot", "eps": "eps", "sigma": "sigma"}})"_json; auto hertz = makePairPotential(hertz_json); CHECK_EQ(hertz_json, json(hertz)); } - SUBCASE("Lorentz-Berthelot mixing") { + SUBCASE("Lorentz-Berthelot mixing") + { using doctest::Approx; pc::temperature = 298.15_K; auto hertz = makePairPotential(R"({"mixing": "lorentz_berthelot"})"_json); @@ -748,41 +906,57 @@ TEST_CASE("[Faunus] Hertz") { // =============== SquareWell =============== -void SquareWell::initPairMatrices() { - const TCombinatorFunc comb_diameter = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); - const TCombinatorFunc comb_depth = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); +void SquareWell::initPairMatrices() +{ + const TCombinatorFunc comb_diameter = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); + const TCombinatorFunc comb_depth = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); - sigma_squared = - PairMixer(extract_sigma, comb_diameter, &PairMixer::modSquared).createPairMatrix(atoms, *custom_pairs); + sigma_squared = PairMixer(extract_sigma, comb_diameter, &PairMixer::modSquared) + .createPairMatrix(atoms, *custom_pairs); epsilon = PairMixer(extract_epsilon, comb_depth).createPairMatrix(atoms, *custom_pairs); - faunus_logger->trace("Pair matrix for {} diameter ({}Γ—{}) and depth ({}Γ—{}) created using {} custom pairs.", name, - sigma_squared->rows(), sigma_squared->cols(), epsilon->rows(), epsilon->cols(), - custom_pairs->size()); + faunus_logger->trace( + "Pair matrix for {} diameter ({}Γ—{}) and depth ({}Γ—{}) created using {} custom pairs.", + name, sigma_squared->rows(), sigma_squared->cols(), epsilon->rows(), epsilon->cols(), + custom_pairs->size()); } -void SquareWell::extractorsFromJson(const json &j) { +void SquareWell::extractorsFromJson(const json& j) +{ auto sigma_name = j.value("sigma", "sigma"); json_extra_params["sigma"] = sigma_name; - extract_sigma = [sigma_name](const InteractionData &a) -> double { return a.at(sigma_name) * 1.0_angstrom; }; + extract_sigma = [sigma_name](const InteractionData& a) -> double { + return a.at(sigma_name) * 1.0_angstrom; + }; auto epsilon_name = j.value("eps", "eps"); json_extra_params["eps"] = epsilon_name; - extract_epsilon = [epsilon_name](const InteractionData &a) -> double { return a.at(epsilon_name) * 1.0_kJmol; }; + extract_epsilon = [epsilon_name](const InteractionData& a) -> double { + return a.at(epsilon_name) * 1.0_kJmol; + }; } + SquareWell::SquareWell(const std::string& name) - : MixerPairPotentialBase(name) {} + : MixerPairPotentialBase(name) +{ +} -TEST_CASE("[Faunus] SquareWell") { +TEST_CASE("[Faunus] SquareWell") +{ atoms = R"([{"A": { "r":5, "sigma":4, "eps":0.2 }}, {"B": { "r":10, "sigma":2, "eps":0.1 }} ])"_json.get(); Particle a = atoms[0]; Particle b = atoms[1]; - SUBCASE("JSON initilization") { - CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), std::runtime_error); + SUBCASE("JSON initilization") + { + CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), + std::runtime_error); CHECK_NOTHROW(makePairPotential(R"({})"_json)); } - SUBCASE("Undefined mixing") { + SUBCASE("Undefined mixing") + { using doctest::Approx; auto sw = makePairPotential(R"({"mixing": "undefined"})"_json); CHECK_EQ(sw(a, a, 3.99 * 3.99, {0, 0, 0}), Approx(-0.2_kJmol)); @@ -790,7 +964,8 @@ TEST_CASE("[Faunus] SquareWell") { // CHECK(std::isnan(sw(a, b, {0, 0, 5.99}))); // fails // CHECK(std::isnan(sw(a, b, {0, 0, 6.01}))); // fails } - SUBCASE("Lorentz-Berthelot mixing") { + SUBCASE("Lorentz-Berthelot mixing") + { using doctest::Approx; auto sw = makePairPotential(R"({"mixing": "LB"})"_json); CHECK_EQ(sw(a, b, 2.99 * 2.99, {0, 0, 0}), Approx(-std::sqrt(0.2_kJmol * 0.1_kJmol))); @@ -800,12 +975,15 @@ TEST_CASE("[Faunus] SquareWell") { // =============== Polarizability =============== -void Polarizability::from_json(const json& j) { +void Polarizability::from_json(const json& j) +{ epsr = j.at("epsr").get(); const auto bjerrum_length = pc::bjerrumLength(epsr); for (const auto& i : Faunus::atoms) { for (const auto& j : Faunus::atoms) { - m_neutral->set(i.id(), j.id(), -3 * i.alphax * pow(0.5 * i.sigma, 3) * j.alphax * pow(0.5 * j.sigma, 3)); + m_neutral->set(i.id(), j.id(), + -3 * i.alphax * pow(0.5 * i.sigma, 3) * j.alphax * + pow(0.5 * j.sigma, 3)); m_charged->set(i.id(), j.id(), -bjerrum_length * 0.5 * (pow(i.charge, 2) * j.alphax * pow(0.5 * j.sigma, 3) + @@ -813,32 +991,42 @@ void Polarizability::from_json(const json& j) { } } } + Polarizability::Polarizability(const std::string& name) - : Coulomb(name) { + : Coulomb(name) +{ m_neutral = std::make_shared>(); m_charged = std::make_shared>(); } -void Polarizability::to_json(json& j) const { j = {{"epsr", epsr}}; } + +void Polarizability::to_json(json& j) const +{ + j = {{"epsr", epsr}}; +} // =============== FunctorPotential =============== -void FunctorPotential::registerSelfEnergy(PairPotential* pot) { +void FunctorPotential::registerSelfEnergy(PairPotential* pot) +{ if (pot->selfEnergy) { if (not selfEnergy) { // no self energy is defined selfEnergy = pot->selfEnergy; - } else // accumulate self energies + } + else // accumulate self energies { selfEnergy = [pot = pot, &selfEnergy = selfEnergy](const Particle& p) { return pot->selfEnergy(p) + selfEnergy(p); }; } faunus_logger->debug("Added selfEnergy function from {} to {}", pot->name, name); - } else { + } + else { faunus_logger->trace("Failed to register non-defined selfEnergy() for {}", pot->name); } } -FunctorPotential::EnergyFunctor FunctorPotential::combinePairPotentials(json& potential_array) { +FunctorPotential::EnergyFunctor FunctorPotential::combinePairPotentials(json& potential_array) +{ if (!potential_array.is_array()) { throw std::runtime_error("potential array required"); } @@ -863,33 +1051,48 @@ FunctorPotential::EnergyFunctor FunctorPotential::combinePairPotentials(json& po have_monopole_self_energy = true; } new_func = coulomb; - } else if (name == "cos2") { + } + else if (name == "cos2") { new_func = makePairPotential(single_record); - } else if (name == "polar") { + } + else if (name == "polar") { new_func = makePairPotential(single_record); - } else if (name == "hardsphere") { + } + else if (name == "hardsphere") { new_func = makePairPotential(single_record); - } else if (name == "lennardjones") { + } + else if (name == "lennardjones") { new_func = makePairPotential(single_record); - } else if (name == "repulsionr3") { + } + else if (name == "repulsionr3") { new_func = makePairPotential(single_record); - } else if (name == "sasa") { + } + else if (name == "sasa") { new_func = makePairPotential(single_record); - } else if (name == "wca") { + } + else if (name == "wca") { new_func = makePairPotential(single_record); - } else if (name == "pm") { + } + else if (name == "pm") { new_func = makePairPotential(j_config); - } else if (name == "pmwca") { + } + else if (name == "pmwca") { new_func = makePairPotential(j_config); - } else if (name == "hertz") { + } + else if (name == "hertz") { new_func = makePairPotential(single_record); - } else if (name == "squarewell") { + } + else if (name == "squarewell") { new_func = makePairPotential(single_record); - } else if (name == "dipoledipole") { + } + else if (name == "dipoledipole") { faunus_logger->error("'{}' is deprecated, use 'multipole' instead", name); - } else if (name == "stockmayer") { - faunus_logger->error("'{}' is deprecated, use 'lennardjones'+'multipole' instead", name); - } else if (name == "multipole") { + } + else if (name == "stockmayer") { + faunus_logger->error( + "'{}' is deprecated, use 'lennardjones'+'multipole' instead", name); + } + else if (name == "multipole") { auto multipole = makePairPotential(j_config); pairpotential::to_json(j_config, multipole); isotropic = false; // potential is now angular dependent @@ -898,13 +1101,16 @@ FunctorPotential::EnergyFunctor FunctorPotential::combinePairPotentials(json& po have_dipole_self_energy = true; } new_func = multipole; - } else if (name == "hs-cigar") { + } + else if (name == "hs-cigar") { new_func = makePairPotential(j_config); - } else if (name == "coswca-psc") { + } + else if (name == "coswca-psc") { new_func = makePairPotential(j_config); } // place additional potentials here... - } catch (std::exception& e) { + } + catch (std::exception& e) { usageTip.pick(name); throw ConfigurationError("{} -> {}", name, e.what()); } @@ -919,16 +1125,20 @@ FunctorPotential::EnergyFunctor FunctorPotential::combinePairPotentials(json& po return func; } -void FunctorPotential::to_json(json &j) const { +void FunctorPotential::to_json(json& j) const +{ j["functor potential"] = backed_up_json_input; - j["selfenergy"] = {{"monopole", have_monopole_self_energy}, {"dipole", have_dipole_self_energy}}; + j["selfenergy"] = {{"monopole", have_monopole_self_energy}, + {"dipole", have_dipole_self_energy}}; } -void FunctorPotential::from_json(const json &j) { +void FunctorPotential::from_json(const json& j) +{ have_monopole_self_energy = false; have_dipole_self_energy = false; backed_up_json_input = j; - umatrix = decltype(umatrix)(atoms.size(), combinePairPotentials(backed_up_json_input.at("default"))); + umatrix = + decltype(umatrix)(atoms.size(), combinePairPotentials(backed_up_json_input.at("default"))); for (const auto& [key, value] : backed_up_json_input.items()) { auto atompair = splitConvert(key); // is this for a pair of atoms? if (atompair.size() == 2) { @@ -939,9 +1149,12 @@ void FunctorPotential::from_json(const json &j) { } FunctorPotential::FunctorPotential(const std::string& name) - : PairPotential(name) {} + : PairPotential(name) +{ +} -TEST_CASE("[Faunus] FunctorPotential") { +TEST_CASE("[Faunus] FunctorPotential") +{ using doctest::Approx; json j = R"({ "atomlist" : [ @@ -959,8 +1172,10 @@ TEST_CASE("[Faunus] FunctorPotential") { ], "C C" : [ { "hardsphere" : {} } ] })"_json); - auto coulomb = pairpotential::makePairPotential(R"({ "coulomb": {"epsr": 80.0} } )"_json); - auto wca = pairpotential::makePairPotential(R"({ "wca" : {"mixing": "LB"} })"_json); + auto coulomb = + pairpotential::makePairPotential(R"({ "coulomb": {"epsr": 80.0} } )"_json); + auto wca = pairpotential::makePairPotential( + R"({ "wca" : {"mixing": "LB"} })"_json); Particle a = atoms[0]; Particle b = atoms[1]; @@ -973,7 +1188,8 @@ TEST_CASE("[Faunus] FunctorPotential") { CHECK_EQ(u(c, c, (r * 1.01).squaredNorm(), r * 1.01), 0); CHECK_EQ(u(c, c, (r * 0.99).squaredNorm(), r * 0.99), pc::infty); - SUBCASE("selfEnergy() - monopole") { + SUBCASE("selfEnergy() - monopole") + { // let's check that the self energy gets properly transferred to the functor potential const auto j = R"( {"default": [{ "coulomb" : {"epsr": 80.0, "type": "qpotential", "cutoff":20, "order":4} }]})"_json; @@ -985,7 +1201,8 @@ TEST_CASE("[Faunus] FunctorPotential") { CHECK_EQ(functor.selfEnergy(a), Approx(galore.selfEnergy(a))); } - SUBCASE("selfEnergy() - multipole") { + SUBCASE("selfEnergy() - multipole") + { // now test w. dipolar particles const auto j = R"( {"default": [{ "multipole" : {"epsr": 80.0, "type": "qpotential", "cutoff":20, "order":4} }]})"_json; @@ -1001,7 +1218,10 @@ TEST_CASE("[Faunus] FunctorPotential") { // =============== SplinedPotential =============== -SplinedPotential::KnotData::KnotData(const base &b) : base(b) {} +SplinedPotential::KnotData::KnotData(const base& b) + : base(b) +{ +} /** * @param stream output stream @@ -1010,7 +1230,8 @@ SplinedPotential::KnotData::KnotData(const base &b) : base(b) {} * * Stream splined and exact energy as a function of particle-particle separation to output stream */ -void SplinedPotential::streamPairPotential(std::ostream& stream, const size_t id1, const size_t id2) { +void SplinedPotential::streamPairPotential(std::ostream& stream, const size_t id1, const size_t id2) +{ if (!stream || atoms.at(id1).implicit || atoms.at(id2).implicit) { return; } @@ -1019,8 +1240,9 @@ void SplinedPotential::streamPairPotential(std::ostream& stream, const size_t id const auto particle_2 = static_cast(Faunus::atoms.at(id2)); const auto rmax = std::sqrt(matrix_of_knots(id1, id2).rmax2); for (auto r : arange(dr, rmax, dr)) { - stream << fmt::format("{:.6E} {:.6E} {:.6E}\n", r, operator()(particle_1, particle_2, r* r, {r, 0, 0}), - FunctorPotential::operator()(particle_1, particle_2, r* r, {r, 0, 0})); + stream << fmt::format( + "{:.6E} {:.6E} {:.6E}\n", r, operator()(particle_1, particle_2, r * r, {r, 0, 0}), + FunctorPotential::operator()(particle_1, particle_2, r * r, {r, 0, 0})); } } @@ -1028,10 +1250,12 @@ void SplinedPotential::streamPairPotential(std::ostream& stream, const size_t id * For each pair of atom types a file is created containing * the exact and splined pair potential as a function of distance */ -void SplinedPotential::savePotentials() { +void SplinedPotential::savePotentials() +{ for (size_t i = 0; i < Faunus::atoms.size(); ++i) { // loop over atom types - for (size_t j = 0; j <= i; ++j) { // and build matrix of spline data (knots) for each pair - auto filename = fmt::format("{}-{}_tabulated.dat", Faunus::atoms.at(i).name, Faunus::atoms.at(j).name); + for (size_t j = 0; j <= i; ++j) { // and build matrix of spline data (knots) for each pair + auto filename = fmt::format("{}-{}_tabulated.dat", Faunus::atoms.at(i).name, + Faunus::atoms.at(j).name); if (auto stream = std::ofstream(filename); stream) { streamPairPotential(stream, i, j); } @@ -1049,21 +1273,26 @@ void SplinedPotential::savePotentials() { * For repulsive potentials (at short sep.), the threshold is the maximum energy splined * For attactive potentials (at short sep.), the threshold is the minimum energy splined */ -double SplinedPotential::findLowerDistance(int i, int j, double energy_threshold, double rmin) { +double SplinedPotential::findLowerDistance(int i, int j, double energy_threshold, double rmin) +{ assert(rmin > 0); Particle particle1 = Faunus::atoms.at(i); Particle particle2 = Faunus::atoms.at(j); int num_iterations = 0; while (rmin >= dr) { if (num_iterations++ == max_iterations) { - throw std::runtime_error("Pair potential spline error: cannot determine minimum distance"); + throw std::runtime_error( + "Pair potential spline error: cannot determine minimum distance"); } - double u = std::fabs(FunctorPotential::operator()(particle1, particle2, rmin *rmin, {rmin, 0, 0})); + double u = std::fabs( + FunctorPotential::operator()(particle1, particle2, rmin * rmin, {rmin, 0, 0})); if (u > energy_threshold * 1.1) { rmin += dr; - } else if (u < energy_threshold / 1.1) { + } + else if (u < energy_threshold / 1.1) { rmin -= dr; - } else { + } + else { break; } } @@ -1082,26 +1311,30 @@ double SplinedPotential::findLowerDistance(int i, int j, double energy_threshold * This function increases `rmin` until the absolute energy is lower than * `energy_threshold`. */ -double SplinedPotential::findUpperDistance(int i, int j, double energy_threshold, double rmax) { +double SplinedPotential::findUpperDistance(int i, int j, double energy_threshold, double rmax) +{ assert(rmax > 0); Particle particle1 = Faunus::atoms.at(i); Particle particle2 = Faunus::atoms.at(j); int num_iterations = 0; while (rmax >= dr) { if (num_iterations++ == max_iterations) { - throw std::runtime_error("Pair potential spline error: cannot determine maximum distance"); + throw std::runtime_error( + "Pair potential spline error: cannot determine maximum distance"); } - double u = FunctorPotential::operator()(particle1, particle2, rmax *rmax, {rmax, 0, 0}); + double u = FunctorPotential::operator()(particle1, particle2, rmax * rmax, {rmax, 0, 0}); if (std::fabs(u) > energy_threshold) { rmax += dr; - } else { + } + else { break; } } return rmax; } -void SplinedPotential::from_json(const json &js) { +void SplinedPotential::from_json(const json& js) +{ FunctorPotential::from_json(js); if (!isotropic) { throw std::runtime_error("Cannot spline anisotropic potentials"); @@ -1114,7 +1347,7 @@ void SplinedPotential::from_json(const json &js) { faunus_logger->trace("Pair potential spline tolerance = {} kT", js.value("utol", 1e-5)); for (size_t i = 0; i < Faunus::atoms.size(); ++i) { // loop over atom types - for (size_t j = 0; j <= i; ++j) { // and build matrix of spline data (knots) for each pair + for (size_t j = 0; j <= i; ++j) { // and build matrix of spline data (knots) for each pair if (atoms[i].implicit || atoms[j].implicit) { continue; } @@ -1123,7 +1356,8 @@ void SplinedPotential::from_json(const json &js) { if (auto it = js.find("cutoff_g2g"); it != js.end()) { if (it->is_number()) { rmax = it->get(); - } else if (it->is_object()) { + } + else if (it->is_object()) { rmax = it->at("default").get(); } } @@ -1138,7 +1372,10 @@ void SplinedPotential::from_json(const json &js) { } } -SplinedPotential::SplinedPotential(const std::string &name) : FunctorPotential(name) {} +SplinedPotential::SplinedPotential(const std::string& name) + : FunctorPotential(name) +{ +} /** * @param i Atom index @@ -1146,7 +1383,8 @@ SplinedPotential::SplinedPotential(const std::string &name) : FunctorPotential(n * @param rmin Minimum splining distance * @param rmax Maximum splining distance */ -void SplinedPotential::createKnots(int i, int j, double rmin, double rmax) { +void SplinedPotential::createKnots(int i, int j, double rmin, double rmax) +{ Particle particle1 = Faunus::atoms.at(i); Particle particle2 = Faunus::atoms.at(j); KnotData knotdata = spline.generate( @@ -1161,36 +1399,41 @@ void SplinedPotential::createKnots(int i, int j, double rmin, double rmax) { knotdata.hardsphere_repulsion = false; // repulsion for attractive potentials } if (knotdata.hardsphere_repulsion) { - faunus_logger->trace("Hardsphere repulsion enabled for {}-{} spline", Faunus::atoms.at(i).name, - Faunus::atoms.at(j).name); + faunus_logger->trace("Hardsphere repulsion enabled for {}-{} spline", + Faunus::atoms.at(i).name, Faunus::atoms.at(j).name); } matrix_of_knots.set(i, j, knotdata); // register knots for the pair double max_error = 0.0; // maximum absolute error of the spline along r for (const auto r : arange(rmin + dr, rmax, dr)) { - auto error = std::fabs(operator()(particle1, particle2, r* r, {r, 0, 0}) - - FunctorPotential::operator()(particle1, particle2, r* r, {r, 0, 0})); + auto error = + std::fabs(operator()(particle1, particle2, r * r, {r, 0, 0}) - + FunctorPotential::operator()(particle1, particle2, r * r, {r, 0, 0})); max_error = std::max(error, max_error); } - faunus_logger->trace( - "{}-{} interaction splined between [{:6.2f}:{:6.2f}] {} using {} knots w. maximum absolute error of {:.1E} kT", - Faunus::atoms[i].name, Faunus::atoms[j].name, rmin, rmax, unicode::angstrom, knotdata.numKnots(), max_error); + faunus_logger->trace("{}-{} interaction splined between [{:6.2f}:{:6.2f}] {} using {} knots w. " + "maximum absolute error of {:.1E} kT", + Faunus::atoms[i].name, Faunus::atoms[j].name, rmin, rmax, + unicode::angstrom, knotdata.numKnots(), max_error); } // =============== NewCoulombGalore =============== -void NewCoulombGalore::setSelfEnergy() { - selfEnergy = [lB = bjerrum_length, self_energy = pot.selfEnergyFunctor](const Particle &p) { +void NewCoulombGalore::setSelfEnergy() +{ + selfEnergy = [lB = bjerrum_length, self_energy = pot.selfEnergyFunctor](const Particle& p) { return lB * self_energy({p.charge * p.charge, 0.0}); }; // expose self-energy as a functor in potential base class } NewCoulombGalore::NewCoulombGalore(const std::string& name) - : PairPotential(name) { + : PairPotential(name) +{ setSelfEnergy(); } -TEST_CASE("[Faunus] NewCoulombGalore") { +TEST_CASE("[Faunus] NewCoulombGalore") +{ Particle a, b; a.charge = 1.0; b.charge = -1.0; @@ -1201,10 +1444,12 @@ TEST_CASE("[Faunus] NewCoulombGalore") { Point force_on_a = pot.force(a, b, b_towards_a.squaredNorm(), b_towards_a); CHECK_EQ(force_on_a.x(), doctest::Approx(0)); CHECK_EQ(force_on_a.y(), doctest::Approx(0)); - CHECK_EQ(force_on_a.z(), doctest::Approx(0.1429734149)); // attraction -> positive direction expected + CHECK_EQ(force_on_a.z(), + doctest::Approx(0.1429734149)); // attraction -> positive direction expected } -void NewCoulombGalore::from_json(const json &j) { +void NewCoulombGalore::from_json(const json& j) +{ using namespace ::CoulombGalore; // namespace for external CoulombGalore library const auto relative_dielectric_constant = j.at("epsr").get(); bjerrum_length = pc::bjerrumLength(relative_dielectric_constant); @@ -1213,69 +1458,96 @@ void NewCoulombGalore::from_json(const json &j) { const auto method = j.at("type").get(); if (method == "yukawa") { if (json _j(j); _j.value("shift", false)) { // zero energy and force at cutoff - faunus_logger->debug("energy and force shifted yukawa uses the 'poisson' scheme with C=1 and D=1"); + faunus_logger->debug( + "energy and force shifted yukawa uses the 'poisson' scheme with C=1 and D=1"); _j["type"] = "poisson"; _j["C"] = 1; _j["D"] = 1; _j["debyelength"] = electrolyte.value().debyeLength(bjerrum_length); pot.spline<::CoulombGalore::Poisson>(_j); - } else { // non-shifted yukawa equals `plain` with exponential screening + } + else { // non-shifted yukawa equals `plain` with exponential screening if (_j.contains("cutoff")) { - throw ConfigurationError("unexpected 'cutoff' for non-shifted yukawa which is always infinity"); + throw ConfigurationError( + "unexpected 'cutoff' for non-shifted yukawa which is always infinity"); } _j["type"] = "plain"; _j["debyelength"] = electrolyte.value().debyeLength(bjerrum_length); pot.spline<::CoulombGalore::Plain>(_j); } - } else if (method == "plain") { + } + else if (method == "plain") { if (j.contains("cutoff")) { throw ConfigurationError("unexpected cutoff for plain: it's *always* infinity"); } pot.spline<::CoulombGalore::Plain>(j); - } else if (method == "qpotential") { + } + else if (method == "qpotential") { pot.spline<::CoulombGalore::qPotential>(j); - } else if (method == "wolf") { + } + else if (method == "wolf") { pot.spline<::CoulombGalore::Wolf>(j); - } else if (method == "poisson") { + } + else if (method == "poisson") { pot.spline<::CoulombGalore::Poisson>(j); - } else if (method == "fanourgakis") { + } + else if (method == "fanourgakis") { pot.spline<::CoulombGalore::Fanourgakis>(j); - } else if (method == "zahn") { + } + else if (method == "zahn") { pot.spline<::CoulombGalore::Zahn>(j); - } else if (method == "fennell") { + } + else if (method == "fennell") { pot.spline<::CoulombGalore::Fennell>(j); - } else if (method == "zerodipole") { + } + else if (method == "zerodipole") { pot.spline<::CoulombGalore::ZeroDipole>(j); - } else if (method == "ewald") { + } + else if (method == "ewald") { auto _j(j); if (electrolyte) { _j["debyelength"] = electrolyte.value().debyeLength(bjerrum_length); } pot.spline<::CoulombGalore::Ewald>(_j); - } else if (method == "reactionfield") { + } + else if (method == "reactionfield") { pot.spline<::CoulombGalore::ReactionField>(j); - } else { + } + else { throw ConfigurationError("unknown type '{}'", method); } - faunus_logger->info("splitting function for '{}' splined with {} knots", method, pot.numKnots().at(0)); + faunus_logger->info("splitting function for '{}' splined with {} knots", method, + pot.numKnots().at(0)); setSelfEnergy(); } -void NewCoulombGalore::to_json(json &j) const { +void NewCoulombGalore::to_json(json& j) const +{ pot.to_json(j); j["lB"] = bjerrum_length; } -const CoulombGalore::Splined& NewCoulombGalore::getCoulombGalore() const { return pot; } -[[maybe_unused]] double NewCoulombGalore::dielectric_constant(double M2V) { return pot.calc_dielectric(M2V); } + +const CoulombGalore::Splined& NewCoulombGalore::getCoulombGalore() const +{ + return pot; +} + +[[maybe_unused]] double NewCoulombGalore::dielectric_constant(double M2V) +{ + return pot.calc_dielectric(M2V); +} // =============== Multipole =============== -Multipole::Multipole(const std::string &name) : NewCoulombGalore(name) { +Multipole::Multipole(const std::string& name) + : NewCoulombGalore(name) +{ isotropic = false; // this potential is angular dependent setSelfEnergy(); } -void Multipole::setSelfEnergy() { +void Multipole::setSelfEnergy() +{ selfEnergy = [lB = bjerrum_length, self_energy = pot.selfEnergyFunctor](const Particle& p) { double mu_x_mu = 0; // dipole-dipole product if (p.hasExtension()) { // only access dipole if the particle has extended properties @@ -1285,7 +1557,8 @@ void Multipole::setSelfEnergy() { }; // expose self-energy as a functor in potential base class } -TEST_CASE("[Faunus] Dipole-dipole interactions") { +TEST_CASE("[Faunus] Dipole-dipole interactions") +{ using doctest::Approx; json j = R"({ "atomlist" : [ {"A": { "mu":[1.0,0.0,0.0], "mulen":3.0 }}, @@ -1296,20 +1569,22 @@ TEST_CASE("[Faunus] Dipole-dipole interactions") { auto u = pairpotential::makePairPotential(R"( { "default": [ { "multipole" : {"epsr": 1.0, "type": "plain"} } ] } )"_json); - auto dipoledipole = pairpotential::makePairPotential(R"({"epsr": 1.0, "type": "plain"})"_json); + auto dipoledipole = + pairpotential::makePairPotential(R"({"epsr": 1.0, "type": "plain"})"_json); Particle a = atoms[0]; Particle b = atoms[1]; Particle c = atoms[2]; Point r = {2, 0, 0}; double r2 = r.squaredNorm(); - CHECK_EQ(u(a, a, r2, r), - Approx(dipoledipole(a, a, r2, - r))); // interaction between two parallell dipoles, directed parallell to their seperation + CHECK_EQ(u(a, a, r2, r), Approx(dipoledipole(a, a, r2, + r))); // interaction between two parallell dipoles, + // directed parallell to their seperation CHECK_EQ(u(b, b, r2, r), - Approx(dipoledipole( - b, b, r2, r))); // interaction between two parallell dipoles, directed perpendicular to their seperation - CHECK_EQ(u(a, b, r2, r), Approx(dipoledipole(a, b, r2, r))); // interaction between two perpendicular dipoles + Approx(dipoledipole(b, b, r2, r))); // interaction between two parallell dipoles, + // directed perpendicular to their seperation + CHECK_EQ(u(a, b, r2, r), + Approx(dipoledipole(a, b, r2, r))); // interaction between two perpendicular dipoles CHECK_EQ(u(a, a, r2, r), -2.25 * dipoledipole.bjerrum_length); CHECK_EQ(u(b, b, r2, r), 1.125 * dipoledipole.bjerrum_length); CHECK_EQ(u(a, c, r2, r), -0.75 * dipoledipole.bjerrum_length); @@ -1318,13 +1593,14 @@ TEST_CASE("[Faunus] Dipole-dipole interactions") { r = {3, 0, 0}; r2 = 3 * 3; - CHECK_EQ(u(a, a, r2, r), - Approx(dipoledipole(a, a, r2, - r))); // interaction between two parallell dipoles, directed parallell to their seperation + CHECK_EQ(u(a, a, r2, r), Approx(dipoledipole(a, a, r2, + r))); // interaction between two parallell dipoles, + // directed parallell to their seperation CHECK_EQ(u(b, b, r2, r), - Approx(dipoledipole( - b, b, r2, r))); // interaction between two parallell dipoles, directed perpendicular to their seperation - CHECK_EQ(u(a, b, r2, r), Approx(dipoledipole(a, b, r2, r))); // interaction between two perpendicular dipoles + Approx(dipoledipole(b, b, r2, r))); // interaction between two parallell dipoles, + // directed perpendicular to their seperation + CHECK_EQ(u(a, b, r2, r), + Approx(dipoledipole(a, b, r2, r))); // interaction between two perpendicular dipoles CHECK_EQ(u(a, a, r2, r), Approx(-(2.0 / 3.0) * dipoledipole.bjerrum_length)); CHECK_EQ(u(b, b, r2, r), (1.0 / 3.0) * dipoledipole.bjerrum_length); CHECK_EQ(u(a, c, r2, r), Approx(-2.0 / 9.0 * dipoledipole.bjerrum_length)); @@ -1334,50 +1610,63 @@ TEST_CASE("[Faunus] Dipole-dipole interactions") { // =============== WeeksChandlerAndersen =============== -WeeksChandlerAndersen::WeeksChandlerAndersen(const std::string &name, const std::string &cite, +WeeksChandlerAndersen::WeeksChandlerAndersen(const std::string& name, const std::string& cite, CombinationRuleType combination_rule) - : LennardJones(name, cite, combination_rule) {} + : LennardJones(name, cite, combination_rule) +{ +} -TEST_CASE("[Faunus] WeeksChandlerAndersen") { - SUBCASE("JSON initilization") { +TEST_CASE("[Faunus] WeeksChandlerAndersen") +{ + SUBCASE("JSON initilization") + { atoms = R"([{"A": {"sigma":2.0, "eps":0.9}}, {"B": {"sigma":8.0, "eps":0.1}}])"_json.get(); Particle a = atoms[0], b = atoms[1]; - CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), std::runtime_error); + CHECK_THROWS_AS(makePairPotential(R"({"mixing": "unknown"})"_json), + std::runtime_error); CHECK_NOTHROW(makePairPotential(R"({})"_json)); CHECK_NOTHROW(makePairPotential( R"({"mixing": "LB", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json)); - SUBCASE("Missing coefficient") { - auto wca = makePairPotential(R"({"mixing": "LB", "sigma": "sigma_wca"})"_json); + SUBCASE("Missing coefficient") + { + auto wca = makePairPotential( + R"({"mixing": "LB", "sigma": "sigma_wca"})"_json); CHECK_EQ(std::isnan(wca(a, a, 10.0 * 10.0, {0, 0, 10.0})), true); CHECK_EQ(std::isnan(wca(a, a, 1.0 * 1.0, {0, 0, 1.0})), true); } } - SUBCASE("JSON initilization custom coefficient names") { + SUBCASE("JSON initilization custom coefficient names") + { atoms = R"([{"A": {"sigma_wca":2.0, "eps":0.9}}, {"B": {"sigma_wca":8.0, "eps":0.1}}])"_json.get(); Particle a = atoms[0], b = atoms[1]; - CHECK_NOTHROW(makePairPotential(R"({"mixing": "LB", "sigma": "sigma_wca"})"_json)); + CHECK_NOTHROW(makePairPotential( + R"({"mixing": "LB", "sigma": "sigma_wca"})"_json)); // Shall throw after non-default potentials are created properly, // i.e., not needed pairs are not evaluated at all for the matrices - // CHECK_THROWS_AS_MESSAGE(WeeksChandlerAndersen wca = R"({"mixing": "LB"})"_json, std::runtime_error, + // CHECK_THROWS_AS_MESSAGE(WeeksChandlerAndersen wca = R"({"mixing": "LB"})"_json, + // std::runtime_error, // "unknown atom property"); - // CHECK_THROWS_AS_MESSAGE(WeeksChandlerAndersen wca = R"({"mixing": "LB", "sigma": "unknown"})"_json, + // CHECK_THROWS_AS_MESSAGE(WeeksChandlerAndersen wca = R"({"mixing": "LB", "sigma": + // "unknown"})"_json, // std::runtime_error, "unknown atom property"); // different atom and custom coefficient names are not allowed // CHECK_THROWS_AS_MESSAGE( // WeeksChandlerAndersen wca = - // R"({"mixing": "LB", "sigma": "sigma_wca", "custom": [{"A B": {"eps": 0.5, "sigma": 8}}]})"_json, + // R"({"mixing": "LB", "sigma": "sigma_wca", "custom": [{"A B": {"eps": 0.5, + // "sigma": 8}}]})"_json, // std::runtime_error, "unknown atom property"); } - SUBCASE("JSON serialization") { - auto wca = - pairpotential::makePairPotential(R"({"mixing": "LB", "sigma": "sigma_wca"})"_json); + SUBCASE("JSON serialization") + { + auto wca = pairpotential::makePairPotential( + R"({"mixing": "LB", "sigma": "sigma_wca"})"_json); json j = wca; - json &j_wca(j["wca"]); + json& j_wca(j["wca"]); CHECK_EQ(j_wca["mixing"], "lorentz_berthelot"); CHECK_EQ(j_wca["sigma"], "sigma_wca"); CHECK_EQ(j_wca["eps"], "eps"); @@ -1385,21 +1674,32 @@ TEST_CASE("[Faunus] WeeksChandlerAndersen") { } PairPotentialException::PairPotentialException(const std::string& msg) - : std::runtime_error(msg){} + : std::runtime_error(msg) +{ +} -void CosAttractMixed::initPairMatrices() { - auto comb_distance = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); - auto comb_epsilon = PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); +void CosAttractMixed::initPairMatrices() +{ + auto comb_distance = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::SIGMA); + auto comb_epsilon = + PairMixer::getCombinator(combination_rule, PairMixer::CoefficientType::EPSILON); - switching_distance = PairMixer(extract_rc, comb_distance, &PairMixer::modIdentity).createPairMatrix(atoms, *custom_pairs); - switching_width = PairMixer(extract_wc, comb_distance, &PairMixer::modIdentity).createPairMatrix(atoms, *custom_pairs); - epsilon = PairMixer(extract_eps, comb_epsilon, &PairMixer::modIdentity).createPairMatrix(atoms, *custom_pairs); + switching_distance = PairMixer(extract_rc, comb_distance, &PairMixer::modIdentity) + .createPairMatrix(atoms, *custom_pairs); + switching_width = PairMixer(extract_wc, comb_distance, &PairMixer::modIdentity) + .createPairMatrix(atoms, *custom_pairs); + epsilon = PairMixer(extract_eps, comb_epsilon, &PairMixer::modIdentity) + .createPairMatrix(atoms, *custom_pairs); - faunus_logger->trace("Pair matrices for {} sigma ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", name, - switching_distance->rows(), switching_distance->cols(), epsilon->rows(), epsilon->cols(), custom_pairs->size()); + faunus_logger->trace( + "Pair matrices for {} sigma ({}Γ—{}) and epsilon ({}Γ—{}) created using {} custom pairs.", + name, switching_distance->rows(), switching_distance->cols(), epsilon->rows(), + epsilon->cols(), custom_pairs->size()); } -void CosAttractMixed::extractorsFromJson(const json& j) { +void CosAttractMixed::extractorsFromJson(const json& j) +{ auto name = j.value("rc", "rc"); json_extra_params["rc"] = name; extract_rc = [=](auto& a) { return a.at(name) * 1.0_angstrom; }; @@ -1413,10 +1713,14 @@ void CosAttractMixed::extractorsFromJson(const json& j) { extract_eps = [=](auto& a) { return a.at(name) * 1.0_kJmol; }; } -CosAttractMixed::CosAttractMixed(const std::string& name, const std::string& cite, CombinationRuleType combination_rule) - : MixerPairPotentialBase(name, cite, combination_rule) {} +CosAttractMixed::CosAttractMixed(const std::string& name, const std::string& cite, + CombinationRuleType combination_rule) + : MixerPairPotentialBase(name, cite, combination_rule) +{ +} -double CosAttractMixed::cutOffSquared(AtomData::index_type id1, AtomData::index_type id2) const { +double CosAttractMixed::cutOffSquared(AtomData::index_type id1, AtomData::index_type id2) const +{ const auto rc = (*switching_distance)(id1, id2); const auto wc = (*switching_width)(id1, id2); return (rc + wc) * (rc + wc); diff --git a/src/potentials.h b/src/potentials.h index b496b280d..017c01967 100644 --- a/src/potentials.h +++ b/src/potentials.h @@ -12,7 +12,8 @@ namespace Faunus::pairpotential { * @brief Lennard-Jones potential with an arbitrary combination rule. * @note Mixing data is _shared_ upon copying */ -class LennardJones : public MixerPairPotentialBase { +class LennardJones : public MixerPairPotentialBase +{ private: TExtractorFunc extract_sigma; TExtractorFunc extract_epsilon; @@ -24,20 +25,24 @@ class LennardJones : public MixerPairPotentialBase { void extractorsFromJson(const json& j) override; public: - explicit LennardJones(const std::string& name = "lennardjones", const std::string& cite = std::string(), - CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); + explicit LennardJones( + const std::string& name = "lennardjones", const std::string& cite = std::string(), + CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); - inline Point force(const Particle& particle_a, const Particle& particle_b, double squared_distance, - const Point& b_towards_a) const override { + inline Point force(const Particle& particle_a, const Particle& particle_b, + double squared_distance, const Point& b_towards_a) const override + { const auto s6 = powi((*sigma_squared)(particle_a.id, particle_b.id), 3); const auto r6 = squared_distance * squared_distance * squared_distance; const auto r14 = r6 * r6 * squared_distance; - return 6.0 * (*epsilon_quadruple)(particle_a.id, particle_b.id) * s6 * (2.0 * s6 - r6) / r14 * - b_towards_a; // force in a + return 6.0 * (*epsilon_quadruple)(particle_a.id, particle_b.id) * s6 * (2.0 * s6 - r6) / + r14 * b_towards_a; // force in a } - inline double operator()(const Particle& particle_a, const Particle& particle_b, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + inline double operator()(const Particle& particle_a, const Particle& particle_b, + double squared_distance, + [[maybe_unused]] const Point& b_towards_a) const override + { auto x = (*sigma_squared)(particle_a.id, particle_b.id) / squared_distance; // s2/r2 x = x * x * x; // s6/r6 return (*epsilon_quadruple)(particle_a.id, particle_b.id) * (x * x - x); @@ -57,10 +62,12 @@ class LennardJones : public MixerPairPotentialBase { * * @note Mixing data is _shared_ upon copying */ -class WeeksChandlerAndersen : public LennardJones { +class WeeksChandlerAndersen : public LennardJones +{ static constexpr double onefourth = 0.25, twototwosixth = 1.2599210498948732; - inline double operator()(const Particle& a, const Particle& b, double squared_distance) const { + inline double operator()(const Particle& a, const Particle& b, double squared_distance) const + { auto x = (*sigma_squared)(a.id, b.id); // s^2 if (squared_distance > x * twototwosixth) { return 0; @@ -71,23 +78,27 @@ class WeeksChandlerAndersen : public LennardJones { } public: - explicit WeeksChandlerAndersen(const std::string& name = "wca", const std::string& cite = "doi:ct4kh9", - CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); + explicit WeeksChandlerAndersen( + const std::string& name = "wca", const std::string& cite = "doi:ct4kh9", + CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); inline double operator()(const Particle& a, const Particle& b, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + [[maybe_unused]] const Point& b_towards_a) const override + { return operator()(a, b, squared_distance); } inline Point force(const Particle& a, const Particle& b, const double squared_distance, - const Point& b_towards_a) const override { + const Point& b_towards_a) const override + { auto x = (*sigma_squared)(a.id, b.id); // s^2 if (squared_distance > x * twototwosixth) { return {0.0, 0.0, 0.0}; } x = x / squared_distance; // (s/r)^2 x = x * x * x; // (s/r)^6 - return (*epsilon_quadruple)(a.id, b.id) * 6.0 * (2.0 * x * x - x) / squared_distance * b_towards_a; + return (*epsilon_quadruple)(a.id, b.id) * 6.0 * (2.0 * x * x - x) / squared_distance * + b_towards_a; } }; // Weeks-Chandler-Andersen potential @@ -97,7 +108,8 @@ class WeeksChandlerAndersen : public LennardJones { * Uses arithmetic mean for sigma as a default combination rule. * @note `PairMatrix` is _shared_ upon copying */ -class HardSphere : public MixerPairPotentialBase { +class HardSphere : public MixerPairPotentialBase +{ TExtractorFunc extract_sigma; TPairMatrixPtr sigma_squared; // sigma_ij * sigma_ij void initPairMatrices() override; @@ -106,8 +118,9 @@ class HardSphere : public MixerPairPotentialBase { public: explicit HardSphere(const std::string& name = "hardsphere"); - inline double operator()(const Particle& particle_a, const Particle& particle_b, double squared_distance, - const Point&) const override { + inline double operator()(const Particle& particle_a, const Particle& particle_b, + double squared_distance, const Point&) const override + { return squared_distance < (*sigma_squared)(particle_a.id, particle_b.id) ? pc::infty : 0.0; } }; @@ -123,7 +136,8 @@ class HardSphere : public MixerPairPotentialBase { * * More info: doi:10.1063/1.3186742 */ -class Hertz : public MixerPairPotentialBase { +class Hertz : public MixerPairPotentialBase +{ TExtractorFunc extract_sigma, extract_epsilon; protected: @@ -136,9 +150,11 @@ class Hertz : public MixerPairPotentialBase { explicit Hertz(const std::string& name = "hertz"); inline double operator()(const Particle& a, const Particle& b, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + [[maybe_unused]] const Point& b_towards_a) const override + { if (squared_distance <= (*sigma_squared)(a.id, b.id)) { - return (*epsilon)(a.id, b.id) * pow((1 - (sqrt(squared_distance / (*sigma_squared)(a.id, b.id)))), 2.5); + return (*epsilon)(a.id, b.id) * + pow((1 - (sqrt(squared_distance / (*sigma_squared)(a.id, b.id)))), 2.5); } return 0.0; } @@ -152,7 +168,8 @@ class Hertz : public MixerPairPotentialBase { * @f] * when r < \sigma, and zero otherwise. */ -class SquareWell : public MixerPairPotentialBase { +class SquareWell : public MixerPairPotentialBase +{ TExtractorFunc extract_sigma, extract_epsilon; protected: @@ -163,13 +180,16 @@ class SquareWell : public MixerPairPotentialBase { public: explicit SquareWell(const std::string& name = "squarewell"); + inline double operator()(const Particle& a, const Particle& b, double squared_distance, - const Point&) const override { + const Point&) const override + { return (squared_distance < (*sigma_squared)(a.id, b.id)) ? -(*epsilon)(a.id, b.id) : 0.0; } }; -class RepulsionR3 : public PairPotential { +class RepulsionR3 : public PairPotential +{ private: double f = 0, s = 0, e = 0; void from_json(const json& j) override; @@ -177,9 +197,11 @@ class RepulsionR3 : public PairPotential { public: explicit RepulsionR3(const std::string& name = "repulsionr3") - : PairPotential(name){}; + : PairPotential(name) {}; - inline double operator()(const Particle&, const Particle&, double squared_distance, const Point&) const override { + inline double operator()(const Particle&, const Particle&, double squared_distance, + const Point&) const override + { const auto r = sqrt(squared_distance); return f / (r * squared_distance) + e * std::pow(s / r, 12); } @@ -204,7 +226,8 @@ class RepulsionR3 : public PairPotential { * `wc` | Decay range, w_c [angstrom] * */ -class CosAttract : public PairPotential { +class CosAttract : public PairPotential +{ double eps = 0.0; double wc = 0.0; double rc = 0.0; @@ -231,7 +254,9 @@ class CosAttract : public PairPotential { * C(%, resultname = "x") * ~~~ */ - inline double operator()(const Particle&, const Particle&, double squared_distance, const Point&) const override { + inline double operator()(const Particle&, const Particle&, double squared_distance, + const Point&) const override + { if (squared_distance < rc2) { return -eps; } @@ -243,7 +268,8 @@ class CosAttract : public PairPotential { } inline Point force(const Particle&, const Particle&, double squared_distance, - const Point& b_towards_a) const override { + const Point& b_towards_a) const override + { if (squared_distance > rcwc2 || squared_distance < rc2) { return {0.0, 0.0, 0.0}; } @@ -252,6 +278,7 @@ class CosAttract : public PairPotential { const auto x2 = std::sin(c * (r - rc)); return -2.0 * c * eps * x1 * x2 / r * b_towards_a; } + void to_json(json& j) const override; }; @@ -262,7 +289,8 @@ class CosAttract : public PairPotential { * combination rules. `EPSILON` is used to mix the energy, `eps`. `SIGMA` is used to mix * distances, `rc` and `wc`. */ -class CosAttractMixed : public MixerPairPotentialBase { +class CosAttractMixed : public MixerPairPotentialBase +{ private: TExtractorFunc extract_rc; TExtractorFunc extract_wc; @@ -276,13 +304,15 @@ class CosAttractMixed : public MixerPairPotentialBase { void extractorsFromJson(const json& j) override; public: - explicit CosAttractMixed(const std::string& name = "cos2", const std::string& cite = "doi:10/chqzjk"s, - CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); + explicit CosAttractMixed( + const std::string& name = "cos2", const std::string& cite = "doi:10/chqzjk"s, + CombinationRuleType combination_rule = CombinationRuleType::LORENTZ_BERTHELOT); double cutOffSquared(AtomData::index_type id1, AtomData::index_type id2) const; //!< (r_c+w_c)^2 inline Point force(const Particle& a, const Particle& b, double squared_distance, - const Point& b_towards_a) const override { + const Point& b_towards_a) const override + { const auto rc = (*switching_distance)(a.id, b.id); const auto wc = (*switching_width)(a.id, b.id); const auto cutoff_squared = (rc + wc) * (rc + wc); @@ -297,7 +327,8 @@ class CosAttractMixed : public MixerPairPotentialBase { } inline double operator()(const Particle& a, const Particle& b, const double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + [[maybe_unused]] const Point& b_towards_a) const override + { const auto rc = (*switching_distance)(a.id, b.id); if (squared_distance < (rc * rc)) { return -(*epsilon)(a.id, b.id); @@ -316,32 +347,39 @@ class CosAttractMixed : public MixerPairPotentialBase { /** * @brief Pairwise SASA potential calculating the surface area of inter-secting spheres */ -class SASApotential : public PairPotential { +class SASApotential : public PairPotential +{ private: bool shift = true; // shift potential to zero at large separations? double proberadius = 0, conc = 0; void from_json(const json& j) override; double area(double R, double r, double center_center_distance_squared) - const; //!< Total surface area of two intersecting spheres or radii R and r as a function of separation + const; //!< Total surface area of two intersecting spheres or radii R and r as a function of + //!< separation public: inline double operator()(const Particle& a, const Particle& b, const double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + [[maybe_unused]] const Point& b_towards_a) const override + { const auto tfe = 0.5 * (atoms[a.id].tfe + atoms[b.id].tfe); const auto tension = 0.5 * (atoms[a.id].tension + atoms[b.id].tension); if (fabs(tfe) > 1e-6 or fabs(tension) > 1e-6) - return (tension + conc * tfe) * area(0.5 * atoms[a.id].sigma, 0.5 * atoms[b.id].sigma, squared_distance); + return (tension + conc * tfe) * + area(0.5 * atoms[a.id].sigma, 0.5 * atoms[b.id].sigma, squared_distance); return 0.0; } - explicit SASApotential(const std::string& name = "sasa", const std::string& cite = std::string()); + + explicit SASApotential(const std::string& name = "sasa", + const std::string& cite = std::string()); void to_json(json& j) const override; }; /** * @brief Plain Coulomb potential */ -class Coulomb : public PairPotential { +class Coulomb : public PairPotential +{ private: void from_json(const json& j) override; @@ -350,24 +388,31 @@ class Coulomb : public PairPotential { double bjerrum_length = 0.0; //!< Bjerrum length inline double operator()(const Particle& a, const Particle& b, const double squared_distance, - const Point&) const override { + const Point&) const override + { return bjerrum_length * a.charge * b.charge / std::sqrt(squared_distance); } + void to_json(json& j) const override; }; -class DipoleDipole : public PairPotential { +class DipoleDipole : public PairPotential +{ private: void from_json(const json& j) override; public: - explicit DipoleDipole(const std::string& name = "dipoledipole", const std::string& cite = std::string()); + explicit DipoleDipole(const std::string& name = "dipoledipole", + const std::string& cite = std::string()); double bjerrum_length{}; - inline double operator()(const Particle& a, const Particle& b, double, const Point& b_towards_a) const override { - return bjerrum_length * - mu2mu(a.getExt().mu, b.getExt().mu, a.getExt().mulen * b.getExt().mulen, b_towards_a, 1.0, 0.0); + inline double operator()(const Particle& a, const Particle& b, double, + const Point& b_towards_a) const override + { + return bjerrum_length * mu2mu(a.getExt().mu, b.getExt().mu, + a.getExt().mulen * b.getExt().mulen, b_towards_a, 1.0, 0.0); } + void to_json(json& j) const override; }; @@ -375,7 +420,8 @@ class DipoleDipole : public PairPotential { * @brief Charge-nonpolar pair interaction * @note Pair data is _shared_ upon copying */ -class Polarizability : public Coulomb { +class Polarizability : public Coulomb +{ private: double epsr; std::shared_ptr> m_neutral, m_charged; @@ -386,7 +432,8 @@ class Polarizability : public Coulomb { void to_json(json& j) const override; inline double operator()(const Particle& a, const Particle& b, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + [[maybe_unused]] const Point& b_towards_a) const override + { double r4inv = 1 / (squared_distance * squared_distance); if (fabs(a.charge) > 1e-9 or fabs(b.charge) > 1e-9) { return (*m_charged)(a.id, b.id) * r4inv; @@ -395,7 +442,8 @@ class Polarizability : public Coulomb { } inline Point force(const Particle& a, const Particle& b, double squared_distance, - const Point& b_towards_a) const override { + const Point& b_towards_a) const override + { double r6inv = 1 / (squared_distance * squared_distance * squared_distance); if (fabs(a.charge) > 1e-9 or fabs(b.charge) > 1e-9) { return 4 * m_charged->operator()(a.id, b.id) * r6inv * b_towards_a; @@ -418,7 +466,8 @@ class Polarizability : public Coulomb { * * More info: doi:10.1103/PhysRevE.59.4248 */ -class FENE : public PairPotential { +class FENE : public PairPotential +{ double k = 0; double r02 = 0; double r02inv = 0; @@ -428,21 +477,27 @@ class FENE : public PairPotential { explicit FENE(const std::string& name = "fene"); void to_json(json& j) const override; - inline double operator()([[maybe_unused]] const Particle& particle1, [[maybe_unused]] const Particle& particle2, - double r2, [[maybe_unused]] const Point& b_towards_a) const override { + inline double operator()([[maybe_unused]] const Particle& particle1, + [[maybe_unused]] const Particle& particle2, double r2, + [[maybe_unused]] const Point& b_towards_a) const override + { return (r2 > r02) ? pc::infty : -0.5 * k * r02 * std::log(1 - r2 * r02inv); } - inline Point force([[maybe_unused]] const Particle& particle_a, [[maybe_unused]] const Particle& particle_b, - double squared_distance, const Point& b_towards_a) const override { - return (squared_distance > r02) ? -pc::infty * b_towards_a : -k * r02 / (r02 - squared_distance) * b_towards_a; + inline Point force([[maybe_unused]] const Particle& particle_a, + [[maybe_unused]] const Particle& particle_b, double squared_distance, + const Point& b_towards_a) const override + { + return (squared_distance > r02) ? -pc::infty * b_towards_a + : -k * r02 / (r02 - squared_distance) * b_towards_a; } }; /** * @brief Wrapper for external CoulombGalore library */ -class NewCoulombGalore : public PairPotential { +class NewCoulombGalore : public PairPotential +{ protected: ::CoulombGalore::Splined pot; virtual void setSelfEnergy(); @@ -451,15 +506,21 @@ class NewCoulombGalore : public PairPotential { public: explicit NewCoulombGalore(const std::string& = "coulomb"); - inline double operator()(const Particle& particle_a, const Particle& particle_b, const double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { - return bjerrum_length * pot.ion_ion_energy(particle_a.charge, particle_b.charge, - sqrt(squared_distance) + std::numeric_limits::epsilon()); + inline double operator()(const Particle& particle_a, const Particle& particle_b, + const double squared_distance, + [[maybe_unused]] const Point& b_towards_a) const override + { + return bjerrum_length * + pot.ion_ion_energy(particle_a.charge, particle_b.charge, + sqrt(squared_distance) + std::numeric_limits::epsilon()); } - inline Point force(const Particle& particle_a, const Particle& particle_b, [[maybe_unused]] double squared_distance, - const Point& b_towards_a) const override { - return bjerrum_length * pot.ion_ion_force(particle_a.charge, particle_b.charge, b_towards_a); // force on "a" + inline Point force(const Particle& particle_a, const Particle& particle_b, + [[maybe_unused]] double squared_distance, + const Point& b_towards_a) const override + { + return bjerrum_length * + pot.ion_ion_force(particle_a.charge, particle_b.charge, b_towards_a); // force on "a" } void to_json(json& j) const override; @@ -472,7 +533,8 @@ class NewCoulombGalore : public PairPotential { * @brief Multipole interactions * @todo Only dipole-dipole interactions are currently enabled */ -class Multipole : public NewCoulombGalore { +class Multipole : public NewCoulombGalore +{ private: void setSelfEnergy() override; @@ -480,7 +542,9 @@ class Multipole : public NewCoulombGalore { explicit Multipole(const std::string& = "multipole"); inline double operator()(const Particle& particle_a, const Particle& particle_b, - [[maybe_unused]] double squared_distance, const Point& b_towards_a) const override { + [[maybe_unused]] double squared_distance, + const Point& b_towards_a) const override + { // Only dipole-dipole for now! Point mua = particle_a.getExt().mu * particle_a.getExt().mulen; Point mub = particle_b.getExt().mu * particle_b.getExt().mulen; @@ -488,7 +552,9 @@ class Multipole : public NewCoulombGalore { } inline Point force(const Faunus::Particle& particle1, const Faunus::Particle& particle2, - [[maybe_unused]] double squared_distance, const Faunus::Point& b_towards_a) const override { + [[maybe_unused]] double squared_distance, + const Faunus::Point& b_towards_a) const override + { Point mua = particle1.getExt().mu * particle1.getExt().mulen; Point mub = particle2.getExt().mu * particle2.getExt().mulen; Point ionion = pot.ion_ion_force(particle1.charge, particle2.charge, b_towards_a); @@ -504,24 +570,31 @@ class Multipole : public NewCoulombGalore { * @note `symbols` is a shared_ptr as this allows it to be modified by `operator() const`. * A hack, but would otherwise require const-removal in all pair-potentials. */ -class CustomPairPotential : public PairPotential { +class CustomPairPotential : public PairPotential +{ private: - // Only ExprFunction is explicitly instantiated in functionparser.cpp. Other types as well as - // the implicit template instantiation is disabled to save reasources during the compilation/build. + // Only ExprFunction is explicitly instantiated in functionparser.cpp. Other types as + // well as the implicit template instantiation is disabled to save reasources during the + // compilation/build. ExprFunction expression; - struct Symbols { + + struct Symbols + { double distance = 0.0; // available as "r" double charge1 = 0.0; // available as "charge1" double charge2 = 0.0; // available as "charge2" double sigma1 = 0.0; // available as "s1" double sigma2 = 0.0; // available as "s2" }; + std::shared_ptr symbols; double squared_cutoff_distance; json original_input; void from_json(const json& j) override; - inline void setSymbols(const Particle& particle1, const Particle& particle2, double squared_distance) const { + inline void setSymbols(const Particle& particle1, const Particle& particle2, + double squared_distance) const + { symbols->distance = std::sqrt(squared_distance); symbols->charge1 = particle1.charge; symbols->charge2 = particle2.charge; @@ -530,8 +603,10 @@ class CustomPairPotential : public PairPotential { } public: - inline double operator()(const Particle& particle1, const Particle& particle2, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + inline double operator()(const Particle& particle1, const Particle& particle2, + double squared_distance, + [[maybe_unused]] const Point& b_towards_a) const override + { if (squared_distance < squared_cutoff_distance) { setSymbols(particle1, particle2, squared_distance); return expression(); @@ -539,8 +614,9 @@ class CustomPairPotential : public PairPotential { return 0.0; } - inline Point force(const Particle& particle_a, const Particle& particle_b, double squared_distance, - const Point& b_towards_b) const override { + inline Point force(const Particle& particle_a, const Particle& particle_b, + double squared_distance, const Point& b_towards_b) const override + { if (squared_distance < squared_cutoff_distance) { setSymbols(particle_a, particle_b, squared_distance); return -expression.derivative(symbols->distance) / symbols->distance * b_towards_b; @@ -552,7 +628,8 @@ class CustomPairPotential : public PairPotential { void to_json(json& j) const override; }; -using PrimitiveModel = CombinedPairPotential; //!< Primitive model of electrolytes +using PrimitiveModel = + CombinedPairPotential; //!< Primitive model of electrolytes using PrimitiveModelWCA = CombinedPairPotential; using CigarCosAttractWCA = CompleteCigarPotential; @@ -566,26 +643,32 @@ using CigarCosAttractWCA = CompleteCigarPotential; +class FunctorPotential : public PairPotential +{ + using EnergyFunctor = + std::function; json backed_up_json_input; // storage for input json bool have_monopole_self_energy = false; bool have_dipole_self_energy = false; void registerSelfEnergy(PairPotential*); //!< helper func to add to selv_energy_vector - EnergyFunctor - combinePairPotentials(json& potential_array); // parse json array of potentials to a single pair-energy functor + EnergyFunctor combinePairPotentials( + json& potential_array); // parse json array of potentials to a single pair-energy functor protected: - PairMatrix umatrix; // matrix with potential for each atom pair; cannot be Eigen matrix + PairMatrix + umatrix; // matrix with potential for each atom pair; cannot be Eigen matrix void from_json(const json& j) override; public: explicit FunctorPotential(const std::string& name = "functor potential"); void to_json(json& j) const override; - inline double operator()(const Particle& particle_a, const Particle& particle_b, const double squared_distance, - const Point& b_towards_a = {0, 0, 0}) const override { - return umatrix(particle_a.id, particle_b.id)(particle_a, particle_b, squared_distance, b_towards_a); + inline double operator()(const Particle& particle_a, const Particle& particle_b, + const double squared_distance, + const Point& b_towards_a = {0, 0, 0}) const override + { + return umatrix(particle_a.id, particle_b.id)(particle_a, particle_b, squared_distance, + b_towards_a); } }; @@ -602,9 +685,11 @@ class FunctorPotential : public PairPotential { * * @todo Add force */ -class SplinedPotential : public FunctorPotential { +class SplinedPotential : public FunctorPotential +{ /** @brief Expand spline data class to hold information about the sign of values for r::data { + class KnotData : public Tabulate::TabulatorBase::data + { public: using base = Tabulate::TabulatorBase::data; bool hardsphere_repulsion = false; //!< Use hardsphere repulsion for r smaller than rmin @@ -615,14 +700,15 @@ class SplinedPotential : public FunctorPotential { PairMatrix matrix_of_knots; //!< Matrix with tabulated potential for each atom pair Tabulate::Andrea spline; //!< Spline method bool hardsphere_repulsion = false; //!< Use hardsphere repulsion for r smaller than rmin - const int max_iterations = 1e6; //!< Max number of iterations when determining spline interval + const int max_iterations = 1e6; //!< Max number of iterations when determining spline interval void streamPairPotential(std::ostream& stream, const size_t id1, - const size_t id2); //!< Stream pair potential to output stream - void savePotentials(); //!< Save splined and exact pair potentials to disk + const size_t id2); //!< Stream pair potential to output stream + void savePotentials(); //!< Save splined and exact pair potentials to disk double findLowerDistance(int, int, double, double); //!< Find lower distance for splining (rmin) double findUpperDistance(int, int, double, double); //!< Find upper distance for splining (rmax) - double dr = 1e-2; //!< Distance interval when searching for rmin and rmax - void createKnots(int, int, double, double); //!< Create spline knots for pair of particles in [rmin:rmax] + double dr = 1e-2; //!< Distance interval when searching for rmin and rmax + void createKnots(int, int, double, + double); //!< Create spline knots for pair of particles in [rmin:rmax] void from_json(const json& j) override; public: @@ -636,8 +722,10 @@ class SplinedPotential : public FunctorPotential { * 3. return infinity if r<=rmin AND `hardsphere_repulsion` has been set to true * 4. return exact energy if r<=rmin */ - inline double operator()(const Particle& particle_a, const Particle& particle_b, double squared_distance, - [[maybe_unused]] const Point& b_towards_a) const override { + inline double operator()(const Particle& particle_a, const Particle& particle_b, + double squared_distance, + [[maybe_unused]] const Point& b_towards_a) const override + { const auto& knots = matrix_of_knots(particle_a.id, particle_b.id); if (squared_distance >= knots.rmax2) { return 0.0; @@ -648,8 +736,9 @@ class SplinedPotential : public FunctorPotential { if (knots.hardsphere_repulsion) { return pc::infty; } - return FunctorPotential::operator()(particle_a, particle_b, squared_distance, {0, 0, 0}); // exact energy + return FunctorPotential::operator()(particle_a, particle_b, squared_distance, + {0, 0, 0}); // exact energy } }; -} // namespace Faunus::Potential \ No newline at end of file +} // namespace Faunus::pairpotential \ No newline at end of file diff --git a/src/potentials_base.h b/src/potentials_base.h index c0c734ea1..3eb6d5447 100644 --- a/src/potentials_base.h +++ b/src/potentials_base.h @@ -12,7 +12,8 @@ namespace Faunus::pairpotential { using TPairMatrix = Eigen::MatrixXd; using TPairMatrixPtr = std::shared_ptr; -//! type of a function extracting a potential coefficient from the InteractionData, e.g., sigma or eps +//! type of a function extracting a potential coefficient from the InteractionData, e.g., sigma or +//! eps using TExtractorFunc = std::function; //! type of a function defining a combination rule of a heterogeneous pair interaction @@ -26,7 +27,8 @@ using TModifierFunc = std::function; * * The very same format is used as for a homogeneous interaction specified directly on an atom type. */ -struct CustomInteractionData { +struct CustomInteractionData +{ std::array atom_id; InteractionData interaction; }; @@ -41,17 +43,25 @@ void from_json(const json& j, std::vector& interactions); * When adding a new one, add a json mapping. Also consider appending the PairMixer::getCombinator() * method to recognize the new rule. */ -enum class CombinationRuleType { UNDEFINED, ARITHMETIC, GEOMETRIC, LORENTZ_BERTHELOT }; -NLOHMANN_JSON_SERIALIZE_ENUM(CombinationRuleType, {{CombinationRuleType::UNDEFINED, "undefined"}, - {CombinationRuleType::ARITHMETIC, "arithmetic"}, - {CombinationRuleType::GEOMETRIC, "geometric"}, - {CombinationRuleType::LORENTZ_BERTHELOT, "lorentz_berthelot"}, - {CombinationRuleType::LORENTZ_BERTHELOT, "LB"}}) +enum class CombinationRuleType +{ + UNDEFINED, + ARITHMETIC, + GEOMETRIC, + LORENTZ_BERTHELOT +}; +NLOHMANN_JSON_SERIALIZE_ENUM(CombinationRuleType, + {{CombinationRuleType::UNDEFINED, "undefined"}, + {CombinationRuleType::ARITHMETIC, "arithmetic"}, + {CombinationRuleType::GEOMETRIC, "geometric"}, + {CombinationRuleType::LORENTZ_BERTHELOT, "lorentz_berthelot"}, + {CombinationRuleType::LORENTZ_BERTHELOT, "LB"}}) /** * @brief Exception for handling pair potential initialization. */ -struct PairPotentialException : public std::runtime_error { +struct PairPotentialException : public std::runtime_error +{ explicit PairPotentialException(const std::string& msg); }; @@ -59,18 +69,22 @@ struct PairPotentialException : public std::runtime_error { * @brief PairMixer creates a matrix of pair potential coefficients based on the atom properties * and/or custom values using an arbitrary combination rule. * - * PairMixer holds three functions that are applied in order extractor β†’ combinator β†’ modifier to create - * a coefficient matrix for all possible interactions. The function createPairMatrix applies the functions - * on all atom type pairs, and optionally also on the list of custom pair parameters (not the combinator - * function). + * PairMixer holds three functions that are applied in order extractor β†’ combinator β†’ modifier to + * create a coefficient matrix for all possible interactions. The function createPairMatrix applies + * the functions on all atom type pairs, and optionally also on the list of custom pair parameters + * (not the combinator function). */ -class PairMixer { - TExtractorFunc extractor; //!< Function extracting the coefficient from the InteractionData structure +class PairMixer +{ + TExtractorFunc + extractor; //!< Function extracting the coefficient from the InteractionData structure TCombinatorFunc combinator; //!< Function combining two values - TModifierFunc modifier; //!< Function modifying the result for fast computations, e.g., a square of + TModifierFunc + modifier; //!< Function modifying the result for fast computations, e.g., a square of public: - PairMixer(TExtractorFunc extractor, TCombinatorFunc combinator, TModifierFunc modifier = &modIdentity); + PairMixer(TExtractorFunc extractor, TCombinatorFunc combinator, + TModifierFunc modifier = &modIdentity); //! @return a square matrix of atoms.size() TPairMatrixPtr createPairMatrix(const std::vector& atoms); @@ -78,17 +92,27 @@ class PairMixer { TPairMatrixPtr createPairMatrix(const std::vector& atoms, const std::vector& interactions); - enum class CoefficientType { ANY, SIGMA, EPSILON }; + enum class CoefficientType + { + ANY, + SIGMA, + EPSILON + }; static TCombinatorFunc getCombinator(CombinationRuleType combination_rule, CoefficientType coefficient = CoefficientType::ANY); // when explicit custom pairs are the only option - inline static constexpr double combUndefined(double = 0.0, double = 0.0) { + inline static constexpr double combUndefined(double = 0.0, double = 0.0) + { return std::numeric_limits::signaling_NaN(); }; + inline static double combArithmetic(double a, double b) { return 0.5 * (a + b); } + inline static double combGeometric(double a, double b) { return std::sqrt(a * b); } + inline static double modIdentity(double x) { return x; } + inline static double modSquared(double x) { return x * x; } }; @@ -105,12 +129,14 @@ class PairMixer { * convenience as we in this way can register the self energy during processing * of the pair potential input. */ -class PairPotential { +class PairPotential +{ private: friend void from_json(const json&, PairPotential&); public: - std::string name; //!< unique name per polymorphic call; used in FunctorPotential::combinePairPotentials + std::string + name; //!< unique name per polymorphic call; used in FunctorPotential::combinePairPotentials std::string cite; //!< Typically a short-doi litterature reference bool isotropic = true; //!< true if pair-potential is independent of particle orientation std::function selfEnergy = nullptr; //!< self energy of particle (kT) @@ -126,7 +152,8 @@ class PairPotential { * @param b_towards_a Distance vector 𝐛 -> 𝐚 = 𝐚 - 𝐛 * @return Force acting on a dur to b in units of kT/Γ… */ - [[nodiscard]] virtual Point force(const Particle& a, const Particle& b, double squared_distance, const Point& b_towards_a) const; + [[nodiscard]] virtual Point force(const Particle& a, const Particle& b, double squared_distance, + const Point& b_towards_a) const; /** * @brief Pair energy between two particles @@ -136,11 +163,11 @@ class PairPotential { * @param b_towards_a Distance vector 𝐛 -> 𝐚 = 𝐚 - 𝐛 * @return Interaction energy in units of kT */ - virtual double operator()(const Particle& particle_a, const Particle& particle_b, double squared_distance, - const Point& b_towards_a) const = 0; + virtual double operator()(const Particle& particle_a, const Particle& particle_b, + double squared_distance, const Point& b_towards_a) const = 0; protected: - explicit PairPotential(std::string name = std::string(), std::string cite = std::string(), + explicit PairPotential(std::string name = std::string(), std::string cite = std::string(), bool isotropic = true); }; @@ -154,7 +181,8 @@ template concept RequirePairPotential = std::derived_from; /** @brief Convenience function to generate a pair potential initialized from JSON object */ -template auto makePairPotential(const json& j) { +template auto makePairPotential(const json& j) +{ T pair_potential; pairpotential::from_json(j, pair_potential); return pair_potential; @@ -164,22 +192,27 @@ template auto makePairPotential(const json& j) { * @brief A common ancestor for potentials that use parameter matrices computed from atomic * properties and/or custom atom pair properties. * - * The class and their descendants have now also a responsibility to create themselves from a json object - * and store back. This is gradually becoming a complex task which shall be moved into other class. + * The class and their descendants have now also a responsibility to create themselves from a json + * object and store back. This is gradually becoming a complex task which shall be moved into other + * class. */ -class MixerPairPotentialBase : public PairPotential { +class MixerPairPotentialBase : public PairPotential +{ protected: CombinationRuleType combination_rule; std::shared_ptr> custom_pairs = std::make_shared>(); - json json_extra_params; //!< pickled extra parameters like a coefficient names mapping - void init(); //!< initialize the potential when data, e.g., atom parameters, are available - virtual void initPairMatrices() = 0; //!< potential-specific initialization of parameter matrices - virtual void extractorsFromJson(const json&); //!< potential-specific assignment of coefficient extracting functions + json json_extra_params; //!< pickled extra parameters like a coefficient names mapping + void init(); //!< initialize the potential when data, e.g., atom parameters, are available + virtual void + initPairMatrices() = 0; //!< potential-specific initialization of parameter matrices + virtual void extractorsFromJson( + const json&); //!< potential-specific assignment of coefficient extracting functions public: - explicit MixerPairPotentialBase(const std::string& name = std::string(), const std::string& cite = std::string(), - CombinationRuleType combination_rule = CombinationRuleType::UNDEFINED, - bool isotropic = true); + explicit MixerPairPotentialBase( + const std::string& name = std::string(), const std::string& cite = std::string(), + CombinationRuleType combination_rule = CombinationRuleType::UNDEFINED, + bool isotropic = true); ~MixerPairPotentialBase() override = default; void from_json(const json& j) override; void to_json(json& j) const override; @@ -191,13 +224,18 @@ class MixerPairPotentialBase : public PairPotential { * This is the most efficient way to combining pair-potentials due * to the possibility for compile-time optimisation. */ -template struct CombinedPairPotential : public PairPotential { +template +struct CombinedPairPotential : public PairPotential +{ T1 first; //!< First pair potential of type T1 T2 second; //!< Second pair potential of type T2 explicit CombinedPairPotential(const std::string& name = "") - : PairPotential(name){}; - inline double operator()(const Particle& particle_a, const Particle& particle_b, const double squared_distance, - const Point& b_towards_a = {0, 0, 0}) const override { + : PairPotential(name) {}; + + inline double operator()(const Particle& particle_a, const Particle& particle_b, + const double squared_distance, + const Point& b_towards_a = {0, 0, 0}) const override + { return first(particle_a, particle_b, squared_distance, b_towards_a) + second(particle_a, particle_b, squared_distance, b_towards_a); } //!< Combine pair energy @@ -210,13 +248,16 @@ template struct CombinedPairP * @param b_towards_a Distance vector 𝐛 -> 𝐚 = 𝐚 - 𝐛 * @return Force on particle a due to particle b */ - [[nodiscard]] inline Point force(const Particle& particle_a, const Particle& particle_b, const double squared_distance, - const Point& b_towards_a) const override { + [[nodiscard]] inline Point force(const Particle& particle_a, const Particle& particle_b, + const double squared_distance, + const Point& b_towards_a) const override + { return first.force(particle_a, particle_b, squared_distance, b_towards_a) + second.force(particle_a, particle_b, squared_distance, b_towards_a); } //!< Combine force - void from_json(const json& j) override { + void from_json(const json& j) override + { Faunus::pairpotential::from_json(j, first); Faunus::pairpotential::from_json(j, second); name = first.name + "/" + second.name; @@ -230,11 +271,14 @@ template struct CombinedPairP } return u2(p); }; - } else { + } + else { selfEnergy = nullptr; } } - void to_json(json& j) const override { + + void to_json(json& j) const override + { assert(j.is_object()); auto& _j = j["default"] = json::array(); _j.push_back(first); @@ -242,4 +286,4 @@ template struct CombinedPairP } }; -} // namespace Faunus::Potential \ No newline at end of file +} // namespace Faunus::pairpotential \ No newline at end of file diff --git a/src/pyfaunus.cpp b/src/pyfaunus.cpp index cd594eef0..01e851b6f 100644 --- a/src/pyfaunus.cpp +++ b/src/pyfaunus.cpp @@ -23,13 +23,15 @@ using Tgroup = typename Space::GroupType; using Thamiltonian = Energy::Hamiltonian; using Tmcsimulation = MetropolisMonteCarlo; -template std::unique_ptr from_dict(py::dict dict) { +template std::unique_ptr from_dict(py::dict dict) +{ auto ptr = std::make_unique(); *ptr = static_cast(json(dict)); return ptr; } // convert py::dict to T through Faunus::json -PYBIND11_MODULE(pyfaunus, m) { +PYBIND11_MODULE(pyfaunus, m) +{ using namespace pybind11::literals; // Random @@ -51,7 +53,8 @@ PYBIND11_MODULE(pyfaunus, m) { .def("getVolume", &Geometry::GeometryBase::getVolume, "Get container volume", "dim"_a = 3) .def("setVolume", &Geometry::GeometryBase::setVolume, "Set container volume", "volume"_a, "method"_a = Geometry::VolumeMethod::ISOTROPIC) - .def("collision", &Geometry::GeometryBase::collision, "pos"_a, "Checks if point is inside container") + .def("collision", &Geometry::GeometryBase::collision, "pos"_a, + "Checks if point is inside container") .def("getLength", &Geometry::GeometryBase::getLength, "Get cuboid sidelengths") .def("vdist", &Geometry::GeometryBase::vdist, "Minimum vector distance, a-b", "a"_a, "b"_a) .def("randompos", @@ -87,7 +90,8 @@ PYBIND11_MODULE(pyfaunus, m) { Faunus::Geometry::from_json(dict, *ptr); return ptr; })) - .def("sqdist", &Geometry::Chameleon::sqdist, "Squared minimum distance, |a-b|^2", "a"_a, "b"_a); + .def("sqdist", &Geometry::Chameleon::sqdist, "Squared minimum distance, |a-b|^2", "a"_a, + "b"_a); // Particle properties py::class_(m, "Charge") @@ -108,9 +112,10 @@ PYBIND11_MODULE(pyfaunus, m) { auto _pvec = py::bind_vector(m, "ParticleVector"); _pvec - .def( - "positions", - [](ParticleVector& particles) { return asEigenMatrix(particles.begin(), particles.end(), &Particle::pos); }) + .def("positions", + [](ParticleVector& particles) { + return asEigenMatrix(particles.begin(), particles.end(), &Particle::pos); + }) .def("charges", [](ParticleVector& particles) { return asEigenVector(particles.begin(), particles.end(), &Particle::charge); @@ -120,13 +125,15 @@ PYBIND11_MODULE(pyfaunus, m) { // Group py::class_(m, "Group") - .def(py::init()) + .def(py::init()) .def_readwrite("groups", &Tgroup::id, "Molecule id") .def_readwrite("id", &Tgroup::id, "Molecule id") .def_readwrite("cm", &Tgroup::mass_center, "Center of mass") .def("__len__", [](Tgroup& self) { return self.size(); }) .def( - "__iter__", [](Tgroup& v) { return py::make_iterator(v.begin(), v.end()); }, py::keep_alive<0, 1>()) + "__iter__", [](Tgroup& v) { return py::make_iterator(v.begin(), v.end()); }, + py::keep_alive<0, 1>()) .def("isAtomic", &Tgroup::isAtomic) .def("isMolecular", &Tgroup::isMolecular) .def("traits", &Tgroup::traits) @@ -161,12 +168,14 @@ PYBIND11_MODULE(pyfaunus, m) { "sigma", [](const AtomData& a) { return a.interaction.at("sigma"); }, [](AtomData& a, double val) { a.interaction.at("sigma") = val; }) .def_readwrite("name", &AtomData::name) - .def_readwrite("activity", &AtomData::activity, "Activity = chemical potential in log scale (mol/l)") + .def_readwrite("activity", &AtomData::activity, + "Activity = chemical potential in log scale (mol/l)") .def("id", (const AtomData::index_type& (AtomData::*)() const) & AtomData::id); // explicit signature due to overload in c++ auto _atomdatavec = py::bind_vector>(m, "AtomDataVector"); - _atomdatavec.def("from_list", [](std::vector& a, py::list list) { Faunus::from_json(list, a); }); + _atomdatavec.def("from_list", + [](std::vector& a, py::list list) { Faunus::from_json(list, a); }); m.attr("atoms") = &Faunus::atoms; // global instance @@ -183,8 +192,8 @@ PYBIND11_MODULE(pyfaunus, m) { .def_readwrite("isotropic", &pairpotential::PairPotential::isotropic) .def_readwrite("selfEnergy", &pairpotential::PairPotential::selfEnergy) .def("force", &pairpotential::PairPotential::force) - .def("energy", [](pairpotential::PairPotential& pot, const Particle& a, const Particle& b, double r2, - const Point& r) { return pot(a, b, r2, r); }); + .def("energy", [](pairpotential::PairPotential& pot, const Particle& a, const Particle& b, + double r2, const Point& r) { return pot(a, b, r2, r); }); // Potentials::FunctorPotential py::class_(m, "FunctorPotential") @@ -226,7 +235,8 @@ PYBIND11_MODULE(pyfaunus, m) { // Hamiltonian py::class_(m, "Hamiltonian") .def(py::init()) - .def(py::init([](Space& spc, py::list list) { return std::make_unique(spc, list); })) + .def(py::init( + [](Space& spc, py::list list) { return std::make_unique(spc, list); })) .def("init", &Thamiltonian::init) .def("energy", &Thamiltonian::energy); diff --git a/src/random.cpp b/src/random.cpp index 12614098f..77dcd7fa2 100644 --- a/src/random.cpp +++ b/src/random.cpp @@ -7,53 +7,70 @@ namespace Faunus { -void from_json(const nlohmann::json &j, Random & rng) { +void from_json(const nlohmann::json& j, Random& rng) +{ if (j.is_object()) { auto seed = j.value("seed", std::string()); try { if (seed == "default" or seed == "fixed") { // use default seed, i.e. do nothing return; - } else if (seed == "hardware") { // use hardware seed + } + else if (seed == "hardware") { // use hardware seed rng.engine = decltype(rng.engine)(std::random_device()()); - } else if (!seed.empty()) { // read engine state + } + else if (!seed.empty()) { // read engine state std::stringstream stream(seed); stream.exceptions(std::ios::badbit | std::ios::failbit); stream >> rng.engine; } - } catch (std::exception &e) { - std::cerr << "could not initialize rng engine - falling back to fixed seed." << std::endl; + } + catch (std::exception& e) { + std::cerr << "could not initialize rng engine - falling back to fixed seed." + << std::endl; } } } -void to_json(nlohmann::json &j, const Random &random) { +void to_json(nlohmann::json& j, const Random& random) +{ std::ostringstream stream; stream << random.engine; // dump engine state to stream j["seed"] = stream.str(); if constexpr (std::is_same::value) { j["engine"] = "Mersenne Twister (std::mt19937)"; - } else { + } + else { j["engine"] = "Permuted Congruential Generator (pcg32)"; } } -void Random::seed() { engine = RandomNumberEngine(std::random_device()()); } +void Random::seed() +{ + engine = RandomNumberEngine(std::random_device()()); +} -Random::Random() : dist01(0, 1) {} +Random::Random() + : dist01(0, 1) +{ +} -double Random::operator()() { return dist01(engine); } +double Random::operator()() +{ + return dist01(engine); +} Random random; // Global instance } // namespace Faunus #ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("[Faunus] Random") { +TEST_CASE("[Faunus] Random") +{ using namespace Faunus; Random slump, slump2; // local instances - CHECK_EQ(slump(), slump2()); // deterministic initialization by default; the global random variable cannot - // be used for comparison as its state is not reset at the beginning of - // each test case + CHECK_EQ(slump(), slump2()); // deterministic initialization by default; the global random + // variable cannot be used for comparison as its state is not reset + // at the beginning of each test case int min = 10, max = 0, N = 1e6; double x = 0; @@ -83,7 +100,8 @@ TEST_CASE("[Faunus] Random") { CHECK((a() != b())); } -TEST_CASE("[Faunus] WeightedDistribution") { +TEST_CASE("[Faunus] WeightedDistribution") +{ using namespace Faunus; WeightedDistribution v; diff --git a/src/random.h b/src/random.h index 49b1703c1..94090e0da 100644 --- a/src/random.h +++ b/src/random.h @@ -29,14 +29,15 @@ using RandomNumberEngine = std::mt19937; * r1.seed(); // non-deterministic seed * ``` */ -class Random { +class Random +{ private: std::uniform_real_distribution dist01; //!< Uniform real distribution [0,1) public: RandomNumberEngine engine; //!< Random number engine used for all operations - Random(); //!< Constructor with deterministic seed - void seed(); //!< Set a non-deterministic ("hardware") seed - double operator()(); //!< Random double in uniform range [0,1) + Random(); //!< Constructor with deterministic seed + void seed(); //!< Set a non-deterministic ("hardware") seed + double operator()(); //!< Random double in uniform range [0,1) /** * @brief Integer in closed interval [min:max] @@ -44,7 +45,8 @@ class Random { * @param max maximum value (included) * @return random integer in [min:max] range */ - auto range(std::integral auto min, std::integral auto max) { + auto range(std::integral auto min, std::integral auto max) + { return std::uniform_int_distribution<>(min, max)(engine); } @@ -54,14 +56,15 @@ class Random { * @param end End iterator * @return Iterator to random element */ - template Iterator sample(Iterator begin, Iterator end) { + template Iterator sample(Iterator begin, Iterator end) + { std::advance(begin, range<>(0, std::distance(begin, end) - 1)); return begin; } }; -void to_json(nlohmann::json &, const Random &); //!< Random to json conversion -void from_json(const nlohmann::json &, Random &); //!< json to Random conversion +void to_json(nlohmann::json&, const Random&); //!< Random to json conversion +void from_json(const nlohmann::json&, Random&); //!< json to Random conversion extern Random random; //!< global instance of Random @@ -75,21 +78,27 @@ extern Random random; //!< global instance of Random * * @tparam T Data type to store */ -template class WeightedDistribution { +template class WeightedDistribution +{ private: std::discrete_distribution<> distribution; std::vector weights; //!< weights for each data point size_t latest_index; //!< index from latest element access via addGroup or get public: - std::vector data; //!< raw vector of T - auto size() const { return data.size(); } //!< Number of data points + std::vector data; //!< raw vector of T + + auto size() const { return data.size(); } //!< Number of data points + [[nodiscard]] bool empty() const { return data.empty(); } //!< True if no data points - [[nodiscard]] size_t getLastIndex() const { + + [[nodiscard]] size_t getLastIndex() const + { assert(!data.empty()); return latest_index; } //!< index of last `get()` or `addGroup()` element - void clear() { + void clear() + { data.clear(); weights.clear(); } //!< Clear all data @@ -102,14 +111,16 @@ template class WeightedDistribution { * The iterable range must match the `size()` of the stored data, * otherwise an exception is thrown. */ - template void setWeight(Iterator begin, Iterator end) { + template void setWeight(Iterator begin, Iterator end) + { static_assert(std::is_convertible_v, double>); if (auto size = std::distance(begin, end); size == data.size()) { weights.resize(size); std::copy(begin, end, weights.begin()); distribution = std::discrete_distribution(weights.begin(), weights.end()); assert(size_t(distribution.max()) == data.size() - 1); - } else { + } + else { throw std::runtime_error("number of weights must match data"); } } @@ -119,7 +130,8 @@ template class WeightedDistribution { * @param value data * @param weight weight (default: 1.0) */ - void push_back(const T &value, double weight = 1.0) { + void push_back(const T& value, double weight = 1.0) + { data.push_back(value); weights.push_back(weight); setWeight(weights.begin(), weights.end()); @@ -131,10 +143,11 @@ template class WeightedDistribution { * @param engine Random number engine * @return Reference to data point */ - template const T &sample(RandomGenerator &engine) { + template const T& sample(RandomGenerator& engine) + { assert(not empty()); latest_index = distribution(engine); return data.at(latest_index); } }; -} // namespace \ No newline at end of file +} // namespace Faunus \ No newline at end of file diff --git a/src/reactioncoordinate.cpp b/src/reactioncoordinate.cpp index 1bdddaa9a..8b60d23e0 100644 --- a/src/reactioncoordinate.cpp +++ b/src/reactioncoordinate.cpp @@ -13,7 +13,8 @@ namespace Faunus::ReactionCoordinate { -ReactionCoordinateBase::ReactionCoordinateBase(const json& j) { +ReactionCoordinateBase::ReactionCoordinateBase(const json& j) +{ auto range = j.value("range", std::vector({0.0, 0.0})); if (range.size() != 2 || range[0] > range[1]) { throw std::runtime_error(name + ": 'range' requires [min, max>=min]"); @@ -25,14 +26,19 @@ ReactionCoordinateBase::ReactionCoordinateBase(const json& j) { void ReactionCoordinateBase::_to_json([[maybe_unused]] json& j) const {} -double ReactionCoordinateBase::operator()() { +double ReactionCoordinateBase::operator()() +{ assert(function != nullptr); return function(); } -bool ReactionCoordinateBase::inRange(double coord) const { return (coord >= minimum_value && coord <= maximum_value); } +bool ReactionCoordinateBase::inRange(double coord) const +{ + return (coord >= minimum_value && coord <= maximum_value); +} -void to_json(json& j, const ReactionCoordinateBase& reaction_coordinate) { +void to_json(json& j, const ReactionCoordinateBase& reaction_coordinate) +{ assert(!reaction_coordinate.name.empty()); auto& _j = j[reaction_coordinate.name]; _j = {{"range", {reaction_coordinate.minimum_value, reaction_coordinate.maximum_value}}, @@ -40,12 +46,14 @@ void to_json(json& j, const ReactionCoordinateBase& reaction_coordinate) { reaction_coordinate._to_json(_j); } -} // namespace +} // namespace Faunus::ReactionCoordinate #ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("[Faunus] ReactionCoordinateBase") { +TEST_CASE("[Faunus] ReactionCoordinateBase") +{ using doctest::Approx; - Faunus::ReactionCoordinate::ReactionCoordinateBase c(R"({"range":[-1.5, 2.1], "resolution":0.2})"_json); + Faunus::ReactionCoordinate::ReactionCoordinateBase c( + R"({"range":[-1.5, 2.1], "resolution":0.2})"_json); CHECK_EQ(c.minimum_value, Approx(-1.5)); CHECK_EQ(c.maximum_value, Approx(2.1)); CHECK_EQ(c.resolution, Approx(0.2)); @@ -64,7 +72,8 @@ namespace Faunus::ReactionCoordinate { * * atom: {resolution: 0.1, ... } */ -std::unique_ptr createReactionCoordinate(const json& j, const Space& spc) { +std::unique_ptr createReactionCoordinate(const json& j, const Space& spc) +{ try { const auto& [key, j_params] = jsonSingleItem(j); try { @@ -78,59 +87,80 @@ std::unique_ptr createReactionCoordinate(const json& j, return std::make_unique(j_params, spc); } throw ConfigurationError("unknown reaction coordinate"); - } catch (std::exception& e) { + } + catch (std::exception& e) { usageTip.pick(fmt::format("coords=[{}]", key)); throw ConfigurationError("'{}': {}", key, e.what()); } - } catch (std::exception& e) { throw ConfigurationError("reaction coordinate: {}", e.what()).attachJson(j); } + } + catch (std::exception& e) { + throw ConfigurationError("reaction coordinate: {}", e.what()).attachJson(j); + } } -void SystemProperty::_to_json(json &j) const { j["property"] = property; } +void SystemProperty::_to_json(json& j) const +{ + j["property"] = property; +} -SystemProperty::SystemProperty(const json &j, const Space &spc) : ReactionCoordinateBase(j) { +SystemProperty::SystemProperty(const json& j, const Space& spc) + : ReactionCoordinateBase(j) +{ namespace rv = ranges::cpp20::views; name = "system"; property = j.at("property").get(); if (property == "V") { function = [&geometry = spc.geometry]() { return geometry.getVolume(); }; - } else if (property == "Lx") { + } + else if (property == "Lx") { function = [&geometry = spc.geometry]() { return geometry.getLength().x(); }; - } else if (property == "Ly") { + } + else if (property == "Ly") { function = [&geometry = spc.geometry]() { return geometry.getLength().y(); }; - } else if (property == "Lz" or property == "height") { + } + else if (property == "Lz" or property == "height") { function = [&geometry = spc.geometry]() { return geometry.getLength().z(); }; - } else if (property == "radius") { - if (spc.geometry.type == Geometry::Variant::CUBOID or spc.geometry.type == Geometry::Variant::SLIT) { + } + else if (property == "radius") { + if (spc.geometry.type == Geometry::Variant::CUBOID or + spc.geometry.type == Geometry::Variant::SLIT) { faunus_logger->warn("`radius` coordinate unavailable for geometry"); - } else { + } + else { function = [&geometry = spc.geometry]() { return 0.5 * geometry.getLength().x(); }; } - } else if (property == "Q") { // system net charge + } + else if (property == "Q") { // system net charge function = [&spc] { auto charges = spc.groups | rv::join | rv::transform(&Particle::charge); return std::accumulate(charges.begin(), charges.end(), 0.0); }; - } else if (property == "mu") { // system dipole moment + } + else if (property == "mu") { // system dipole moment function = [&spc]() { auto particles = spc.groups | rv::join; return Faunus::dipoleMoment(particles.begin(), particles.end()).norm(); }; - } else if (property == "mu_x") { // system dipole moment + } + else if (property == "mu_x") { // system dipole moment function = [&spc]() { auto particles = spc.groups | rv::join; return Faunus::dipoleMoment(particles.begin(), particles.end()).x(); }; - } else if (property == "mu_y") { // system dipole moment + } + else if (property == "mu_y") { // system dipole moment function = [&spc]() { auto particles = spc.groups | rv::join; return Faunus::dipoleMoment(particles.begin(), particles.end()).y(); }; - } else if (property == "mu_z") { // system dipole moment + } + else if (property == "mu_z") { // system dipole moment function = [&spc]() { auto particles = spc.groups | rv::join; return Faunus::dipoleMoment(particles.begin(), particles.end()).z(); }; - } else if (property == "N") { // number of particles + } + else if (property == "N") { // number of particles function = [&spc]() { auto sizes = spc.groups | rv::transform(&Space::GroupType::size); return static_cast(std::accumulate(sizes.begin(), sizes.end(), size_t(0))); @@ -142,7 +172,8 @@ SystemProperty::SystemProperty(const json &j, const Space &spc) : ReactionCoordi } } -void AtomProperty::_to_json(json &j) const { +void AtomProperty::_to_json(json& j) const +{ j["property"] = property; j["index"] = index; if (dir.squaredNorm() > 1e-9) { @@ -151,11 +182,13 @@ void AtomProperty::_to_json(json &j) const { } /** - * @warning For the lambda capture, always capture "Space&" and not unerlying objects like `particles` or `groups`. - * This is because the memory location of the latter may be modified after the lambda cration, thus - * leading to undefined dereferencing. + * @warning For the lambda capture, always capture "Space&" and not unerlying objects like + * `particles` or `groups`. This is because the memory location of the latter may be modified after + * the lambda cration, thus leading to undefined dereferencing. */ -AtomProperty::AtomProperty(const json &j, const Space &spc) : ReactionCoordinateBase(j) { +AtomProperty::AtomProperty(const json& j, const Space& spc) + : ReactionCoordinateBase(j) +{ name = "atom"; index = j.at("index"); if (index >= spc.particles.size()) { @@ -164,18 +197,25 @@ AtomProperty::AtomProperty(const json &j, const Space &spc) : ReactionCoordinate property = j.at("property").get(); if (property == "x") { function = [&spc, i = index]() { return spc.particles.at(i).pos.x(); }; - } else if (property == "y") { + } + else if (property == "y") { function = [&spc, i = index]() { return spc.particles.at(i).pos.y(); }; - } else if (property == "z") { + } + else if (property == "z") { function = [&spc, i = index]() { return spc.particles.at(i).pos.z(); }; - } else if (property == "R") { + } + else if (property == "R") { function = [&spc, i = index]() { return spc.particles.at(i).pos.norm(); }; - } else if (property == "q") { + } + else if (property == "q") { function = [&spc, i = index]() { return spc.particles.at(i).charge; }; - } else if (property == "N") { + } + else if (property == "N") { function = [&spc, id = index]() { - return static_cast(ranges::cpp20::count_if( - spc.activeParticles(), [&](const Particle& particle) { return particle.id == id; })); + return static_cast( + ranges::cpp20::count_if(spc.activeParticles(), [&](const Particle& particle) { + return particle.id == id; + })); }; } @@ -185,7 +225,8 @@ AtomProperty::AtomProperty(const json &j, const Space &spc) : ReactionCoordinate } } -void MoleculeProperty::_to_json(json& j) const { +void MoleculeProperty::_to_json(json& j) const +{ j["property"] = property; j["index"] = index; if (direction.squaredNorm() > 1e-9) { @@ -197,11 +238,13 @@ void MoleculeProperty::_to_json(json& j) const { } /** - * @warning For the lambda capture, always capture "Space&" and not unerlying objects like `particle` or `group`. - * This is because the memory location of the latter may be modified after the lambda cration, thus - * leading to undefined dereferencing. + * @warning For the lambda capture, always capture "Space&" and not unerlying objects like + * `particle` or `group`. This is because the memory location of the latter may be modified after + * the lambda cration, thus leading to undefined dereferencing. */ -MoleculeProperty::MoleculeProperty(const json &j, const Space &spc) : ReactionCoordinateBase(j) { +MoleculeProperty::MoleculeProperty(const json& j, const Space& spc) + : ReactionCoordinateBase(j) +{ name = "molecule"; index = j.value("index", 0); if (index >= spc.groups.size()) { @@ -213,64 +256,91 @@ MoleculeProperty::MoleculeProperty(const json &j, const Space &spc) : ReactionCo if (property == "active") { // if molecule is active (1) or not (0) function = [&spc, i = index]() { return static_cast(!spc.groups.at(i).empty()); }; - } else if (property == "confid") { - function = [&spc, i = index]() { return static_cast(spc.groups.at(i).conformation_id); }; - } else if (property == "com_x") { + } + else if (property == "confid") { + function = [&spc, i = index]() { + return static_cast(spc.groups.at(i).conformation_id); + }; + } + else if (property == "com_x") { function = [&spc, i = index]() { return spc.groups.at(i).mass_center.x(); }; - } else if (property == "com_y") { + } + else if (property == "com_y") { function = [&spc, i = index]() { return spc.groups.at(i).mass_center.y(); }; - } else if (property == "com_z") { + } + else if (property == "com_z") { function = [&spc, i = index]() { return spc.groups.at(i).mass_center.z(); }; - } else if (property == "N") { + } + else if (property == "N") { function = [&spc, i = index]() { return static_cast(spc.groups.at(i).size()); }; - } else if (property == "Q") { - function = [&spc, i = index]() { return monopoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end()); }; - } else if (property == "mu_x") { + } + else if (property == "Q") { + function = [&spc, i = index]() { + return monopoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end()); + }; + } + else if (property == "mu_x") { function = [&spc, i = index, b]() { return dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).x(); }; - } else if (property == "mu_y") { + } + else if (property == "mu_y") { function = [&spc, i = index, b]() { return dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).y(); }; - } else if (property == "mu_z") { + } + else if (property == "mu_z") { function = [&spc, i = index, b]() { return dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).z(); }; - } else if (property == "mu") { + } + else if (property == "mu") { function = [&spc, i = index, b]() { return dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).norm(); }; - } else if (property == "end2end") { + } + else if (property == "end2end") { function = [&spc, i = index]() { - return std::sqrt( - spc.geometry.sqdist(spc.groups.at(i).begin()->pos, std::prev(spc.groups.at(i).end())->pos)); + return std::sqrt(spc.geometry.sqdist(spc.groups.at(i).begin()->pos, + std::prev(spc.groups.at(i).end())->pos)); }; - } else if (property == "Rg") { + } + else if (property == "Rg") { selectGyrationRadius(spc); - } else if (property == "muangle") { + } + else if (property == "muangle") { selectDipoleAngle(j, spc, b); - } else if (property == "atomatom") { + } + else if (property == "atomatom") { selectAtomAtomDistance(j, spc); - } else if (property == "cmcm_z") { + } + else if (property == "cmcm_z") { selectMassCenterDistanceZ(j, spc); - } else if (property == "cmcm") { + } + else if (property == "cmcm") { selectMassCenterDistance(j, spc); - } else if (property == "L/R") { + } + else if (property == "L/R") { selectLengthOverRadiusRatio(j, spc); - } else if (property == "mindist") { + } + else if (property == "mindist") { selectMinimumGroupDistance(j, spc); - } else if (property == "Rinner") { + } + else if (property == "Rinner") { selectRinner(j, spc); - } else if (property == "angle") { + } + else if (property == "angle") { selectAngleWithVector(j, spc); } if (function == nullptr) { usageTip.pick("coords=[molecule]"); - throw ConfigurationError("{}: unknown or impossible property property '{}'", name, property); + throw ConfigurationError("{}: unknown or impossible property property '{}'", name, + property); } } -void MoleculeProperty::selectLengthOverRadiusRatio(const json& j, const Space& spc) { + +void MoleculeProperty::selectLengthOverRadiusRatio(const json& j, const Space& spc) +{ direction = j.at("dir"); indexes = j.value("indexes", decltype(indexes)()); if (indexes.size() != 2) { @@ -281,42 +351,53 @@ void MoleculeProperty::selectLengthOverRadiusRatio(const json& j, const Space& s Average Rin; Average Rout; auto particles_i = spc.findAtoms(i); - Point mass_center_i = - Geometry::massCenter(particles_i.begin(), particles_i.end(), spc.geometry.getBoundaryFunc()); + Point mass_center_i = Geometry::massCenter(particles_i.begin(), particles_i.end(), + spc.geometry.getBoundaryFunc()); for (const Particle& particle : spc.findAtoms(j)) { - mean_radius_j += spc.geometry.vdist(particle.pos, mass_center_i).cwiseProduct(dir.cast()).norm(); + mean_radius_j += spc.geometry.vdist(particle.pos, mass_center_i) + .cwiseProduct(dir.cast()) + .norm(); } const auto Rjavg = mean_radius_j.avg(); for (const auto& particle_i : particles_i) { - const auto radial_distance = - spc.geometry.vdist(particle_i.pos, mass_center_i).cwiseProduct(dir.cast()).norm(); + const auto radial_distance = spc.geometry.vdist(particle_i.pos, mass_center_i) + .cwiseProduct(dir.cast()) + .norm(); if (radial_distance < Rjavg) { Rin += radial_distance; - } else if (radial_distance > Rjavg) { + } + else if (radial_distance > Rjavg) { Rout += radial_distance; } } return 2.0 * spc.geometry.getLength().z() / (Rin.avg() + Rout.avg()); }; } -void MoleculeProperty::selectMassCenterDistanceZ(const json& j, const Space& spc) { + +void MoleculeProperty::selectMassCenterDistanceZ(const json& j, const Space& spc) +{ indexes = j.value("indexes", decltype(indexes)()); if (indexes.size() == 4) { - function = [&spc, i = indexes[0], j = indexes[1] + 1, k = indexes[2], l = indexes[3] + 1]() { + function = [&spc, i = indexes[0], j = indexes[1] + 1, k = indexes[2], + l = indexes[3] + 1]() { Point cm1 = Geometry::massCenter(spc.particles.begin() + i, spc.particles.begin() + j, spc.geometry.getBoundaryFunc()); Point cm2 = Geometry::massCenter(spc.particles.begin() + k, spc.particles.begin() + l, spc.geometry.getBoundaryFunc()); return spc.geometry.vdist(cm1, cm2).z(); }; - } else if (indexes.size() == 2) { + } + else if (indexes.size() == 2) { function = [&spc, i = indexes[0], j = indexes[1]]() { - return spc.geometry.vdist(spc.groups.at(i).mass_center, spc.groups.at(j).mass_center).z(); + return spc.geometry.vdist(spc.groups.at(i).mass_center, spc.groups.at(j).mass_center) + .z(); }; } throw ConfigurationError("An array of 2 or 4 indexes should be specified."); } -void MoleculeProperty::selectAtomAtomDistance(const json& j, const Space& spc) { + +void MoleculeProperty::selectAtomAtomDistance(const json& j, const Space& spc) +{ direction = j.at("dir"); indexes = j.at("indexes").get(); if (indexes.size() != 2) { @@ -328,38 +409,46 @@ void MoleculeProperty::selectAtomAtomDistance(const json& j, const Space& spc) { .norm(); }; } -void MoleculeProperty::selectGyrationRadius(const Space& spc) { + +void MoleculeProperty::selectGyrationRadius(const Space& spc) +{ function = [&spc, i = index]() { assert(spc.groups.at(i).size() > 1); - Tensor S = Geometry::gyration(spc.groups.at(i).begin(), spc.groups.at(i).end(), spc.groups.at(i).mass_center, - spc.geometry.getBoundaryFunc()); + Tensor S = Geometry::gyration(spc.groups.at(i).begin(), spc.groups.at(i).end(), + spc.groups.at(i).mass_center, spc.geometry.getBoundaryFunc()); return sqrt(S.trace()); // S.trace() == S.eigenvalues().sum() but faster }; } -void MoleculeProperty::selectDipoleAngle(const json& j, const Space& spc, Geometry::BoundaryFunction& b) { + +void MoleculeProperty::selectDipoleAngle(const json& j, const Space& spc, + Geometry::BoundaryFunction& b) +{ direction = j.at("dir").get().normalized(); if (spc.groups.at(index).isMolecular()) { function = [&spc, i = index, b, &dir = direction]() { - const auto dot_product = dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).dot(dir); + const auto dot_product = + dipoleMoment(spc.groups.at(i).begin(), spc.groups.at(i).end(), b).dot(dir); return acos(dot_product) * 180.0 / pc::pi; }; } } -void MoleculeProperty::selectMassCenterDistance(const json& j, const Space& spc) { +void MoleculeProperty::selectMassCenterDistance(const json& j, const Space& spc) +{ direction = j.at("dir"); indexes = j.value("indexes", decltype(indexes)()); assert(indexes.size() > 1 && "An array of 2 or 4 indexes should be specified."); if (indexes.size() == 4) { - function = [&spc, dir = direction.cast(), i = indexes[0], j = indexes[1] + 1, k = indexes[2], - l = indexes[3] + 1]() { + function = [&spc, dir = direction.cast(), i = indexes[0], j = indexes[1] + 1, + k = indexes[2], l = indexes[3] + 1]() { Point cm1 = Geometry::massCenter(spc.particles.begin() + i, spc.particles.begin() + j, spc.geometry.getBoundaryFunc()); Point cm2 = Geometry::massCenter(spc.particles.begin() + k, spc.particles.begin() + l, spc.geometry.getBoundaryFunc()); return spc.geometry.vdist(cm1, cm2).cwiseProduct(dir).norm(); }; - } else if (indexes.size() == 2) { + } + else if (indexes.size() == 2) { function = [&spc, dir = direction.cast(), i = indexes[0], j = indexes[1]]() { const auto& cm1 = spc.groups.at(i).mass_center; const auto& cm2 = spc.groups.at(j).mass_center; @@ -367,7 +456,9 @@ void MoleculeProperty::selectMassCenterDistance(const json& j, const Space& spc) }; } } -void MoleculeProperty::selectMinimumGroupDistance(const json& j, const Space& spc) { + +void MoleculeProperty::selectMinimumGroupDistance(const json& j, const Space& spc) +{ indexes = j.value("indexes", decltype(indexes)()); if (indexes.size() != 2) { throw ConfigurationError("indexes must have two elements"); @@ -383,16 +474,20 @@ void MoleculeProperty::selectMinimumGroupDistance(const json& j, const Space& sp return sqrt(minimum_distance_squared); }; } -void MoleculeProperty::selectRinner(const json& j, const Space& spc) { + +void MoleculeProperty::selectRinner(const json& j, const Space& spc) +{ direction = j.at("dir"); indexes = j.value("indexes", decltype(indexes)()); if (indexes.size() != 4) { throw ConfigurationError("indexes must have four elements"); } - function = [&spc, &dir = direction, i = indexes.at(0), j = indexes.at(1), k = indexes.at(2), l = indexes.at(3)]() { + function = [&spc, &dir = direction, i = indexes.at(0), j = indexes.at(1), k = indexes.at(2), + l = indexes.at(3)]() { namespace rv = ranges::cpp20::views; auto slicei = spc.findAtoms(i); - const auto mass_center = Geometry::massCenter(slicei.begin(), slicei.end(), spc.geometry.getBoundaryFunc()); + const auto mass_center = + Geometry::massCenter(slicei.begin(), slicei.end(), spc.geometry.getBoundaryFunc()); // filter and transform lambdas auto k_or_l = [k, l](auto& particle) { return (particle.id == k) or (particle.id == l); }; @@ -413,13 +508,16 @@ void MoleculeProperty::selectRinner(const json& j, const Space& spc) { return mean(radii); }; } -void MoleculeProperty::selectAngleWithVector(const json& j, const Space& spc) { + +void MoleculeProperty::selectAngleWithVector(const json& j, const Space& spc) +{ direction = j.at("dir").get().normalized(); if (spc.groups.at(index).isMolecular()) { function = [&spc, &dir = direction, i = index]() { auto& group = spc.groups.at(i); auto& cm = group.mass_center; - Tensor S = Geometry::gyration(group.begin(), group.end(), cm, spc.geometry.getBoundaryFunc()); + Tensor S = + Geometry::gyration(group.begin(), group.end(), cm, spc.geometry.getBoundaryFunc()); Eigen::SelfAdjointEigenSolver esf(S); const Point& eivals = esf.eigenvalues(); ptrdiff_t i_eival; diff --git a/src/reactioncoordinate.h b/src/reactioncoordinate.h index 00b87c5b4..872703b02 100644 --- a/src/reactioncoordinate.h +++ b/src/reactioncoordinate.h @@ -18,53 +18,60 @@ namespace ReactionCoordinate { * the penalty's energy function and can also be used to probe the system * during analysis. */ -class ReactionCoordinateBase { +class ReactionCoordinateBase +{ protected: std::function function = nullptr; //!< returns reaction coordinate //!< Default 1.0; currently unused public: - explicit ReactionCoordinateBase(const json& j); //!< constructor reads resolution, min, max - double resolution = 0.0; //!< Resolution used when binning (histograms etc.) - double minimum_value = 0.0; //!< Minimum allowed value - double maximum_value = 0.0; //!< Maximum allowed value - std::string name; //!< Meaningful, short name. Don't use spaces or weird characters - - double operator()(); //!< Calculates reaction coordinate - virtual void _to_json(json &j) const; //!< json serialization - [[nodiscard]] bool inRange(double coord) const; //!< Determines if coordinate is within [min,max] + explicit ReactionCoordinateBase(const json& j); //!< constructor reads resolution, min, max + double resolution = 0.0; //!< Resolution used when binning (histograms etc.) + double minimum_value = 0.0; //!< Minimum allowed value + double maximum_value = 0.0; //!< Maximum allowed value + std::string name; //!< Meaningful, short name. Don't use spaces or weird characters + + double operator()(); //!< Calculates reaction coordinate + virtual void _to_json(json& j) const; //!< json serialization + [[nodiscard]] bool + inRange(double coord) const; //!< Determines if coordinate is within [min,max] virtual ~ReactionCoordinateBase() = default; }; -void to_json(json& j, const ReactionCoordinateBase& reaction_coordinate); //!< Serialize any reaction coordinate to json +void to_json(json& j, const ReactionCoordinateBase& + reaction_coordinate); //!< Serialize any reaction coordinate to json std::unique_ptr -createReactionCoordinate(const json&, const Space&); //!< Factory function to create all known penalty functions +createReactionCoordinate(const json&, + const Space&); //!< Factory function to create all known penalty functions -class SystemProperty : public ReactionCoordinateBase { +class SystemProperty : public ReactionCoordinateBase +{ protected: std::string property; public: - SystemProperty(const json &j, const Space &spc); - void _to_json(json &j) const override; + SystemProperty(const json& j, const Space& spc); + void _to_json(json& j) const override; }; -class AtomProperty : public ReactionCoordinateBase { +class AtomProperty : public ReactionCoordinateBase +{ protected: size_t index; // atom index Point dir = {0.0, 0.0, 0.0}; public: std::string property; - AtomProperty(const json &j, const Space &spc); - void _to_json(json &j) const override; + AtomProperty(const json& j, const Space& spc); + void _to_json(json& j) const override; }; /** * @todo Refactor so that each scheme is a derived class implementing * a virtual energy function instead of the std::function object */ -struct MoleculeProperty : public ReactionCoordinateBase { +struct MoleculeProperty : public ReactionCoordinateBase +{ private: size_t index; //!< Group index Point direction = {0.0, 0.0, 0.0}; @@ -82,8 +89,8 @@ struct MoleculeProperty : public ReactionCoordinateBase { void selectLengthOverRadiusRatio(const json& j, const Space& spc); public: - MoleculeProperty(const json &j, const Space &spc); - void _to_json(json &j) const override; + MoleculeProperty(const json& j, const Space& spc); + void _to_json(json& j) const override; }; } // namespace ReactionCoordinate diff --git a/src/regions.cpp b/src/regions.cpp index dfd10fe01..008e38d26 100644 --- a/src/regions.cpp +++ b/src/regions.cpp @@ -8,11 +8,22 @@ namespace Faunus { namespace Region { RegionBase::RegionBase(RegionType type) - : type(type) {} -std::optional RegionBase::volume() const { return std::nullopt; } -bool RegionBase::inside(const Particle& particle) const { return isInside(particle.pos); } + : type(type) +{ +} + +std::optional RegionBase::volume() const +{ + return std::nullopt; +} + +bool RegionBase::inside(const Particle& particle) const +{ + return isInside(particle.pos); +} -bool RegionBase::inside(const Group& group) const { +bool RegionBase::inside(const Group& group) const +{ if (use_group_mass_center) { if (auto mass_center = group.massCenter()) { return isInside(mass_center.value()); @@ -34,7 +45,8 @@ bool RegionBase::inside(const Group& group) const { * com: true * ~~~ */ -std::unique_ptr createRegion(const Space& spc, const json& j) { +std::unique_ptr createRegion(const Space& spc, const json& j) +{ const auto type = j.at("policy").get(); switch (type) { case RegionType::WITHIN_MOLID: @@ -48,12 +60,14 @@ std::unique_ptr createRegion(const Space& spc, const json& j) { } } -void to_json(json& j, const RegionBase& region) { +void to_json(json& j, const RegionBase& region) +{ region.to_json(j); j["policy"] = region.type; } -void WithinMoleculeType::to_json(json& j) const { +void WithinMoleculeType::to_json(json& j) const +{ j = {{"threshold", sqrt(threshold_squared)}, {"com", use_region_mass_center}, {"molecule", molecules.at(molid).name}, @@ -66,39 +80,48 @@ void WithinMoleculeType::to_json(json& j) const { * @param threshold Radial distance threshold to particles or mass center in target groups * @param use_mass_center Use mass-center distance for molecular groups * @param use_group_mass_center Use group mass-center of when testing if molecules are within region - * @throw if `use_mass_center==true` and the given molecule type is not molecular, i.e. has ill-defined mass center + * @throw if `use_mass_center==true` and the given molecule type is not molecular, i.e. has + * ill-defined mass center */ -WithinMoleculeType::WithinMoleculeType(const Space& spc, std::string_view molecule_name, double threshold, - bool use_region_mass_center, bool use_group_mass_center) +WithinMoleculeType::WithinMoleculeType(const Space& spc, std::string_view molecule_name, + double threshold, bool use_region_mass_center, + bool use_group_mass_center) : RegionBase(RegionType::WITHIN_MOLID) , spc(spc) , molid(findMoleculeByName(molecule_name).id()) , use_region_mass_center(use_region_mass_center) - , threshold_squared(threshold * threshold) { + , threshold_squared(threshold * threshold) +{ if (use_region_mass_center && !Faunus::molecules.at(molid).isMolecular()) { throw ConfigurationError("center of mass threshold ill-defined for `{}`", molecule_name); } - RegionBase::use_group_mass_center = use_group_mass_center; // use mass-center of groups to be tested? + RegionBase::use_group_mass_center = + use_group_mass_center; // use mass-center of groups to be tested? } WithinMoleculeType::WithinMoleculeType(const Space& spc, const json& j) : WithinMoleculeType(spc, j.at("molname").get(), j.at("threshold").get(), - j.value("com", false), j.value("group_com", false)) {} + j.value("com", false), j.value("group_com", false)) +{ +} -bool WithinMoleculeType::isInside(const Point& position) const { +bool WithinMoleculeType::isInside(const Point& position) const +{ using ranges::cpp20::any_of; auto has_position_inside = [&](const Group& group) { if (use_region_mass_center) { return within_threshold(position, *group.massCenter()); } - return any_of(group.positions(), - [&](const auto& position_in_group) { return within_threshold(position, position_in_group); }); + return any_of(group.positions(), [&](const auto& position_in_group) { + return within_threshold(position, position_in_group); + }); }; // true if `position` is within the threshold distance of `group` auto groups = spc.findMolecules(molid, Space::Selection::ACTIVE); return any_of(groups, has_position_inside); } -std::optional WithinMoleculeType::volume() const { +std::optional WithinMoleculeType::volume() const +{ if (use_region_mass_center) { return 4.0 * pc::pi / 3.0 * std::pow(threshold_squared, 1.5); } @@ -110,49 +133,61 @@ std::optional WithinMoleculeType::volume() const { * @param index Particle index used to define center of region * @param radius Radius of spherical region */ -SphereAroundParticle::SphereAroundParticle(const Space& spc, ParticleVector::size_type index, double radius) +SphereAroundParticle::SphereAroundParticle(const Space& spc, ParticleVector::size_type index, + double radius) : RegionBase(RegionType::WITHIN_PARTICLE) , spc(spc) , particle_index(index) - , radius_squared(radius * radius) {} + , radius_squared(radius * radius) +{ +} SphereAroundParticle::SphereAroundParticle(const Space& spc, const json& j) - : SphereAroundParticle(spc, j.at("index").get(), j.at("radius").get()) {} + : SphereAroundParticle(spc, j.at("index").get(), j.at("radius").get()) +{ +} -bool SphereAroundParticle::isInside(const Point& position) const { +bool SphereAroundParticle::isInside(const Point& position) const +{ return spc.geometry.sqdist(position, spc.particles.at(particle_index).pos) < radius_squared; } -std::optional SphereAroundParticle::volume() const { +std::optional SphereAroundParticle::volume() const +{ return 4.0 * pc::pi / 3.0 * std::pow(radius_squared, 1.5); } -void SphereAroundParticle::to_json(json& j) const { +void SphereAroundParticle::to_json(json& j) const +{ j = {{"index", particle_index}, {"threshold", std::sqrt(radius_squared)}}; } -bool MovingEllipsoid::isInside(const Point& position) const { +bool MovingEllipsoid::isInside(const Point& position) const +{ const auto [midpoint, direction] = getEllipsoidPositionAndDirection(); - Point midpoint_pos = spc.geometry.vdist(position, midpoint); // midpoint -> pos - const auto midpoint_pos_len = midpoint_pos.norm() + pc::epsilon_dbl; // must not be *exactly* zero + Point midpoint_pos = spc.geometry.vdist(position, midpoint); // midpoint -> pos + const auto midpoint_pos_len = + midpoint_pos.norm() + pc::epsilon_dbl; // must not be *exactly* zero const auto cos_theta = midpoint_pos.dot(direction) / midpoint_pos_len; const auto theta = std::acos(cos_theta); const auto x = cos_theta * midpoint_pos_len; const auto y = std::sin(theta) * midpoint_pos_len; - const auto coord = - x * x / parallel_radius_squared + y * y / (perpendicular_radius * perpendicular_radius); // normalized coord - return coord < 1.0; // < 1.0 -> inside + const auto coord = x * x / parallel_radius_squared + + y * y / (perpendicular_radius * perpendicular_radius); // normalized coord + return coord < 1.0; // < 1.0 -> inside } /** * @return Center of ellipsoid and it's normalized direction */ -std::pair MovingEllipsoid::getEllipsoidPositionAndDirection() const { +std::pair MovingEllipsoid::getEllipsoidPositionAndDirection() const +{ Point direction = 0.5 * spc.geometry.vdist(reference_position_2, reference_position_1); const auto distance = direction.norm(); if (parallel_radius < distance) { // is this check needed? - faunus_logger->error("Parallel radius ({} Γ…) smaller than half distance between reference atoms ({} Γ…)", - parallel_radius, distance); + faunus_logger->error( + "Parallel radius ({} Γ…) smaller than half distance between reference atoms ({} Γ…)", + parallel_radius, distance); } Point midpoint = spc.geometry.vdist(reference_position_2, direction); // half 2 -> 1 return {midpoint, (direction / distance).eval()}; @@ -180,7 +215,8 @@ MovingEllipsoid::MovingEllipsoid(const Space& spc, ParticleVector::size_type par , perpendicular_radius(perpendicular_radius) , parallel_radius_squared(parallel_radius * parallel_radius) , reference_position_1(spc.particles.at(particle_index_1).pos) - , reference_position_2(spc.particles.at(particle_index_2).pos) { + , reference_position_2(spc.particles.at(particle_index_2).pos) +{ this->use_group_mass_center = use_group_mass_center; if (particle_index_1 == particle_index_2) { throw ConfigurationError("reference indices must differ"); @@ -188,10 +224,14 @@ MovingEllipsoid::MovingEllipsoid(const Space& spc, ParticleVector::size_type par } MovingEllipsoid::MovingEllipsoid(const Space& spc, const json& j) - : MovingEllipsoid(spc, j.at("index1").get(), j.at("index2").get(), j.at("parallel_radius").get(), - j.at("perpendicular_radius").get(), j.value("group_com", false)) {} + : MovingEllipsoid(spc, j.at("index1").get(), j.at("index2").get(), + j.at("parallel_radius").get(), + j.at("perpendicular_radius").get(), j.value("group_com", false)) +{ +} -void MovingEllipsoid::to_json(json& j) const { +void MovingEllipsoid::to_json(json& j) const +{ j["index1"] = particle_index_1; j["index2"] = particle_index_2; j["perpendicular_radius"] = perpendicular_radius; @@ -199,7 +239,8 @@ void MovingEllipsoid::to_json(json& j) const { j["group_com"] = use_group_mass_center; } -TEST_CASE("[Faunus] Region::MovingEllipsoid") { +TEST_CASE("[Faunus] Region::MovingEllipsoid") +{ const auto delta = 1e-6; // spatial resolution const auto parallel_radius = 4.0; const auto perpendilar_radius = 5.0; @@ -210,14 +251,16 @@ TEST_CASE("[Faunus] Region::MovingEllipsoid") { spc.particles.at(1).pos = {1.0, 0.0, 0.0}; // second reference MovingEllipsoid region(spc, 0, 1, parallel_radius, perpendilar_radius, false); - SUBCASE("parallel axis") { + SUBCASE("parallel axis") + { CHECK_EQ(region.isInside({parallel_radius + delta, 0.0, 0.0}), false); CHECK(region.isInside({parallel_radius - delta, 0.0, 0.0})); CHECK_EQ(region.isInside({-(parallel_radius + delta), 0.0, 0.0}), false); CHECK(region.isInside({-(parallel_radius - delta), 0.0, 0.0})); } - SUBCASE("perpendicular axis") { + SUBCASE("perpendicular axis") + { CHECK_EQ(region.isInside({0.0, perpendilar_radius + delta, 0.0}), false); CHECK(region.isInside({0.0, perpendilar_radius - delta, 0.0})); CHECK_EQ(region.isInside({0.0, -(perpendilar_radius + delta), 0.0}), false); @@ -227,7 +270,10 @@ TEST_CASE("[Faunus] Region::MovingEllipsoid") { CHECK_EQ(region.isInside({0.0, 0.0, -(perpendilar_radius + delta)}), false); CHECK(region.isInside({0.0, 0.0, -(perpendilar_radius - delta)})); } - SUBCASE("exactly on midpoint") { CHECK(region.isInside({0.0, 0.0, 0.0})); } + SUBCASE("exactly on midpoint") + { + CHECK(region.isInside({0.0, 0.0, 0.0})); + } } } // namespace Region diff --git a/src/regions.h b/src/regions.h index 8f63cf524..f42134c02 100644 --- a/src/regions.h +++ b/src/regions.h @@ -14,7 +14,13 @@ class Space; */ namespace Region { -enum class RegionType { WITHIN_MOLID, WITHIN_PARTICLE, WITHIN_ELLIPSOID, INVALID }; +enum class RegionType +{ + WITHIN_MOLID, + WITHIN_PARTICLE, + WITHIN_ELLIPSOID, + INVALID +}; NLOHMANN_JSON_SERIALIZE_ENUM(RegionType, {{RegionType::INVALID, nullptr}, {RegionType::WITHIN_MOLID, "within_molid"}, @@ -29,22 +35,26 @@ NLOHMANN_JSON_SERIALIZE_ENUM(RegionType, {{RegionType::INVALID, nullptr}, * is inside the region. A region need not be static and can * follow molecules. A region may or may not have a well-defined volume. */ -class RegionBase { +class RegionBase +{ private: - [[nodiscard]] virtual bool isInside(const Point& position) const = 0; //!< true if point is inside region + [[nodiscard]] virtual bool + isInside(const Point& position) const = 0; //!< true if point is inside region public: const RegionType type; - bool use_group_mass_center = false; //!< Use group mass-center to check if inside region + bool use_group_mass_center = false; //!< Use group mass-center to check if inside region [[nodiscard]] virtual std::optional volume() const; //!< Volume of region if applicable virtual void to_json(json& j) const = 0; virtual ~RegionBase() = default; explicit RegionBase(RegionType type); - [[nodiscard]] bool inside(const Particle& particle) const; //!< Determines if particle is inside region - [[nodiscard]] bool inside(const Group& group) const; //!< Determines of groups is inside region + [[nodiscard]] bool + inside(const Particle& particle) const; //!< Determines if particle is inside region + [[nodiscard]] bool inside(const Group& group) const; //!< Determines of groups is inside region /** Selects particles within the region */ - template auto filterInside(const ParticleRange& particles) const { + template auto filterInside(const ParticleRange& particles) const + { namespace rv = ranges::cpp20::views; return particles | rv::transform(&Particle::pos) | rv::filter(&RegionBase::isInside); } @@ -66,19 +76,22 @@ void to_json(json& j, const RegionBase& region); * volume around the mass center. If so, `volume()` returns the * spherical volume, otherwise `nullopt` */ -class WithinMoleculeType : public RegionBase { +class WithinMoleculeType : public RegionBase +{ private: const Space& spc; //!< reference to space const MoleculeData::index_type molid; //!< molid to target const bool use_region_mass_center; //!< true = with respect to center of mass of `molid` - const double threshold_squared; //!< squared distance threshold from other particles or com - [[nodiscard]] inline bool within_threshold(const Point& position1, const Point& position2) const { + const double threshold_squared; //!< squared distance threshold from other particles or com + + [[nodiscard]] inline bool within_threshold(const Point& position1, const Point& position2) const + { return spc.geometry.sqdist(position1, position2) < threshold_squared; } public: - WithinMoleculeType(const Space& spc, std::string_view molecule_name, double threshold, bool use_region_mass_center, - bool use_group_mass_center); + WithinMoleculeType(const Space& spc, std::string_view molecule_name, double threshold, + bool use_region_mass_center, bool use_group_mass_center); WithinMoleculeType(const Space& spc, const json& j); [[nodiscard]] bool isInside(const Point& position) const override; [[nodiscard]] std::optional volume() const override; @@ -88,11 +101,13 @@ class WithinMoleculeType : public RegionBase { /** * Spherical region centered on a particle */ -class SphereAroundParticle : public RegionBase { +class SphereAroundParticle : public RegionBase +{ private: - const Space& spc; //!< reference to space - const ParticleVector::size_type particle_index; //!< Index of particle that defines the center of the region - const double radius_squared; //!< squared distance threshold from other particles or com + const Space& spc; //!< reference to space + const ParticleVector::size_type + particle_index; //!< Index of particle that defines the center of the region + const double radius_squared; //!< squared distance threshold from other particles or com public: SphereAroundParticle(const Space& spc, ParticleVector::size_type index, double radius); @@ -105,13 +120,14 @@ class SphereAroundParticle : public RegionBase { /** * An ellipsoid defined by two (moving) particles */ -class MovingEllipsoid : public RegionBase { +class MovingEllipsoid : public RegionBase +{ private: const Space& spc; const ParticleVector::size_type particle_index_1; //!< Index of first reference particle const ParticleVector::size_type particle_index_2; //!< Index of second reference particle - const double parallel_radius; //!< radius along axis connecting reference atoms - const double perpendicular_radius; //!< radius perpendicular to axis connecting reference atoms + const double parallel_radius; //!< radius along axis connecting reference atoms + const double perpendicular_radius; //!< radius perpendicular to axis connecting reference atoms const double parallel_radius_squared; const Point& reference_position_1; //!< Reference to first reference particle position @@ -121,8 +137,8 @@ class MovingEllipsoid : public RegionBase { public: MovingEllipsoid(const Space& spc, ParticleVector::size_type particle_index1, - ParticleVector::size_type particle_index2, double parallel_radius, double perpendicular_radius, - bool use_group_mass_center); + ParticleVector::size_type particle_index2, double parallel_radius, + double perpendicular_radius, bool use_group_mass_center); MovingEllipsoid(const Space& spc, const json& j); [[nodiscard]] bool isInside(const Point& position) const override; void to_json(json& j) const override; diff --git a/src/rotate.cpp b/src/rotate.cpp index 28db1a589..42d6c5a08 100644 --- a/src/rotate.cpp +++ b/src/rotate.cpp @@ -11,13 +11,17 @@ namespace Faunus { * @param angle rotation angle (radians) * @param axis rotation axis */ -QuaternionRotate::QuaternionRotate(double angle, Point axis) { set(angle, std::move(axis)); } +QuaternionRotate::QuaternionRotate(double angle, Point axis) +{ + set(angle, std::move(axis)); +} /** * @param angle rotation angle (radians) * @param axis rotation axis */ -void QuaternionRotate::set(double angle, Point axis) { +void QuaternionRotate::set(double angle, Point axis) +{ this->angle = angle; axis.normalize(); // make unit vector quaternion = Eigen::AngleAxisd(angle, axis); @@ -35,7 +39,8 @@ void QuaternionRotate::set(double angle, Point axis) { * @return Rotated vector */ QuaternionRotate::Point QuaternionRotate::operator()(Point r, std::function boundary, - const Point& shift) const { + const Point& shift) const +{ r = r - shift; boundary(r); // Note that the Eigen * operator secretly converts to a rotation matrix which is @@ -52,32 +57,43 @@ QuaternionRotate::Point QuaternionRotate::operator()(Point r, std::function boundary = [](Point &) {}, - const Point &shift = {0, 0, 0}) const; //!< Rotate point w. optional PBC boundaries + Point vector, std::function boundary = [](Point&) {}, + const Point& shift = {0, 0, 0}) const; //!< Rotate point w. optional PBC boundaries }; } // namespace Faunus diff --git a/src/sasa.cpp b/src/sasa.cpp index 8657662b8..f5c13fa5f 100644 --- a/src/sasa.cpp +++ b/src/sasa.cpp @@ -17,9 +17,10 @@ namespace SASA { * @param target_indices absolute indicies of target particles in ParticleVector */ void SASABase::updateSASA(const std::vector& neighbours, - const std::vector& target_indices) { + const std::vector& target_indices) +{ // here is a potential place for parallelization? - //#pragma OMP parallel num_threads(2) + // #pragma OMP parallel num_threads(2) //{ for (const auto& [neighbour, index] : ranges::views::zip(neighbours, target_indices)) { areas.at(index) = calcSASAOfParticle(neighbour); @@ -27,20 +28,23 @@ void SASABase::updateSASA(const std::vector& neighbours, //} } -double SASABase::calcSASAOfParticle(const Space& spc, const Particle& particle) const { +double SASABase::calcSASAOfParticle(const Space& spc, const Particle& particle) const +{ const auto neighbours = calcNeighbourDataOfParticle(spc, indexOf(particle)); return calcSASAOfParticle(neighbours); } /** * @brief Calcuates SASA of a single particle defined by NeighbourData object - * @details cuts a sphere in z-direction, for each slice, radius of circle_i in the corresponding z-plane is - calculated - * then for each neighbour, calculate the overlaping part of circle_i with neighbouring circle_j and add these + * @details cuts a sphere in z-direction, for each slice, radius of circle_i in the corresponding + z-plane is calculated + * then for each neighbour, calculate the overlaping part of circle_i with neighbouring circle_j and + add these * arcs into vector, finally from this vector, calculate the exposed part of circle_i * @param neighbour NeighbourData object of given particle */ -double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const { +double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const +{ const auto sasa_radius_i = sasa_radii.at(neighbour.index); double area(0.); @@ -61,12 +65,14 @@ double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const std::vector> arcs; bool is_buried = false; - for (const auto& [d_r, neighbour_index] : ranges::views::zip(neighbour.points, neighbour.indices)) { + for (const auto& [d_r, neighbour_index] : + ranges::views::zip(neighbour.points, neighbour.indices)) { const auto sasa_radius_j = sasa_radii.at(neighbour_index); const auto z_distance = std::fabs(d_r.z() - z); if (z_distance < sasa_radius_j) { - const auto sqrd_circle_radius_j = sasa_radius_j * sasa_radius_j - z_distance * z_distance; + const auto sqrd_circle_radius_j = + sasa_radius_j * sasa_radius_j - z_distance * z_distance; const auto circle_radius_j = std::sqrt(sqrd_circle_radius_j); const auto xy_distance = std::sqrt(d_r.x() * d_r.x() + d_r.y() * d_r.y()); if (xy_distance >= circle_radius_i + circle_radius_j) { @@ -83,9 +89,9 @@ double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const continue; } /* arc of circle i intersected by circle j */ - auto intersected_arc_halfsize = - std::acos((sqrd_circle_radius_i + xy_distance * xy_distance - sqrd_circle_radius_j) / - (2.0 * circle_radius_i * xy_distance)); + auto intersected_arc_halfsize = std::acos( + (sqrd_circle_radius_i + xy_distance * xy_distance - sqrd_circle_radius_j) / + (2.0 * circle_radius_i * xy_distance)); /* position of mid-point of intersection along circle i */ auto intersection_midpoint_angle = std::atan2(d_r.y(), d_r.x()) + M_PI; auto beginning_arc_angle = intersection_midpoint_angle - intersected_arc_halfsize; @@ -100,7 +106,8 @@ double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const if (end_arc_angle < beginning_arc_angle) { /* store arcs as pairs of angles */ arcs.insert(arcs.end(), {{0.0, end_arc_angle}, {beginning_arc_angle, two_pi}}); - } else { + } + else { arcs.emplace_back(beginning_arc_angle, end_arc_angle); } } @@ -116,7 +123,8 @@ double SASABase::calcSASAOfParticle(const SASABase::Neighbours& neighbour) const * @brief Calcuates total arc length in radians of overlapping arcs defined by two angles * @param vector of arcs, defined by a pair (first angle, second angle) */ -double SASABase::exposedArcLength(std::vector>& arcs) const { +double SASABase::exposedArcLength(std::vector>& arcs) const +{ if (arcs.empty()) { return two_pi; } @@ -137,7 +145,10 @@ double SASABase::exposedArcLength(std::vector>& arcs) return total_arc_angle + two_pi - end_arc_angle; } -const std::vector& SASABase::getAreas() const { return areas; } +const std::vector& SASABase::getAreas() const +{ + return areas; +} /** * @param spc @@ -146,27 +157,36 @@ const std::vector& SASABase::getAreas() const { return areas; } */ SASABase::SASABase(const Space& spc, double probe_radius, int slices_per_atom) : probe_radius(probe_radius) - , slices_per_atom(slices_per_atom) - , first_particle(std::addressof(spc.particles.at(0))) {} + , slices_per_atom(slices_per_atom) + , first_particle(std::addressof(spc.particles.at(0))) +{ +} /** * @brief resizes areas buffer to size of ParticleVector and fill radii buffer with radii * @param space */ -void SASA::init(const Space& spc) { +void SASA::init(const Space& spc) +{ using namespace ranges::cpp20::views; - auto sasa_radius_l = [this](const Particle& particle) { return 0.5 * particle.traits().sigma + probe_radius; }; - sasa_radii = spc.particles | ranges::cpp20::views::transform(sasa_radius_l) | ranges::to; + auto sasa_radius_l = [this](const Particle& particle) { + return 0.5 * particle.traits().sigma + probe_radius; + }; + sasa_radii = + spc.particles | ranges::cpp20::views::transform(sasa_radius_l) | ranges::to; areas.resize(spc.particles.size()); } /** - * @brief calculates neighbourData object of a target particle specified by target indiex in ParticleVector + * @brief calculates neighbourData object of a target particle specified by target indiex in + * ParticleVector * @brief using the naive O(N) neighbour search for a given target particle * @param space * @param target_index indicex of target particle in ParticleVector */ -SASA::Neighbours SASA::calcNeighbourDataOfParticle(const Space& spc, const index_type target_index) const { +SASA::Neighbours SASA::calcNeighbourDataOfParticle(const Space& spc, + const index_type target_index) const +{ SASA::Neighbours neighbour; @@ -179,7 +199,8 @@ SASA::Neighbours SASA::calcNeighbourDataOfParticle(const Space& spc, const index const auto sasa_radius_j = sasa_radii.at(neighbour_index); const auto sq_cutoff = (sasa_radius_i + sasa_radius_j) * (sasa_radius_i + sasa_radius_j); - if (target_index != neighbour_index && spc.geometry.sqdist(particle_i.pos, particle_j.pos) < sq_cutoff) { + if (target_index != neighbour_index && + spc.geometry.sqdist(particle_i.pos, particle_j.pos) < sq_cutoff) { const auto dr = spc.geometry.vdist(particle_i.pos, particle_j.pos); neighbour.points.push_back(dr); neighbour.indices.push_back(neighbour_index); @@ -189,15 +210,18 @@ SASA::Neighbours SASA::calcNeighbourDataOfParticle(const Space& spc, const index } /** - * @brief calculates neighbourData objects of particles specified by target indices in ParticleVector + * @brief calculates neighbourData objects of particles specified by target indices in + * ParticleVector * @param space * @param target_indices absolute indicies of target particles in ParticleVector */ -std::vector SASA::calcNeighbourData(const Space& spc, - const std::vector& target_indices) const { +std::vector +SASA::calcNeighbourData(const Space& spc, const std::vector& target_indices) const +{ - return target_indices | - ranges::views::transform([&](auto index) { return calcNeighbourDataOfParticle(spc, index); }) | + return target_indices | ranges::views::transform([&](auto index) { + return calcNeighbourDataOfParticle(spc, index); + }) | ranges::to; } @@ -207,10 +231,14 @@ std::vector SASA::calcNeighbourData(const Space& spc, * @param slices_per_atom number of slices of each sphere in sasa calculations */ SASA::SASA(const Space& spc, double probe_radius, int slices_per_atom) - : SASABase(spc, probe_radius, slices_per_atom) {} + : SASABase(spc, probe_radius, slices_per_atom) +{ +} SASA::SASA(const json& j, const Space& spc) - : SASABase(spc, j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 20)) {} + : SASABase(spc, j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 20)) +{ +} /** * @brief updates radii vector in case of matter change @@ -219,7 +247,8 @@ SASA::SASA(const json& j, const Space& spc) */ void SASA::update([[maybe_unused]] const Space& spc, [[maybe_unused]] const Change& change) {} -TEST_CASE("[Faunus] SASAPBC") { +TEST_CASE("[Faunus] SASAPBC") +{ using doctest::Approx; Change change; // change object telling that a full energy calculation change.everything = true; @@ -241,7 +270,8 @@ TEST_CASE("[Faunus] SASAPBC") { const std::vector radii = {4.0 * 0.5, 2.4 * 0.5}; sasa.init(spc); - SUBCASE("not intersecting") { + SUBCASE("not intersecting") + { spc.particles.at(0).pos = {30.0, 0.0, 0.0}; spc.particles.at(1).pos = {5.0, 0.0, 0.0}; @@ -253,7 +283,8 @@ TEST_CASE("[Faunus] SASAPBC") { CHECK_EQ(areas[0], Approx(3.4 * 3.4 * M_PI * 4.)); } - SUBCASE("intersecting") { + SUBCASE("intersecting") + { spc.particles.at(0).pos = {7.0, 0.0, 0.0}; spc.particles.at(1).pos = {5.0, 0.0, 0.0}; @@ -265,7 +296,8 @@ TEST_CASE("[Faunus] SASAPBC") { CHECK_EQ(areas[0], Approx(119.48260171150575)); } - SUBCASE("intersecting accross boundary") { + SUBCASE("intersecting accross boundary") + { spc.particles.at(0).pos = {199., 0.0, 0.0}; spc.particles.at(1).pos = {1., 0.0, 199.}; @@ -277,7 +309,8 @@ TEST_CASE("[Faunus] SASAPBC") { CHECK_EQ(areas[0], Approx(118.99710056237043)); } - SUBCASE("smaller buried in larger") { + SUBCASE("smaller buried in larger") + { spc.particles.at(0).pos = {1.0, 0.0, 0.0}; spc.particles.at(1).pos = {1.1, 0.0, 0.0}; @@ -298,17 +331,22 @@ TEST_CASE("[Faunus] SASAPBC") { */ template SASACellList::SASACellList(const Space& spc, double probe_radius, int slices_per_atom) - : SASABase(spc, probe_radius, slices_per_atom) {} + : SASABase(spc, probe_radius, slices_per_atom) +{ +} template SASACellList::SASACellList(const json& j, const Space& spc) - : SASABase(spc, j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 20)) {} + : SASABase(spc, j.value("radius", 1.4) * 1.0_angstrom, j.value("slices", 20)) +{ +} /** * @brief constructs cell_list with appropriate cell_length and fills it with particles from space * @param space */ -template void SASACellList::init(const Space& spc) { +template void SASACellList::init(const Space& spc) +{ cell_offsets.clear(); for (auto i = -1; i <= 1; ++i) { for (auto j = -1; j <= 1; ++j) { @@ -319,7 +357,8 @@ template void SASACellList::init(const Space& spc) } auto get_sasa_radius = [this](auto& i) { return 0.5 * i.traits().sigma + probe_radius; }; - sasa_radii = spc.particles | ranges::cpp20::views::transform(get_sasa_radius) | ranges::to; + sasa_radii = + spc.particles | ranges::cpp20::views::transform(get_sasa_radius) | ranges::to; const auto max_sasa_radius = ranges::cpp20::max(sasa_radii); cell_length = 2.0 * (max_sasa_radius); @@ -336,15 +375,18 @@ template void SASACellList::init(const Space& spc) * @param target_index indicex of target particle in ParticleVector */ template -SASABase::Neighbours SASACellList::calcNeighbourDataOfParticle(const Space& spc, - const index_type target_index) const { +SASABase::Neighbours +SASACellList::calcNeighbourDataOfParticle(const Space& spc, + const index_type target_index) const +{ SASABase::Neighbours neighbours; const auto& particle_i = spc.particles.at(target_index); neighbours.index = target_index; const auto sasa_radius_i = sasa_radii.at(target_index); - const auto& center_cell = cell_list->getGrid().coordinatesAt(particle_i.pos + 0.5 * spc.geometry.getLength()); + const auto& center_cell = + cell_list->getGrid().coordinatesAt(particle_i.pos + 0.5 * spc.geometry.getLength()); auto neighour_particles_at = [&](const CellCoord& offset) { return cell_list->getNeighborMembers(center_cell, offset); @@ -355,7 +397,8 @@ SASABase::Neighbours SASACellList::calcNeighbourDataOfParticle(const S for (const auto neighbour_particle_index : neighbour_particle_indices) { const auto& particle_j = spc.particles.at(neighbour_particle_index); const auto sasa_radius_j = sasa_radii.at(neighbour_particle_index); - const auto sq_cutoff = (sasa_radius_i + sasa_radius_j) * (sasa_radius_i + sasa_radius_j); + const auto sq_cutoff = + (sasa_radius_i + sasa_radius_j) * (sasa_radius_i + sasa_radius_j); if (target_index != neighbour_particle_index && spc.geometry.sqdist(particle_i.pos, particle_j.pos) <= sq_cutoff) { @@ -376,9 +419,12 @@ SASABase::Neighbours SASACellList::calcNeighbourDataOfParticle(const S */ template std::vector -SASACellList::calcNeighbourData(const Space& spc, const std::vector& target_indices) const { - return target_indices | - ranges::views::transform([&](auto index) { return calcNeighbourDataOfParticle(spc, index); }) | +SASACellList::calcNeighbourData(const Space& spc, + const std::vector& target_indices) const +{ + return target_indices | ranges::views::transform([&](auto index) { + return calcNeighbourDataOfParticle(spc, index); + }) | ranges::to; } @@ -388,28 +434,35 @@ SASACellList::calcNeighbourData(const Space& spc, const std::vector void SASACellList::update(const Space& spc, const Change& change) { +template +void SASACellList::update(const Space& spc, const Change& change) +{ if (change.everything || change.volume_change) { const auto active_particles = spc.activeParticles(); createCellList(active_particles.begin(), active_particles.end(), spc.geometry); - } else if (change.matter_change) { + } + else if (change.matter_change) { updateMatterChange(spc, change); - } else { + } + else { updatePositionsChange(spc, change); } } /** - * @brief creates cell_list if it does not exist, otherwise resets its and fills it with active particles + * @brief creates cell_list if it does not exist, otherwise resets its and fills it with active + * particles * @param space */ template template -void SASACellList::createCellList(TBegin begin, TEnd end, const GeometryType& geometry) { +void SASACellList::createCellList(TBegin begin, TEnd end, const GeometryType& geometry) +{ if (cell_list.get()) { delete cell_list.release(); cell_list = std::make_unique(geometry.getLength(), cell_length); - } else { + } + else { cell_list = std::make_unique(geometry.getLength(), cell_length); } std::for_each(begin, end, [&, half_box = 0.5 * geometry.getLength()](const Particle& particle) { @@ -422,7 +475,9 @@ void SASACellList::createCellList(TBegin begin, TEnd end, const Geomet * @param space * @param change */ -template void SASACellList::updateMatterChange(const Space& spc, const Change& change) { +template +void SASACellList::updateMatterChange(const Space& spc, const Change& change) +{ for (const auto& group_change : change.groups) { const auto& group = spc.groups.at(group_change.group_index); const auto offset = spc.getFirstParticleIndex(group); @@ -431,9 +486,10 @@ template void SASACellList::updateMatterChange(con if (relative_index >= group.size()) { // if index lies behind last active index cell_list->removeMember(absolute_index); - } else if (relative_index < group.size()) { - cell_list->insertMember(absolute_index, - spc.particles.at(absolute_index).pos + 0.5 * spc.geometry.getLength()); + } + else if (relative_index < group.size()) { + cell_list->insertMember(absolute_index, spc.particles.at(absolute_index).pos + + 0.5 * spc.geometry.getLength()); } } } @@ -445,7 +501,8 @@ template void SASACellList::updateMatterChange(con * @param change */ template -void SASACellList::updatePositionsChange(const Space& spc, const Change& change) { +void SASACellList::updatePositionsChange(const Space& spc, const Change& change) +{ for (const auto& group_change : change.groups) { const auto& group = spc.groups.at(group_change.group_index); const auto offset = spc.getFirstParticleIndex(group); @@ -455,11 +512,14 @@ void SASACellList::updatePositionsChange(const Space& spc, const Chang }; if (group_change.relative_atom_indices.empty()) { - const auto changed_atom_indices = ranges::cpp20::views::iota(offset, offset + group.size()); + const auto changed_atom_indices = + ranges::cpp20::views::iota(offset, offset + group.size()); ranges::cpp20::for_each(changed_atom_indices, update); - } else { - const auto changed_atom_indices = group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); + } + else { + const auto changed_atom_indices = + group_change.relative_atom_indices | + ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); ranges::cpp20::for_each(changed_atom_indices, update); } } @@ -477,7 +537,8 @@ template class SASACellList; * * @return total sasa of a given group */ -template <> double SASABase::calcSASAOf(const Space& spc, const Group& group) const { +template <> double SASABase::calcSASAOf(const Space& spc, const Group& group) const +{ return calcSASA(spc, group.begin(), group.end()); } @@ -488,13 +549,15 @@ template <> double SASABase::calcSASAOf(const Space& spc, const Group& gr * * @return total sasa of a given particle */ -template <> double SASABase::calcSASAOf(const Space& spc, const Particle& particle) const { +template <> double SASABase::calcSASAOf(const Space& spc, const Particle& particle) const +{ return calcSASAOfParticle(spc, particle); } } // namespace SASA -TEST_CASE("[Faunus] SASA_CellList") { +TEST_CASE("[Faunus] SASA_CellList") +{ using namespace SASA; using doctest::Approx; Change change; @@ -508,7 +571,8 @@ TEST_CASE("[Faunus] SASA_CellList") { { "M": { "atoms": ["A", "B"], "atomic": true } } ])"_json.get(); - SUBCASE("periodic boundary") { + SUBCASE("periodic boundary") + { json j = R"({ "geometry": {"type": "cuboid", "length": [50.0, 50.0, 50.0] }, "insertmolecules": [ { "M": { "N": 1 } } ] @@ -539,7 +603,8 @@ TEST_CASE("[Faunus] SASA_CellList") { CHECK(neighbours[0].indices.empty()); CHECK_EQ(neighbours[1].indices[0], 0); } - SUBCASE("fixed boundary") { + SUBCASE("fixed boundary") + { json j = R"({ "geometry": {"type": "sphere", "radius": 50.0 }, "insertmolecules": [ { "M": { "N": 1 } } ] @@ -558,5 +623,4 @@ TEST_CASE("[Faunus] SASA_CellList") { } } - } // namespace Faunus diff --git a/src/sasa.h b/src/sasa.h index 6d3a0d739..d16298efe 100644 --- a/src/sasa.h +++ b/src/sasa.h @@ -28,9 +28,11 @@ using GeometryType = Geometry::Chameleon; * derived classes implement specific neighbour search algorithms * */ -class SASABase { -public: - struct Neighbours { +class SASABase +{ + public: + struct Neighbours + { std::vector indices; //!< indices of neighbouring particles in ParticleVector PointVector points; //!< vectors to neighbouring particles index_type index; //!< index of particle whose neighbours are in indices @@ -41,7 +43,7 @@ class SASABase { //!< this is important in case there is a particle insertion //!< which is rejected due to containerOverlap, because the sasa->update is not called -protected: + protected: double probe_radius = 1.4; //!< radius of the probe sphere std::vector areas; //!< vector holding SASA area of each atom std::vector sasa_radii; //!< Radii buffer for all particles @@ -53,25 +55,28 @@ class SASABase { * @brief returns absolute index of particle in ParticleVector * @param particle */ - [[nodiscard]] inline index_type indexOf(const Particle& particle) const { + [[nodiscard]] inline index_type indexOf(const Particle& particle) const + { return static_cast(std::addressof(particle) - first_particle); } [[nodiscard]] double calcSASAOfParticle(const Neighbours& neighbour) const; double exposedArcLength(std::vector>& arcs) const; -public: + public: [[nodiscard]] double calcSASAOfParticle(const Space& spc, const Particle& particle) const; /** - * @brief calculates total sasa of either particles or groups between given iterators - * @param spc - * @param begin iterator to either a first particle or group in a range - * @param end iterator to either end of particle or group in a range - * @tparam TBegin - * @tparam TEnd + * @brief calculates total sasa of either particles or groups between given iterators + * @param spc + * @param begin iterator to either a first particle or group in a range + * @param end iterator to either end of particle or group in a range + * @tparam TBegin + * @tparam TEnd */ - template double calcSASA(const Space& spc, TBegin begin, TEnd end) const { + template + double calcSASA(const Space& spc, TBegin begin, TEnd end) const + { return ranges::accumulate(begin, end, 0.0, [&spc, this](auto& area, const auto& species) { return area + this->calcSASAOf(spc, species); }); @@ -85,7 +90,8 @@ class SASABase { virtual void init(const Space& spc) = 0; [[nodiscard]] virtual std::vector calcNeighbourData(const Space& spc, const std::vector& target_indices) const = 0; - [[nodiscard]] virtual SASABase::Neighbours calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const = 0; + [[nodiscard]] virtual SASABase::Neighbours + calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const = 0; virtual void update(const Space& spc, const Change& change) = 0; [[nodiscard]] const std::vector& getAreas() const; SASABase(const Space& spc, double probe_radius, int slices_per_atom); @@ -96,12 +102,15 @@ class SASABase { * @brief derived class of SASABase which uses O(N^2) neighbour search * **/ -class SASA : public SASABase { -public: +class SASA : public SASABase +{ + public: void init(const Space& spc) override; - [[nodiscard]] std::vector calcNeighbourData(const Space& spc, - const std::vector& target_indices) const override; - [[nodiscard]] SASA::Neighbours calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const override; + [[nodiscard]] std::vector + calcNeighbourData(const Space& spc, + const std::vector& target_indices) const override; + [[nodiscard]] SASA::Neighbours + calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const override; void update([[maybe_unused]] const Space& spc, [[maybe_unused]] const Change& change) override; SASA(const Space& spc, double probe_radius, int slices_per_atom); SASA(const json& j, const Space& spc); @@ -114,25 +123,30 @@ class SASA : public SASABase { * @todo update function does perhaps unnecessary containsMember(Member&) checks * @todo create a wrapper class for cell_list so that the Space dependence is in there and not here **/ -template class SASACellList : public SASABase { -private: +template class SASACellList : public SASABase +{ + private: using CellCoord = typename CellList::Grid::CellCoord; std::unique_ptr cell_list; //!< pointer to cell list double cell_length; //!< dimension of a single cell - std::vector cell_offsets; //!< holds offsets which define a 3x3x3 cube around central cell + std::vector + cell_offsets; //!< holds offsets which define a 3x3x3 cube around central cell -public: + public: SASACellList(const Space& spc, double probe_radius, int slices_per_atom); SASACellList(const json& j, const Space& spc); ~SASACellList() override = default; void init(const Space& spc) override; - [[nodiscard]] SASABase::Neighbours calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const override; - [[nodiscard]] std::vector calcNeighbourData(const Space& spc, - const std::vector& target_indices) const override; + [[nodiscard]] SASABase::Neighbours + calcNeighbourDataOfParticle(const Space& spc, index_type target_index) const override; + [[nodiscard]] std::vector + calcNeighbourData(const Space& spc, + const std::vector& target_indices) const override; void update(const Space& spc, const Change& change) override; -private: - template void createCellList(TBegin begin, TEnd end, const GeometryType& geometry); + private: + template + void createCellList(TBegin begin, TEnd end, const GeometryType& geometry); void updateMatterChange(const Space& spc, const Change& change); void updatePositionsChange(const Space& spc, const Change& change); }; @@ -146,8 +160,8 @@ template using SparseContainer = CellList::Container::SparseContainer; template class TContainer = DenseContainer> -using CellListType = -CellList::CellListSpatial>; +using CellListType = CellList::CellListSpatial< + CellList::CellListType>; using DensePeriodicCellList = CellListType; using DenseFixedCellList = CellListType; diff --git a/src/scatter.cpp b/src/scatter.cpp index be83b07d2..1557da306 100644 --- a/src/scatter.cpp +++ b/src/scatter.cpp @@ -2,20 +2,21 @@ #include "analysis.h" #include "io.h" -//#define ANKERL_NANOBENCH_IMPLEMENT +// #define ANKERL_NANOBENCH_IMPLEMENT #include namespace Faunus::Scatter { using doctest::Approx; -TEST_CASE_TEMPLATE("[Faunus] StructureFactorPBC", T, StructureFactorPBC, StructureFactorPBC, - StructureFactorPBC) { +TEST_CASE_TEMPLATE("[Faunus] StructureFactorPBC", T, StructureFactorPBC, + StructureFactorPBC, StructureFactorPBC) +{ size_t cnt = 0; Point box = {80.0, 80.0, 80.0}; - const std::vector positions = { - {10, 20, 30}, {-32, 19, 1}, {34, -2, 23}, {0, 0, 1}, {25, 0, -12}, - {-6, -4, -29}, {-12, 23, -3}, {3, 1, -4}, {-31, 29, -20}}; // random position vector + const std::vector positions = {{10, 20, 30}, {-32, 19, 1}, {34, -2, 23}, {0, 0, 1}, + {25, 0, -12}, {-6, -4, -29}, {-12, 23, -3}, {3, 1, -4}, + {-31, 29, -20}}; // random position vector std::vector result = {0.0785, 1.48621, 0.1111, 0.567279, 0.136, 1.39515, 0.1571, 0.730579, 0.2221, 0.701547, 0.2721, 0.692064}; @@ -29,7 +30,8 @@ TEST_CASE_TEMPLATE("[Faunus] StructureFactorPBC", T, StructureFactorPBC positions(1000); for (auto& position : positions) { @@ -43,12 +45,13 @@ TEST_CASE("Benchmark") { } #endif -TEST_CASE("[Faunus] StructureFactorIPBC") { +TEST_CASE("[Faunus] StructureFactorIPBC") +{ size_t cnt = 0; Point box = {80.0, 80.0, 80.0}; - const std::vector positions = { - {10, 20, 30}, {-32, 19, 1}, {34, -2, 23}, {0, 0, 1}, {25, 0, -12}, - {-6, -4, -29}, {-12, 23, -3}, {3, 1, -4}, {-31, 29, -20}}; // random position vector + const std::vector positions = {{10, 20, 30}, {-32, 19, 1}, {34, -2, 23}, {0, 0, 1}, + {25, 0, -12}, {-6, -4, -29}, {-12, 23, -3}, {3, 1, -4}, + {-31, 29, -20}}; // random position vector std::vector result = {0.0785, 0.384363, 0.1111, 1.51652, 0.136, 1.18027, 0.1571, 1.40662, 0.2221, 2.06042, 0.2721, 1.53482}; StructureFactorIPBC scatter(2); diff --git a/src/scatter.h b/src/scatter.h index 4b487d8ba..e611ed4da 100644 --- a/src/scatter.h +++ b/src/scatter.h @@ -10,13 +10,20 @@ namespace Faunus { */ namespace Scatter { -enum Algorithm { SIMD, EIGEN, GENERIC }; //!< Selections for math algorithms +enum Algorithm +{ + SIMD, + EIGEN, + GENERIC +}; //!< Selections for math algorithms /** @brief Form factor, `F(q)`, for a hard sphere of radius `R`. */ -template class FormFactorSphere { +template class FormFactorSphere +{ private: - T j1(T x) const { // spherical Bessel function + T j1(T x) const + { // spherical Bessel function T xinv = 1 / x; return xinv * (sin(x) * xinv - cos(x)); } @@ -28,7 +35,8 @@ template class FormFactorSphere { * @returns * @f$I(q)=\left [\frac{3}{(qR)^3}\left (\sin{qR}-qR\cos{qR}\right )\right ]^2@f$ */ - template T operator()(T q, const Tparticle &a) const { + template T operator()(T q, const Tparticle& a) const + { assert(q > 0 && a.radius > 0 && "Particle radius and q must be positive"); T qR = q * a.radius; qR = 3. / (qR * qR * qR) * (sin(qR) - qR * cos(qR)); @@ -39,15 +47,16 @@ template class FormFactorSphere { /** * @brief Unity form factor (q independent). */ -template struct FormFactorUnity { - template T operator()(T, const Tparticle &) const { return 1; } +template struct FormFactorUnity +{ + template T operator()(T, const Tparticle&) const { return 1; } }; /** * @brief Calculate scattering intensity, I(q), on a mesh using the Debye formula. * - * It is important to note that distances should be calculated without periodicity and if molecules cross - * periodic boundaries, these must be made whole before performing the analysis. + * It is important to note that distances should be calculated without periodicity and if molecules + * cross periodic boundaries, these must be made whole before performing the analysis. * * The JSON object is scanned for the following keywords: * @@ -61,18 +70,20 @@ template struct FormFactorUnity { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdouble-promotion" -template class DebyeFormula { - static constexpr T r_cutoff_infty = 1e9; // class DebyeFormula +{ + static constexpr T r_cutoff_infty = + 1e9; // class DebyeFormula { * @param q_max Maximum q-value to sample (1/A) * @param q_step Spacing between mesh points (1/A) */ - void init_mesh(T q_min, T q_max, T q_step) { + void init_mesh(T q_min, T q_max, T q_step) + { if (q_step <= 0 || q_min <= 0 || q_max <= 0 || q_min > q_max || q_step / q_max < 4 * std::numeric_limits::epsilon()) { throw std::range_error("DebyeFormula: Invalid mesh parameters for q"); @@ -93,24 +105,30 @@ template class DebyeFormula { const int q_resolution = numeric_cast(1.0 + std::floor((q_max - q_min) / q_step)); intensity.resize(q_resolution, 0.0); sampling.resize(q_resolution, 0.0); - } catch (std::overflow_error &e) { + } + catch (std::overflow_error& e) { throw std::range_error("DebyeFormula: Too many samples"); } } - T r_cutoff; //!< cut-off distance for scattering contributions (angstrom) - Tformfactor form_factor; //!< scattering from a single particle - std::vector intensity; //!< sampled average I(q) - std::vector sampling; //!< weighted number of samplings + T r_cutoff; //!< cut-off distance for scattering contributions (angstrom) + Tformfactor form_factor; //!< scattering from a single particle + std::vector intensity; //!< sampled average I(q) + std::vector sampling; //!< weighted number of samplings public: - DebyeFormula(T q_min, T q_max, T q_step, T r_cutoff) : r_cutoff(r_cutoff) { init_mesh(q_min, q_max, q_step); }; + DebyeFormula(T q_min, T q_max, T q_step, T r_cutoff) + : r_cutoff(r_cutoff) + { + init_mesh(q_min, q_max, q_step); + }; - DebyeFormula(T q_min, T q_max, T q_step) : DebyeFormula(r_cutoff_infty, q_min, q_max, q_step) {}; + DebyeFormula(T q_min, T q_max, T q_step) + : DebyeFormula(r_cutoff_infty, q_min, q_max, q_step) {}; - explicit DebyeFormula(const json &j) - : DebyeFormula(j.at("qmin").get(), j.at("qmax").get(), j.at("dq").get(), - j.value("cutoff", r_cutoff_infty)){}; + explicit DebyeFormula(const json& j) + : DebyeFormula(j.at("qmin").get(), j.at("qmax").get(), + j.at("dq").get(), j.value("cutoff", r_cutoff_infty)) {}; /** * @brief Sample I(q) and add to average. @@ -118,54 +136,62 @@ template class DebyeFormula { * @param weight weight of sampled configuration in biased simulations * @param volume simulation volume (angstrom cubed) used only for cut-off correction * - * An isotropic correction is added beyond a given cut-off distance. For physics details see for example + * An isotropic correction is added beyond a given cut-off distance. For physics details see for + * example * @see https://debyer.readthedocs.org/en/latest/. * - * O(N^2) * O(M) complexity where N is the number of particles and M the number of mesh points. The quadratic - * complexity in N comes from the fact that the radial distribution function has to be computed. - * The current implementation supports OpenMP parallelization. Roughly half of the execution time is spend - * on computing sin values, e.g., in sinf_avx2. + * O(N^2) * O(M) complexity where N is the number of particles and M the number of mesh points. + * The quadratic complexity in N comes from the fact that the radial distribution function has + * to be computed. The current implementation supports OpenMP parallelization. Roughly half of + * the execution time is spend on computing sin values, e.g., in sinf_avx2. */ - template void sample(const Tpvec &p, const T weight = 1, const T volume = -1) { - const int N = (int) p.size(); // number of particles - const int M = (int) intensity.size(); // number of mesh points + template void sample(const Tpvec& p, const T weight = 1, const T volume = -1) + { + const int N = (int)p.size(); // number of particles + const int M = (int)intensity.size(); // number of mesh points std::vector intensity_sum(M, 0.0); - // Allow parallelization with a hand-written reduction of intensity_sum at the end. - // https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing - // #pragma omp parallel default(none) shared(N, M) shared(geo, r_cutoff, p) shared(intensity_sum) - #pragma omp parallel default(shared) shared(intensity_sum) +// Allow parallelization with a hand-written reduction of intensity_sum at the end. +// https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing +// #pragma omp parallel default(none) shared(N, M) shared(geo, r_cutoff, p) shared(intensity_sum) +#pragma omp parallel default(shared) shared(intensity_sum) { std::vector intensity_sum_private(M, 0.0); // a temporal private intensity_sum - #pragma omp for schedule(dynamic) +#pragma omp for schedule(dynamic) for (int i = 0; i < N - 1; ++i) { for (int j = i + 1; j < N; ++j) { - T r = T(Faunus::Geometry::Sphere::sqdist(p[i], p[j])); // the square root follows + T r = + T(Faunus::Geometry::Sphere::sqdist(p[i], p[j])); // the square root follows if (r < r_cutoff * r_cutoff) { r = std::sqrt(r); - // Black magic: The q_mesh function must be inlineable otherwise the loop cannot be unrolled - // using advanced SIMD instructions leading to a huge performance penalty (a factor of 4). - // The unrolled loop uses a different sin implementation, which may be spotted when profiling. - // TODO: Optimize also for other compilers than GCC by using a vector math library, e.g., + // Black magic: The q_mesh function must be inlineable otherwise the loop + // cannot be unrolled using advanced SIMD instructions leading to a huge + // performance penalty (a factor of 4). The unrolled loop uses a different + // sin implementation, which may be spotted when profiling. + // TODO: Optimize also for other compilers than GCC by using a vector math + // library, e.g., // TODO: https://github.com/vectorclass/version2 - // #pragma GCC unroll 16 // for diagnostics, GCC issues warning when cannot unroll + // #pragma GCC unroll 16 // for diagnostics, GCC issues warning when cannot + // unroll for (int m = 0; m < M; ++m) { const T q = q_mesh(m); - intensity_sum_private[m] += - form_factor(q, p[i]) * form_factor(q, p[j]) * std::sin(q * r) / (q * r); + intensity_sum_private[m] += form_factor(q, p[i]) * + form_factor(q, p[j]) * std::sin(q * r) / + (q * r); } } } } - // reduce intensity_sum_private into intensity_sum - #pragma omp critical - std::transform(intensity_sum.begin(), intensity_sum.end(), intensity_sum_private.begin(), - intensity_sum.begin(), std::plus()); +// reduce intensity_sum_private into intensity_sum +#pragma omp critical + std::transform(intensity_sum.begin(), intensity_sum.end(), + intensity_sum_private.begin(), intensity_sum.begin(), std::plus()); } - // https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing - // #pragma omp parallel for default(none) shared(N, M, weight, volume) shared(p, r_cutoff, intensity_sum) shared(sampling, intensity) - #pragma omp parallel for shared(sampling, intensity) +// https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing +// #pragma omp parallel for default(none) shared(N, M, weight, volume) shared(p, r_cutoff, +// intensity_sum) shared(sampling, intensity) +#pragma omp parallel for shared(sampling, intensity) for (int m = 0; m < M; ++m) { const T q = q_mesh(m); T intensity_self_sum = 0; @@ -175,24 +201,24 @@ template class DebyeFormula { T intensity_corr = 0; if (r_cutoff < r_cutoff_infty && volume > 0) { intensity_corr = 4 * pc::pi * N / (volume * std::pow(q, 3)) * - (q * r_cutoff * std::cos(q * r_cutoff) - std::sin(q * r_cutoff)); + (q * r_cutoff * std::cos(q * r_cutoff) - std::sin(q * r_cutoff)); } sampling[m] += weight; - intensity[m] += ((2 * intensity_sum[m] + intensity_self_sum) / N + intensity_corr) * weight; + intensity[m] += + ((2 * intensity_sum[m] + intensity_self_sum) / N + intensity_corr) * weight; } } /** * @return a tuple of min, max, and step parameters of a q-mash */ - auto getQMeshParameters() { - return std::make_tuple(q_mesh_min, q_mesh_max, q_mesh_step); - } + auto getQMeshParameters() { return std::make_tuple(q_mesh_min, q_mesh_max, q_mesh_step); } /** * @return a map containing q (key) and average intensity (value) */ - auto getIntensity() { + auto getIntensity() + { std::map averaged_intensity; for (size_t m = 0; m < intensity.size(); ++m) { const T average = intensity[m] / (sampling[m] != T(0.0) ? sampling[m] : T(1.0)); @@ -201,6 +227,7 @@ template class DebyeFormula { return averaged_intensity; } }; + #pragma GCC diagnostic pop /** @@ -208,19 +235,24 @@ template class DebyeFormula { * * @tparam T float or double */ -template class SamplingPolicy { +template class SamplingPolicy +{ public: - struct sampled_value { + struct sampled_value + { T value; T weight; }; + typedef std::map TSampledValueMap; + private: TSampledValueMap samples; const T precision = 10000.0; //!< precision of the key for better binning public: - std::map getSampling() const { + std::map getSampling() const + { std::map average; for (auto [key, sample] : samples) { average.emplace(key, sample.value / sample.weight); @@ -233,8 +265,10 @@ template class SamplingPolicy { * @param value * @param weight */ - void addSampling(T key_approx, T value, T weight = 1.0) { - const T key = std::round(key_approx * precision) / precision; // round |q| for better binning + void addSampling(T key_approx, T value, T weight = 1.0) + { + const T key = + std::round(key_approx * precision) / precision; // round |q| for better binning samples[key].value += value * weight; samples[key].weight += weight; } @@ -253,8 +287,10 @@ template class SamplingPolicy { * * For more information, see @see http://doi.org/d8zgw5 and @see http://doi.org/10.1063/1.449987. */ -template > -class StructureFactorPBC : private TSamplingPolicy { +template > +class StructureFactorPBC : private TSamplingPolicy +{ //! sample directions (h,k,l) const std::vector directions = { {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, // 3 permutations @@ -262,21 +298,28 @@ class StructureFactorPBC : private TSamplingPolicy { {1, 1, 1}, {-1, 1, 1}, {1, -1, 1}, {1, 1, -1} // 4 permutations }; - const int p_max; //!< multiples of q to be sampled + const int p_max; //!< multiples of q to be sampled using TSamplingPolicy::addSampling; public: - StructureFactorPBC(int q_multiplier) : p_max(q_multiplier){} + StructureFactorPBC(int q_multiplier) + : p_max(q_multiplier) + { + } /** * https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing - * #pragma omp parallel for collapse(2) default(none) shared(directions, p_max, boxlength) shared(positions) + * #pragma omp parallel for collapse(2) default(none) shared(directions, p_max, boxlength) + * shared(positions) */ - template void sample(const Tpositions& positions, const Point& boxlength) { + template + void sample(const Tpositions& positions, const Point& boxlength) + { #pragma omp parallel for collapse(2) default(shared) - for (size_t i = 0; i < directions.size(); ++i) { // openmp req. tradional loop - for (int p = 1; p <= p_max; ++p) { // loop over multiples of q - const Point q = 2.0 * pc::pi * p * directions[i].cwiseQuotient(boxlength); // scattering vector + for (size_t i = 0; i < directions.size(); ++i) { // openmp req. tradional loop + for (int p = 1; p <= p_max; ++p) { // loop over multiples of q + const Point q = + 2.0 * pc::pi * p * directions[i].cwiseQuotient(boxlength); // scattering vector const auto s_of_q = calculateStructureFactor(positions, q); #pragma omp critical // avoid race conditions when updating the map addSampling(q.norm(), s_of_q, 1.0); @@ -284,26 +327,34 @@ class StructureFactorPBC : private TSamplingPolicy { } } - template T calculateStructureFactor(const Tpositions& positions, const Point& q) const { + template + T calculateStructureFactor(const Tpositions& positions, const Point& q) const + { T sum_cos = 0.0; T sum_sin = 0.0; if constexpr (method == SIMD) { // When sine and cosine is computed in separate loops, sine and cosine SIMD // instructions may be used to get at least 4 times performance boost. - // Note January 2020: only GCC exploits this using libmvec library if --ffast-math is enabled. + // Note January 2020: only GCC exploits this using libmvec library if --ffast-math is + // enabled. auto dot_product = [q](const auto& pos) { return static_cast(q.dot(pos)); }; - auto qdotr = positions | ranges::cpp20::views::transform(dot_product) | ranges::to; + auto qdotr = + positions | ranges::cpp20::views::transform(dot_product) | ranges::to; std::for_each(qdotr.begin(), qdotr.end(), [&](auto qr) { sum_cos += cos(qr); }); std::for_each(qdotr.begin(), qdotr.end(), [&](auto qr) { sum_sin += sin(qr); }); - } else if constexpr (method == EIGEN) { + } + else if constexpr (method == EIGEN) { // Map is a Nx3 matrix facade into original positions (std::vector) using namespace Eigen; static_assert(std::is_same_v>); auto qdotr = - (Map>((double*)positions.data(), positions.size(), 3) * q).array().eval(); + (Map>((double*)positions.data(), positions.size(), 3) * q) + .array() + .eval(); sum_cos = qdotr.cast().cos().sum(); sum_sin = qdotr.cast().sin().sum(); - } else if constexpr (method == GENERIC) { + } + else if constexpr (method == GENERIC) { for (const auto& r : positions) { const auto qr = static_cast(q.dot(r)); sum_cos += cos(qr); // sine and cosine in same loop obstructs @@ -313,41 +364,49 @@ class StructureFactorPBC : private TSamplingPolicy { return std::norm(std::complex(sum_cos, sum_sin)) / static_cast(positions.size()); } - int getQMultiplier() { - return p_max; - } + int getQMultiplier() { return p_max; } using TSamplingPolicy::getSampling; }; /** - * @brief Calculate structure factor using explicit q averaging in isotropic periodic boundary conditions (IPBC). + * @brief Calculate structure factor using explicit q averaging in isotropic periodic boundary + * conditions (IPBC). * - * The sample directions reduce to 3 compared to 13 in regular periodic boundary conditions. Overall simplification - * shall yield roughly 10 times faster computation. + * The sample directions reduce to 3 compared to 13 in regular periodic boundary conditions. Overall + * simplification shall yield roughly 10 times faster computation. */ template > -class StructureFactorIPBC : private TSamplingPolicy { +class StructureFactorIPBC : private TSamplingPolicy +{ //! Sample directions (h,k,l). //! Due to the symmetry in IPBC we need not consider permutations of directions. std::vector directions = {{1, 0, 0}, {1, 1, 0}, {1, 1, 1}}; - int p_max; //!< multiples of q to be sampled + int p_max; //!< multiples of q to be sampled using TSamplingPolicy::addSampling; public: - explicit StructureFactorIPBC(int q_multiplier) : p_max(q_multiplier) {} + explicit StructureFactorIPBC(int q_multiplier) + : p_max(q_multiplier) + { + } - template void sample(const Tpositions& positions, const Point& boxlength) { - // https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing - // #pragma omp parallel for collapse(2) default(none) shared(directions, p_max, positions, boxlength) - #pragma omp parallel for collapse(2) default(shared) + template + void sample(const Tpositions& positions, const Point& boxlength) + { +// https://gcc.gnu.org/gcc-9/porting_to.html#ompdatasharing +// #pragma omp parallel for collapse(2) default(none) shared(directions, p_max, positions, +// boxlength) +#pragma omp parallel for collapse(2) default(shared) for (size_t i = 0; i < directions.size(); ++i) { - for (int p = 1; p <= p_max; ++p) { // loop over multiples of q - const Point q = 2.0 * pc::pi * p * directions[i].cwiseQuotient(boxlength); // scattering vector + for (int p = 1; p <= p_max; ++p) { // loop over multiples of q + const Point q = + 2.0 * pc::pi * p * directions[i].cwiseQuotient(boxlength); // scattering vector T sum_cos = 0; - for (auto &r : positions) { // loop over positions - // if q[i] == 0 then its cosine == 1 hence we can avoid cosine computation for performance reasons + for (auto& r : positions) { // loop over positions + // if q[i] == 0 then its cosine == 1 hence we can avoid cosine computation for + // performance reasons T product = std::cos(T(q[0] * r[0])); if (q[1] != 0) product *= std::cos(T(q[1] * r[1])); @@ -356,18 +415,17 @@ class StructureFactorIPBC : private TSamplingPolicy { sum_cos += product; } // collect average, `norm()` gives the scattering vector length - const T ipbc_factor = std::pow(2, directions[i].count()); // 2 ^ number of non-zero elements + const T ipbc_factor = + std::pow(2, directions[i].count()); // 2 ^ number of non-zero elements const T sf = (sum_cos * sum_cos) / (float)(positions.size()) * ipbc_factor; - #pragma omp critical +#pragma omp critical // avoid race conditions when updating the map addSampling(q.norm(), sf, 1.0); } } } - int getQMultiplier() { - return p_max; - } + int getQMultiplier() { return p_max; } using TSamplingPolicy::getSampling; }; diff --git a/src/smart_montecarlo.cpp b/src/smart_montecarlo.cpp index 3aaa0e6a5..e77f47a6f 100644 --- a/src/smart_montecarlo.cpp +++ b/src/smart_montecarlo.cpp @@ -12,7 +12,9 @@ namespace Faunus::SmarterMonteCarlo { * - See Allen and Tildesley p. 318 (2017 ed.). * - Original reference: [doi:10/frvx8j](https://doi.org/frvx8j) */ -double bias(double outside_acceptance, const int n_total, const int n_inside, BiasDirection direction) { +double bias(double outside_acceptance, const int n_total, const int n_inside, + BiasDirection direction) +{ const auto p = outside_acceptance; const auto n_prime = p * n_total + (1.0 - p) * n_inside; switch (direction) { @@ -25,7 +27,8 @@ double bias(double outside_acceptance, const int n_total, const int n_inside, Bi } } -TEST_CASE("[Faunus] SmartMonteCarlo::bias") { +TEST_CASE("[Faunus] SmartMonteCarlo::bias") +{ using doctest::Approx; CHECK_EQ(bias(1.0, 20, 5, BiasDirection::NO_CROSSING), Approx(0.0)); CHECK_EQ(bias(1.0, 20, 5, BiasDirection::EXIT_REGION), Approx(0.0)); @@ -40,25 +43,30 @@ TEST_CASE("[Faunus] SmartMonteCarlo::bias") { * @param outside_acceptance Probability to accept element outside the region * @param region Region to preferentially pick from */ -RegionSampler::RegionSampler(const double outside_acceptance, std::unique_ptr region) +RegionSampler::RegionSampler(const double outside_acceptance, + std::unique_ptr region) : outside_acceptance(outside_acceptance) - , region(std::move(region)) { + , region(std::move(region)) +{ if (outside_acceptance <= pc::epsilon_dbl || outside_acceptance > 1.0) { throw ConfigurationError("outside_acceptance (p), must be in the range (0,1]"); } } /** Determines the direction of a transition */ -BiasDirection RegionSampler::getDirection(const bool inside_before, const bool inside_after) { +BiasDirection RegionSampler::getDirection(const bool inside_before, const bool inside_after) +{ if (inside_before && (not inside_after)) { return BiasDirection::EXIT_REGION; - } else if (inside_after && (not inside_before)) { + } + else if (inside_after && (not inside_before)) { return BiasDirection::ENTER_REGION; } return BiasDirection::NO_CROSSING; } -void RegionSampler::to_json(json& j) const { +void RegionSampler::to_json(json& j) const +{ j["region"] = static_cast(*region); j["p"] = outside_acceptance; if (fixed_number_inside) { diff --git a/src/smart_montecarlo.h b/src/smart_montecarlo.h index 5af226883..25b758bdf 100644 --- a/src/smart_montecarlo.h +++ b/src/smart_montecarlo.h @@ -15,7 +15,8 @@ concept GroupOrParticle = std::is_convertible_v || std::is_convertible * This contains data useful for performing a SMC move. This object should be created * only with `SmartMonteCarlo::select` and hence has a protected constructor. */ -template class Selection { +template class Selection +{ public: T* item = nullptr; //!< Selected group, particle etc. int n_total = 0; @@ -23,6 +24,7 @@ template class Selection { bool is_inside = false; Selection(T& item, int number_total, int number_inside, bool item_is_inside); }; + /** * @tparam T Particle or group (`item') * @param item Reference to item @@ -35,9 +37,16 @@ Selection::Selection(T& item, int number_total, int number_inside, bool item_ : item(&item) , n_total(number_total) , n_inside(number_inside) - , is_inside(item_is_inside) {} + , is_inside(item_is_inside) +{ +} -enum class BiasDirection { ENTER_REGION, EXIT_REGION, NO_CROSSING }; //!< Controls the bias direction +enum class BiasDirection +{ + ENTER_REGION, + EXIT_REGION, + NO_CROSSING +}; //!< Controls the bias direction double bias(double outside_acceptance, int n_total, int n_inside, BiasDirection direction); //!< Bias energy (kT) @@ -50,10 +59,12 @@ double bias(double outside_acceptance, int n_total, int n_inside, * - Randomly select groups or particles with respect to an arbitrary `Region` * - Calculate the corresponding energy bias due to the non-uniform sampling */ -class RegionSampler { +class RegionSampler +{ private: - const double outside_acceptance = 1.0; //!< Or "p" between ]0:1]; 1 --> uniform sampling (no regional preference) - static BiasDirection getDirection(bool inside_before, bool inside_after) ; + const double outside_acceptance = + 1.0; //!< Or "p" between ]0:1]; 1 --> uniform sampling (no regional preference) + static BiasDirection getDirection(bool inside_before, bool inside_after); template double getNumberInside(Range& range) const; protected: @@ -77,11 +88,13 @@ class RegionSampler { * * @returns (mean) number of elements inside region */ -template double RegionSampler::getNumberInside(Range& range) const { +template double RegionSampler::getNumberInside(Range& range) const +{ if (fixed_number_inside) { return fixed_number_inside.value(); } - return static_cast(ranges::count_if(range, [&](const auto& i) { return region->inside(i); })); // expensive + return static_cast( + ranges::count_if(range, [&](const auto& i) { return region->inside(i); })); // expensive } /** @@ -97,7 +110,8 @@ template double RegionSampler::getNumberInside(Rang * number of attempts is currently set to 10x the size of the given range. */ template -std::optional> RegionSampler::select(Range& range, Random& random) { +std::optional> RegionSampler::select(Range& range, Random& random) +{ const auto n_total = ranges::distance(range.begin(), range.end()); int max_selection_attempts = 10 * n_total; do { @@ -123,25 +137,30 @@ std::optional> RegionSampler::select(Range& range, Random& random) * bias due to non-uniform sampling which depends on the transition directions, * i.e. if an element is moved from _inside_ of the region to the _outside_ etc. */ -template double RegionSampler::bias(const Selection& selection) { +template double RegionSampler::bias(const Selection& selection) +{ const auto is_inside_after = region->inside(*(selection.item)); // may have changed due to move const auto direction = getDirection(selection.is_inside, is_inside_after); - return SmarterMonteCarlo::bias(outside_acceptance, selection.n_total, selection.n_inside, direction); + return SmarterMonteCarlo::bias(outside_acceptance, selection.n_total, selection.n_inside, + direction); } /** * Helper class for constructing smart monte carlo moves */ -template class MoveSupport { +template class MoveSupport +{ private: - using OptionalElement = std::optional>; //!< Reference to selected element - std::optional> selection; //!< Contains data on currently selected group (if any) - SmarterMonteCarlo::RegionSampler region_sampler; //!< Biased sampling of elements - Average mean_bias_energy; //!< Statistics of bias energy - Average mean_selected_inside; //!< Fraction of selections found inside - AverageStdev mean_number_inside; //!< Average number of groups found inside region - AverageStdev blocks_inside; //!< Used for block averaging of `mean_number_inside` - void analyseSelection(int count_inside); //!< Track and analyze inside count + using OptionalElement = + std::optional>; //!< Reference to selected element + std::optional> + selection; //!< Contains data on currently selected group (if any) + SmarterMonteCarlo::RegionSampler region_sampler; //!< Biased sampling of elements + Average mean_bias_energy; //!< Statistics of bias energy + Average mean_selected_inside; //!< Fraction of selections found inside + AverageStdev mean_number_inside; //!< Average number of groups found inside region + AverageStdev blocks_inside; //!< Used for block averaging of `mean_number_inside` + void analyseSelection(int count_inside); //!< Track and analyze inside count public: MoveSupport(const Space& spc, const json& j); @@ -152,7 +171,9 @@ template class MoveSupport { template MoveSupport::MoveSupport(const Space& spc, const json& j) - : region_sampler(j.at("p").get(), Region::createRegion(spc, j)) {} + : region_sampler(j.at("p").get(), Region::createRegion(spc, j)) +{ +} /** * This is used to sample and analyse the average number of groups inside the region. @@ -161,7 +182,8 @@ MoveSupport::MoveSupport(const Space& spc, const json& j) * * @todo convergence thresholds currently hardcoded; bootstrapping would be better... */ -template void MoveSupport::analyseSelection(int count_inside) { +template void MoveSupport::analyseSelection(int count_inside) +{ assert(selection); mean_selected_inside += static_cast(selection->is_inside); @@ -170,12 +192,15 @@ template void MoveSupport::analyseSelection(int count_ins } mean_number_inside += static_cast(count_inside); - if (mean_number_inside.size() % 100 == 0) { // build up a block average - blocks_inside += mean_number_inside.avg(); // add a new block - if (blocks_inside.size() % 2 == 0 && blocks_inside.rsd() < 0.04) { // check if block has converged - region_sampler.fixed_number_inside = blocks_inside.avg(); // disable all further sampling! - faunus_logger->info("Regional = {:.1f} converged and settled after {} iterations", - region_sampler.fixed_number_inside.value(), mean_number_inside.size()); + if (mean_number_inside.size() % 100 == 0) { // build up a block average + blocks_inside += mean_number_inside.avg(); // add a new block + if (blocks_inside.size() % 2 == 0 && + blocks_inside.rsd() < 0.04) { // check if block has converged + region_sampler.fixed_number_inside = + blocks_inside.avg(); // disable all further sampling! + faunus_logger->info( + "Regional = {:.1f} converged and settled after {} iterations", + region_sampler.fixed_number_inside.value(), mean_number_inside.size()); } } } @@ -188,7 +213,8 @@ template void MoveSupport::analyseSelection(int count_ins * * @return Bias energy (kT) */ -template double MoveSupport::bias() { +template double MoveSupport::bias() +{ auto bias_energy(0.0); if (selection) { analyseSelection(selection->n_inside); @@ -198,7 +224,8 @@ template double MoveSupport::bias() { return bias_energy; } -template void MoveSupport::to_json(json& j) const { +template void MoveSupport::to_json(json& j) const +{ region_sampler.to_json(j); if (mean_number_inside) { j["mean number inside"] = mean_number_inside.avg(); @@ -219,7 +246,8 @@ template void MoveSupport::to_json(json& j) const { */ template template -typename MoveSupport::OptionalElement MoveSupport::select(Range& mollist, Random& random) { +typename MoveSupport::OptionalElement MoveSupport::select(Range& mollist, Random& random) +{ selection = region_sampler.select(mollist, random); if (selection) { return *(selection->item); diff --git a/src/space.cpp b/src/space.cpp index b8103be35..4bb195234 100644 --- a/src/space.cpp +++ b/src/space.cpp @@ -8,27 +8,42 @@ namespace Faunus { -bool Change::GroupChange::operator<(const Faunus::Change::GroupChange& other) const { +bool Change::GroupChange::operator<(const Faunus::Change::GroupChange& other) const +{ return group_index < other.group_index; } -void Change::GroupChange::sort() { + +void Change::GroupChange::sort() +{ std::sort(relative_atom_indices.begin(), relative_atom_indices.end()); } -void Change::clear() { +void Change::clear() +{ *this = Change(); assert(empty()); } -bool Change::empty() const { return not(volume_change || everything || matter_change || !groups.empty()); } -Change::operator bool() const { return !empty(); } +bool Change::empty() const +{ + return not(volume_change || everything || matter_change || !groups.empty()); +} + +Change::operator bool() const +{ + return !empty(); +} -[[maybe_unused]] std::vector Change::touchedParticleIndex(const std::vector& group_vector) const { +[[maybe_unused]] std::vector +Change::touchedParticleIndex(const std::vector& group_vector) const +{ std::vector indices; // atom index rel. to first particle in system auto begin_first = group_vector.front().begin(); // first particle, first group for (const auto& changed : groups) { // loop over changed groups - auto begin_current = group_vector.at(changed.group_index).begin(); // first particle, current group - auto offset = std::distance(begin_first, begin_current); // abs. distance from first particle + auto begin_current = + group_vector.at(changed.group_index).begin(); // first particle, current group + auto offset = + std::distance(begin_first, begin_current); // abs. distance from first particle if (offset < 0) { throw std::runtime_error("negative index"); } @@ -45,7 +60,8 @@ Change::operator bool() const { return !empty(); } * @param group_vector Vector of group connected to the change; typically `Space::groups`. * @throw If the atoms in the change object is outside range of given group index. */ -void Change::sanityCheck(const std::vector& group_vector) const { +void Change::sanityCheck(const std::vector& group_vector) const +{ const auto first_particle = group_vector.at(0).begin(); // first particle in first group for (const auto& changed : groups) { const auto& group = group_vector.at(changed.group_index); @@ -63,12 +79,15 @@ void Change::sanityCheck(const std::vector& group_vector) const { /** * @brief Determines if the change reflects a single particle change - * @return Optional pair with index of group (first) and relative particle index (second) in the group + * @return Optional pair with index of group (first) and relative particle index (second) in the + * group * * Since single particle updates are frequently used, this convenience function helps * to establish if this is the case. */ -std::optional> Change::singleParticleChange() const { +std::optional> +Change::singleParticleChange() const +{ if (!everything || !volume_change) { if (groups.size() == 1 && groups.front().relative_atom_indices.size() == 1) { const auto group_index = groups.front().group_index; @@ -79,13 +98,15 @@ std::optional> Change::singleP return std::nullopt; } -void to_json(json& j, const Change::GroupChange& group_change) { +void to_json(json& j, const Change::GroupChange& group_change) +{ j = {{"all", group_change.all}, {"internal", group_change.internal}, {"dNswap", group_change.dNswap}, {"dNatomic", group_change.dNatomic}, {"index", group_change.group_index}, {"atoms", group_change.relative_atom_indices}}; } -void to_json(json& j, const Change& change) { +void to_json(json& j, const Change& change) +{ j = {{"dV", change.volume_change}, {"all", change.everything}, {"dN", change.matter_change}, @@ -93,7 +114,8 @@ void to_json(json& j, const Change& change) { {"groups", change.groups}}; } -TEST_CASE("[Faunus] Change") { +TEST_CASE("[Faunus] Change") +{ Change change; CHECK(not change); change.volume_change = true; @@ -103,7 +125,8 @@ TEST_CASE("[Faunus] Change") { CHECK(change.empty()); } -void Space::clear() { +void Space::clear() +{ particles.clear(); groups.clear(); implicit_reservoir.clear(); @@ -122,28 +145,33 @@ void Space::clear() { * @return Reference to the inserted group * @throw if particles is empty or if the given particles do not match the molecule id */ -Space::GroupType& Space::addGroup(MoleculeData::index_type molid, const ParticleVector& new_particles) { +Space::GroupType& Space::addGroup(MoleculeData::index_type molid, + const ParticleVector& new_particles) +{ if (new_particles.empty()) { throw std::runtime_error("cannot add empty molecule"); } auto original_begin = particles.begin(); // used to detect if `particles` is relocated - particles.insert(particles.end(), new_particles.begin(), new_particles.end()); // insert particle into space + particles.insert(particles.end(), new_particles.begin(), + new_particles.end()); // insert particle into space if (particles.begin() != original_begin) { // update group iterators if `particles` is relocated std::for_each(groups.begin(), groups.end(), [&](auto& group) { group.relocate(original_begin, particles.begin()); }); } - GroupType group(molid, particles.end() - new_particles.size(), particles.end()); // create a group + GroupType group(molid, particles.end() - new_particles.size(), + particles.end()); // create a group const auto& moldata = Faunus::molecules.at(molid); group.id = molid; if (group.isMolecular()) { if (new_particles.size() != moldata.atoms.size()) { - faunus_logger->error("{} requires {} atoms but {} were provided", moldata.name, moldata.atoms.size(), - new_particles.size()); + faunus_logger->error("{} requires {} atoms but {} were provided", moldata.name, + moldata.atoms.size(), new_particles.size()); throw std::runtime_error("particle size mismatch"); } - group.mass_center = - Geometry::massCenter(group.begin(), group.end(), geometry.getBoundaryFunc(), -new_particles.begin()->pos); - } else { + group.mass_center = Geometry::massCenter( + group.begin(), group.end(), geometry.getBoundaryFunc(), -new_particles.begin()->pos); + } + else { if (new_particles.size() % moldata.atoms.size() != 0) { throw std::runtime_error("indivisible by atomic group size: "s + moldata.name); } @@ -167,7 +195,8 @@ Space::GroupType& Space::addGroup(MoleculeData::index_type molid, const Particle * - particles * - implicit molecules */ -void Space::sync(const Space &other, const Change &change) { +void Space::sync(const Space& other, const Change& change) +{ if (&other == this || change.empty()) { return; } @@ -178,25 +207,29 @@ void Space::sync(const Space &other, const Change &change) { if (change.volume_change or change.everything) { geometry = other.geometry; // copy simulation geometry } - if (change.everything) { // deep copy *everything* - implicit_reservoir = other.implicit_reservoir; // copy all implicit molecules - particles = other.particles; // copy all positions - groups = other.groups; // copy all groups - assert(particles.begin() != other.particles.begin()); // check deep copy problem + if (change.everything) { // deep copy *everything* + implicit_reservoir = other.implicit_reservoir; // copy all implicit molecules + particles = other.particles; // copy all positions + groups = other.groups; // copy all groups + assert(particles.begin() != other.particles.begin()); // check deep copy problem assert(groups.front().begin() != other.groups.front().begin()); // check deep copy problem - } else { + } + else { for (const auto& changed : change.groups) { // look over changed groups auto& group = groups.at(changed.group_index); // old group auto& other_group = other.groups.at(changed.group_index); // new group assert(group.id == other_group.id); if (group.traits().isImplicit()) { // the molecule is implicit implicit_reservoir[group.id] = other.implicit_reservoir.at(group.id); - } else if (changed.all) { - group = other_group; // copy everything - } else { // copy only a subset - group.shallowCopy(other_group); // copy group data but *not* particles - for (auto i : changed.relative_atom_indices) { // loop over atom index (rel. to group) - group.at(i) = other_group.at(i); // deep copy select particles + } + else if (changed.all) { + group = other_group; // copy everything + } + else { // copy only a subset + group.shallowCopy(other_group); // copy group data but *not* particles + for (auto i : + changed.relative_atom_indices) { // loop over atom index (rel. to group) + group.at(i) = other_group.at(i); // deep copy select particles } } } @@ -209,42 +242,47 @@ void Space::sync(const Space &other, const Change &change) { * @param Vnew New volume * @param method Scaling policy * @returns Scaling factors in each dimension - * @warning Check new_volume/old_volume for ISOCHORIC in case of external triggers (see end of function) + * @warning Check new_volume/old_volume for ISOCHORIC in case of external triggers (see end of + * function) */ -Point Space::scaleVolume(double Vnew, Geometry::VolumeMethod method) { - for (auto &group : groups) { // remove PBC on molecular groups ... +Point Space::scaleVolume(double Vnew, Geometry::VolumeMethod method) +{ + for (auto& group : groups) { // remove PBC on molecular groups ... group.unwrap(geometry.getDistanceFunc()); // ... before scaling the volume } auto Vold = geometry.getVolume(); // simulation volume before move Point scale = geometry.setVolume(Vnew, method); // scale volume of simulation container - auto scale_position = [&](auto &particle) { + auto scale_position = [&](auto& particle) { particle.pos = particle.pos.cwiseProduct(scale); geometry.boundary(particle.pos); }; //!< unary helper function to scale position and apply PBC - for (auto &group : groups) { // loop over all molecules in system + for (auto& group : groups) { // loop over all molecules in system if (group.empty()) { continue; - } else if (group.isAtomic()) { // scale all particle positions + } + else if (group.isAtomic()) { // scale all particle positions std::for_each(group.begin(), group.end(), scale_position); - } else { + } + else { auto original_mass_center = group.mass_center; if (group.traits().compressible) { // scale positions; recalculate mass center std::for_each(group.begin(), group.end(), scale_position); - group.mass_center = - Geometry::massCenter(group.begin(), group.end(), geometry.getBoundaryFunc(), -original_mass_center); - } else { // scale mass center; translate positions + group.mass_center = Geometry::massCenter( + group.begin(), group.end(), geometry.getBoundaryFunc(), -original_mass_center); + } + else { // scale mass center; translate positions group.mass_center = group.mass_center.cwiseProduct(scale); geometry.boundary(group.mass_center); auto mass_center_displacement = group.mass_center - original_mass_center; - std::for_each(group.begin(), group.end(), [&](auto &particle) { + std::for_each(group.begin(), group.end(), [&](auto& particle) { particle.pos += mass_center_displacement; // translate internal coordinates geometry.boundary(particle.pos); // apply PBC }); #ifndef NDEBUG - auto recalc_cm = - Geometry::massCenter(group.begin(), group.end(), geometry.getBoundaryFunc(), -group.mass_center); + auto recalc_cm = Geometry::massCenter( + group.begin(), group.end(), geometry.getBoundaryFunc(), -group.mass_center); if (double error = geometry.sqdist(group.mass_center, recalc_cm); error > 1e-6) { assert(false); // mass center mismatch } @@ -255,17 +293,21 @@ Point Space::scaleVolume(double Vnew, Geometry::VolumeMethod method) { if (method == Geometry::VolumeMethod::ISOCHORIC) { // ? not used for anything... Vold = std::pow(Vold, 1.0 / 3.0); // ? } - for (const auto& trigger_function : scaleVolumeTriggers) { // external clients may have added function - trigger_function(*this, Vold, Vnew); // to be triggered upon each volume change + for (const auto& trigger_function : + scaleVolumeTriggers) { // external clients may have added function + trigger_function(*this, Vold, Vnew); // to be triggered upon each volume change } return scale; } -json Space::info() { - json j = {{"number of particles", particles.size()}, {"number of groups", groups.size()}, {"geometry", geometry}}; - auto &j_groups = j["groups"]; +json Space::info() +{ + json j = {{"number of particles", particles.size()}, + {"number of groups", groups.size()}, + {"geometry", geometry}}; + auto& j_groups = j["groups"]; for (const auto& group : groups) { - auto &molname = Faunus::molecules.at(group.id).name; + auto& molname = Faunus::molecules.at(group.id).name; json tmp, d = group; d.erase("cm"); d.erase("id"); @@ -277,42 +319,52 @@ json Space::info() { tmp[molname] = d; j_groups.push_back(tmp); } - auto &j_reactionlist = j["reactionlist"]; - for (auto &reaction : Faunus::reactions) { + auto& j_reactionlist = j["reactionlist"]; + for (auto& reaction : Faunus::reactions) { j_reactionlist.push_back(reaction); } return j; } Space::GroupVector::iterator Space::randomMolecule(MoleculeData::index_type molid, Random& rand, - Space::Selection selection) { + Space::Selection selection) +{ auto found_molecules = findMolecules(molid, selection); if (ranges::cpp20::empty(found_molecules)) { return groups.end(); } - return groups.begin() + (&*rand.sample(found_molecules.begin(), found_molecules.end()) - &*groups.begin()); + return groups.begin() + + (&*rand.sample(found_molecules.begin(), found_molecules.end()) - &*groups.begin()); } -const std::map& Space::getImplicitReservoir() const { +const std::map& Space::getImplicitReservoir() const +{ return implicit_reservoir; } -std::map& Space::getImplicitReservoir() { return implicit_reservoir; } +std::map& Space::getImplicitReservoir() +{ + return implicit_reservoir; +} std::vector>::iterator -Space::findGroupContaining(const Particle& particle, bool include_inactive) { +Space::findGroupContaining(const Particle& particle, bool include_inactive) +{ return std::find_if(groups.begin(), groups.end(), [&](Group& group) { return group.contains(particle, include_inactive); }); } std::vector>::iterator -Space::findGroupContaining(AtomData::index_type atom_index) { +Space::findGroupContaining(AtomData::index_type atom_index) +{ assert(atom_index < particles.size()); - return std::find_if(groups.begin(), groups.end(), - [&](auto& g) { return atom_index < std::distance(particles.begin(), g.end()); }); + return std::find_if(groups.begin(), groups.end(), [&](auto& g) { + return atom_index < std::distance(particles.begin(), g.end()); + }); } -std::size_t Space::numParticles(Space::Selection selection) const { +std::size_t Space::numParticles(Space::Selection selection) const +{ if (selection == Selection::ALL) { return particles.size(); } @@ -326,7 +378,8 @@ std::size_t Space::numParticles(Space::Selection selection) const { /** * @throw If group is not part of space */ -std::size_t Space::getGroupIndex(const Space::GroupType& group) const { +std::size_t Space::getGroupIndex(const Space::GroupType& group) const +{ const auto distance = std::addressof(group) - std::addressof(groups.front()); // std::ptrdiff_t if (distance >= 0) { const auto index = static_cast(distance); @@ -337,9 +390,11 @@ std::size_t Space::getGroupIndex(const Space::GroupType& group) const { throw std::out_of_range("invalid group index"); } -std::size_t Space::getFirstParticleIndex(const GroupType& group) const { +std::size_t Space::getFirstParticleIndex(const GroupType& group) const +{ if (group.capacity() > 0 && !particles.empty()) { - const auto distance = std::distance(particles.cbegin(), group.begin()); + const auto distance = + std::distance(particles.cbegin(), group.begin()); if (distance >= 0) { const auto particle_index = static_cast(distance); if (particle_index < particles.size()) { @@ -353,42 +408,54 @@ std::size_t Space::getFirstParticleIndex(const GroupType& group) const { /** * Returns the index of the first particle of the group in the range returned by `activeParticles()` */ -std::size_t Space::getFirstActiveParticleIndex(const GroupType& group) const { +std::size_t Space::getFirstActiveParticleIndex(const GroupType& group) const +{ const auto group_index = getGroupIndex(group); std::size_t index = 0u; - std::for_each(groups.begin(), groups.begin() + group_index, [&](const auto& g) { index += g.size(); }); + std::for_each(groups.begin(), groups.begin() + group_index, + [&](const auto& g) { index += g.size(); }); return index; } -size_t Space::countAtoms(AtomData::index_type atomid) const { - return ranges::cpp20::count_if(activeParticles(), [&](auto& particle) { return particle.id == atomid; }); +size_t Space::countAtoms(AtomData::index_type atomid) const +{ + return ranges::cpp20::count_if(activeParticles(), + [&](auto& particle) { return particle.id == atomid; }); } -void Space::updateInternalState(const Change& change) { - std::for_each(changeTriggers.begin(), changeTriggers.end(), [&](auto& trigger) { trigger(*this, change); }); +void Space::updateInternalState(const Change& change) +{ + std::for_each(changeTriggers.begin(), changeTriggers.end(), + [&](auto& trigger) { trigger(*this, change); }); } -TEST_CASE("Space::numParticles") { +TEST_CASE("Space::numParticles") +{ Space spc; spc.particles.resize(10); CHECK_EQ(spc.particles.size(), spc.numParticles(Space::Selection::ALL)); - CHECK_EQ(spc.numParticles(Space::Selection::ACTIVE), 0); // zero as there are still no groups - spc.groups.emplace_back(0, spc.particles.begin(), spc.particles.end() - 2); // enclose first 8 particles in group + CHECK_EQ(spc.numParticles(Space::Selection::ACTIVE), 0); // zero as there are still no groups + spc.groups.emplace_back(0, spc.particles.begin(), + spc.particles.end() - 2); // enclose first 8 particles in group CHECK_EQ(spc.numParticles(Space::Selection::ACTIVE), 8); - spc.groups.emplace_back(0, spc.particles.end() - 2, spc.particles.end()); // enclose last 2 particles in group + spc.groups.emplace_back(0, spc.particles.end() - 2, + spc.particles.end()); // enclose last 2 particles in group CHECK_EQ(spc.numParticles(Space::Selection::ACTIVE), 10); spc.groups.front().resize(0); // deactivate first group with 8 particles CHECK_EQ(spc.numParticles(Space::Selection::ACTIVE), 2); } -void to_json(json& j, const Space& spc) { +void to_json(json& j, const Space& spc) +{ j["geometry"] = spc.geometry; j["groups"] = spc.groups; j["particles"] = spc.particles; j["reactionlist"] = reactions; j["implicit_reservoir"] = spc.getImplicitReservoir(); } -void from_json(const json &j, Space &spc) { + +void from_json(const json& j, Space& spc) +{ try { if (atoms.empty()) { atoms = j.at("atomlist").get(); @@ -405,12 +472,13 @@ void from_json(const json &j, Space &spc) { if (!j.contains("groups")) { InsertMoleculesInSpace::insertMolecules(j.at("insertmolecules"), spc); - } else { + } + else { spc.particles = j.at("particles").get(); if (!spc.particles.empty()) { auto begin = spc.particles.begin(); Space::GroupType g(0, begin, begin); // create new group - for (auto &i : j.at("groups")) { + for (auto& i : j.at("groups")) { g.begin() = begin; from_json(i, g); spc.groups.push_back(g); @@ -434,20 +502,29 @@ void from_json(const json &j, Space &spc) { auto check_mass_center = [&](const auto& group) { const auto should_be_small = spc.geometry.sqdist( group.mass_center, - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.mass_center)); + Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), + -group.mass_center)); if (should_be_small > 1e-9) { throw std::runtime_error(fmt::format( - "couldn't calculate mass center for {}; increase periodic box size?", group.traits().name)); + "couldn't calculate mass center for {}; increase periodic box size?", + group.traits().name)); } }; - auto active_and_molecular = [](const auto& group) { return (!group.empty() && group.isMolecular()); }; - ranges::cpp20::for_each(spc.groups | ranges::cpp20::views::filter(active_and_molecular), check_mass_center); - } catch (const std::exception& e) { throw std::runtime_error("error building space -> "s + e.what()); } + auto active_and_molecular = [](const auto& group) { + return (!group.empty() && group.isMolecular()); + }; + ranges::cpp20::for_each(spc.groups | ranges::cpp20::views::filter(active_and_molecular), + check_mass_center); + } + catch (const std::exception& e) { + throw std::runtime_error("error building space -> "s + e.what()); + } } TEST_SUITE_BEGIN("Space"); -TEST_CASE("[Faunus] Space") { +TEST_CASE("[Faunus] Space") +{ using doctest::Approx; Space spc1; spc1.geometry = R"( {"type": "sphere", "radius": 1e9} )"_json; @@ -478,7 +555,8 @@ TEST_CASE("[Faunus] Space") { // check `positions()` CHECK_EQ(&spc1.positions()[0], &spc1.particles[0].pos); - SUBCASE("ActiveParticles") { + SUBCASE("ActiveParticles") + { // add three groups to space Space spc; spc.geometry = R"( {"type": "sphere", "radius": 1e9} )"_json; @@ -530,7 +608,8 @@ TEST_CASE("[Faunus] Space") { } } -TEST_CASE("[Faunus] Space::toIndices") { +TEST_CASE("[Faunus] Space::toIndices") +{ Space spc; spc.particles.resize(3); @@ -544,7 +623,8 @@ TEST_CASE("[Faunus] Space::toIndices") { CHECK_THROWS(spc.toIndices(particles)); } -TEST_CASE("[Faunus] Space::updateParticles") { +TEST_CASE("[Faunus] Space::updateParticles") +{ using doctest::Approx; Space spc; Geometry::Cuboid geo({100, 100, 100}); @@ -566,31 +646,35 @@ TEST_CASE("[Faunus] Space::updateParticles") { CHECK_EQ(spc.particles[0].pos.x(), 2.1); CHECK_EQ(spc.particles[1].pos.x(), 0.9); - SUBCASE("Group update") { + SUBCASE("Group update") + { Space spc; SpaceFactory::makeWater(spc, 2, R"( {"type": "cuboid", "length": 20} )"_json); CHECK_EQ(spc.groups.size(), 2); - auto copy_function = [](const auto &pos, auto &particle) { particle.pos = pos; }; + auto copy_function = [](const auto& pos, auto& particle) { particle.pos = pos; }; std::vector positions = {{0, 0, 0}, {3, 3, 3}, {6, 6, 6}}; // first group affected spc.groups[0].mass_center.x() = -1; spc.groups[1].mass_center.x() = -1; - spc.updateParticles(positions.begin(), positions.end(), spc.groups[1].begin(), copy_function); + spc.updateParticles(positions.begin(), positions.end(), spc.groups[1].begin(), + copy_function); CHECK_EQ(spc.groups[0].mass_center.x(), Approx(-1)); CHECK_EQ(spc.groups[1].mass_center.x(), Approx(0.5031366235)); // second group affected spc.groups[1].mass_center.x() = -1; - spc.updateParticles(positions.begin(), positions.end(), spc.groups[0].begin(), copy_function); + spc.updateParticles(positions.begin(), positions.end(), spc.groups[0].begin(), + copy_function); CHECK_EQ(spc.groups[0].mass_center.x(), Approx(0.5031366235)); CHECK_EQ(spc.groups[1].mass_center.x(), Approx(-1)); // both groups affected spc.groups[0].mass_center.x() = -1; spc.groups[1].mass_center.x() = -1; - spc.updateParticles(positions.begin(), positions.end(), spc.groups[0].begin() + 1, copy_function); + spc.updateParticles(positions.begin(), positions.end(), spc.groups[0].begin() + 1, + copy_function); CHECK_EQ(spc.groups[0].mass_center.x(), Approx(0.1677122078)); CHECK_EQ(spc.groups[1].mass_center.x(), Approx(5.8322877922)); } @@ -608,7 +692,8 @@ namespace SpaceFactory { * Create a system with two atom types, "Na" and "Cl", forming * an atomic molecule, "salt". N salt pairs a randomly inserted */ -void makeNaCl(Space& space, size_t num_particles, const Geometry::Chameleon& geometry) { +void makeNaCl(Space& space, size_t num_particles, const Geometry::Chameleon& geometry) +{ pc::temperature = 298.15_K; space.geometry = geometry; @@ -631,7 +716,8 @@ void makeNaCl(Space& space, size_t num_particles, const Geometry::Chameleon& geo * @param num_particles Number of salt pairs to insert * @param geometry Geometry to use */ -void makeWater(Space& space, size_t num_particles, const Geometry::Chameleon& geometry) { +void makeWater(Space& space, size_t num_particles, const Geometry::Chameleon& geometry) +{ pc::temperature = 298.15_K; space.geometry = geometry; @@ -656,12 +742,14 @@ void makeWater(Space& space, size_t num_particles, const Geometry::Chameleon& ge } // namespace SpaceFactory -TEST_CASE("SpaceFactory") { +TEST_CASE("SpaceFactory") +{ Space spc; SpaceFactory::makeNaCl(spc, 10, R"( {"type": "cuboid", "length": 20} )"_json); CHECK_EQ(spc.numParticles(), 20); - SUBCASE("makeWater") { + SUBCASE("makeWater") + { Space spc; SpaceFactory::makeWater(spc, 2, R"( {"type": "cuboid", "length": 20} )"_json); CHECK_EQ(spc.numParticles(), 6); @@ -678,12 +766,13 @@ TEST_CASE("SpaceFactory") { * The atoms in the atomic groups are repeated `num_molecules` times, then inserted * into Space. */ -void InsertMoleculesInSpace::insertAtomicGroups(MoleculeData& moldata, Space& spc, size_t num_molecules, - size_t num_inactive_molecules) { +void InsertMoleculesInSpace::insertAtomicGroups(MoleculeData& moldata, Space& spc, + size_t num_molecules, size_t num_inactive_molecules) +{ assert(moldata.atomic); ParticleVector repeated_particles; repeated_particles.reserve(num_molecules * moldata.atoms.size()); // prepare memory - for (size_t i = 0; i < num_molecules; i++) { // repeat insertion into the same atomic group + for (size_t i = 0; i < num_molecules; i++) { // repeat insertion into the same atomic group auto particles = moldata.getRandomConformation(spc.geometry, spc.particles); repeated_particles.insert(repeated_particles.end(), particles.begin(), particles.end()); } @@ -699,8 +788,9 @@ void InsertMoleculesInSpace::insertAtomicGroups(MoleculeData& moldata, Space& sp } } -void InsertMoleculesInSpace::insertMolecularGroups(MoleculeData& moldata, Space& spc, size_t num_molecules, - size_t num_inactive) { +void InsertMoleculesInSpace::insertMolecularGroups(MoleculeData& moldata, Space& spc, + size_t num_molecules, size_t num_inactive) +{ assert(moldata.atomic == false); if (num_inactive > num_molecules) { throw std::runtime_error("too many inactive molecules requested"); @@ -730,7 +820,8 @@ void InsertMoleculesInSpace::insertMolecularGroups(MoleculeData& moldata, Space& */ void InsertMoleculesInSpace::setPositionsForTrailingGroups(Space& spc, size_t num_molecules, const Faunus::ParticleVector& particles, - const Point& offset) { + const Point& offset) +{ assert(spc.groups.size() >= num_molecules); if (particles.size() != num_molecules * (spc.groups.rbegin())->traits().atoms.size()) { throw std::runtime_error("number of particles doesn't match groups"); @@ -744,12 +835,12 @@ void InsertMoleculesInSpace::setPositionsForTrailingGroups(Space& spc, size_t nu return dst; }; - std::transform(particles.rbegin(), particles.rend(), spc.particles.rbegin(), spc.particles.rbegin(), - copy_with_offset_and_boundary_check); + std::transform(particles.rbegin(), particles.rend(), spc.particles.rbegin(), + spc.particles.rbegin(), copy_with_offset_and_boundary_check); std::for_each(spc.groups.rbegin(), spc.groups.rbegin() + num_molecules, [&](auto& group) { - group.mass_center = - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.begin()->pos); + group.mass_center = Geometry::massCenter( + group.begin(), group.end(), spc.geometry.getBoundaryFunc(), -group.begin()->pos); }); // update mass-centers on modified groups; start from the back } @@ -759,7 +850,9 @@ void InsertMoleculesInSpace::setPositionsForTrailingGroups(Space& spc, size_t nu * @param spc Space to insert into. * @param num_molecules Nunber of implicit molecules to insert */ -void InsertMoleculesInSpace::insertImplicitGroups(const MoleculeData& moldata, Space& spc, size_t num_molecules) { +void InsertMoleculesInSpace::insertImplicitGroups(const MoleculeData& moldata, Space& spc, + size_t num_molecules) +{ assert(moldata.isImplicit()); spc.getImplicitReservoir()[moldata.id()] = num_molecules; } @@ -769,27 +862,35 @@ void InsertMoleculesInSpace::insertImplicitGroups(const MoleculeData& moldata, S * @param properties json object with insertion properties ('N', 'molarity', 'inactive' etc) * @param spc Space to insert into */ -void InsertMoleculesInSpace::insertItem(const std::string &molname, const json &properties, Space &spc) { +void InsertMoleculesInSpace::insertItem(const std::string& molname, const json& properties, + Space& spc) +{ auto& moldata = findMoleculeByName(molname); const auto num_molecules = getNumberOfMolecules(properties, spc.geometry.getVolume(), molname); if (num_molecules == 0) { if (!moldata.isImplicit()) { - throw ConfigurationError("one or more {} molecule(s) required; concentration too low?", molname); + throw ConfigurationError("one or more {} molecule(s) required; concentration too low?", + molname); } - } else { + } + else { const auto num_inactive = getNumberOfInactiveMolecules(properties, num_molecules); const auto molarity = (num_molecules - num_inactive) / spc.geometry.getVolume() / 1.0_molar; if (moldata.isImplicit()) { - faunus_logger->info("adding {} implicit {} molecules --> {:.5E} mol/l", num_molecules, molname, molarity); + faunus_logger->info("adding {} implicit {} molecules --> {:.5E} mol/l", num_molecules, + molname, molarity); insertImplicitGroups(moldata, spc, num_molecules); - } else { - faunus_logger->info("adding {} {} molecules --> {:.5E} mol/l ({} inactive)", num_molecules, molname, - molarity, num_inactive); + } + else { + faunus_logger->info("adding {} {} molecules --> {:.5E} mol/l ({} inactive)", + num_molecules, molname, molarity, num_inactive); if (moldata.atomic) { insertAtomicGroups(moldata, spc, num_molecules, num_inactive); - } else { + } + else { insertMolecularGroups(moldata, spc, num_molecules, num_inactive); - if (auto particles = getExternalPositions(properties, molname); !particles.empty()) { + if (auto particles = getExternalPositions(properties, molname); + !particles.empty()) { auto offset = properties.value("translate", Point(0, 0, 0)); setPositionsForTrailingGroups(spc, num_molecules, particles, offset); } @@ -806,7 +907,9 @@ void InsertMoleculesInSpace::insertItem(const std::string &molname, const json & * @returns Particle vector; empty if no external positions are requested * @throw If the 'positions' key is there, but could not be loaded */ -ParticleVector InsertMoleculesInSpace::getExternalPositions(const json &j, const std::string &molname) { +ParticleVector InsertMoleculesInSpace::getExternalPositions(const json& j, + const std::string& molname) +{ ParticleVector particles; if (auto filename = j.value("positions", ""s); !filename.empty()) { particles = loadStructure(filename, false); // throws on error @@ -820,7 +923,8 @@ ParticleVector InsertMoleculesInSpace::getExternalPositions(const json &j, const * @param j JSON array * @param spc Space to insert into */ -void InsertMoleculesInSpace::insertMolecules(const json &j, Space &spc) { +void InsertMoleculesInSpace::insertMolecules(const json& j, Space& spc) +{ if (!j.is_array()) { throw ConfigurationError("molecules to insert must be an array"); } @@ -831,7 +935,10 @@ void InsertMoleculesInSpace::insertMolecules(const json &j, Space &spc) { const auto& [molecule_name, properties] = jsonSingleItem(item); try { insertItem(molecule_name, properties, spc); - } catch (std::exception& e) { throw ConfigurationError("error inserting {}: {}", molecule_name, e.what()); } + } + catch (std::exception& e) { + throw ConfigurationError("error inserting {}: {}", molecule_name, e.what()); + } } faunus_logger->trace("particles inserted = {}", spc.particles.size()); faunus_logger->trace("groups inserted = {}", spc.groups.size()); @@ -840,7 +947,8 @@ void InsertMoleculesInSpace::insertMolecules(const json &j, Space &spc) { /** * Allocate space for particles and groups to be inserted */ -void InsertMoleculesInSpace::reserveMemory(const json& j, Space& spc) { +void InsertMoleculesInSpace::reserveMemory(const json& j, Space& spc) +{ auto required_number_of_particles = spc.particles.size(); auto required_number_of_groups = spc.groups.size(); @@ -850,11 +958,13 @@ void InsertMoleculesInSpace::reserveMemory(const json& j, Space& spc) { if (moldata.isImplicit()) { continue; } - const auto num_molecules = getNumberOfMolecules(properties, spc.geometry.getVolume(), molecule_name); + const auto num_molecules = + getNumberOfMolecules(properties, spc.geometry.getVolume(), molecule_name); required_number_of_particles += num_molecules * moldata.atoms.size(); if (moldata.isAtomic()) { required_number_of_groups++; - } else { + } + else { required_number_of_groups += num_molecules; } } @@ -874,16 +984,21 @@ void InsertMoleculesInSpace::reserveMemory(const json& j, Space& spc) { * Looks for json key `N` or `molarity`. For the latter, the nearest corresponding * number of particles is calculated based on the given system volume. */ -size_t InsertMoleculesInSpace::getNumberOfMolecules(const json& j, double volume, const std::string& molecule_name) { +size_t InsertMoleculesInSpace::getNumberOfMolecules(const json& j, double volume, + const std::string& molecule_name) +{ size_t num_molecules = 0; if (j.contains("N")) { num_molecules = j.at("N").get(); - } else { + } + else { auto density = j.at("molarity").get() * 1.0_molar; num_molecules = std::round(density * volume); const double error_limit = 0.01; // warn if relative density error is above this - if (auto error = (density - num_molecules / volume) / density; std::fabs(error) > error_limit) { - faunus_logger->warn("{}: initial molarity differs by {}% from target value", molecule_name, error * 100); + if (auto error = (density - num_molecules / volume) / density; + std::fabs(error) > error_limit) { + faunus_logger->warn("{}: initial molarity differs by {}% from target value", + molecule_name, error * 100); } } return num_molecules; @@ -900,14 +1015,17 @@ size_t InsertMoleculesInSpace::getNumberOfMolecules(const json& j, double volume * - number: number of molecules to declare inactive * - no `inactive` key found, return zero */ -size_t InsertMoleculesInSpace::getNumberOfInactiveMolecules(const json& j, size_t number_of_molecules) { +size_t InsertMoleculesInSpace::getNumberOfInactiveMolecules(const json& j, + size_t number_of_molecules) +{ size_t number_of_inactive_molecules = 0; // number of inactive molecules if (auto inactive = j.find("inactive"); inactive != j.end()) { if (inactive->is_boolean()) { if (inactive->get()) { number_of_inactive_molecules = number_of_molecules; // all molecules are inactive } - } else if (inactive->is_number_integer()) { + } + else if (inactive->is_number_integer()) { number_of_inactive_molecules = inactive->get(); // a subset are inactive if (number_of_inactive_molecules > number_of_molecules) { throw ConfigurationError("too many inactive particles requested"); diff --git a/src/space.h b/src/space.h index 799b4c731..f7591dcae 100644 --- a/src/space.h +++ b/src/space.h @@ -26,25 +26,31 @@ namespace Faunus { * - If `GroupChange::all==true` then `relative_atom_indices` may be left empty. This is to * avoid constucting a large size N vector of indices. */ -struct Change { +struct Change +{ using index_type = std::size_t; - bool everything = false; //!< Everything has changed (particles, groups, volume) - bool volume_change = false; //!< The volume has changed - bool matter_change = false; //!< The number of atomic or molecular species has changed - bool moved_to_moved_interactions = true; //!< If several groups are moved, should they interact with each other? + bool everything = false; //!< Everything has changed (particles, groups, volume) + bool volume_change = false; //!< The volume has changed + bool matter_change = false; //!< The number of atomic or molecular species has changed + bool moved_to_moved_interactions = + true; //!< If several groups are moved, should they interact with each other? bool disable_translational_entropy = false; //!< Force exclusion of translational entropy //! Properties of changed groups - struct GroupChange { + struct GroupChange + { index_type group_index; //!< Touched group index bool dNatomic = false; //!< The number of atomic molecules has changed bool dNswap = false; //!< The number of atoms has changed as a result of a swap move bool internal = false; //!< The internal energy or configuration has changed - bool all = false; //!< All particles in the group have changed (leave relative_atom_indices empty) - std::vector relative_atom_indices; //!< A subset of particles changed (sorted; empty if `all`=true) - - bool operator<(const GroupChange& other) const; //!< Comparison operator based on `group_index` - void sort(); //!< Sort group indices + bool all = + false; //!< All particles in the group have changed (leave relative_atom_indices empty) + std::vector + relative_atom_indices; //!< A subset of particles changed (sorted; empty if `all`=true) + + bool + operator<(const GroupChange& other) const; //!< Comparison operator based on `group_index` + void sort(); //!< Sort group indices }; std::vector groups; //!< Touched groups by index in group vector @@ -52,15 +58,20 @@ struct Change { [[nodiscard]] std::optional> singleParticleChange() const; //! List of moved groups (index) - [[nodiscard]] inline auto touchedGroupIndex() const { return ranges::cpp20::views::transform(groups, &GroupChange::group_index); } + [[nodiscard]] inline auto touchedGroupIndex() const + { + return ranges::cpp20::views::transform(groups, &GroupChange::group_index); + } //! List of changed atom index relative to first particle in system - [[maybe_unused]] [[nodiscard]] std::vector touchedParticleIndex(const std::vector&) const; - - void clear(); //!< Clear all change data - [[nodiscard]] bool empty() const; //!< Check if change object is empty - explicit operator bool() const; //!< True if object is not empty - void sanityCheck(const std::vector& group_vector) const; //!< Sanity check on contained object data + [[maybe_unused]] [[nodiscard]] std::vector + touchedParticleIndex(const std::vector&) const; + + void clear(); //!< Clear all change data + [[nodiscard]] bool empty() const; //!< Check if change object is empty + explicit operator bool() const; //!< True if object is not empty + void sanityCheck( + const std::vector& group_vector) const; //!< Sanity check on contained object data } __attribute__((aligned(32))); void to_json(json& j, const Change::GroupChange& group_change); //!< Serialize Change data to json @@ -78,7 +89,8 @@ void to_json(json& j, const Change& change); //!< Serialise C * It has methods to insert, find, and probe particles, groups, as * well as scaling the volume of the system */ -class Space { +class Space +{ public: using GeometryType = Geometry::Chameleon; using GroupType = Group; //!< Continuous range of particles defining molecules @@ -101,37 +113,57 @@ class Space { std::map implicit_reservoir; std::vector changeTriggers; //!< Call when a Change object is applied (unused) - std::vector onSyncTriggers; //!< Every element called after two Space objects are synched with `sync()` + std::vector + onSyncTriggers; //!< Every element called after two Space objects are synched with `sync()` public: - ParticleVector particles; //!< All particles are stored here! - GroupVector groups; //!< All groups are stored here (i.e. molecules) - GeometryType geometry; //!< Container geometry (boundaries, shape, volume) - std::vector scaleVolumeTriggers; //!< Functions triggered whenever the volume is scaled + ParticleVector particles; //!< All particles are stored here! + GroupVector groups; //!< All groups are stored here (i.e. molecules) + GeometryType geometry; //!< Container geometry (boundaries, shape, volume) + std::vector + scaleVolumeTriggers; //!< Functions triggered whenever the volume is scaled - [[nodiscard]] const std::map& getImplicitReservoir() const; //!< Implicit molecules - std::map& getImplicitReservoir(); //!< Implicit molecules + [[nodiscard]] const std::map& + getImplicitReservoir() const; //!< Implicit molecules + std::map& getImplicitReservoir(); //!< Implicit molecules //!< Keywords to select particles based on the their active/inactive state and charge neutrality - enum class Selection { ALL, ACTIVE, INACTIVE, ALL_NEUTRAL, ACTIVE_NEUTRAL, INACTIVE_NEUTRAL }; + enum class Selection + { + ALL, + ACTIVE, + INACTIVE, + ALL_NEUTRAL, + ACTIVE_NEUTRAL, + INACTIVE_NEUTRAL + }; void clear(); //!< Clears particle and molecule list - GroupType& addGroup(MoleculeData::index_type molid, const ParticleVector& particles); //!< Append a group - GroupVector::iterator findGroupContaining(const Particle& particle, bool include_inactive = false); //!< Finds the group containing the given atom - GroupVector::iterator findGroupContaining(AtomData::index_type atom_index); //!< Find group containing atom index - [[nodiscard]] size_t numParticles(Selection selection = Selection::ACTIVE) const; //!< Number of (active) particles - - Point scaleVolume( - double new_volume, - Geometry::VolumeMethod method = Geometry::VolumeMethod::ISOTROPIC); //!< Scales atoms, molecules, container - - GroupVector::iterator randomMolecule(MoleculeData::index_type molid, Random& rand, - Selection selection = Selection::ACTIVE); //!< Random group matching molid + GroupType& addGroup(MoleculeData::index_type molid, + const ParticleVector& particles); //!< Append a group + GroupVector::iterator findGroupContaining( + const Particle& particle, + bool include_inactive = false); //!< Finds the group containing the given atom + GroupVector::iterator + findGroupContaining(AtomData::index_type atom_index); //!< Find group containing atom index + [[nodiscard]] size_t + numParticles(Selection selection = Selection::ACTIVE) const; //!< Number of (active) particles + + Point + scaleVolume(double new_volume, + Geometry::VolumeMethod method = + Geometry::VolumeMethod::ISOTROPIC); //!< Scales atoms, molecules, container + + GroupVector::iterator + randomMolecule(MoleculeData::index_type molid, Random& rand, + Selection selection = Selection::ACTIVE); //!< Random group matching molid json info(); - [[nodiscard]] std::size_t getGroupIndex(const GroupType& group) const; //!< Get index of given group in the group vector - [[nodiscard]] std::size_t getFirstParticleIndex(const GroupType& group) const; //!< Index of first particle in group + [[nodiscard]] std::size_t + getGroupIndex(const GroupType& group) const; //!< Get index of given group in the group vector + [[nodiscard]] std::size_t + getFirstParticleIndex(const GroupType& group) const; //!< Index of first particle in group [[nodiscard]] std::size_t getFirstActiveParticleIndex( const GroupType& group) const; //!< Index of first particle w. respect to active particles @@ -139,7 +171,8 @@ class Space { * @brief Update particles in Space from a source range * * @tparam iterator Iterator for source range - * @tparam copy_operation Functor used to copy data from an element in the source range to element in `Space:p` + * @tparam copy_operation Functor used to copy data from an element in the source range to + * element in `Space:p` * @param begin Begin of source particle range * @param end End of source particle range * @param destination Iterator to target particle vector (should be in `Space::p`) @@ -157,31 +190,37 @@ class Space { * * @todo Since Space::groups is ordered, binary search could be used to filter */ - template > + template > void updateParticles( const iterator begin, const iterator end, ParticleVector::iterator destination, - copy_operation copy_function = [](const Particle& src, Particle& dst) { dst = src; }) { + copy_operation copy_function = [](const Particle& src, Particle& dst) { dst = src; }) + { const auto size = std::distance(begin, end); // number of affected particles assert(destination >= particles.begin() && destination < particles.end()); assert(size <= std::distance(destination, particles.end())); - auto affected_groups = groups | ranges::cpp20::views::filter([=](auto &group) { - return (group.begin() < destination + size) && (group.end() > destination); - }); // filtered group with affected groups only. Note we copy in org. `destination` + auto affected_groups = + groups | ranges::cpp20::views::filter([=](auto& group) { + return (group.begin() < destination + size) && (group.end() > destination); + }); // filtered group with affected groups only. Note we copy in org. `destination` // copy data from source range (this modifies `destination`) - std::for_each(begin, end, [&](const auto &source) { copy_function(source, *destination++); }); + std::for_each(begin, end, + [&](const auto& source) { copy_function(source, *destination++); }); - std::for_each(affected_groups.begin(), affected_groups.end(), - [&](Group& group) { group.updateMassCenter(geometry.getBoundaryFunc(), group.begin()->pos); }); + std::for_each(affected_groups.begin(), affected_groups.end(), [&](Group& group) { + group.updateMassCenter(geometry.getBoundaryFunc(), group.begin()->pos); + }); } /** * @brief This will make sure that the internal state is updated to reflect a change. * - * Typically this is called after a modification of the system, e.g. a MC move and will update the following: + * Typically this is called after a modification of the system, e.g. a MC move and will update + * the following: * * - mass centers; * - particle trackers @@ -192,20 +231,23 @@ class Space { void updateInternalState(const Change& change); //! Iterable range of all particle positions - [[nodiscard]] auto positions() const { - return ranges::cpp20::views::transform(particles, [](auto& particle) -> const Point& { return particle.pos; }); + [[nodiscard]] auto positions() const + { + return ranges::cpp20::views::transform( + particles, [](auto& particle) -> const Point& { return particle.pos; }); } //! Mutable iterable range of all particle positions auto positions() { return ranges::cpp20::views::transform(particles, &Particle::pos); } static std::function getGroupFilter(MoleculeData::index_type molid, - const Selection& selection) { + const Selection& selection) + { auto is_active = [](const GroupType& group) { return group.size() == group.capacity(); }; auto is_neutral = [](RequireParticleIterator auto begin, RequireParticleIterator auto end) { - auto charge = - std::accumulate(begin, end, 0.0, [](auto sum, auto& particle) { return sum + particle.charge; }); + auto charge = std::accumulate( + begin, end, 0.0, [](auto sum, auto& particle) { return sum + particle.charge; }); return (fabs(charge) < 1e-6); }; //!< determines if range of particles is neutral @@ -225,10 +267,14 @@ class Space { f = [=](auto& group) { return is_neutral(group.begin(), group.trueend()); }; break; case (Selection::INACTIVE_NEUTRAL): - f = [=](auto& group) { return !is_active(group) && is_neutral(group.begin(), group.trueend()); }; + f = [=](auto& group) { + return !is_active(group) && is_neutral(group.begin(), group.trueend()); + }; break; case (Selection::ACTIVE_NEUTRAL): - f = [=](auto& group) { return is_active(group) && is_neutral(group.begin(), group.end()); }; + f = [=](auto& group) { + return is_active(group) && is_neutral(group.begin(), group.end()); + }; break; } return [f, molid](auto& group) { return group.id == molid && f(group); }; @@ -240,25 +286,37 @@ class Space { * @param selection Selection * @return range with all groups of molid */ - auto findMolecules(MoleculeData::index_type molid, Selection selection = Selection::ACTIVE) { + auto findMolecules(MoleculeData::index_type molid, Selection selection = Selection::ACTIVE) + { auto group_filter = getGroupFilter(molid, selection); return groups | ranges::cpp20::views::filter(group_filter); } - [[nodiscard]] auto findMolecules(MoleculeData::index_type molid, Selection selection = Selection::ACTIVE) const { + [[nodiscard]] auto findMolecules(MoleculeData::index_type molid, + Selection selection = Selection::ACTIVE) const + { auto group_filter = getGroupFilter(molid, selection); return groups | ranges::cpp20::views::filter(group_filter); } - auto activeParticles() { return groups | ranges::cpp20::views::join; } //!< Range with all active particles - [[nodiscard]] auto activeParticles() const { return groups | ranges::cpp20::views::join; } //!< Range with all active particles + auto activeParticles() + { + return groups | ranges::cpp20::views::join; + } //!< Range with all active particles + + [[nodiscard]] auto activeParticles() const + { + return groups | ranges::cpp20::views::join; + } //!< Range with all active particles /** * @brief Get vector of indices of given range of particles * @returns std::vector of indices pointing to Space::particles * @throw std::out_of_range if any particle in range does not belong to Space::particles */ - template auto toIndices(const RequireParticles auto& particle_range) const { + template + auto toIndices(const RequireParticles auto& particle_range) const + { auto to_index = [&](auto& particle) { const auto index = std::addressof(particle) - std::addressof(particles.at(0)); if (index < 0 || index >= particles.size()) { @@ -274,9 +332,11 @@ class Space { * @param atomid Atom id to look for * @return Range of filtered particles */ - auto findAtoms(AtomData::index_type atomid) { - return activeParticles() | - ranges::cpp20::views::filter([atomid](const Particle& particle) { return particle.id == atomid; }); + auto findAtoms(AtomData::index_type atomid) + { + return activeParticles() | ranges::cpp20::views::filter([atomid](const Particle& particle) { + return particle.id == atomid; + }); } /** @@ -284,9 +344,11 @@ class Space { * @param atomid Atom id to look for * @return Range of filtered particles */ - [[nodiscard]] auto findAtoms(AtomData::index_type atomid) const { - return activeParticles() | - ranges::cpp20::views::filter([atomid](const Particle& particle) { return particle.id == atomid; }); + [[nodiscard]] auto findAtoms(AtomData::index_type atomid) const + { + return activeParticles() | ranges::cpp20::views::filter([atomid](const Particle& particle) { + return particle.id == atomid; + }); } [[nodiscard]] size_t countAtoms(AtomData::index_type atomid) const; //!< Count active particles @@ -297,19 +359,21 @@ class Space { * @tparam mask Selection mask based on `Group::Selectors` * @return Number of molecules matching molid and mask */ - template auto numMolecules(MoleculeData::index_type molid) const { + template auto numMolecules(MoleculeData::index_type molid) const + { auto filter = [&](const GroupType& group) { return (group.id == molid) ? group.template match() : false; }; return std::count_if(groups.begin(), groups.end(), filter); } - void sync(const Space& other, const Change& change); //!< Copy differing data from other Space using Change object + void sync(const Space& other, + const Change& change); //!< Copy differing data from other Space using Change object }; // end of space void to_json(json& j, const Space& spc); //!< Serialize Space to json object -void from_json(const json &j, Space &spc); //!< Deserialize json object to Space +void from_json(const json& j, Space& spc); //!< Deserialize json object to Space /** * @brief Insert molecules into space @@ -323,29 +387,32 @@ void from_json(const json &j, Space &spc); //!< Deserialize json object to Space * - dummy: { N: 100, inactive: true } # all 100 inactive * ~~~ */ -class InsertMoleculesInSpace { +class InsertMoleculesInSpace +{ private: static void insertAtomicGroups(MoleculeData& moldata, Space& spc, size_t num_molecules, size_t num_inactive_molecules); - static void insertMolecularGroups(MoleculeData& moldata, Space& spc, size_t num_molecules, size_t num_inactive); + static void insertMolecularGroups(MoleculeData& moldata, Space& spc, size_t num_molecules, + size_t num_inactive); - static void setPositionsForTrailingGroups(Space& spc, size_t num_molecules, const Faunus::ParticleVector&, - const Point&); + static void setPositionsForTrailingGroups(Space& spc, size_t num_molecules, + const Faunus::ParticleVector&, const Point&); static void insertImplicitGroups(const MoleculeData& moldata, Space& spc, size_t num_molecules); //! Get number of molecules to insert from json object - static size_t getNumberOfMolecules(const json& j, double volume, const std::string& molecule_name); + static size_t getNumberOfMolecules(const json& j, double volume, + const std::string& molecule_name); //! Get number of inactive molecules from json object static size_t getNumberOfInactiveMolecules(const json& j, size_t number_of_molecules); //!< Get position vector using json object - static ParticleVector getExternalPositions(const json &j, const std::string &molname); + static ParticleVector getExternalPositions(const json& j, const std::string& molname); //!< Aggregated version of the above, called on each item in json array - static void insertItem(const std::string &molname, const json &properties, Space &spc); + static void insertItem(const std::string& molname, const json& properties, Space& spc); static void reserveMemory(const json& j, Space& spc); @@ -355,8 +422,10 @@ class InsertMoleculesInSpace { namespace SpaceFactory { -void makeNaCl(Space& space, size_t num_particles, const Geometry::Chameleon& geometry); //!< Create a salt system -void makeWater(Space& space, size_t num_particles, const Geometry::Chameleon& geometry); //!< Create a water system +void makeNaCl(Space& space, size_t num_particles, + const Geometry::Chameleon& geometry); //!< Create a salt system +void makeWater(Space& space, size_t num_particles, + const Geometry::Chameleon& geometry); //!< Create a water system } // namespace SpaceFactory diff --git a/src/speciation.cpp b/src/speciation.cpp index 4f476b129..a439b4f39 100644 --- a/src/speciation.cpp +++ b/src/speciation.cpp @@ -12,18 +12,24 @@ namespace Faunus::Speciation { /** * @brief Internal exception to carry errors preventing the speciation move. */ -struct SpeciationMoveException : public std::exception {}; +struct SpeciationMoveException : public std::exception +{ +}; // ---------------------------------- -void ReactionDirectionRatio::AcceptanceData::update(ReactionData::Direction direction, bool accept) { +void ReactionDirectionRatio::AcceptanceData::update(ReactionData::Direction direction, bool accept) +{ if (direction == ReactionData::Direction::RIGHT) { right += static_cast(accept); - } else { + } + else { left += static_cast(accept); } } -void ReactionDirectionRatio::to_json(json& j) const { + +void ReactionDirectionRatio::to_json(json& j) const +{ auto& _j = j["reactions"] = json::object(); for (const auto& [reaction, data] : acceptance) { _j[reaction->getReactionString()] = {{"attempts", data.left.size() + data.right.size()}, @@ -33,38 +39,47 @@ void ReactionDirectionRatio::to_json(json& j) const { } ReactionDirectionRatio::AcceptanceData& -ReactionDirectionRatio::operator[](ReactionDirectionRatio::reaction_iterator iter) { +ReactionDirectionRatio::operator[](ReactionDirectionRatio::reaction_iterator iter) +{ return acceptance[iter]; } // ----------------------------------------- ReactionValidator::ReactionValidator(const Space& spc) - : spc(spc) {} + : spc(spc) +{ +} /** * @param reaction Reaction to check - * @return If true, it is guarantied that reactants/products are available and no further checks are needed + * @return If true, it is guarantied that reactants/products are available and no further checks are + * needed */ -bool ReactionValidator::isPossible(const ReactionData& reaction) const { - return canReduceImplicitGroups(reaction) && canSwapAtoms(reaction) && canReduceMolecularGroups(reaction) && - canProduceMolecularGroups(reaction) && canReduceAtomicGroups(reaction) && canProduceAtomicGroups(reaction); +bool ReactionValidator::isPossible(const ReactionData& reaction) const +{ + return canReduceImplicitGroups(reaction) && canSwapAtoms(reaction) && + canReduceMolecularGroups(reaction) && canProduceMolecularGroups(reaction) && + canReduceAtomicGroups(reaction) && canProduceAtomicGroups(reaction); } -bool ReactionValidator::canReduceImplicitGroups(const ReactionData& reaction) const { +bool ReactionValidator::canReduceImplicitGroups(const ReactionData& reaction) const +{ auto has_enough = [&](const auto& key_value) { const auto& [molid, number_to_delete] = key_value; return spc.getImplicitReservoir().at(molid) >= number_to_delete; }; - auto implicit_reactants = - reaction.getReactants().second | ranges::cpp20::views::filter(ReactionData::is_implicit_group); + auto implicit_reactants = reaction.getReactants().second | + ranges::cpp20::views::filter(ReactionData::is_implicit_group); return ranges::cpp20::all_of(implicit_reactants, has_enough); } -bool ReactionValidator::canSwapAtoms(const ReactionData& reaction) const { +bool ReactionValidator::canSwapAtoms(const ReactionData& reaction) const +{ namespace rv = ranges::cpp20::views; if (reaction.containsAtomicSwap()) { - auto reactive_atoms = reaction.getReactants().first | rv::filter(ReactionData::not_implicit_atom); + auto reactive_atoms = + reaction.getReactants().first | rv::filter(ReactionData::not_implicit_atom); for (const auto& [atomid, number_to_swap] : reactive_atoms) { auto particles = spc.findAtoms(atomid) | rv::take(number_to_swap); if (range_size(particles) != number_to_swap) { @@ -75,9 +90,11 @@ bool ReactionValidator::canSwapAtoms(const ReactionData& reaction) const { return true; } -bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) const { +bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) const +{ namespace rv = ranges::cpp20::views; - auto selection = reaction.only_neutral_molecules ? Space::Selection::ACTIVE_NEUTRAL : Space::Selection::ACTIVE; + auto selection = reaction.only_neutral_molecules ? Space::Selection::ACTIVE_NEUTRAL + : Space::Selection::ACTIVE; auto can_reduce = [&](auto key_value) { // molecular reactant (non-atomic) const auto [molid, number_to_delete] = key_value; @@ -85,7 +102,8 @@ bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) c auto groups = spc.findMolecules(molid, selection) | rv::take(number_to_delete); if (range_size(groups) != number_to_delete) { if (Faunus::molecules.at(molid).activity > 0.0) { - faunus_logger->warn("all grand canonical {} molecules have been deleted; increase system volume?", + faunus_logger->warn("all grand canonical {} molecules have been deleted; " + "increase system volume?", Faunus::molecules.at(molid).name); } return false; @@ -94,18 +112,21 @@ bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) c return true; }; - auto molecular_groups = reaction.getReactants().second | rv::filter(ReactionData::not_implicit_group) | + auto molecular_groups = reaction.getReactants().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_molecular_group); return ranges::cpp20::all_of(molecular_groups, can_reduce); } -bool ReactionValidator::canReduceAtomicGroups(const ReactionData& reaction) const { +bool ReactionValidator::canReduceAtomicGroups(const ReactionData& reaction) const +{ namespace rv = ranges::cpp20::views; auto can_reduce = [&](auto key_value) { const auto [molid, number_to_delete] = key_value; if (number_to_delete > 0) { - auto groups = spc.findMolecules(molid, Space::Selection::ALL) | ranges::to; + auto groups = spc.findMolecules(molid, Space::Selection::ALL) | + ranges::to; if (groups.size() != 1) { return false; } @@ -119,14 +140,17 @@ bool ReactionValidator::canReduceAtomicGroups(const ReactionData& reaction) cons return true; }; - auto atomic_groups = reaction.getReactants().second | rv::filter(ReactionData::not_implicit_group) | + auto atomic_groups = reaction.getReactants().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); return ranges::cpp20::all_of(atomic_groups, can_reduce); } -bool ReactionValidator::canProduceMolecularGroups(const ReactionData& reaction) const { +bool ReactionValidator::canProduceMolecularGroups(const ReactionData& reaction) const +{ namespace rv = ranges::cpp20::views; - auto selection = reaction.only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL : Space::Selection::INACTIVE; + auto selection = reaction.only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL + : Space::Selection::INACTIVE; auto can_create = [&](auto key_value) { const auto [molid, number_to_insert] = key_value; @@ -141,29 +165,34 @@ bool ReactionValidator::canProduceMolecularGroups(const ReactionData& reaction) return true; }; - auto molecular_groups = reaction.getProducts().second | rv::filter(ReactionData::not_implicit_group) | + auto molecular_groups = reaction.getProducts().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_molecular_group); return ranges::cpp20::all_of(molecular_groups, can_create); } -bool ReactionValidator::canProduceAtomicGroups(const ReactionData& reaction) const { +bool ReactionValidator::canProduceAtomicGroups(const ReactionData& reaction) const +{ namespace rv = ranges::cpp20::views; auto can_expand = [&](auto key_value) { const auto [molid, number_to_insert] = key_value; - auto groups = spc.findMolecules(molid, Space::Selection::ALL) | ranges::to; + auto groups = spc.findMolecules(molid, Space::Selection::ALL) | + ranges::to; if (groups.empty()) { return false; } const Group& group = groups.front(); if (group.size() + number_to_insert > group.capacity()) { - faunus_logger->warn("atomic molecule {} is full; increase capacity?", group.traits().name); + faunus_logger->warn("atomic molecule {} is full; increase capacity?", + group.traits().name); return false; } return true; }; - auto atomic_groups = reaction.getProducts().second | rv::filter(ReactionData::not_implicit_group) | + auto atomic_groups = reaction.getProducts().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); return ranges::cpp20::all_of(atomic_groups, can_expand); @@ -174,7 +203,8 @@ bool ReactionValidator::canProduceAtomicGroups(const ReactionData& reaction) con /** * Randomly assign a new mass center and random orientation */ -void MolecularGroupDeActivator::setPositionAndOrientation(Group& group) const { +void MolecularGroupDeActivator::setPositionAndOrientation(Group& group) const +{ auto& geometry = spc.geometry; // translate to random position within simulation cell @@ -190,7 +220,6 @@ void MolecularGroupDeActivator::setPositionAndOrientation(Group& group) const { group.rotate(quaternion, geometry.getBoundaryFunc()); } - /** * Activates a number of particles in an atomic group. Before calling this, make sure that there's * sufficient capacity. @@ -199,7 +228,8 @@ void MolecularGroupDeActivator::setPositionAndOrientation(Group& group) const { * @param num_particles Number of particles to expand with */ MolecularGroupDeActivator::ChangeAndBias -MolecularGroupDeActivator::activate(Group& group, GroupDeActivator::OptionalInt num_particles) { +MolecularGroupDeActivator::activate(Group& group, GroupDeActivator::OptionalInt num_particles) +{ assert(group.isMolecular()); // must be a molecule group assert(group.empty()); // must be inactive group.activate(group.inactive().begin(), group.inactive().end()); // activate all particles @@ -211,21 +241,24 @@ MolecularGroupDeActivator::activate(Group& group, GroupDeActivator::OptionalInt setPositionAndOrientation(group); - assert(spc.geometry.sqdist(group.mass_center, - Geometry::massCenter(group.begin(), group.end(), spc.geometry.getBoundaryFunc(), - -group.mass_center)) < 1e-9); + assert( + spc.geometry.sqdist(group.mass_center, Geometry::massCenter(group.begin(), group.end(), + spc.geometry.getBoundaryFunc(), + -group.mass_center)) < 1e-9); - Change::GroupChange group_change; // describes the changed - used for energy evaluation + Change::GroupChange group_change; // describes the changed - used for energy evaluation group_change.group_index = spc.getGroupIndex(group); // index* of moved group group_change.all = true; // all atoms in group were moved group_change.internal = true; group_change.relative_atom_indices.resize(group.capacity()); // list of changed atom index - std::iota(group_change.relative_atom_indices.begin(), group_change.relative_atom_indices.end(), 0); + std::iota(group_change.relative_atom_indices.begin(), group_change.relative_atom_indices.end(), + 0); return {group_change, -getBondEnergy(group)}; } MolecularGroupDeActivator::ChangeAndBias -MolecularGroupDeActivator::deactivate(Group& group, GroupDeActivator::OptionalInt num_particles) { +MolecularGroupDeActivator::deactivate(Group& group, GroupDeActivator::OptionalInt num_particles) +{ assert(group.isMolecular()); assert(!group.empty()); assert(group.size() == group.capacity()); @@ -242,20 +275,26 @@ MolecularGroupDeActivator::deactivate(Group& group, GroupDeActivator::OptionalIn change_data.group_index = spc.getGroupIndex(group); change_data.all = true; // all atoms in group were moved change_data.relative_atom_indices.resize(group.capacity()); // list of changed atom index - std::iota(change_data.relative_atom_indices.begin(), change_data.relative_atom_indices.end(), 0); + std::iota(change_data.relative_atom_indices.begin(), change_data.relative_atom_indices.end(), + 0); return {change_data, getBondEnergy(group)}; } -MolecularGroupDeActivator::MolecularGroupDeActivator(Space& spc, Random& random, bool apply_bond_bias) +MolecularGroupDeActivator::MolecularGroupDeActivator(Space& spc, Random& random, + bool apply_bond_bias) : spc(spc) , random(random) - , apply_bond_bias(apply_bond_bias) {} + , apply_bond_bias(apply_bond_bias) +{ +} -double MolecularGroupDeActivator::getBondEnergy(const Group& group) const { +double MolecularGroupDeActivator::getBondEnergy(const Group& group) const +{ double energy = 0.0; if (apply_bond_bias) { - auto bonds = group.traits().bonds | ranges::views::transform(&pairpotential::BondData::clone); + auto bonds = + group.traits().bonds | ranges::views::transform(&pairpotential::BondData::clone); ranges::cpp20::for_each(bonds, [&](auto bond) { bond->shiftIndices(spc.getFirstParticleIndex(group)); bond->setEnergyFunction(spc.particles); @@ -270,11 +309,15 @@ double MolecularGroupDeActivator::getBondEnergy(const Group& group) const { AtomicGroupDeActivator::AtomicGroupDeActivator(Space& spc, Space& old_spc, Random& random) : spc(spc) , old_spc(old_spc) - , random(random) {} + , random(random) +{ +} -GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::activate(Group& group, - GroupDeActivator::OptionalInt number_to_insert) { - if (!group.isAtomic() || !number_to_insert || number_to_insert.value() + group.size() > group.capacity()) { +GroupDeActivator::ChangeAndBias +AtomicGroupDeActivator::activate(Group& group, GroupDeActivator::OptionalInt number_to_insert) +{ + if (!group.isAtomic() || !number_to_insert || + number_to_insert.value() + group.size() > group.capacity()) { throw std::runtime_error("atomic group expansion bug"); } Change::GroupChange change_data; @@ -286,7 +329,8 @@ GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::activate(Group& group, auto last_atom = group.end() - 1; spc.geometry.randompos(last_atom->pos, random); // give it a random position spc.geometry.getBoundaryFunc()(last_atom->pos); // apply PBC if needed - change_data.relative_atom_indices.push_back(std::distance(group.begin(), last_atom)); // index relative to group + change_data.relative_atom_indices.push_back( + std::distance(group.begin(), last_atom)); // index relative to group } change_data.sort(); return {change_data, 0.0}; @@ -301,8 +345,9 @@ GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::activate(Group& group, * @warning Directly modifying the groups in spc and old_spc might interfere with * a future neighbour list implementation. */ -GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::deactivate(Group& group, - GroupDeActivator::OptionalInt number_to_delete) { +GroupDeActivator::ChangeAndBias +AtomicGroupDeActivator::deactivate(Group& group, GroupDeActivator::OptionalInt number_to_delete) +{ if (!group.isAtomic() || !number_to_delete || number_to_delete.value() > group.size()) { throw std::runtime_error("atomic group expansion bug"); } @@ -319,8 +364,9 @@ GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::deactivate(Group& group, const auto dist = std::distance(particle_to_delete, group.end()); if (std::distance(particle_to_delete, last_particle) > 1) { - std::iter_swap(particle_to_delete, last_particle); // Shuffle back to end in trial ... - std::iter_swap(old_group.end() - dist - i, old_group.end() - (1 + i)); // ...and in old group + std::iter_swap(particle_to_delete, last_particle); // Shuffle back to end in trial ... + std::iter_swap(old_group.end() - dist - i, + old_group.end() - (1 + i)); // ...and in old group } const auto deactivated_particle_index = std::distance(group.begin(), last_particle); change_data.relative_atom_indices.push_back(deactivated_particle_index); @@ -338,7 +384,8 @@ GroupDeActivator::ChangeAndBias AtomicGroupDeActivator::deactivate(Group& group, namespace Faunus::move { -void SpeciationMove::_to_json(json& j) const { +void SpeciationMove::_to_json(json& j) const +{ direction_ratio.to_json(j); for (auto [molid, size] : average_reservoir_size) { j["implicit_reservoir"][molecules.at(molid).name] = size.avg(); @@ -356,7 +403,8 @@ void SpeciationMove::_to_json(json& j) const { * * @todo If particle has extended properties, make sure to copy the state of those */ -void SpeciationMove::atomicSwap(Change& change) { +void SpeciationMove::atomicSwap(Change& change) +{ if (!reaction->containsAtomicSwap()) { return; } @@ -365,9 +413,10 @@ void SpeciationMove::atomicSwap(Change& change) { assert(atomic_products.size() == 1 and atomic_reactants.size() == 1); - auto atomlist = spc.findAtoms(atomic_reactants.begin()->first); // search all active molecules - auto& target_particle = *random_internal.sample(atomlist.begin(), atomlist.end()); // target particle to swap - auto& group = *spc.findGroupContaining(target_particle); // find enclosing group + auto atomlist = spc.findAtoms(atomic_reactants.begin()->first); // search all active molecules + auto& target_particle = + *random_internal.sample(atomlist.begin(), atomlist.end()); // target particle to swap + auto& group = *spc.findGroupContaining(target_particle); // find enclosing group auto& group_change = change.groups.emplace_back(); group_change.relative_atom_indices.push_back(group.getParticleIndex(target_particle)); @@ -387,15 +436,19 @@ void SpeciationMove::atomicSwap(Change& change) { * This will keep original positions and internal orientation of particles, while swapping * the atomid and other properties related to the new atom type * - * @todo This could make use of policies to customize how particles should be updated. Split to helper class. + * @todo This could make use of policies to customize how particles should be updated. Split to + * helper class. */ -void SpeciationMove::swapParticleProperties(Particle& particle, const int new_atomid) { - Particle new_particle(atoms.at(new_atomid), particle.pos); // new particle with old position - if (new_particle.hasExtension() || particle.hasExtension()) { // keep also other orientational data +void SpeciationMove::swapParticleProperties(Particle& particle, const int new_atomid) +{ + Particle new_particle(atoms.at(new_atomid), particle.pos); // new particle with old position + if (new_particle.hasExtension() || + particle.hasExtension()) { // keep also other orientational data auto& source_ext = new_particle.getExt(); auto& target_ext = particle.getExt(); if (target_ext.isCylindrical() || source_ext.isCylindrical()) { - source_ext.setDirections(new_particle.traits().sphero_cylinder, target_ext.scdir, target_ext.patchdir); + source_ext.setDirections(new_particle.traits().sphero_cylinder, target_ext.scdir, + target_ext.patchdir); } if (target_ext.isDipolar() || source_ext.isQuadrupolar()) { throw std::runtime_error("dipolar/quadrupolar properties not yet implemented"); @@ -404,16 +457,20 @@ void SpeciationMove::swapParticleProperties(Particle& particle, const int new_at particle = new_particle; // deep copy } -void SpeciationMove::activateProducts(Change& change) { +void SpeciationMove::activateProducts(Change& change) +{ activateAtomicGroups(change); activateMolecularGroups(change); } -void SpeciationMove::activateMolecularGroups(Change& change) { +void SpeciationMove::activateMolecularGroups(Change& change) +{ namespace rv = ranges::cpp20::views; - auto selection = reaction->only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL : Space::Selection::INACTIVE; + auto selection = reaction->only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL + : Space::Selection::INACTIVE; - auto molecular_products = reaction->getProducts().second | rv::filter(ReactionData::not_implicit_group) | + auto molecular_products = reaction->getProducts().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_molecular_group); for (auto [molid, number_to_insert] : molecular_products) { @@ -429,14 +486,17 @@ void SpeciationMove::activateMolecularGroups(Change& change) { } } -void SpeciationMove::activateAtomicGroups(Change& change) { +void SpeciationMove::activateAtomicGroups(Change& change) +{ namespace rv = ranges::cpp20::views; - auto atomic_products = reaction->getProducts().second | rv::filter(ReactionData::not_implicit_group) | + auto atomic_products = reaction->getProducts().second | + rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); for (auto [molid, number_to_insert] : atomic_products) { auto groups = spc.findMolecules(molid, Space::Selection::ALL); - auto [change_data, bias] = atomic_group_bouncer->activate(*groups.begin(), number_to_insert); + auto [change_data, bias] = + atomic_group_bouncer->activate(*groups.begin(), number_to_insert); change.groups.emplace_back(change_data); bias_energy += bias; } @@ -445,18 +505,22 @@ void SpeciationMove::activateAtomicGroups(Change& change) { /** * @brief Deactivate all atomic and molecular reactants. */ -void SpeciationMove::deactivateReactants(Change& change) { +void SpeciationMove::deactivateReactants(Change& change) +{ deactivateAtomicGroups(change); deactivateMolecularGroups(change); } -void SpeciationMove::deactivateMolecularGroups(Change& change) { +void SpeciationMove::deactivateMolecularGroups(Change& change) +{ namespace rv = ranges::cpp20::views; auto nonzero_stoichiometric_coeff = [](auto key_value) { return key_value.second > 0; }; - auto selection = (reaction->only_neutral_molecules) ? Space::Selection::ACTIVE_NEUTRAL : Space::Selection::ACTIVE; + auto selection = (reaction->only_neutral_molecules) ? Space::Selection::ACTIVE_NEUTRAL + : Space::Selection::ACTIVE; - auto molecular_reactants = reaction->getReactants().second | rv::filter(ReactionData::not_implicit_group) | - rv::filter(nonzero_stoichiometric_coeff) | rv::filter(ReactionData::is_molecular_group); + auto molecular_reactants = + reaction->getReactants().second | rv::filter(ReactionData::not_implicit_group) | + rv::filter(nonzero_stoichiometric_coeff) | rv::filter(ReactionData::is_molecular_group); for (const auto& [molid, number_to_delete] : molecular_reactants) { auto groups = spc.findMolecules(molid, selection) | @@ -470,12 +534,14 @@ void SpeciationMove::deactivateMolecularGroups(Change& change) { } } -void SpeciationMove::deactivateAtomicGroups(Change& change) { +void SpeciationMove::deactivateAtomicGroups(Change& change) +{ namespace rv = ranges::cpp20::views; auto nonzero_stoichiometric_coeff = [](auto key_value) { return key_value.second > 0; }; - auto atomic_reactants = reaction->getReactants().second | rv::filter(ReactionData::not_implicit_group) | - rv::filter(nonzero_stoichiometric_coeff) | rv::filter(ReactionData::is_atomic_group); + auto atomic_reactants = + reaction->getReactants().second | rv::filter(ReactionData::not_implicit_group) | + rv::filter(nonzero_stoichiometric_coeff) | rv::filter(ReactionData::is_atomic_group); for (auto [molid, number_to_delete] : atomic_reactants) { auto groups = spc.findMolecules(molid, Space::Selection::ALL); @@ -487,7 +553,8 @@ void SpeciationMove::deactivateAtomicGroups(Change& change) { } } -TEST_CASE("[Faunus] Speciation - Ranges::sample") { +TEST_CASE("[Faunus] Speciation - Ranges::sample") +{ std::vector vec = {1, 2, 3, 4}; auto take_nothing = vec | ranges::views::sample(0); auto take_less = vec | ranges::views::sample(2); @@ -500,7 +567,8 @@ TEST_CASE("[Faunus] Speciation - Ranges::sample") { CHECK_EQ(range_size(take_too_much), 4); } -void SpeciationMove::_move(Change& change) { +void SpeciationMove::_move(Change& change) +{ if (Faunus::reactions.empty()) { return; } @@ -511,16 +579,21 @@ void SpeciationMove::_move(Change& change) { atomicSwap(change); deactivateReactants(change); activateProducts(change); - std::sort(change.groups.begin(), change.groups.end()); // change groups *must* be sorted! + std::sort(change.groups.begin(), + change.groups.end()); // change groups *must* be sorted! if (change) { change.matter_change = true; updateGroupMassCenters(change); } } - } catch (Speciation::SpeciationMoveException&) { change.clear(); } + } + catch (Speciation::SpeciationMoveException&) { + change.clear(); + } } -void SpeciationMove::setRandomReactionAndDirection() { +void SpeciationMove::setRandomReactionAndDirection() +{ reaction = random_internal.sample(reactions.begin(), reactions.end()); reaction->setRandomDirection(random_internal); } @@ -531,13 +604,17 @@ void SpeciationMove::setRandomReactionAndDirection() { * * - Swap moves (as the swapped atom may have a different mass) */ -void SpeciationMove::updateGroupMassCenters(const Change& change) const { +void SpeciationMove::updateGroupMassCenters(const Change& change) const +{ namespace rv = ranges::cpp20::views; auto atomic_or_swap = [](const Change::GroupChange& c) { return c.dNatomic || c.dNswap; }; - auto to_group = [&](const Change::GroupChange& c) -> Group& { return spc.groups.at(c.group_index); }; + auto to_group = [&](const Change::GroupChange& c) -> Group& { + return spc.groups.at(c.group_index); + }; auto has_mass_center = [](Group& group) { return group.massCenter().has_value(); }; - auto groups = change.groups | rv::filter(atomic_or_swap) | rv::transform(to_group) | rv::filter(has_mass_center); + auto groups = change.groups | rv::filter(atomic_or_swap) | rv::transform(to_group) | + rv::filter(has_mass_center); ranges::cpp20::for_each(groups, [&](Group& group) { group.updateMassCenter(spc.geometry.getBoundaryFunc(), group.massCenter().value()); @@ -549,52 +626,65 @@ void SpeciationMove::updateGroupMassCenters(const Change& change) const { * but unaffected by the change in internal bond energy */ double SpeciationMove::bias([[maybe_unused]] Change& change, [[maybe_unused]] double old_energy, - [[maybe_unused]] double new_energy) { + [[maybe_unused]] double new_energy) +{ return reaction->freeEnergy() + bias_energy; } -void SpeciationMove::_accept([[maybe_unused]] Change &change) { +void SpeciationMove::_accept([[maybe_unused]] Change& change) +{ namespace rv = ranges::cpp20::views; direction_ratio[reaction].update(reaction->getDirection(), true); - auto implicit_reactants = reaction->getReactants().second | rv::filter(ReactionData::is_implicit_group); + auto implicit_reactants = + reaction->getReactants().second | rv::filter(ReactionData::is_implicit_group); for (const auto& [molid, nu] : implicit_reactants) { spc.getImplicitReservoir()[molid] -= nu; average_reservoir_size[molid] += spc.getImplicitReservoir().at(molid); } - auto implicit_products = reaction->getProducts().second | rv::filter(ReactionData::is_implicit_group); + auto implicit_products = + reaction->getProducts().second | rv::filter(ReactionData::is_implicit_group); for (const auto& [molid, nu] : implicit_products) { spc.getImplicitReservoir()[molid] += nu; average_reservoir_size[molid] += spc.getImplicitReservoir().at(molid); } } -void SpeciationMove::_reject([[maybe_unused]] Change& change) { +void SpeciationMove::_reject([[maybe_unused]] Change& change) +{ namespace rv = ranges::cpp20::views; direction_ratio[reaction].update(reaction->getDirection(), false); - auto implicit_reactants = reaction->getReactants().second | rv::filter(ReactionData::is_implicit_group); + auto implicit_reactants = + reaction->getReactants().second | rv::filter(ReactionData::is_implicit_group); for (auto [molid, nu] : implicit_reactants) { average_reservoir_size[molid] += spc.getImplicitReservoir().at(molid); } - auto implicit_products = reaction->getProducts().second | rv::filter(ReactionData::is_implicit_group); + auto implicit_products = + reaction->getProducts().second | rv::filter(ReactionData::is_implicit_group); for (auto [molid, nu] : implicit_products) { average_reservoir_size[molid] += spc.getImplicitReservoir().at(molid); } } -SpeciationMove::SpeciationMove(Space& spc, Space& old_spc, std::string_view name, std::string_view cite) +SpeciationMove::SpeciationMove(Space& spc, Space& old_spc, std::string_view name, + std::string_view cite) : Move(spc, name, cite) , random_internal(slump) - , reaction_validator(spc) { - molecular_group_bouncer = std::make_unique(spc, random_internal, true); - atomic_group_bouncer = std::make_unique(spc, old_spc, random_internal); + , reaction_validator(spc) +{ + molecular_group_bouncer = + std::make_unique(spc, random_internal, true); + atomic_group_bouncer = + std::make_unique(spc, old_spc, random_internal); } SpeciationMove::SpeciationMove(Space& spc, Space& old_spc) - : SpeciationMove(spc, old_spc, "rcmc", "doi:10/fqcpg3") {} + : SpeciationMove(spc, old_spc, "rcmc", "doi:10/fqcpg3") +{ +} void SpeciationMove::_from_json([[maybe_unused]] const json& j) {} diff --git a/src/speciation.h b/src/speciation.h index 39febc429..e03c5a608 100644 --- a/src/speciation.h +++ b/src/speciation.h @@ -8,7 +8,8 @@ namespace Faunus::Speciation { * Helper class to check if a reaction is possible, i.e. * that there's sufficient reactant and product capacity */ -class ReactionValidator { +class ReactionValidator +{ private: const Space& spc; [[nodiscard]] bool canSwapAtoms(const ReactionData& reaction) const; @@ -26,7 +27,8 @@ class ReactionValidator { /** * Helper base class for (de)activating groups in speciation move */ -class GroupDeActivator { +class GroupDeActivator +{ public: using ChangeAndBias = std::pair; //!< Group change and bias energy using OptionalInt = std::optional; @@ -38,7 +40,8 @@ class GroupDeActivator { /** * Helper class for contracting and expanding atomic groups */ -class AtomicGroupDeActivator : public GroupDeActivator { +class AtomicGroupDeActivator : public GroupDeActivator +{ private: Space& spc; //!< Trial space Space& old_spc; //!< Old (accepted) space @@ -62,13 +65,15 @@ class AtomicGroupDeActivator : public GroupDeActivator { * - Remove PBC before deactivation * - Bias is set to *positive* internal bond energy */ -class MolecularGroupDeActivator : public GroupDeActivator { +class MolecularGroupDeActivator : public GroupDeActivator +{ private: Space& spc; Random& random; const bool apply_bond_bias; //!< Set to true to use internal bond energy as bias [[nodiscard]] double getBondEnergy(const Group& group) const; - virtual void setPositionAndOrientation(Group& group) const; //!< Applied to newly activated groups + virtual void + setPositionAndOrientation(Group& group) const; //!< Applied to newly activated groups public: MolecularGroupDeActivator(Space& spc, Random& random, bool apply_bond_bias); @@ -79,14 +84,18 @@ class MolecularGroupDeActivator : public GroupDeActivator { /** * Helper class to keep track of acceptance in left or right direction */ -class ReactionDirectionRatio { +class ReactionDirectionRatio +{ private: using reaction_iterator = decltype(Faunus::reactions)::iterator; - struct AcceptanceData { + + struct AcceptanceData + { Average right; //!< Acceptance ratio left -> right Average left; //!< Acceptance ratio right -> left void update(ReactionData::Direction direction, bool accept); }; + std::map acceptance; public: @@ -118,16 +127,19 @@ namespace Faunus::move { * * @todo Split atom-swap functionality to separate helper class */ -class SpeciationMove : public Move { +class SpeciationMove : public Move +{ private: using reaction_iterator = decltype(Faunus::reactions)::iterator; - Random random_internal; //!< Private generator so as not to touch MoveBase::slump - reaction_iterator reaction; //!< Randomly selected reaction - double bias_energy = 0.0; //!< Group (de)activators may add bias - Speciation::ReactionValidator reaction_validator; //!< Helper to check if reaction is doable - Speciation::ReactionDirectionRatio direction_ratio; //!< Track acceptance in each direction - std::unique_ptr molecular_group_bouncer; //!< (de)activator for molecular groups - std::unique_ptr atomic_group_bouncer; //!< (de)activator for atomic groups + Random random_internal; //!< Private generator so as not to touch MoveBase::slump + reaction_iterator reaction; //!< Randomly selected reaction + double bias_energy = 0.0; //!< Group (de)activators may add bias + Speciation::ReactionValidator reaction_validator; //!< Helper to check if reaction is doable + Speciation::ReactionDirectionRatio direction_ratio; //!< Track acceptance in each direction + std::unique_ptr + molecular_group_bouncer; //!< (de)activator for molecular groups + std::unique_ptr + atomic_group_bouncer; //!< (de)activator for atomic groups std::map> average_reservoir_size; //!< Average number of implicit molecules @@ -138,7 +150,7 @@ class SpeciationMove : public Move { void _accept(Change& change) override; void _reject(Change& change) override; - void setRandomReactionAndDirection(); //!< Set random reaction and direction + void setRandomReactionAndDirection(); //!< Set random reaction and direction void atomicSwap(Change& change); //!< Swap atom type void deactivateReactants(Change& change); //!< Delete all reactants void activateProducts(Change& change); //!< Insert all products @@ -146,7 +158,8 @@ class SpeciationMove : public Move { void activateAtomicGroups(Change& change); void deactivateMolecularGroups(Change& change); void activateMolecularGroups(Change& change); - void updateGroupMassCenters(const Change& change) const; //!< Update affected molecular mass centers + void + updateGroupMassCenters(const Change& change) const; //!< Update affected molecular mass centers static void swapParticleProperties(Particle& particle, int new_atomid); SpeciationMove(Space& spc, Space& old_spc, std::string_view name, std::string_view cite); diff --git a/src/spherocylinder.cpp b/src/spherocylinder.cpp index 209ee3058..2dd62c5a4 100644 --- a/src/spherocylinder.cpp +++ b/src/spherocylinder.cpp @@ -13,7 +13,8 @@ namespace Faunus::SpheroCylinder { * @return */ Point mindist_segment2segment(const Point& dir1, const double half_length1, const Point& dir2, - const double half_length2, const Point& r_cm) { + const double half_length2, const Point& r_cm) +{ constexpr auto very_small_number = 0.00000001; Point u = dir1 * (half_length1 * 2); // S1.P1 - S1.P0; Point v = dir2 * (half_length2 * 2); // S2.P1 - S2.P0; @@ -37,14 +38,16 @@ Point mindist_segment2segment(const Point& dir1, const double half_length1, cons sD = 1.0; // to prevent possible division by 0.0 later tN = e; tD = c; - } else { // get the closest points on the infinite lines + } + else { // get the closest points on the infinite lines sN = (b * e - c * d); tN = (a * e - b * d); if (sN < 0.0) { // sc < 0 => the s=0 edge is visible sN = 0.0; tN = e; tD = c; - } else if (sN > sD) { // sc > 1 => the s=1 edge is visible + } + else if (sN > sD) { // sc > 1 => the s=1 edge is visible sN = sD; tN = e + b; tD = c; @@ -55,20 +58,25 @@ Point mindist_segment2segment(const Point& dir1, const double half_length1, cons // recompute sc for this edge if (-d < 0.0) { sN = 0.0; - } else if (-d > a) { + } + else if (-d > a) { sN = sD; - } else { + } + else { sN = -d; sD = a; } - } else if (tN > tD) { // tc > 1 => the t=1 edge is visible + } + else if (tN > tD) { // tc > 1 => the t=1 edge is visible tN = tD; // recompute sc for this edge if ((-d + b) < 0.0) { sN = 0; - } else if ((-d + b) > a) { + } + else if ((-d + b) > a) { sN = sD; - } else { + } + else { sN = (-d + b); sD = a; } @@ -91,8 +99,10 @@ Point mindist_segment2segment(const Point& dir1, const double half_length1, cons * @param intersections * @return */ -int find_intersect_plane(const Cigar& part1, const Cigar& part2, const Point& r_cm, const Point& w_vec, - const double cutoff_squared, const double cospatch, std::array& intersections) { +int find_intersect_plane(const Cigar& part1, const Cigar& part2, const Point& r_cm, + const Point& w_vec, const double cutoff_squared, const double cospatch, + std::array& intersections) +{ Point nplane = part1.scdir.cross(w_vec).normalized(); const auto a = nplane.dot(part2.scdir); if (std::fabs(a) <= pc::epsilon_dbl) { @@ -134,7 +144,9 @@ int find_intersect_plane(const Cigar& part1, const Cigar& part2, const Point& r_ * @param intersections * @return */ -int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, std::array& intersections) { +int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, + std::array& intersections) +{ /*test if we have intersection*/ /* do projection to patch plane*/ vec = vec_perpproject(vec, part1.scdir).normalized(); @@ -167,8 +179,10 @@ int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, s * @param intersections * @return */ -int find_intersect_planec(const Cigar& part1, const Cigar& part2, const Point& r_cm, const Point& w_vec, double rcut2, - double cospatch, std::array& intersections) { +int find_intersect_planec(const Cigar& part1, const Cigar& part2, const Point& r_cm, + const Point& w_vec, double rcut2, double cospatch, + std::array& intersections) +{ Point nplane = part1.scdir.cross(w_vec).normalized(); auto a = nplane.dot(part2.scdir); if (std::fabs(a) <= pc::epsilon_dbl) { @@ -215,7 +229,8 @@ int find_intersect_planec(const Cigar& part1, const Cigar& part2, const Point& r * @todo Add documentation and split into smaller parts(!) */ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r_cm, - std::array& intersections, const double cutoff_squared) { + std::array& intersections, const double cutoff_squared) +{ double a, b, c, d, e, x1, x2; Point cm21, vec1, vec2, vec3, vec4; @@ -227,12 +242,12 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r /* plane1 */ /* find intersections of part2 with plane by par1 and patchsides[0] */ int intrs = 0; - intrs += find_intersect_plane(particle1, particle2, r_cm, particle1.patchsides[0], cutoff_squared, - particle1.pcanglsw, intersections); + intrs += find_intersect_plane(particle1, particle2, r_cm, particle1.patchsides[0], + cutoff_squared, particle1.pcanglsw, intersections); /* plane2 */ /* find intersections of part2 with plane by par1 and patchsides[1] */ - intrs += find_intersect_plane(particle1, particle2, r_cm, particle1.patchsides[1], cutoff_squared, - particle1.pcanglsw, intersections); + intrs += find_intersect_plane(particle1, particle2, r_cm, particle1.patchsides[1], + cutoff_squared, particle1.pcanglsw, intersections); if ((intrs == 2) && (particle1.pcanglsw < 0)) { assert("Patch>180 -> two segments."); @@ -274,7 +289,8 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r if ((e >= particle1.half_length) || (e <= -particle1.half_length)) intrs += 0; /*intersection is outside sc1*/ else { - intrs += test_intrpatch(particle1, vec2, particle1.pcanglsw, x2, intersections); + intrs += + test_intrpatch(particle1, vec2, particle1.pcanglsw, x2, intersections); } } } @@ -315,7 +331,8 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r e = particle1.scdir.dot(vec4); if ((e >= particle1.half_length) || (e <= -particle1.half_length)) { /*if not intersection is inside sc1*/ - intrs += test_intrpatch(particle1, vec4, particle1.pcanglsw, x2, intersections); + intrs += + test_intrpatch(particle1, vec4, particle1.pcanglsw, x2, intersections); } } } @@ -348,7 +365,8 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r e = particle1.scdir.dot(vec4); if ((e >= particle1.half_length) || (e <= -particle1.half_length)) { /*if not intersection is inside sc1*/ - intrs += test_intrpatch(particle1, vec4, particle1.pcanglsw, x2, intersections); + intrs += + test_intrpatch(particle1, vec4, particle1.pcanglsw, x2, intersections); } } } @@ -374,7 +392,8 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r } /*is inside caps*/ /*c is distance squared from line or end to test if is inside sc*/ if (c < cutoff_squared) { - intrs += test_intrpatch(particle1, vec1, particle1.pcanglsw, particle2.half_length, intersections); + intrs += test_intrpatch(particle1, vec1, particle1.pcanglsw, particle2.half_length, + intersections); } if (intrs < 2) { vec2 = -particle2.scdir * particle2.half_length - r_cm; @@ -391,8 +410,8 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r } /*is inside caps*/ /*c is distance squared from line or end to test if is inside sc*/ if (c < cutoff_squared) { - intrs += - test_intrpatch(particle1, vec2, particle1.pcanglsw, -1.0 * particle2.half_length, intersections); + intrs += test_intrpatch(particle1, vec2, particle1.pcanglsw, + -1.0 * particle2.half_length, intersections); } } } @@ -407,8 +426,9 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r * @param cutoff_squared * @return */ -int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, std::array& intersections, - const double cutoff_squared) { +int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, + std::array& intersections, const double cutoff_squared) +{ int intrs; double a, b, c, d, e, x1, x2; Point cm21, vec1, vec2, vec3, vec4; @@ -421,15 +441,16 @@ int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, /* plane1 */ /* find intersections of part2 with plane by par1 and part1.patchsides[0] */ - intrs += find_intersect_planec(cigar1, cigar2, r_cm, cigar1.patchsides[0], cutoff_squared, cigar1.pcanglsw, - intersections); + intrs += find_intersect_planec(cigar1, cigar2, r_cm, cigar1.patchsides[0], cutoff_squared, + cigar1.pcanglsw, intersections); /* plane2 */ /* find intersections of part2 with plane by par1 and part1.patchsides[1] */ - intrs += find_intersect_planec(cigar1, cigar2, r_cm, cigar1.patchsides[1], cutoff_squared, cigar1.pcanglsw, - intersections); + intrs += find_intersect_planec(cigar1, cigar2, r_cm, cigar1.patchsides[1], cutoff_squared, + cigar1.pcanglsw, intersections); if ((intrs == 2) && (cigar1.pcanglsw < 0)) { - throw std::runtime_error("Patch larger than 180 deg -> two segments - this is not yet implemented"); + throw std::runtime_error( + "Patch larger than 180 deg -> two segments - this is not yet implemented"); } /*1b- test intersection with cylinder - it is at distance C*/ @@ -461,7 +482,8 @@ int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, x2 = (-b - sqrt(d)) * 0.5 / a; /*parameter on line of SC2 determining intersection*/ if ((x2 >= cigar2.half_length) || (x2 <= -cigar2.half_length)) { intrs += 0; /*intersection is outside sc2*/ - } else { + } + else { vec2 = cigar2.scdir * x2 - r_cm; e = cigar1.scdir.dot(vec2); if ((e >= cigar1.half_length) || (e <= -cigar1.half_length)) { @@ -530,7 +552,8 @@ int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, if (d <= 0) { /*is in cylindrical part*/ /*c is distance squared from line or end to test if is inside sc*/ if (b < cutoff_squared) { - intrs += test_intrpatch(cigar1, vec1, cigar1.pcanglsw, cigar2.half_length, intersections); + intrs += test_intrpatch(cigar1, vec1, cigar1.pcanglsw, cigar2.half_length, + intersections); } } if (intrs < 2) { @@ -543,7 +566,8 @@ int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, if (d <= 0) { /*c is distance squared from line or end to test if is inside sc*/ if (b < cutoff_squared) { - intrs += test_intrpatch(cigar1, vec2, cigar1.pcanglsw, -1.0 * cigar2.half_length, intersections); + intrs += test_intrpatch(cigar1, vec2, cigar1.pcanglsw, + -1.0 * cigar2.half_length, intersections); } } } @@ -555,7 +579,9 @@ int cpsc_intersect(const Cigar& cigar1, const Cigar& cigar2, const Point& r_cm, namespace Faunus::pairpotential { HardSpheroCylinder::HardSpheroCylinder() - : PairPotential("hardspherocylinder", "", false) {} + : PairPotential("hardspherocylinder", "", false) +{ +} void HardSpheroCylinder::to_json([[maybe_unused]] json& j) const {} @@ -567,30 +593,35 @@ void HardSpheroCylinder::from_json([[maybe_unused]] const json& j) {} * @param center_separation * @return */ -template +template double CigarWithCigar::patchyPatchyEnergy( - const Particle& particle1, const Particle& particle2, - const Point& center_separation) const { // patchy sc with patchy sc + const Particle& particle1, const Particle& particle2, const Point& center_separation) const +{ // patchy sc with patchy sc const auto cutoff_squared = patch_potential.cutOffSquared(particle1.id, particle1.id); std::array intersections; // distance for repulsion const auto rclose_squared = - SpheroCylinder::mindist_segment2segment(particle1.ext->scdir, particle1.ext->half_length, particle2.ext->scdir, - particle2.ext->half_length, center_separation) + SpheroCylinder::mindist_segment2segment(particle1.ext->scdir, particle1.ext->half_length, + particle2.ext->scdir, particle2.ext->half_length, + center_separation) .squaredNorm(); // 1- do intersections of spherocylinder2 with patch of spherocylinder1 at. // cut distance C int intrs = 0; intersections.fill(0.0); if (particle1.traits().sphero_cylinder.type == SpheroCylinderData::PatchType::Full) { - intrs = SpheroCylinder::psc_intersect(particle1.getExt(), particle2.getExt(), center_separation, intersections, - cutoff_squared); - } else { + intrs = SpheroCylinder::psc_intersect(particle1.getExt(), particle2.getExt(), + center_separation, intersections, cutoff_squared); + } + else { if (particle1.traits().sphero_cylinder.type == SpheroCylinderData::PatchType::Capped) { - intrs = SpheroCylinder::cpsc_intersect(particle1.getExt(), particle2.getExt(), center_separation, - intersections, cutoff_squared); - } else { + intrs = + SpheroCylinder::cpsc_intersect(particle1.getExt(), particle2.getExt(), + center_separation, intersections, cutoff_squared); + } + else { throw std::runtime_error("unimplemented"); } } @@ -604,14 +635,18 @@ double CigarWithCigar::patchyPatchyEnergy( intersections.fill(0.0); if (particle1.traits().sphero_cylinder.type == SpheroCylinderData::PatchType::Full) { //!< @warning should this not be b.traits()? - intrs = SpheroCylinder::psc_intersect(particle2.getExt(), particle1.getExt(), -center_separation, intersections, - cutoff_squared); - } else { + intrs = SpheroCylinder::psc_intersect(particle2.getExt(), particle1.getExt(), + -center_separation, intersections, cutoff_squared); + } + else { if (particle1.traits().sphero_cylinder.type == - SpheroCylinderData::PatchType::Capped) { //!< @warning should this not be particle2.traits()? - intrs = SpheroCylinder::cpsc_intersect(particle2.getExt(), particle1.getExt(), -center_separation, - intersections, cutoff_squared); - } else { + SpheroCylinderData::PatchType::Capped) { //!< @warning should this not be + //!< particle2.traits()? + intrs = + SpheroCylinder::cpsc_intersect(particle2.getExt(), particle1.getExt(), + -center_separation, intersections, cutoff_squared); + } + else { throw std::runtime_error("unimplemented"); } } @@ -643,8 +678,8 @@ double CigarWithCigar::patchyPatchyEnergy( const auto f2 = SpheroCylinder::fanglscale(s, particle2.getExt()); // 7 - calculate closest distance attractive energy from it - auto vec_mindist = - SpheroCylinder::mindist_segment2segment(particle1.ext->scdir, v1, particle2.ext->scdir, v2, vec_intrs); + auto vec_mindist = SpheroCylinder::mindist_segment2segment(particle1.ext->scdir, v1, + particle2.ext->scdir, v2, vec_intrs); auto ndistsq = vec_mindist.dot(vec_mindist); // 8- put it all together and output scale @@ -652,37 +687,43 @@ double CigarWithCigar::patchyPatchyEnergy( cylinder_potential(particle1, particle2, rclose_squared, Point::Zero()); } -template +template double CigarWithCigar::isotropicIsotropicEnergy( - const Particle& particle1, const Particle& particle2, - const Point& center_separation) const { // isotropic sc with isotropic sc - const auto mindist = - SpheroCylinder::mindist_segment2segment(particle1.ext->scdir, particle1.ext->half_length, particle2.ext->scdir, - particle2.ext->half_length, center_separation) - .squaredNorm(); + const Particle& particle1, const Particle& particle2, const Point& center_separation) const +{ // isotropic sc with isotropic sc + const auto mindist = SpheroCylinder::mindist_segment2segment( + particle1.ext->scdir, particle1.ext->half_length, particle2.ext->scdir, + particle2.ext->half_length, center_separation) + .squaredNorm(); return patch_potential(particle1, particle2, mindist, Point::Zero()) + cylinder_potential(particle1, particle2, mindist, Point::Zero()); } template -void CigarWithCigar::to_json(json& j) const { +void CigarWithCigar::to_json(json& j) const +{ j["patch"] = static_cast(patch_potential); j["cylinder"] = static_cast(cylinder_potential); } template -void CigarWithCigar::from_json(const json& j) { +void CigarWithCigar::from_json(const json& j) +{ pairpotential::from_json(j, patch_potential); pairpotential::from_json(j, cylinder_potential); } template CigarWithCigar::CigarWithCigar() - : PairPotential("cigar-cigar", ""s, false) {} + : PairPotential("cigar-cigar", ""s, false) +{ +} template class CigarWithCigar; // explicit initialization -TEST_CASE("[Faunus] CigarWithCigar") { +TEST_CASE("[Faunus] CigarWithCigar") +{ using CigarCigar = CigarWithCigar; // Check that we use same temperature as R. Vacha. Note also that the epsilon value @@ -702,7 +743,8 @@ TEST_CASE("[Faunus] CigarWithCigar") { a = Faunus::atoms.at(0); b = Faunus::atoms.at(0); - SUBCASE("maximum contact attraction") { + SUBCASE("maximum contact attraction") + { // place two parallel CPSC in contact and with patches facing each other auto pairpot = CigarCigar(R"( {"cos2": {"mixing": "LB"}, "wca": {"mixing": "LB"}} )"_json); const auto& atom_data = Faunus::atoms.at(0); // atom type "A" @@ -721,13 +763,17 @@ TEST_CASE("[Faunus] CigarWithCigar") { template -void CompleteCigarPotential::to_json(json& j) const { +void CompleteCigarPotential::to_json( + json& j) const +{ j = {sphere_sphere, cigar_cigar, cigar_sphere}; } template -void CompleteCigarPotential::from_json(const json& j) { +void CompleteCigarPotential::from_json( + const json& j) +{ pairpotential::from_json(j, sphere_sphere); pairpotential::from_json(j, cigar_cigar); pairpotential::from_json(j, cigar_sphere); @@ -735,29 +781,37 @@ void CompleteCigarPotential template -CompleteCigarPotential::CompleteCigarPotential() - : PairPotential("complete cigar", ""s, false) {} +CompleteCigarPotential::CompleteCigarPotential() + : PairPotential("complete cigar", ""s, false) +{ +} -template class CompleteCigarPotential; // explicit initialization +template class CompleteCigarPotential; // explicit initialization // ------------------------------- template CigarWithSphere::CigarWithSphere() - : PairPotential("cigar-sphere", ""s, false) {} + : PairPotential("cigar-sphere", ""s, false) +{ +} template -void CigarWithSphere::to_json(json& j) const { +void CigarWithSphere::to_json(json& j) const +{ j["patch"] = static_cast(patch_potential); j["cylinder"] = static_cast(cylinder_potential); } template -void CigarWithSphere::from_json(const json& j) { +void CigarWithSphere::from_json(const json& j) +{ pairpotential::from_json(j, patch_potential); pairpotential::from_json(j, cylinder_potential); } template class CigarWithSphere; // explicit initialization -} // namespace Faunus::Potential \ No newline at end of file +} // namespace Faunus::pairpotential \ No newline at end of file diff --git a/src/spherocylinder.h b/src/spherocylinder.h index 08d8823e5..d60a9a197 100644 --- a/src/spherocylinder.h +++ b/src/spherocylinder.h @@ -18,7 +18,10 @@ namespace Faunus::SpheroCylinder { * @param a the first vector * @param b the second vector */ -inline Point vec_perpproject(const Point& a, const Point& b) { return a - b * a.dot(b); } +inline Point vec_perpproject(const Point& a, const Point& b) +{ + return a - b * a.dot(b); +} /** * @brief Calculate minimum distance between two line segments @@ -39,19 +42,24 @@ inline Point vec_perpproject(const Point& a, const Point& b) { return a - b * a. * @param halfl2 Half length of second segment * @param r_cm Distance vector between the middle of the two segments */ -Point mindist_segment2segment(const Point& dir1, double halfl1, const Point& dir2, double halfl2, const Point& r_cm); +Point mindist_segment2segment(const Point& dir1, double halfl1, const Point& dir2, double halfl2, + const Point& r_cm); /** * @param segment_direction Direction of segment * @param half_length Half length of segment * @param separation Distance vector between the middle segment to point */ -[[maybe_unused]] inline Point mindist_segment2point(const Point& segment_direction, const double half_length, const Point& separation) { +[[maybe_unused]] inline Point mindist_segment2point(const Point& segment_direction, + const double half_length, + const Point& separation) +{ const auto c = segment_direction.dot(separation); double d; if (c > half_length) { d = half_length; - } else { + } + else { d = (c > -half_length) ? c : -half_length; } return -separation + (segment_direction * d); @@ -61,13 +69,15 @@ Point mindist_segment2segment(const Point& dir1, double halfl1, const Point& dir * Finds intersections of spherocylinder and plane defined by vector * "w_vec" and if they are in all-way patch then returns number of them (PSC) */ -int find_intersect_plane(const Cigar& part1, const Cigar& part2, const Point& r_cm, const Point& w_vec, - double cutoff_squared, double cospatch, std::array& intersections); +int find_intersect_plane(const Cigar& part1, const Cigar& part2, const Point& r_cm, + const Point& w_vec, double cutoff_squared, double cospatch, + std::array& intersections); /** * @brief Finds if vector "vec" has angular intersection w. patch of part1 */ -int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, std::array& intersections); +int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, + std::array& intersections); /** * @brief Intersect of plane @@ -75,11 +85,13 @@ int test_intrpatch(const Cigar& part1, Point& vec, double cospatch, double ti, s * Finds intersections of plane defined by vector "w_vec" * and if they are in cylindrical patch then returns number of them (CPSC) */ -int find_intersect_planec(const Cigar& part1, const Cigar& part2, const Point& r_cm, const Point& w_vec, double rcut2, - double cospatch, std::array& intersections); +int find_intersect_planec(const Cigar& part1, const Cigar& part2, const Point& r_cm, + const Point& w_vec, double rcut2, double cospatch, + std::array& intersections); /** - * @brief Intersections of spherocylinder2 with a all-way patch of spherocylinder1 and return them (PSC) + * @brief Intersections of spherocylinder2 with a all-way patch of spherocylinder1 and return them + * (PSC) */ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r_cm, std::array& intersections, double cutoff_squared); @@ -87,10 +99,11 @@ int psc_intersect(const Cigar& particle1, const Cigar& particle2, const Point& r /** * @brief Intersection of PSC2 with cylindrical patch of PSC1 and return them (CPSC) */ -int cpsc_intersect(const Cigar& part1, const Cigar& part2, const Point& r_cm, std::array& intersections, - double rcut2); +int cpsc_intersect(const Cigar& part1, const Cigar& part2, const Point& r_cm, + std::array& intersections, double rcut2); -inline double fanglscale(const double a, const Cigar& cigar) { +inline double fanglscale(const double a, const Cigar& cigar) +{ // a = r_ij * n_i if (a <= cigar.pcanglsw) { return 0.0; @@ -108,15 +121,19 @@ inline double fanglscale(const double a, const Cigar& cigar) { namespace Faunus::pairpotential { /** @brief Hard-sphere pair potential for spherocylinders */ -class HardSpheroCylinder : public PairPotential { +class HardSpheroCylinder : public PairPotential +{ public: - double operator()(const Particle& particle1, const Particle& particle2, [[maybe_unused]] double d, - const Point& center_to_center_distance) const override { + double operator()(const Particle& particle1, const Particle& particle2, + [[maybe_unused]] double d, + const Point& center_to_center_distance) const override + { assert(particle1.hasExtension() && particle2.hasExtension()); - auto minimum_distance_squared = SpheroCylinder::mindist_segment2segment( - particle1.ext->scdir, particle1.ext->half_length, particle2.ext->scdir, - particle1.ext->half_length, center_to_center_distance) - .squaredNorm(); + auto minimum_distance_squared = + SpheroCylinder::mindist_segment2segment( + particle1.ext->scdir, particle1.ext->half_length, particle2.ext->scdir, + particle1.ext->half_length, center_to_center_distance) + .squaredNorm(); const auto contact_distance = 0.5 * (particle1.traits().sigma + particle2.traits().sigma); if (minimum_distance_squared < contact_distance * contact_distance) { return pc::infty; @@ -130,12 +147,15 @@ class HardSpheroCylinder : public PairPotential { }; /** - * @brief Pair potential between a patchy sphero-cylinder (first particle) and a sphere (second particle) + * @brief Pair potential between a patchy sphero-cylinder (first particle) and a sphere (second + * particle) * @tparam PatchPotential Pair potential between sphere and point on patch (isotropic) * @tparam CylinderPotential Pair potential between sphere and closest cylinder part (isotropic) */ -template -class CigarWithSphere : public PairPotential { +template +class CigarWithSphere : public PairPotential +{ private: PatchPotential patch_potential; //!< Isotropic pair-potential between patches CylinderPotential cylinder_potential; //!< Isotropic pair-potential between non-patchy parts @@ -145,17 +165,21 @@ class CigarWithSphere : public PairPotential { void to_json(json& j) const override; void from_json(const json& j) override; - inline double operator()(const Particle& cigar, const Particle& sphere, [[maybe_unused]] double distance_squared, - const Point& center_separation) const override { + inline double operator()(const Particle& cigar, const Particle& sphere, + [[maybe_unused]] double distance_squared, + const Point& center_separation) const override + { assert(cigar.hasExtension()); const auto c = cigar.getExt().scdir.dot(center_separation); double contt; if (c > cigar.ext->half_length) { contt = cigar.ext->half_length; - } else { + } + else { if (c > -cigar.ext->half_length) { contt = c; - } else { + } + else { contt = -cigar.ext->half_length; } } @@ -178,12 +202,14 @@ class CigarWithSphere : public PairPotential { double f0; if (contt + t > cigar.ext->half_length) { f0 = cigar.ext->half_length; - } else { + } + else { f0 = contt + t; } if (contt - t < -cigar.ext->half_length) { f0 -= -cigar.ext->half_length; - } else { + } + else { f0 -= contt - t; } return f1 * (f0 + 1.0) * patch_potential(cigar, sphere, ndist_squared, Point::Zero()) + @@ -199,21 +225,25 @@ class CigarWithSphere : public PairPotential { * PSCs withn their patches. f0 is for size of overlapping segment * whicle f1 anf f2 are scaling fators for orientation of pacthes. * - * @tparam PatchPotential Pair potential between two points on the patches (isotropic, e.g. CosAttract) + * @tparam PatchPotential Pair potential between two points on the patches (isotropic, e.g. + * CosAttract) * @tparam CylinderPotential Pair potential between closest cylinder parts (isotropic, e.g. WCA) * @todo Energy calculation badly needs refactoring! */ -template -class CigarWithCigar : public PairPotential { +template +class CigarWithCigar : public PairPotential +{ private: PatchPotential patch_potential; //!< Isotropic pair-potential for patchy parts CylinderPotential cylinder_potential; //!< Isotropic pair-potential for cylindrical parts [[nodiscard]] double patchyPatchyEnergy(const Particle& particle1, const Particle& particle2, - const Point& center_separation) const; + const Point& center_separation) const; - [[nodiscard]] double isotropicIsotropicEnergy(const Particle& particle1, const Particle& particle2, - const Point& center_separation) const; + [[nodiscard]] double isotropicIsotropicEnergy(const Particle& particle1, + const Particle& particle2, + const Point& center_separation) const; public: CigarWithCigar(); @@ -222,7 +252,8 @@ class CigarWithCigar : public PairPotential { inline double operator()(const Particle& particle1, const Particle& particle2, [[maybe_unused]] double center_separation_squared, - const Point& center_separation) const override { + const Point& center_separation) const override + { assert(particle1.hasExtension() && particle2.hasExtension()); if (particle1.traits().sphero_cylinder.type != SpheroCylinderData::PatchType::None && particle2.traits().sphero_cylinder.type != SpheroCylinderData::PatchType::None) { @@ -243,17 +274,22 @@ class CigarWithCigar : public PairPotential { * @tparam CylinderPotential Pair potential used for cylindrical path (isotropic, e.g. WCA) * @tparam CylinderPotential Pair potential used sphere-sphere interaction (isotropic, e.g. WCA) */ -template -class CompleteCigarPotential : public PairPotential { +class CompleteCigarPotential : public PairPotential +{ private: - SphereWithSphere sphere_sphere; // pair potential between spheres - CigarWithCigar cigar_cigar; // pair potential between cigars - CigarWithSphere cigar_sphere; // pair potential cigar <-> sphere + SphereWithSphere sphere_sphere; // pair potential between spheres + CigarWithCigar cigar_cigar; // pair potential between cigars + CigarWithSphere + cigar_sphere; // pair potential cigar <-> sphere public: - inline double operator()(const Particle& a, const Particle& b, [[maybe_unused]] double distance_squared, - const Point& distance) const override { + inline double operator()(const Particle& a, const Particle& b, + [[maybe_unused]] double distance_squared, + const Point& distance) const override + { const double small_number = 1e-6; const auto a_is_sphere = !a.hasExtension() || a.ext->half_length < small_number; const auto b_is_sphere = !a.hasExtension() || b.ext->half_length < small_number; @@ -275,4 +311,4 @@ class CompleteCigarPotential : public PairPotential { CompleteCigarPotential(); }; -} // namespace Faunus::Potential +} // namespace Faunus::pairpotential diff --git a/src/tabulate.h b/src/tabulate.h index ea2cd8fc3..6af466322 100644 --- a/src/tabulate.h +++ b/src/tabulate.h @@ -25,18 +25,26 @@ namespace Faunus { namespace Tabulate { /* base class for all tabulators - no dependencies */ -template class TabulatorBase { +template class TabulatorBase +{ protected: T utol = 1e-5, ftol = -1, umaxtol = -1, fmaxtol = -1; T numdr = 0.0001; // dr for derivative evaluation // First derivative with respect to x - T f1(std::function f, T x) const { return (f(x + numdr * 0.5) - f(x - numdr * 0.5)) / (numdr); } + T f1(std::function f, T x) const + { + return (f(x + numdr * 0.5) - f(x - numdr * 0.5)) / (numdr); + } // Second derivative with respect to x - T f2(std::function f, T x) const { return (f1(f, x + numdr * 0.5) - f1(f, x - numdr * 0.5)) / (numdr); } + T f2(std::function f, T x) const + { + return (f1(f, x + numdr * 0.5) - f1(f, x - numdr * 0.5)) / (numdr); + } - void check() const { + void check() const + { if (ftol != -1 && ftol <= 0.0) { std::cerr << "ftol=" << ftol << " too small\n" << std::endl; abort(); @@ -52,15 +60,19 @@ template class TabulatorBase { } public: - struct data { + struct data + { std::vector r2; // r2 for intervals std::vector c; // c for coefficents T rmin2 = 0, rmax2 = 0; // useful to save these with table + bool empty() const { return r2.empty() && c.empty(); } + inline size_t numKnots() const { return r2.size(); } }; - void setTolerance(T _utol, T _ftol = -1, T _umaxtol = -1, T _fmaxtol = -1) { + void setTolerance(T _utol, T _ftol = -1, T _umaxtol = -1, T _fmaxtol = -1) + { utol = _utol; ftol = _ftol; umaxtol = _umaxtol; @@ -80,14 +92,17 @@ template class TabulatorBase { * @note Slow on Intel compiler * @todo Hide data and functions; clean up r vs r2 mess. */ -template class Andrea : public TabulatorBase { +template class Andrea : public TabulatorBase +{ private: typedef TabulatorBase base; // for convenience int mngrid = 1200; // Max number of controlpoints int ndr = 100; // Max number of trials to decr dr T drfrac = 0.9; // Multiplicative factor to decr dr - std::vector SetUBuffer(T, T zlow, T, T zupp, T u0low, T u1low, T u2low, T u0upp, T u1upp, T u2upp) { + std::vector SetUBuffer(T, T zlow, T, T zupp, T u0low, T u1low, T u2low, T u0upp, T u1upp, + T u2upp) + { // Zero potential and force return no coefficients if (std::fabs(u0low) < 1e-9) @@ -121,7 +136,9 @@ template class Andrea : public TabulatorBase * - `[0]==true`: tolerance is approved, * - `[1]==true` Repulsive part is found. */ - std::vector CheckUBuffer(std::vector& ubuft, T rlow, T rupp, std::function f) const { + std::vector CheckUBuffer(std::vector& ubuft, T rlow, T rupp, + std::function f) const + { // Number of points to control int ncheck = 11; @@ -134,12 +151,15 @@ template class Andrea : public TabulatorBase T u0 = f(r2); T u1 = base::f1(f, r2); T dz = r2 - rlow * rlow; - T usum = - ubuft.at(1) + - dz * (ubuft.at(2) + dz * (ubuft.at(3) + dz * (ubuft.at(4) + dz * (ubuft.at(5) + dz * ubuft.at(6))))); + T usum = ubuft.at(1) + + dz * (ubuft.at(2) + + dz * (ubuft.at(3) + + dz * (ubuft.at(4) + dz * (ubuft.at(5) + dz * ubuft.at(6))))); - T fsum = ubuft.at(2) + - dz * (2 * ubuft.at(3) + dz * (3 * ubuft.at(4) + dz * (4 * ubuft.at(5) + dz * (5 * ubuft.at(6))))); + T fsum = + ubuft.at(2) + + dz * (2 * ubuft.at(3) + + dz * (3 * ubuft.at(4) + dz * (4 * ubuft.at(5) + dz * (5 * ubuft.at(6))))); if (std::fabs(usum - u0) > base::utol) return vb; @@ -161,7 +181,8 @@ template class Andrea : public TabulatorBase * @param r2 value * @note Auto-vectorization in Clang: https://llvm.org/docs/Vectorizers.html */ - inline T eval(const typename base::data& d, T r2) const { + inline T eval(const typename base::data& d, T r2) const + { size_t pos = std::lower_bound(d.r2.begin(), d.r2.end(), r2) - d.r2.begin() - 1; size_t pos6 = 6 * pos; assert((pos6 + 5) < d.c.size()); @@ -172,10 +193,12 @@ template class Andrea : public TabulatorBase for (size_t i = 5; i > 0; i--) sum = dz * (sum + d.c[pos6 + i]); return sum + d.c[pos6]; - } else // manually unrolled version + } + else // manually unrolled version return d.c[pos6] + dz * (d.c[pos6 + 1] + - dz * (d.c[pos6 + 2] + dz * (d.c[pos6 + 3] + dz * (d.c[pos6 + 4] + dz * (d.c[pos6 + 5]))))); + dz * (d.c[pos6 + 2] + + dz * (d.c[pos6 + 3] + dz * (d.c[pos6 + 4] + dz * (d.c[pos6 + 5]))))); } /** @@ -183,19 +206,22 @@ template class Andrea : public TabulatorBase * @param d Table data * @param r2 value */ - T evalDer(const typename base::data& d, T r2) const { + T evalDer(const typename base::data& d, T r2) const + { size_t pos = std::lower_bound(d.r2.begin(), d.r2.end(), r2) - d.r2.begin() - 1; size_t pos6 = 6 * pos; T dz = r2 - d.r2[pos]; return (d.c[pos6 + 1] + dz * (2.0 * d.c[pos6 + 2] + - dz * (3.0 * d.c[pos6 + 3] + dz * (4.0 * d.c[pos6 + 4] + dz * (5.0 * d.c[pos6 + 5]))))); + dz * (3.0 * d.c[pos6 + 3] + + dz * (4.0 * d.c[pos6 + 4] + dz * (5.0 * d.c[pos6 + 5]))))); } /** * @brief Tabulate f(x) in interval ]min,max] */ - typename base::data generate(std::function f, double rmin, double rmax) { + typename base::data generate(std::function f, double rmin, double rmax) + { rmin = std::sqrt(rmin); rmax = std::sqrt(rmax); base::check(); @@ -236,7 +262,8 @@ template class Andrea : public TabulatorBase T u1upp = base::f1(f, zupp); T u2upp = base::f2(f, zupp); - ubuft = SetUBuffer(rlow, zlow, rupp, zupp, u0low, u1low, u2low, u0upp, u1upp, u2upp); + ubuft = + SetUBuffer(rlow, zlow, rupp, zupp, u0low, u1low, u2low, u0upp, u1upp, u2upp); std::vector vb = CheckUBuffer(ubuft, rlow, rupp, f); repul = vb[1]; if (vb[0]) { @@ -249,7 +276,8 @@ template class Andrea : public TabulatorBase if (j >= ndr) throw std::runtime_error("Andrea spline: try to increase utol/ftol"); if (ubuft.size() != 7) - throw std::runtime_error("Andrea spline: wrong size of ubuft, min value + 6 coefficients"); + throw std::runtime_error( + "Andrea spline: wrong size of ubuft, min value + 6 coefficients"); td.r2.push_back(zlow); for (size_t k = 1; k < ubuft.size(); k++) @@ -273,7 +301,8 @@ template class Andrea : public TabulatorBase assert(std::is_sorted(td.r2.rbegin(), td.r2.rend())); std::reverse(td.r2.begin(), td.r2.end()); // reverse all elements for (size_t i = 0; i < td.c.size() / 2; i += 6) // reverse knot order in packets of six - std::swap_ranges(td.c.begin() + i, td.c.begin() + i + 6, td.c.end() - i - 6); // c++17 only + std::swap_ranges(td.c.begin() + i, td.c.begin() + i + 6, + td.c.end() - i - 6); // c++17 only return td; } }; @@ -281,7 +310,8 @@ template class Andrea : public TabulatorBase } // namespace Faunus #ifdef DOCTEST_LIBRARY_INCLUDED -TEST_CASE("[Faunus] Andrea") { +TEST_CASE("[Faunus] Andrea") +{ using doctest::Approx; using namespace Faunus::Tabulate; @@ -323,7 +353,9 @@ TEST_CASE("[Faunus] Andrea") { // Check if analytical spline derivative matches // derivative of original function - auto f_prime_exact = [&](double x, double dx = 1e-10) { return (f(x + dx) - f(x - dx)) / (2 * dx); }; + auto f_prime_exact = [&](double x, double dx = 1e-10) { + return (f(x + dx) - f(x - dx)) / (2 * dx); + }; x = 1e-9; CHECK(spline.evalDer(d, x) == Approx(f_prime_exact(x))); x = 1; diff --git a/src/tensor.cpp b/src/tensor.cpp index cd6d18557..5ec6f23b6 100644 --- a/src/tensor.cpp +++ b/src/tensor.cpp @@ -7,21 +7,33 @@ namespace Faunus { -Tensor::Tensor() { base::setZero(); } +Tensor::Tensor() +{ + base::setZero(); +} -Tensor::Tensor(double xx, double xy, double xz, double yy, double yz, double zz) { +Tensor::Tensor(double xx, double xy, double xz, double yy, double yz, double zz) +{ (*this) << xx, xy, xz, xy, yy, yz, xz, yz, zz; } -void Tensor::rotate(const Tensor::base &rotation_matrix) { +void Tensor::rotate(const Tensor::base& rotation_matrix) +{ (*this) = rotation_matrix * (*this) * rotation_matrix.transpose(); } -[[maybe_unused]] void Tensor::eye() { *this = base::Identity(3, 3); } +[[maybe_unused]] void Tensor::eye() +{ + *this = base::Identity(3, 3); +} -void to_json(nlohmann::json &j, const Tensor &t) { j = {t(0, 0), t(0, 1), t(0, 2), t(1, 1), t(1, 2), t(2, 2)}; } +void to_json(nlohmann::json& j, const Tensor& t) +{ + j = {t(0, 0), t(0, 1), t(0, 2), t(1, 1), t(1, 2), t(2, 2)}; +} -void from_json(const nlohmann::json &j, Tensor &t) { +void from_json(const nlohmann::json& j, Tensor& t) +{ if (j.size() == 6 and j.is_array()) t = Tensor(j[0], j[1], j[2], j[3], j[4], j[5]); else @@ -30,11 +42,12 @@ void from_json(const nlohmann::json &j, Tensor &t) { TEST_SUITE_BEGIN("Tensor"); -TEST_CASE("[Faunus] Tensor") { +TEST_CASE("[Faunus] Tensor") +{ using doctest::Approx; using namespace nlohmann; Tensor Q1 = Tensor(1, 2, 3, 4, 5, 6); - Tensor Q2 = json(Q1); // Q1 --> json --> Q2 + Tensor Q2 = json(Q1); // Q1 --> json --> Q2 CHECK_EQ(json(Q1), json(Q2)); // Q1 --> json == json <-- Q2 ? CHECK_EQ(Q2, Tensor(1, 2, 3, 4, 5, 6)); diff --git a/src/tensor.h b/src/tensor.h index 2380a812d..de8a39bfc 100644 --- a/src/tensor.h +++ b/src/tensor.h @@ -7,7 +7,8 @@ namespace Faunus { * @brief Tensor class * @todo add documentation */ -struct Tensor : public Eigen::Matrix3d { +struct Tensor : public Eigen::Matrix3d +{ typedef Eigen::Matrix3d base; Tensor(); //!< Constructor, clear data @@ -18,18 +19,23 @@ struct Tensor : public Eigen::Matrix3d { */ Tensor(double, double, double, double, double, double); //!< Constructor - void rotate(const base &); //!< Rotate using rotation matrix + void rotate(const base&); //!< Rotate using rotation matrix [[maybe_unused]] [[maybe_unused]] void eye(); - template Tensor(const Eigen::MatrixBase &other) : base(other) {} + template + Tensor(const Eigen::MatrixBase& other) + : base(other) + { + } - template Tensor &operator=(const Eigen::MatrixBase &other) { + template Tensor& operator=(const Eigen::MatrixBase& other) + { base::operator=(other); return *this; } }; //!< Tensor class -void to_json(nlohmann::json &, const Tensor &); //!< Tensor -> Json -void from_json(const nlohmann::json &, Tensor &); //!< Json -> Tensor +void to_json(nlohmann::json&, const Tensor&); //!< Tensor -> Json +void from_json(const nlohmann::json&, Tensor&); //!< Json -> Tensor } // namespace Faunus diff --git a/src/units.cpp b/src/units.cpp index d8e321239..8bfc7dcb9 100644 --- a/src/units.cpp +++ b/src/units.cpp @@ -5,9 +5,13 @@ double Faunus::PhysicalConstants::temperature = 298.15; -std::string Faunus::unicode::bracket(std::string_view sv) { return fmt::format("\u27e8{}\u27e9", sv); } +std::string Faunus::unicode::bracket(std::string_view sv) +{ + return fmt::format("\u27e8{}\u27e9", sv); +} -TEST_CASE("[Faunus] infinite math") { +TEST_CASE("[Faunus] infinite math") +{ using namespace Faunus; CHECK_EQ(std::isfinite(1.0 + pc::infty), false); CHECK(std::isinf(1.0 + pc::infty)); @@ -26,7 +30,8 @@ TEST_CASE("[Faunus] infinite math") { CHECK(std::isnan(std::sqrt(-1.0))); // is this needed? } -TEST_CASE("[Faunus] Units and string literals") { +TEST_CASE("[Faunus] Units and string literals") +{ using doctest::Approx; using namespace Faunus; pc::temperature = 298.15_K; diff --git a/src/units.h b/src/units.h index d0604e080..5c3a61f0c 100644 --- a/src/units.h +++ b/src/units.h @@ -13,7 +13,8 @@ constexpr T infty = std::numeric_limits::infinity(), //!< Numerical infinity neg_infty = -std::numeric_limits::infinity(), //!< Numerical negative infinity epsilon_dbl = std::numeric_limits::epsilon(), //!< Numerical precision max_value = std::numeric_limits::max(), //!< Maximal (finite) representable value - max_exp_argument = 709.782712893384, //!< Largest value exp() can take before overflow (hard-coded for double) + max_exp_argument = + 709.782712893384, //!< Largest value exp() can take before overflow (hard-coded for double) pi = std::numbers::pi, vacuum_permittivity = 8.85419e-12, //!< Permittivity of vacuum [C^2/(J*m)] elementary_charge = 1.602177e-19, //!< Absolute electronic unit charge [C] @@ -23,15 +24,24 @@ constexpr T infty = std::numeric_limits::infinity(), //!< Numerical infinity molar_gas_constant = boltzmann_constant * avogadro; //!< Molar gas constant [J/(K*mol)] extern T temperature; //!< Temperature [K] -static inline T kT() { return temperature * boltzmann_constant; } //!< Thermal energy [J] -static inline T RT() { return temperature * molar_gas_constant; } //!< Thermal energy [J/mol] +static inline T kT() +{ + return temperature * boltzmann_constant; +} //!< Thermal energy [J] -static inline T bjerrumLength(T relative_dielectric_constant) { +static inline T RT() +{ + return temperature * molar_gas_constant; +} //!< Thermal energy [J/mol] + +static inline T bjerrumLength(T relative_dielectric_constant) +{ return elementary_charge * elementary_charge / (4 * pi * vacuum_permittivity * relative_dielectric_constant * 1e-10 * kT()); } //!< Bjerrum length [Γ…] -static inline T relativeDielectricFromBjerrumLength(T bjerrumlength) { +static inline T relativeDielectricFromBjerrumLength(T bjerrumlength) +{ return bjerrumLength(bjerrumlength); } //!< Bjerrum length to a relative dielectric constant } // namespace PhysicalConstants @@ -60,68 +70,173 @@ namespace pc = PhysicalConstants; */ namespace ChemistryUnits { -using T = double; //!< floating point size -constexpr T operator"" _rad(long double a) { return a; } //!< angle in radians -constexpr T operator"" _deg(long double a) { return a * pc::pi / 180; } //!< angle in degrees (to radians) -constexpr T operator"" _K(long double temp) { return temp; } //!< temperature in kelvins -constexpr T operator"" _C(long double temp) { return 273.15 + temp; } //!< temperature in degrees Celcius (to kelvins) -constexpr T operator"" _ps(long double tau) { return tau; } //!< time in picoseconds -constexpr T operator"" _s(long double tau) { return tau * 1e12; } //!< time in seconds (to picoseconds) -constexpr T operator"" _angstrom(long double l) { return l; } //!< length in Γ₯ngstrΓΆms -constexpr T operator"" _nm(long double l) { return l * 10; } //!< length in nanometers (to Γ₯ngstrΓΆms) -constexpr T operator"" _m(long double l) { return l * 1e10; } //!< length in meters (to Γ₯ngstrΓΆms) -constexpr T operator"" _bohr(long double l) { return l * 0.52917721092; } //!< length in Bohr radii (to Γ₯ngstrΓΆms) -constexpr T operator"" _angstrom3(long double l) { return l; } //!< volume in cubic Γ₯ngstrΓΆms -constexpr T operator"" _m3(long double v) { return v * 1e30; } //!< volume in cubic meters (to cubic Γ₯ngstrΓΆms) -constexpr T operator"" _liter(long double v) { return v * 1e27; } //!< volume in liters (to cubic Γ₯ngstrΓΆms) -constexpr T operator"" _gmol(long double m) { return m; } //!< mass in grams per mole -constexpr T operator"" _kg(long double m) { +using T = double; //!< floating point size + +constexpr T operator"" _rad(long double a) +{ + return a; +} //!< angle in radians + +constexpr T operator"" _deg(long double a) +{ + return a * pc::pi / 180; +} //!< angle in degrees (to radians) + +constexpr T operator"" _K(long double temp) +{ + return temp; +} //!< temperature in kelvins + +constexpr T operator"" _C(long double temp) +{ + return 273.15 + temp; +} //!< temperature in degrees Celcius (to kelvins) + +constexpr T operator"" _ps(long double tau) +{ + return tau; +} //!< time in picoseconds + +constexpr T operator"" _s(long double tau) +{ + return tau * 1e12; +} //!< time in seconds (to picoseconds) + +constexpr T operator"" _angstrom(long double l) +{ + return l; +} //!< length in Γ₯ngstrΓΆms + +constexpr T operator"" _nm(long double l) +{ + return l * 10; +} //!< length in nanometers (to Γ₯ngstrΓΆms) + +constexpr T operator"" _m(long double l) +{ + return l * 1e10; +} //!< length in meters (to Γ₯ngstrΓΆms) + +constexpr T operator"" _bohr(long double l) +{ + return l * 0.52917721092; +} //!< length in Bohr radii (to Γ₯ngstrΓΆms) + +constexpr T operator"" _angstrom3(long double l) +{ + return l; +} //!< volume in cubic Γ₯ngstrΓΆms + +constexpr T operator"" _m3(long double v) +{ + return v * 1e30; +} //!< volume in cubic meters (to cubic Γ₯ngstrΓΆms) + +constexpr T operator"" _liter(long double v) +{ + return v * 1e27; +} //!< volume in liters (to cubic Γ₯ngstrΓΆms) + +constexpr T operator"" _gmol(long double m) +{ + return m; +} //!< mass in grams per mole + +constexpr T operator"" _kg(long double m) +{ return m * 1e3 * pc::avogadro; } //!< mass in kilograms per particle (to grams per mole) -constexpr T operator"" _Da(long double m) { return m; } //!< mass in daltons per particle (to grams per mole) + +constexpr T operator"" _Da(long double m) +{ + return m; +} //!< mass in daltons per particle (to grams per mole) //! amount of substance in moles (to number of particles) -constexpr T operator"" _mol(long double n) { return n * pc::avogadro; } +constexpr T operator"" _mol(long double n) +{ + return n * pc::avogadro; +} //! molar concentration (to particle density in number of particles per cubic Γ₯ngstrΓΆm) -constexpr T operator"" _molar(long double c) { return c * 1.0_mol / 1.0_liter; } +constexpr T operator"" _molar(long double c) +{ + return c * 1.0_mol / 1.0_liter; +} //! millimolar concentration (to particle density in number of particles per cubic Γ₯ngstrΓΆm) -constexpr T operator"" _millimolar(long double c) { return c * 1.0e-3_mol / 1.0_liter; } +constexpr T operator"" _millimolar(long double c) +{ + return c * 1.0e-3_mol / 1.0_liter; +} -//! pressure in pascals (to particle density in number of particles per cubic Γ₯ngstrΓΆm – assuming the ideal gas law) -inline T operator"" _Pa(long double p) { return p / pc::kT() / 1.0_m3; } +//! pressure in pascals (to particle density in number of particles per cubic Γ₯ngstrΓΆm – assuming +//! the ideal gas law) +inline T operator"" _Pa(long double p) +{ + return p / pc::kT() / 1.0_m3; +} -//! pressure in atmospheres (to particle density in number of particles per cubic Γ₯ngstrΓΆm – assuming the ideal gas -//! law) -inline T operator"" _atm(long double p) { return p * 101325.0_Pa; } +//! pressure in atmospheres (to particle density in number of particles per cubic Γ₯ngstrΓΆm – +//! assuming the ideal gas law) +inline T operator"" _atm(long double p) +{ + return p * 101325.0_Pa; +} //! pressure in bars (to particles per cubic Γ₯ngstrΓΆm – assuming the ideal gas law) -inline T operator"" _bar(long double p) { return p * 100000.0_Pa; } +inline T operator"" _bar(long double p) +{ + return p * 100000.0_Pa; +} //! dipole moment in electronβ€”Γ₯ngstrΓΆms -constexpr T operator"" _eA(long double mu) { return mu; } +constexpr T operator"" _eA(long double mu) +{ + return mu; +} //! dipole moment in debyes (to electron-Γ₯ngstrΓΆms) -constexpr T operator"" _debye(long double mu) { return mu * 0.208194334424626; } +constexpr T operator"" _debye(long double mu) +{ + return mu * 0.208194334424626; +} //! dipole moment in coulomb-meters (to electron Γ₯ngstrΓΆms) -constexpr T operator"" _Cm(long double mu) { return mu * 1.0_debye / 3.335640951981520e-30; } +constexpr T operator"" _Cm(long double mu) +{ + return mu * 1.0_debye / 3.335640951981520e-30; +} //! energy in kT (to kT) -constexpr T operator"" _kT(long double u) { return u; } +constexpr T operator"" _kT(long double u) +{ + return u; +} //! energy in joules (to thermal energy units kT) -inline T operator"" _J(long double u) { return u / pc::kT(); } +inline T operator"" _J(long double u) +{ + return u / pc::kT(); +} //! energy in hartrees (to kT) -inline T operator"" _hartree(long double u) { return u * 4.35974434e-18_J; } +inline T operator"" _hartree(long double u) +{ + return u * 4.35974434e-18_J; +} //! energy in kilojoules per mole (to kT per particle) -inline T operator"" _kJmol(long double u) { return u / pc::kT() / pc::avogadro * 1e3; } +inline T operator"" _kJmol(long double u) +{ + return u / pc::kT() / pc::avogadro * 1e3; +} //! energy in kilocalories per mole (to kT per particle) -inline T operator"" _kcalmol(long double u) { return u * 4.1868_kJmol; } +inline T operator"" _kcalmol(long double u) +{ + return u * 4.1868_kJmol; +} } // namespace ChemistryUnits diff --git a/src/voronota.cpp b/src/voronota.cpp index 86518caa3..814b7f787 100644 --- a/src/voronota.cpp +++ b/src/voronota.cpp @@ -7,7 +7,8 @@ namespace Faunus::analysis { Voronota::Voronota(double probe_radius, const Faunus::Space& spc) : Analysis(spc, "voronota") - , probe_radius(probe_radius) { + , probe_radius(probe_radius) +{ cite = "doi:10/mq8k"; auto n_pbc = spc.geometry.asSimpleGeometry()->boundary_conditions.isPeriodic().count(); switch (n_pbc) { @@ -26,32 +27,38 @@ Voronota::Voronota(double probe_radius, const Faunus::Space& spc) } Voronota::Voronota(const Faunus::json& input, const Faunus::Space& spc) - : Voronota(input.value("radius", 1.4_angstrom), spc) { + : Voronota(input.value("radius", 1.4_angstrom), spc) +{ from_json(input); } -void Voronota::_from_json(const Faunus::json& input) { +void Voronota::_from_json(const Faunus::json& input) +{ if (filename = input.value("file", ""s); !filename.empty()) { output_stream = IO::openCompressedOutputStream(MPI::prefix + filename); *output_stream << "# step SASA\n"; } } -void Voronota::_to_json(json& json_output) const { +void Voronota::_to_json(json& json_output) const +{ if (!average_data.area.empty()) { json_output = {{"⟨SASA⟩", average_data.area.avg()}, - {"⟨SASA²⟩-⟨SASA⟩²", average_data.area_squared.avg() - std::pow(average_data.area.avg(), 2)}}; + {"⟨SASA²⟩-⟨SASA⟩²", + average_data.area_squared.avg() - std::pow(average_data.area.avg(), 2)}}; } json_output["radius"] = probe_radius; } -void Voronota::_to_disk() { +void Voronota::_to_disk() +{ if (output_stream) { output_stream->flush(); } } -void Voronota::_sample() { +void Voronota::_sample() +{ using voronotalt::SimplePoint; using namespace ranges::cpp20::views; @@ -68,7 +75,8 @@ void Voronota::_sample() { const Point corner = 0.5 * spc.geometry.getLength(); const std::vector box_corners = {to_point(-corner), to_point(corner)}; voronotalt::RadicalTessellation::construct_full_tessellation(spheres, box_corners, result); - } else { + } + else { voronotalt::RadicalTessellation::construct_full_tessellation(spheres, result); } From 596ecccf1827cb46e90c3971db977761776c8b7a Mon Sep 17 00:00:00 2001 From: mikael Date: Mon, 17 Jun 2024 13:32:22 +0200 Subject: [PATCH 02/12] Add git blame ignore file --- .git-blame-ignore-revs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..a72a034de --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,11 @@ +# Run this command to always ignore formatting commits in `git blame` +# +# ~~~ +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# ~~~ +# +# See: https://www.stefanjudis.com/today-i-learned/how-to-exclude-commits-from-git-blame/ + +# Apply clang-format (17-jun-2024) +62b6a50d47f6687d8a2e39d562e04c7b3fab0411 + From 6e85032a2339ee846d1615df3afb8505526cd830 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Mon, 1 Jul 2024 13:10:22 +0200 Subject: [PATCH 03/12] Output inter-particle bond info (#450) --- src/energy.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/energy.cpp b/src/energy.cpp index fb60afd2d..8c5423311 100644 --- a/src/energy.cpp +++ b/src/energy.cpp @@ -1016,6 +1016,11 @@ Bonded::Bonded(const Space& spc, BondVector external_bonds = BondVector()) name = "bonded"; updateInternalBonds(); for (auto& bond : this->external_bonds) { + std::stringstream indices; + std::copy(bond->indices.begin(), bond->indices.end(), + std::ostream_iterator(indices, " ")); + faunus_logger->info("{}: adding inter-particle bonds involving indices [ {}]", name, + indices.str()); bond->setEnergyFunction(spc.particles); } } From 371cb7cd7ba0350c8c38e1015563b6066e80778b Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Tue, 2 Jul 2024 11:18:57 +0200 Subject: [PATCH 04/12] Make atomic displacement parameters optional (#451) * Add get_optional json function * Make dp and dprot optional * Add default dp and dprot to atomtransrot move * Clang format * Update schema.yml with dp/dprot --- docs/_docs/moves.md | 6 +++++- docs/_docs/topology.md | 4 ++-- docs/schema.yml | 2 ++ src/atomdata.cpp | 29 ++++++++++++++++++++++------- src/atomdata.h | 31 ++++++++++++++++--------------- src/core.h | 13 +++++++++++++ src/move.cpp | 8 ++++++-- src/move.h | 4 +++- 8 files changed, 69 insertions(+), 28 deletions(-) diff --git a/docs/_docs/moves.md b/docs/_docs/moves.md index 647d4d204..bc29d6d6d 100644 --- a/docs/_docs/moves.md +++ b/docs/_docs/moves.md @@ -77,11 +77,15 @@ Upon MC movement, the mean squared displacement will be tracked. `molecule` | Molecule name to operate on `dir=[1,1,1]` | Translational directions `energy_resolution` | If set to a non-zero value (kT), an energy histogram will be generated. +`dp=0` | Default translational displacement parameter (Γ…) +`dprot=0` | Default rotational displacement parameter (radians) As `moltransrot` but instead of operating on the molecular mass center, this translates -and rotates individual atoms in the group. The repeat is set to the number of atoms in the specified group and the +and rotates individual atoms in the group. +The repeat is set to the number of atoms in the specified group and the displacement parameters `dp` and `dprot` for the individual atoms are taken from the atom properties defined in the [topology](topology). +If `dp` and `dprot` are not defined for an atom, the default values for the move are used. Atomic _rotation_ affects only anisotropic particles such as dipoles, spherocylinders, quadrupoles etc. An energy histogram of each participating species will be written to disk if the `energy_resolution` diff --git a/docs/_docs/topology.md b/docs/_docs/topology.md index d7de2b34d..2b27b93e5 100644 --- a/docs/_docs/topology.md +++ b/docs/_docs/topology.md @@ -61,8 +61,8 @@ Atoms are the smallest possible particle entities with properties defined below. `activity=0` | Chemical activity for grand canonical MC [mol/l] `pactivity` | βˆ’log10 of chemical activity (will be converted to activity) `alphax=0` | Excess polarizability (unit-less) -`dp=0` | Translational displacement parameter [Γ…] -`dprot=0` | Rotational displacement parameter [radians] +`dp` | Translational displacement parameter [Γ…] (optional) +`dprot` | Rotational displacement parameter [radians] (optional) `eps=0` | Lennard-Jones/WCA energy parameter [kJ/mol] `mu=[0,0,0]` | Dipole moment vector [eΓ…] `mulen=|mu|` | Dipole moment scalar [eΓ…] diff --git a/docs/schema.yml b/docs/schema.yml index e984457b6..b51230367 100644 --- a/docs/schema.yml +++ b/docs/schema.yml @@ -775,6 +775,8 @@ properties: minItems: 3 maxItems: 3 default: [1,1,1] + dp: {type: number, minimum: 0.0, description: "default translational displacement", default: 0.0} + dprot: {type: number, minimum: 0.0, description: "default rotational displacement", default: 0.0} required: [molecule] additionalProperties: false type: object diff --git a/src/atomdata.cpp b/src/atomdata.cpp index c7c01cb9e..1e036e3b8 100644 --- a/src/atomdata.cpp +++ b/src/atomdata.cpp @@ -93,14 +93,18 @@ void to_json(json& j, const AtomData& a) {"alphax", a.alphax}, {"mw", a.mw}, {"q", a.charge}, - {"dp", a.dp / 1.0_angstrom}, - {"dprot", a.dprot / 1.0_rad}, {"tension", a.tension * 1.0_angstrom * 1.0_angstrom / 1.0_kJmol}, {"tfe", a.tfe * 1.0_angstrom * 1.0_angstrom * 1.0_molar / 1.0_kJmol}, {"mu", a.mu}, {"mulen", a.mulen}, {"psc", a.sphero_cylinder}, {"id", a.id()}}; + if (a.dp.has_value()) { + _j["dp"] = a.dp.value() / 1.0_angstrom; + } + if (a.dprot.has_value()) { + _j["dprot"] = a.dprot.value() / 1.0_rad; + } to_json(_j, a.interaction); // append other interactions if (a.hydrophobic) { _j["hydrophobic"] = a.hydrophobic; @@ -110,6 +114,21 @@ void to_json(json& j, const AtomData& a) } } +/// Handles optional translational and rotational displacement +void set_dp_and_dprot(const json& j, AtomData& atomdata) +{ + // todo: use `std::optional::and_then()` when C++23 is available + if (const auto dp = get_optional(j, "dp")) { + atomdata.dp = dp.value() * 1.0_angstrom; + } + if (const auto dprot = get_optional(j, "dprot")) { + atomdata.dprot = dprot.value() * 1.0_rad; + if (std::fabs(atomdata.dprot.value()) > 2.0 * pc::pi) { + faunus_logger->warn("rotational displacement should be between [0:2Ο€]"); + } + } +} + void from_json(const json& j, AtomData& a) { if (!j.is_object() || j.size() != 1) { @@ -120,11 +139,6 @@ void from_json(const json& j, AtomData& a) auto val = SingleUseJSON(atom_iter.value()); a.alphax = val.value("alphax", a.alphax); a.charge = val.value("q", a.charge); - a.dp = val.value("dp", a.dp) * 1.0_angstrom; - a.dprot = val.value("dprot", a.dprot) * 1.0_rad; - if (std::fabs(a.dprot) > 2.0 * pc::pi) { - faunus_logger->warn("rotational displacement should be between [0:2Ο€]"); - } a.id() = val.value("id", a.id()); a.mu = val.value("mu", a.mu); a.mulen = val.value("mulen", a.mulen); @@ -140,6 +154,7 @@ void from_json(const json& j, AtomData& a) a.tfe = val.value("tfe", a.tfe) * 1.0_kJmol / (1.0_angstrom * 1.0_angstrom * 1.0_molar); a.hydrophobic = val.value("hydrophobic", false); a.implicit = val.value("implicit", false); + set_dp_and_dprot(val, a); if (val.contains("activity")) { a.activity = val.at("activity").get() * 1.0_molar; } diff --git a/src/atomdata.h b/src/atomdata.h index edc2fd5f7..63159143c 100644 --- a/src/atomdata.h +++ b/src/atomdata.h @@ -1,5 +1,6 @@ #pragma once #include "core.h" +#include #include #include #include @@ -79,21 +80,21 @@ class AtomData friend void from_json(const json&, AtomData&); public: - std::string name; //!< Name - double charge = 0; //!< Particle charge [e] - double mw = 1; //!< Weight - double sigma = 0; //!< Diameter for e.g Lennard-Jones etc. [angstrom] - //!< Do not set! Only a temporal class member during the refactorization - double activity = 0; //!< Chemical activity [mol/l] - double alphax = 0; //!< Excess polarisability (unit-less) - double dp = 0; //!< Translational displacement parameter [angstrom] - double dprot = 0; //!< Rotational displacement parameter [degrees] - double tension = 0; //!< Surface tension [kT/Γ…^2] - double tfe = 0; //!< Transfer free energy [J/mol/angstrom^2/M] - Point mu = {0, 0, 0}; //!< Dipole moment unit vector - double mulen = 0; //!< Dipole moment length - bool hydrophobic = false; //!< Is the particle hydrophobic? - bool implicit = false; //!< Is the particle implicit (e.g. proton)? + std::string name; //!< Name + double charge = 0; //!< Particle charge [e] + double mw = 1; //!< Weight + double sigma = 0; //!< Diameter for e.g Lennard-Jones etc. [angstrom] + //!< Do not set! Only a temporal class member during the refactorization + double activity = 0; //!< Chemical activity [mol/l] + double alphax = 0; //!< Excess polarisability (unit-less) + std::optional dp = std::nullopt; //!< Translational displacement parameter [angstrom] + std::optional dprot = std::nullopt; //!< Rotational displacement parameter [degrees] + double tension = 0; //!< Surface tension [kT/Γ…^2] + double tfe = 0; //!< Transfer free energy [J/mol/angstrom^2/M] + Point mu = {0, 0, 0}; //!< Dipole moment unit vector + double mulen = 0; //!< Dipole moment length + bool hydrophobic = false; //!< Is the particle hydrophobic? + bool implicit = false; //!< Is the particle implicit (e.g. proton)? InteractionData interaction; //!< Arbitrary interaction parameters, e.g., epsilons in various potentials SpheroCylinderData sphero_cylinder; //!< Data for patchy sphero cylinders (PSCs) diff --git a/src/core.h b/src/core.h index da63ba660..cdb664c11 100644 --- a/src/core.h +++ b/src/core.h @@ -254,4 +254,17 @@ class Electrolyte void to_json(json& j, const Electrolyte& electrolyte); std::optional makeElectrolyte(const json& j); //!< Create ionic salt object from json +/// Extract JSON value associated with `key` into `std::optional` +/// +/// If the value does not exist, return `std::nullopt` +/// +/// @throws If value exists but cannot be extracted as `T`. +template std::optional get_optional(const json& j, std::string_view key) +{ + if (const auto it = j.find(key); it != j.end()) { + return it->get(); // may throw exception + } + return std::nullopt; +} + } // namespace Faunus diff --git a/src/move.cpp b/src/move.cpp index 1276c4c96..86a48bfea 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -189,6 +189,8 @@ void AtomicTranslateRotate::_to_json(json& j) const { j = {{"dir", directions}, {"molid", molid}, + {"dp", default_dp}, + {"dprot", default_dprot}, {unicode::rootof + unicode::bracket("r" + unicode::squared), std::sqrt(mean_square_displacement.avg())}, {"molecule", molecule_name}}; @@ -214,6 +216,8 @@ void AtomicTranslateRotate::_from_json(const json& j) } } energy_resolution = j.value("energy_resolution", 0.0); + default_dp = j.value("dp", 0.0); + default_dprot = j.value("dprot", 0.0); } void AtomicTranslateRotate::translateParticle(ParticleVector::iterator particle, @@ -262,8 +266,8 @@ void AtomicTranslateRotate::_move(Change& change) { if (auto particle = randomAtom(); particle != spc.particles.end()) { latest_particle = particle; - const auto translational_displacement = particle->traits().dp; - const auto rotational_displacement = particle->traits().dprot; + const double translational_displacement = particle->traits().dp.value_or(default_dp); + const double rotational_displacement = particle->traits().dprot.value_or(default_dprot); if (translational_displacement > 0.0) { // translate translateParticle(particle, translational_displacement); diff --git a/src/move.h b/src/move.h index 8cb8607e1..280071cf0 100644 --- a/src/move.h +++ b/src/move.h @@ -139,7 +139,7 @@ class [[maybe_unused]] AtomicSwapCharge : public Move }; /** - * @brief Translate and rotate a molecular group + * @brief Translate and rotate individual atoms */ class AtomicTranslateRotate : public Move { @@ -149,6 +149,8 @@ class AtomicTranslateRotate : public Move energy_histogram; //!< Energy histogram (value) for each particle type (key) double energy_resolution = 0.0; //!< Resolution of sampled energy histogram double latest_displacement_squared; //!< temporary squared displacement + double default_dp = 0.0; //!< Default translational displacement (Γ…) + double default_dprot = 0.0; //!< Default rotational displacement (rad) void sampleEnergyHistogram(); //!< Update energy histogram based on latest move void saveHistograms(); //!< Write histograms for file void checkMassCenter( From 85fa933b9fe4080526e9f6d02d5132b34df32750 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Tue, 2 Jul 2024 13:34:40 +0200 Subject: [PATCH 05/12] Ignore doctest TEST* in clang tidy --- .clang-tidy | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 109dcd35e..6cb0da647 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -10,17 +10,16 @@ CheckOptions: - { key: readability-identifier-naming.ClassCase, value: CamelCase } - { key: readability-identifier-naming.StructCase, value: CamelCase } - { key: readability-identifier-naming.VariableCase, value: lower_case} + - { key: readability-identifier-naming.VariableIgnoredRegexp, value: 'TEST.*'} - { key: readability-identifier-naming.LocalVariableCase, value: lower_case} - { key: readability-identifier-naming.FunctionCase, value: lower_case} - - { key: readability-identifier-naming.FunctionIgnoredRegexp, value: '^to_json$|^from_json$'} + - { key: readability-identifier-naming.FunctionIgnoredRegexp, value: 'TEST_.*'} - { key: readability-identifier-naming.ClassMethodCase, value: lower_case} - - { key: readability-identifier-naming.ClassMethodIgnoredRegexp, value: '^to_json$|^from_json$'} + - { key: readability-identifier-naming.ClassIgnoredRegexp, value: 'TEST_.*'} - { key: readability-identifier-naming.ParameterCase, value: lower_case} - - { key: readability-identifier-naming.ParameterIgnoredRegexp, value: '^j$'} - { key: readability-identifier-naming.EnumCase, value: CamelCase} - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase} - { key: readability-identifier-naming.ConstantParameterCase, value: lower_case} - - { key: readability-identifier-naming.ConstexprVariableCase,, value: UPPER_CASE} - - { key: readability-identifier-naming.ConstantParameterIgnoredRegexp, value: lower_case} + - { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE} - { key: readability-identifier-naming.TypedefCase, value: CamelCase} - { key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic, value: 'true'} From 06a3497285da0766d80a18178fe404d5eb7cb45a Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Tue, 2 Jul 2024 14:06:07 +0200 Subject: [PATCH 06/12] Update clang-tidy --- .clang-tidy | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.clang-tidy b/.clang-tidy index 6cb0da647..889f36467 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,5 @@ --- -Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm-*,-modernize-use-nodiscard,-llvmlibc-callee-namespace,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers' +Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm-*,-modernize-use-nodiscard,-llvmlibc-callee-namespace,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,readability-identifier-naming' WarningsAsErrors: '*' HeaderFilterRegex: '' FormatStyle: file @@ -23,3 +23,4 @@ CheckOptions: - { key: readability-identifier-naming.ConstexprVariableCase, value: UPPER_CASE} - { key: readability-identifier-naming.TypedefCase, value: CamelCase} - { key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic, value: 'true'} + From 9ab572311ecedde3542e4507ba0445b23f62f06c Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Wed, 3 Jul 2024 09:58:33 +0200 Subject: [PATCH 07/12] Fix typos in actions.h --- src/actions.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index 45df8d707..eb33e3f36 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -21,7 +21,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) if (j.contains("traj")) { trajectory = std::make_unique(j["traj"]); - if (angles.size() > 1e5) { + if (angles.size() > 10000) { faunus_logger->warn("{}: large trajectory with {} frames will be generated", name, angles.size()); } @@ -125,7 +125,7 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) for (const auto& q_body2 : angles.quaternions_2) { for (const auto& q_dihedral : angles.dihedrals) { const auto q2 = - q_dihedral * q_body2; // simulataneous rotations (noncummutative) + q_dihedral * q_body2; // simultaneous rotations (non-commutative) auto particles2 = molecules.second.getRotatedReference(spc.groups, q2); ranges::cpp20::for_each(particles2, translate); auto group2 = Group(0, particles2.begin(), particles2.end()); From ffd01689116c71539b46b9ae46a39b76ef93eb1a Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Wed, 3 Jul 2024 10:18:45 +0200 Subject: [PATCH 08/12] Refactor actions according to clang-tidy --- src/actions.cpp | 16 ++++++++-------- src/actions.h | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index eb33e3f36..f2e1c9ebc 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -22,7 +22,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) if (j.contains("traj")) { trajectory = std::make_unique(j["traj"]); if (angles.size() > 10000) { - faunus_logger->warn("{}: large trajectory with {} frames will be generated", name, + faunus_logger->warn("{}: large trajectory with {} frames will be generated", NAME, angles.size()); } } @@ -40,7 +40,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) << "# Column 9: Energy (kJ/mol)\n"; faunus_logger->info("{}: COM distance range = [{:.1f}, {:.1f}) max energy = {:.1f} kJ/mol", - name, zmin, zmax, max_energy / 1.0_kJmol); + NAME, zmin, zmax, max_energy / 1.0_kJmol); } /** @@ -53,7 +53,7 @@ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int mol index = molecule_index; const auto& group = groups.at(index); if (group.isAtomic()) { - throw ConfigurationError("{}: group {} is not molecular", name, index); + throw ConfigurationError("{}: group {} is not molecular", NAME, index); } auto as_centered_position = [&](auto& particle) -> Point { return particle.pos - group.mass_center; @@ -105,11 +105,11 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) { auto nonbonded = hamiltonian.findFirstOf(); if (!nonbonded) { - throw ConfigurationError("{}: at least one nonbonded energy term required", name); + throw ConfigurationError("{}: at least one nonbonded energy term required", NAME); } for (const auto z_pos : arange(zmin, zmax, dz)) { - faunus_logger->info("{}: separation = {}", name, z_pos); + faunus_logger->info("{}: separation = {}", NAME, z_pos); auto translate = [=](auto& particle) { particle.pos.z() += z_pos; }; auto progress_tracker = std::make_unique( angles.quaternions_1.size() * angles.quaternions_2.size()); @@ -178,8 +178,8 @@ std::vector> createActionList(const json& input, S void AngularScan::EnergyAnalysis::clear() { mean_exp_energy.clear(); - partition_sum = 0; - energy_sum = 0; + partition_sum = 0.0; + energy_sum = 0.0; } void AngularScan::EnergyAnalysis::add(const double energy) @@ -202,7 +202,7 @@ double AngularScan::EnergyAnalysis::getMeanEnergy() const void AngularScan::EnergyAnalysis::printLog() const { - faunus_logger->info("{}: free energy = {:.3f} mean energy = {:.3f}", name, + faunus_logger->info("{}: free energy = {:.3f} mean energy = {:.3f}", NAME, getFreeEnergy(), getMeanEnergy()); } diff --git a/src/actions.h b/src/actions.h index 2b50c86f2..9e8fcf50c 100644 --- a/src/actions.h +++ b/src/actions.h @@ -29,20 +29,20 @@ struct SystemAction */ class AngularScan : public SystemAction { - inline static const std::string name = "angular scan"; //!< Name used for logging + static constexpr std::string_view NAME = "angular scan"; //!< Name used for logging /// @brief Helper class to analyse (free) energies class EnergyAnalysis { - double partition_sum = 0; //!< Partition function (per COM separation) - double energy_sum = 0; //!< Thermal energy sum for each COM separation run + double partition_sum = 0.0; //!< Partition function (per COM separation) + double energy_sum = 0.0; //!< Thermal energy sum for each COM separation run Average mean_exp_energy; //!< Free energy public: - void clear(); //!< Zeros all data - void add(double energy); //!< Add energy (in kT) - double getFreeEnergy() const; //!< w = -kT ln < exp(-energy/kT) > (in kT) - double getMeanEnergy() const; //!< = βˆ‘ u * exp(-energy/kT) / Q (in kT) - void printLog() const; //!< Print to global logger + void clear(); //!< Zeros all data + void add(double energy); //!< Add energy (in kT) + [[nodiscard]] double getFreeEnergy() const; //!< w = -kT ln < exp(-energy/kT) > (in kT) + [[nodiscard]] double getMeanEnergy() const; //!< = βˆ‘ u * exp(-energy/kT) / Q (in kT) + void printLog() const; //!< Print to global logger }; /// @brief Helper class for store information about each of the two rigid bodies From 4abb33018db790da7505cf55d5f828ae95e505fc Mon Sep 17 00:00:00 2001 From: mikael Date: Wed, 3 Jul 2024 11:11:18 +0200 Subject: [PATCH 09/12] More meaningful variable names in units --- src/units.h | 122 ++++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/src/units.h b/src/units.h index 5c3a61f0c..37d0475a3 100644 --- a/src/units.h +++ b/src/units.h @@ -72,170 +72,170 @@ namespace ChemistryUnits { using T = double; //!< floating point size -constexpr T operator"" _rad(long double a) +constexpr T operator"" _rad(long double radians) { - return a; + return radians; } //!< angle in radians -constexpr T operator"" _deg(long double a) +constexpr T operator"" _deg(long double degrees) { - return a * pc::pi / 180; + return degrees * pc::pi / 180.0; } //!< angle in degrees (to radians) -constexpr T operator"" _K(long double temp) +constexpr T operator"" _K(long double kelvin) { - return temp; + return T(kelvin); } //!< temperature in kelvins -constexpr T operator"" _C(long double temp) +constexpr T operator"" _C(long double celsius) { - return 273.15 + temp; -} //!< temperature in degrees Celcius (to kelvins) + return T(celsius) + 273.15; +} //!< temperature in degrees Celsius (to kelvins) -constexpr T operator"" _ps(long double tau) +constexpr T operator"" _ps(long double picoseconds) { - return tau; + return picoseconds; } //!< time in picoseconds -constexpr T operator"" _s(long double tau) +constexpr T operator"" _s(long double secs) { - return tau * 1e12; + return T(secs) * 1e12; } //!< time in seconds (to picoseconds) -constexpr T operator"" _angstrom(long double l) +constexpr T operator"" _angstrom(long double angstroms) { - return l; + return angstroms; } //!< length in Γ₯ngstrΓΆms -constexpr T operator"" _nm(long double l) +constexpr T operator"" _nm(long double nanometers) { - return l * 10; + return T(nanometers * 10.0); } //!< length in nanometers (to Γ₯ngstrΓΆms) -constexpr T operator"" _m(long double l) +constexpr T operator"" _m(long double meters) { - return l * 1e10; + return T(meters * 1e10); } //!< length in meters (to Γ₯ngstrΓΆms) -constexpr T operator"" _bohr(long double l) +constexpr T operator"" _bohr(long double bohrs) { - return l * 0.52917721092; + return T(bohrs * 0.52917721092); } //!< length in Bohr radii (to Γ₯ngstrΓΆms) -constexpr T operator"" _angstrom3(long double l) +constexpr T operator"" _angstrom3(long double cubic_angstroms) { - return l; + return T(cubic_angstroms); } //!< volume in cubic Γ₯ngstrΓΆms -constexpr T operator"" _m3(long double v) +constexpr T operator"" _m3(long double cubic_meters) { - return v * 1e30; + return T(cubic_meters * 1e30); } //!< volume in cubic meters (to cubic Γ₯ngstrΓΆms) -constexpr T operator"" _liter(long double v) +constexpr T operator"" _liter(long double liters) { - return v * 1e27; + return T(liters * 1e27); } //!< volume in liters (to cubic Γ₯ngstrΓΆms) -constexpr T operator"" _gmol(long double m) +constexpr T operator"" _gmol(long double grams_per_mole) { - return m; + return grams_per_mole; } //!< mass in grams per mole -constexpr T operator"" _kg(long double m) +constexpr T operator"" _kg(long double kilograms) { - return m * 1e3 * pc::avogadro; + return T(kilograms) * 1e3 * pc::avogadro; } //!< mass in kilograms per particle (to grams per mole) -constexpr T operator"" _Da(long double m) +constexpr T operator"" _Da(long double daltons) { - return m; + return T(daltons); } //!< mass in daltons per particle (to grams per mole) //! amount of substance in moles (to number of particles) -constexpr T operator"" _mol(long double n) +constexpr T operator"" _mol(long double moles) { - return n * pc::avogadro; + return T(moles) * pc::avogadro; } //! molar concentration (to particle density in number of particles per cubic Γ₯ngstrΓΆm) -constexpr T operator"" _molar(long double c) +constexpr T operator"" _molar(long double molarity) { - return c * 1.0_mol / 1.0_liter; + return T(molarity) * 1.0_mol / 1.0_liter; } //! millimolar concentration (to particle density in number of particles per cubic Γ₯ngstrΓΆm) -constexpr T operator"" _millimolar(long double c) +constexpr T operator"" _millimolar(long double millimolar) { - return c * 1.0e-3_mol / 1.0_liter; + return T(millimolar) * 1.0e-3_mol / 1.0_liter; } //! pressure in pascals (to particle density in number of particles per cubic Γ₯ngstrΓΆm – assuming //! the ideal gas law) -inline T operator"" _Pa(long double p) +inline T operator"" _Pa(long double pascals) { - return p / pc::kT() / 1.0_m3; + return T(pascals) / pc::kT() / 1.0_m3; } //! pressure in atmospheres (to particle density in number of particles per cubic Γ₯ngstrΓΆm – //! assuming the ideal gas law) -inline T operator"" _atm(long double p) +inline T operator"" _atm(long double atmospheres) { - return p * 101325.0_Pa; + return T(atmospheres) * 101325.0_Pa; } //! pressure in bars (to particles per cubic Γ₯ngstrΓΆm – assuming the ideal gas law) -inline T operator"" _bar(long double p) +inline T operator"" _bar(long double bars) { - return p * 100000.0_Pa; + return T(bars) * 100000.0_Pa; } //! dipole moment in electronβ€”Γ₯ngstrΓΆms -constexpr T operator"" _eA(long double mu) +constexpr T operator"" _eA(long double electron_angstroms) { - return mu; + return T(electron_angstroms); } //! dipole moment in debyes (to electron-Γ₯ngstrΓΆms) -constexpr T operator"" _debye(long double mu) +constexpr T operator"" _debye(long double debyes) { - return mu * 0.208194334424626; + return T(debyes) * 0.208194334424626; } //! dipole moment in coulomb-meters (to electron Γ₯ngstrΓΆms) -constexpr T operator"" _Cm(long double mu) +constexpr T operator"" _Cm(long double coulomb_meters) { - return mu * 1.0_debye / 3.335640951981520e-30; + return T(coulomb_meters) * 1.0_debye / 3.335640951981520e-30; } //! energy in kT (to kT) -constexpr T operator"" _kT(long double u) +constexpr T operator"" _kT(long double kT) { - return u; + return T(kT); } //! energy in joules (to thermal energy units kT) -inline T operator"" _J(long double u) +inline T operator"" _J(long double joules) { - return u / pc::kT(); + return T(joules) / pc::kT(); } //! energy in hartrees (to kT) -inline T operator"" _hartree(long double u) +inline T operator"" _hartree(long double hartrees) { - return u * 4.35974434e-18_J; + return T(hartrees) * 4.35974434e-18_J; } //! energy in kilojoules per mole (to kT per particle) -inline T operator"" _kJmol(long double u) +inline T operator"" _kJmol(long double kJ_per_mole) { - return u / pc::kT() / pc::avogadro * 1e3; + return T(kJ_per_mole) / pc::kT() / pc::avogadro * 1e3; } //! energy in kilocalories per mole (to kT per particle) -inline T operator"" _kcalmol(long double u) +inline T operator"" _kcalmol(long double kcal_per_mole) { - return u * 4.1868_kJmol; + return T(kcal_per_mole) * 4.1868_kJmol; } } // namespace ChemistryUnits From 235b0c935bad9ea5839160f6006280fe01d8c1a8 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Wed, 3 Jul 2024 13:50:07 +0200 Subject: [PATCH 10/12] Use std::ranges --- src/actions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index f2e1c9ebc..868b6a41d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -4,6 +4,7 @@ #include "io.h" #include "aux/arange.h" #include +#include #include namespace Faunus { @@ -49,7 +50,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) */ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int molecule_index) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; index = molecule_index; const auto& group = groups.at(index); if (group.isAtomic()) { @@ -66,7 +67,7 @@ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int mol ParticleVector AngularScan::Molecule::getRotatedReference(const Space::GroupVector& groups, const Eigen::Quaterniond& q) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; const auto& group = groups.at(index); auto particles = ParticleVector(group.begin(), group.end()); // copy particles from Space auto positions = @@ -95,7 +96,7 @@ void AngularScan::report(const Group& group1, const Group& group2, const Eigen:: << fmt::format("{:8.4f} {:>10.3E}\n", group2.mass_center.z(), energy / 1.0_kJmol); if (trajectory) { auto positions = ranges::views::concat(group1, group2) | - ranges::cpp20::views::transform(&Particle::pos); + std::views::transform(&Particle::pos); trajectory->writeNext({500, 500, 500}, positions.begin(), positions.end()); } } @@ -127,7 +128,7 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) const auto q2 = q_dihedral * q_body2; // simultaneous rotations (non-commutative) auto particles2 = molecules.second.getRotatedReference(spc.groups, q2); - ranges::cpp20::for_each(particles2, translate); + std::ranges::for_each(particles2, translate); auto group2 = Group(0, particles2.begin(), particles2.end()); group2.mass_center = {0.0, 0.0, z_pos}; report(group1, group2, q1, q2, *nonbonded); From 15c1f28693e6099caf384912b938528d42cdc756 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Thu, 4 Jul 2024 13:18:21 +0200 Subject: [PATCH 11/12] Partial migration from range-v3 to `std::ranges` (#452) --- .idea/inspectionProfiles/Project_Default.xml | 10 -- src/actions.cpp | 11 +- src/analysis.cpp | 101 ++++++++++--------- src/atomdata.h | 11 +- src/aux/arange.h | 17 ++-- src/auxiliary.h | 8 +- src/celllistimpl.h | 8 +- src/clustermove.cpp | 24 ++--- src/clustermove.h | 2 +- src/core.cpp | 7 +- src/core.h | 3 +- src/energy.cpp | 63 ++++++------ src/energy.h | 26 +++-- src/geometry.cpp | 2 +- src/geometry.h | 27 +++-- src/group.h | 32 +++--- src/io.h | 11 +- src/molecule.cpp | 14 ++- src/montecarlo.cpp | 6 +- src/move.cpp | 34 ++++--- src/move.h | 12 +-- src/mpicontroller.cpp | 15 ++- src/mpicontroller.h | 14 +-- src/particle.h | 4 +- src/random.h | 6 +- src/reactioncoordinate.cpp | 36 ++++--- src/regions.cpp | 5 +- src/regions.h | 5 +- src/sasa.cpp | 23 ++--- src/scatter.h | 3 +- src/smart_montecarlo.h | 20 ++-- src/space.cpp | 18 ++-- src/space.h | 21 ++-- src/speciation.cpp | 61 ++++++----- src/voronota.cpp | 2 +- 35 files changed, 318 insertions(+), 344 deletions(-) delete mode 100644 .idea/inspectionProfiles/Project_Default.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 146ab09b7..000000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/actions.cpp b/src/actions.cpp index f2e1c9ebc..8e1939622 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -4,6 +4,7 @@ #include "io.h" #include "aux/arange.h" #include +#include #include namespace Faunus { @@ -49,7 +50,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) */ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int molecule_index) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; index = molecule_index; const auto& group = groups.at(index); if (group.isAtomic()) { @@ -66,7 +67,7 @@ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int mol ParticleVector AngularScan::Molecule::getRotatedReference(const Space::GroupVector& groups, const Eigen::Quaterniond& q) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; const auto& group = groups.at(index); auto particles = ParticleVector(group.begin(), group.end()); // copy particles from Space auto positions = @@ -94,8 +95,8 @@ void AngularScan::report(const Group& group1, const Group& group2, const Eigen:: *stream << format(q1) << format(q2) << fmt::format("{:8.4f} {:>10.3E}\n", group2.mass_center.z(), energy / 1.0_kJmol); if (trajectory) { - auto positions = ranges::views::concat(group1, group2) | - ranges::cpp20::views::transform(&Particle::pos); + auto positions = + ranges::views::concat(group1, group2) | std::views::transform(&Particle::pos); trajectory->writeNext({500, 500, 500}, positions.begin(), positions.end()); } } @@ -127,7 +128,7 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) const auto q2 = q_dihedral * q_body2; // simultaneous rotations (non-commutative) auto particles2 = molecules.second.getRotatedReference(spc.groups, q2); - ranges::cpp20::for_each(particles2, translate); + std::ranges::for_each(particles2, translate); auto group2 = Group(0, particles2.begin(), particles2.end()); group2.mass_center = {0.0, 0.0, z_pos}; report(group1, group2, q1, q2, *nonbonded); diff --git a/src/analysis.cpp b/src/analysis.cpp index bbe2b9044..f21647636 100644 --- a/src/analysis.cpp +++ b/src/analysis.cpp @@ -385,7 +385,7 @@ void SystemEnergy::createOutputStream() *output_stream << "#"; } *output_stream << fmt::format("{:>9}{}{:12}", "step", separator, "total"); - ranges::for_each(hamiltonian, [&](auto& energy) { + std::ranges::for_each(hamiltonian, [&](auto& energy) { *output_stream << fmt::format("{}{:12}", separator, energy->name); }); *output_stream << "\n"; @@ -405,7 +405,7 @@ std::vector SystemEnergy::calculateEnergies() const { Change change; change.everything = true; // trigger full energy calculation - return hamiltonian | ranges::views::transform([&](auto& i) { return i->energy(change); }) | + return hamiltonian | std::views::transform([&](auto& i) { return i->energy(change); }) | ranges::to_vector; } @@ -540,7 +540,7 @@ GroupMatrixAnalysis::GroupMatrixAnalysis(const json& j, const Space& spc, Energy::Hamiltonian& hamiltonian) : PairMatrixAnalysis(j, spc) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; // finds the molecule ids and store their indices auto molids = Faunus::parseMolecules(j.at("molecules")); auto is_selected = [&](const auto& group) { @@ -562,8 +562,7 @@ GroupMatrixAnalysis::GroupMatrixAnalysis(const json& j, const Space& spc, void GroupMatrixAnalysis::setPairMatrix() { auto is_active = [&](auto index) { return !spc.groups.at(index).empty(); }; - const auto indices = - group_indices | ranges::cpp20::views::filter(is_active) | ranges::to_vector; + const auto indices = group_indices | std::views::filter(is_active) | ranges::to_vector; // zero matrix, then fill it pair_matrix.setZero(); @@ -1090,7 +1089,7 @@ AtomicDisplacement::AtomicDisplacement(const json& j, const Space& spc, std::str molid = molecule_data.id(); auto positions = getPositions(); - const auto num_positions = ranges::cpp20::distance(positions.begin(), positions.end()); + const auto num_positions = std::ranges::distance(positions.begin(), positions.end()); reference_positions = positions | ranges::to_vector; cell_indices.resize(num_positions, {0, 0, 0}); mean_squared_displacement.resize(num_positions); @@ -1191,7 +1190,7 @@ void AtomicDisplacement::_to_disk() PointVector AtomicDisplacement::getPositions() const { auto active_and_inactive = [&](const Group& group) { - return ranges::make_subrange(group.begin(), group.trueend()); + return std::ranges::subrange(group.begin(), group.trueend()); }; namespace rv = ranges::cpp20::views; return spc.findMolecules(molid, Space::Selection::ALL) | rv::transform(active_and_inactive) | @@ -1211,7 +1210,7 @@ MassCenterDisplacement::MassCenterDisplacement(const json& j, const Space& spc, PointVector MassCenterDisplacement::getPositions() const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; return spc.findMolecules(molid, Space::Selection::ACTIVE) | rv::transform(&Group::mass_center) | ranges::to; } @@ -1227,7 +1226,7 @@ void WidomInsertion::selectGhostGroup() { change.clear(); auto inactive_groups = spc.findMolecules(molid, Space::Selection::INACTIVE); - if (!ranges::cpp20::empty(inactive_groups)) { + if (!std::ranges::empty(inactive_groups)) { const auto& group = *inactive_groups.begin(); // select first group if (group.empty() && group.capacity() > 0) { // must be inactive and have a non-zero capacity @@ -1384,7 +1383,7 @@ void AtomDensity::_sample() const auto& atomic_ids = reaction.participatingAtomsAndMolecules().first; unique_reactive_atoms.insert(atomic_ids.begin(), atomic_ids.end()); } - ranges::cpp20::for_each(unique_reactive_atoms, [&](auto id) { + std::ranges::for_each(unique_reactive_atoms, [&](auto id) { const auto count = spc.countAtoms(id); atomswap_probability_density[id](count)++; }); @@ -1395,21 +1394,23 @@ void AtomDensity::_sample() */ std::map AtomDensity::count() const { - namespace rv = ranges::cpp20::views; + using ranges::cpp20::views::filter; + using ranges::cpp20::views::join; + using ranges::cpp20::views::transform; // All ids incl. inactive are counted; std::vector ensures constant lookup (index = id) std::vector atom_count(names.size(), 0); // Count number of active atoms in atomic groups auto particle_ids_in_atomic_groups = - spc.groups | rv::filter(&Group::isAtomic) | rv::join | rv::transform(&Particle::id); - ranges::cpp20::for_each(particle_ids_in_atomic_groups, [&](auto id) { atom_count.at(id)++; }); + spc.groups | filter(&Group::isAtomic) | join | transform(&Particle::id); + std::ranges::for_each(particle_ids_in_atomic_groups, [&](auto id) { atom_count.at(id)++; }); // Copy vector --> map id_type id = 0U; std::map map; - ranges::cpp20::for_each(atom_count, - [&id, &map](auto count) { map.emplace_hint(map.end(), id++, count); }); + std::ranges::for_each(atom_count, + [&id, &map](auto count) { map.emplace_hint(map.end(), id++, count); }); return map; } @@ -1442,20 +1443,20 @@ void AtomDensity::_to_disk() */ std::map MoleculeDensity::count() const { - using namespace ranges::cpp20; + using std::views::transform; + using std::views::filter; std::map molecular_group_count; // ensure that also inactive groups are registered (as zero) - for_each(Faunus::molecules | views::filter(&MoleculeData::isMolecular), - [&](auto& moldata) { molecular_group_count[moldata.id()] = 0; }); + std::ranges::for_each(Faunus::molecules | filter(&MoleculeData::isMolecular), + [&](auto& moldata) { molecular_group_count[moldata.id()] = 0; }); auto non_empty_molecular = [](const Group& group) { return group.isMolecular() && !group.empty(); }; - auto molecular_group_ids = - spc.groups | views::filter(non_empty_molecular) | views::transform(&Group::id); + auto molecular_group_ids = spc.groups | filter(non_empty_molecular) | transform(&Group::id); - for_each(molecular_group_ids, [&](auto id) { molecular_group_count[id]++; }); + std::ranges::for_each(molecular_group_ids, [&](auto id) { molecular_group_count[id]++; }); return molecular_group_count; } @@ -1506,7 +1507,7 @@ void SanityCheck::checkGroupsCoverParticles() void SanityCheck::checkWithinContainer(const Space::GroupType& group) { bool outside_simulation_cell = false; - auto outside_particles = group | ranges::cpp20::views::filter([&](const Particle& particle) { + auto outside_particles = group | std::views::filter([&](const Particle& particle) { return spc.geometry.collision(particle.pos); }); @@ -1703,15 +1704,19 @@ XTCtraj::XTCtraj(const Space& spc, const std::string& filename, const std::vector& molecule_names) : Analysis(spc, "xtcfile") { - namespace rv = ranges::cpp20::views; + using ranges::cpp20::views::filter; + using ranges::cpp20::views::join; + using ranges::cpp20::views::transform; + using ranges::views::cache1; + writer = std::make_unique(filename); if (!molecule_names.empty()) { group_ids = Faunus::names2ids(Faunus::molecules, molecule_names); // molecule types to save group_indices = group_ids | - rv::transform([&](auto id) { return spc.findMolecules(id, Space::Selection::ALL); }) | - ranges::views::cache1 | rv::join | - rv::transform([&](const Group& group) { return spc.getGroupIndex(group); }) | + transform([&](auto id) { return spc.findMolecules(id, Space::Selection::ALL); }) | + cache1 | join | + transform([&](const Group& group) { return spc.getGroupIndex(group); }) | ranges::to_vector; if (group_indices.empty()) { throw ConfigurationError("xtc selection is empty - nothing to sample"); @@ -1732,7 +1737,7 @@ void XTCtraj::_to_json(json& j) const if (!group_ids.empty()) { j["molecules"] = group_ids | - ranges::cpp20::views::transform([](auto id) { return Faunus::molecules.at(id).name; }) | + std::views::transform([](auto id) { return Faunus::molecules.at(id).name; }) | ranges::to_vector; } } @@ -1744,7 +1749,7 @@ void XTCtraj::_sample() { namespace rv = ranges::cpp20::views; if (group_ids.empty()) { - auto positions = spc.particles | rv::transform(&Particle::pos); + auto positions = spc.particles | std::views::transform(&Particle::pos); writer->writeNext(spc.geometry.getLength(), positions.begin(), positions.end()); } else { @@ -1895,7 +1900,7 @@ void InertiaTensor::_to_json(json& j) const std::pair InertiaTensor::compute() const { const auto& group = spc.groups.at(group_index); - auto subgroup = ranges::make_subrange(group.begin() + particle_range.at(0), + auto subgroup = std::ranges::subrange(group.begin() + particle_range.at(0), group.begin() + particle_range.at(1) + 1); auto I = Geometry::inertia(subgroup.begin(), subgroup.end(), group.mass_center, @@ -2323,11 +2328,11 @@ void AtomsInMoleculePolicy::sample(const Space& spc, SASAAnalysis& analysis) { auto molecules = spc.findMolecules(molecule_id); - ranges::for_each(molecules, [&](const auto& molecule) { + std::ranges::for_each(molecules, [&](const auto& molecule) { auto is_selected = [&](const Particle& particle) { return selected_indices.count(molecule.getParticleIndex(particle)) > 0; }; - auto selected_atoms = molecule | ranges::cpp20::views::filter(is_selected); + auto selected_atoms = molecule | std::views::filter(is_selected); sampleTotalSASA(selected_atoms.begin(), selected_atoms.end(), analysis); }); } @@ -2396,7 +2401,7 @@ void AtomProfile::_sample() } auto selected_particles = - spc.activeParticles() | ranges::cpp20::views::filter([&](const Particle& particle) { + spc.activeParticles() | std::views::filter([&](const Particle& particle) { return atom_id_selection.count(particle.id) > 0; }); @@ -2485,7 +2490,7 @@ void SlicedDensity::_sample() } auto filtered_particles = - spc.activeParticles() | ranges::cpp20::views::filter([&](const auto& particle) { + spc.activeParticles() | std::views::filter([&](const auto& particle) { return std::find(atom_ids.begin(), atom_ids.end(), particle.id) != atom_ids.end(); }); @@ -2553,29 +2558,28 @@ std::vector ChargeFluctuations::getPredominantParticleNames() const return Faunus::atoms[atomid].name; }; // in a histogram, find the atom name with most counts - auto atom_names = atom_histograms | ranges::cpp20::views::transform(most_frequent_name); - return std::vector(ranges::cpp20::begin(atom_names), - ranges::cpp20::end(atom_names)); + auto atom_names = atom_histograms | std::views::transform(most_frequent_name); + return std::vector(std::ranges::begin(atom_names), std::ranges::end(atom_names)); } std::vector ChargeFluctuations::getChargeStandardDeviation() const { - auto stdev = - atom_mean_charges | ranges::cpp20::views::transform([](auto& i) { return i.stdev(); }); - return std::vector(ranges::cpp20::begin(stdev), ranges::cpp20::end(stdev)); + using namespace std::ranges; + auto stdev = atom_mean_charges | views::transform([](auto& i) { return i.stdev(); }); + return std::vector(begin(stdev), end(stdev)); } std::vector ChargeFluctuations::getMeanCharges() const { - return std::vector(ranges::cpp20::begin(atom_mean_charges), - ranges::cpp20::end(atom_mean_charges)); + return std::vector(std::ranges::begin(atom_mean_charges), + std::ranges::end(atom_mean_charges)); } void ChargeFluctuations::_to_disk() { if (not filename.empty()) { auto molecules = spc.findMolecules(mol_iter->id(), Space::Selection::ALL); - if (not ranges::cpp20::empty(molecules)) { + if (not std::ranges::empty(molecules)) { const auto particles_with_avg_charges = averageChargeParticles(*molecules.begin()); PQRWriter().save(MPI::prefix + filename, particles_with_avg_charges.begin(), particles_with_avg_charges.end(), spc.geometry.getLength()); @@ -2667,7 +2671,8 @@ void ScatteringFunction::_sample() scatter_positions.clear(); auto groups = molecule_ids | rv::transform([&](auto id) { return spc.findMolecules(id); }) | rv::join; - ranges::cpp20::for_each(groups, [&](auto& group) { + + std::ranges::for_each(groups, [&](auto& group) { if (mass_center_scattering && group.isMolecular()) { scatter_positions.push_back(group.mass_center); } @@ -2809,8 +2814,8 @@ void VirtualTranslate::_sample() return; } if (auto mollist = mutable_space.findMolecules(molid, Space::Selection::ACTIVE); - !ranges::cpp20::empty(mollist)) { - if (ranges::distance(mollist.begin(), mollist.end()) > 1) { + !std::ranges::empty(mollist)) { + if (std::ranges::distance(mollist.begin(), mollist.end()) > 1) { throw std::runtime_error("exactly ONE active molecule expected"); } if (auto group_it = random.sample(mollist.begin(), mollist.end()); not group_it->empty()) { @@ -3004,8 +3009,7 @@ void ElectricPotential::getTargets(const json& j) if (structure->is_string()) { // load positions from chemical structure file auto particles = loadStructure(structure->get(), false); - positions = - particles | ranges::cpp20::views::transform(&Particle::pos) | ranges::to_vector; + positions = particles | std::views::transform(&Particle::pos) | ranges::to_vector; } else if (structure->is_array()) { // list of positions @@ -3049,8 +3053,7 @@ double ElectricPotential::calcPotentialOnTarget(const ElectricPotential::Target& std::sqrt(spc.geometry.sqdist(particle.pos, target.position)); return coulomb->getCoulombGalore().ion_potential(particle.charge, distance_to_target); }; - auto potentials = - spc.activeParticles() | ranges::cpp20::views::transform(potential_from_particle); + auto potentials = spc.activeParticles() | std::views::transform(potential_from_particle); return std::accumulate(potentials.begin(), potentials.end(), 0.0); } diff --git a/src/atomdata.h b/src/atomdata.h index 63159143c..543833f7e 100644 --- a/src/atomdata.h +++ b/src/atomdata.h @@ -1,9 +1,8 @@ #pragma once #include "core.h" #include -#include -#include -#include +#include +#include #include namespace Faunus { @@ -121,7 +120,7 @@ template concept RequireNamedElements = requires(T db) { { db.begin() }; { db.begin()->name } -> std::convertible_to; - { std::is_integral_v::index_type> }; + { std::is_integral_v::index_type> }; }; /** @@ -178,10 +177,10 @@ AtomData& findAtomByName(std::string_view name); template auto names2ids(const T& database, const std::vector& names) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto is_wildcard = [](auto& name) { return name == "*"; }; - if (ranges::cpp20::any_of(names, is_wildcard)) { // return all id's from database + if (std::ranges::any_of(names, is_wildcard)) { // return all id's from database return database | rv::transform([](auto& i) { return i.id(); }) | ranges::to_vector; } diff --git a/src/aux/arange.h b/src/aux/arange.h index 10ea398f3..8bfbbb7ac 100644 --- a/src/aux/arange.h +++ b/src/aux/arange.h @@ -21,8 +21,7 @@ OTHER DEALINGS IN THE SOFTWARE. #pragma once #include #include -#include -#include +#include #include namespace Faunus { @@ -54,8 +53,8 @@ template constexpr auto arange(const T start, const T stop, const T using int_type = typename std::conditional, T, int>::type; const auto length = static_cast(std::ceil((stop - start) / static_cast(step))); - return ranges::cpp20::views::iota(int_type(0), length) | - ranges::cpp20::views::transform( + return std::views::iota(int_type(0), length) | + std::views::transform( [start, step](auto i) -> T { return start + static_cast(i) * step; }); } @@ -65,7 +64,7 @@ TEST_CASE("[Faunus] arange") { auto r = arange(4.0, 10.0, 1.0); // --> 4 5 6 7 8 9 auto pos = r.begin(); - CHECK_EQ(ranges::size(r), 6); + CHECK_EQ(std::ranges::size(r), 6); CHECK_EQ(*(pos++), doctest::Approx(4.0)); CHECK_EQ(*(pos++), doctest::Approx(5.0)); CHECK_EQ(*(pos++), doctest::Approx(6.0)); @@ -77,7 +76,7 @@ TEST_CASE("[Faunus] arange") { auto r = arange(4, 10, 1); // --> 4 5 6 7 8 9 auto pos = r.begin(); - CHECK_EQ(ranges::size(r), 6); + CHECK_EQ(std::ranges::size(r), 6); CHECK_EQ(*(pos++), 4); CHECK_EQ(*(pos++), 5); CHECK_EQ(*(pos++), 6); @@ -89,7 +88,7 @@ TEST_CASE("[Faunus] arange") { auto r = arange(4.0, 20.0, 3.0); // --> 4 7 10 13 16 19 auto pos = r.begin(); - CHECK_EQ(ranges::size(r), 6); + CHECK_EQ(std::ranges::size(r), 6); CHECK_EQ(*(pos++), doctest::Approx(4.0)); CHECK_EQ(*(pos++), doctest::Approx(7.0)); CHECK_EQ(*(pos++), doctest::Approx(10.0)); @@ -101,7 +100,7 @@ TEST_CASE("[Faunus] arange") { auto r = arange(4, 20, 3); // --> 4 7 10 13 16 19 auto pos = r.begin(); - CHECK_EQ(ranges::size(r), 6); + CHECK_EQ(std::ranges::size(r), 6); CHECK_EQ(*(pos++), 4); CHECK_EQ(*(pos++), 7); CHECK_EQ(*(pos++), 10); @@ -114,7 +113,7 @@ TEST_CASE("[Faunus] arange") { auto r = arange(-1.0, 1.0, 0.5); // --> -1 -0.5 0 0.5 auto pos = r.begin(); - CHECK_EQ(ranges::size(r), 4); + CHECK_EQ(std::ranges::size(r), 4); CHECK_EQ(*(pos++), doctest::Approx(-1.0)); CHECK_EQ(*(pos++), doctest::Approx(-0.5)); CHECK_EQ(*(pos++), doctest::Approx(0.0)); diff --git a/src/auxiliary.h b/src/auxiliary.h index 1c3d3fe99..513e495ff 100644 --- a/src/auxiliary.h +++ b/src/auxiliary.h @@ -1,7 +1,7 @@ #pragma once #include "average.h" #include -#include +#include #include #include #include @@ -66,7 +66,7 @@ T for_each_unique_pair(Titer begin, Titer end, Tfunction f, } /** @brief Erase from `target` range all values found in `values` range */ -template T erase_range(T target, const T& values) +template T erase_range(T target, const T& values) { target.erase(std::remove_if(target.begin(), target.end(), [&](auto i) { @@ -222,9 +222,9 @@ template auto splitConvert(const std::string& words) * @param values Range (vector, set, ...) of values to convert * @return String with space sepatated values */ -template +template std::string joinToString(const Range& values) - requires StringStreamable> + requires StringStreamable> { std::ostringstream o; if (!values.empty()) { diff --git a/src/celllistimpl.h b/src/celllistimpl.h index 7182ba004..54e437d48 100644 --- a/src/celllistimpl.h +++ b/src/celllistimpl.h @@ -15,8 +15,7 @@ #include #include #include -#include -#include +#include #include "celllist.h" #include "core.h" #include @@ -682,9 +681,8 @@ class CellListBase std::vector getCells() const override { const auto indices = this->indices(); - return ranges::cpp20::views::all(indices) | - ranges::cpp20::views::transform( - [this](auto index) { return this->coordinates(index); }) | + return std::views::all(indices) | + std::views::transform([this](auto index) { return this->coordinates(index); }) | ranges::to_vector; } diff --git a/src/clustermove.cpp b/src/clustermove.cpp index eb21adafa..b338261e8 100644 --- a/src/clustermove.cpp +++ b/src/clustermove.cpp @@ -1,8 +1,8 @@ #include "clustermove.h" #include "aux/eigensupport.h" +#include #include #include -#include namespace Faunus { namespace move { @@ -96,7 +96,7 @@ double FindCluster::clusterProbability(const Group& group1, const Group& group2) */ void FindCluster::updateMoleculeIndex() { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto group_to_index = [&](auto& group) { return &group - &spc.groups.front(); }; auto matching_molid = [&](auto& group) { if (group.isAtomic() || group.end() != group.trueend()) { @@ -174,8 +174,8 @@ std::optional FindCluster::findSeed(Random& random) auto avoid_satellites = [&](auto index) { return (satellites.count(spc.groups.at(index).id) == 0); }; - auto not_satellites = molecule_index | ranges::cpp20::views::filter(avoid_satellites); - if (ranges::cpp20::empty(not_satellites)) { + auto not_satellites = molecule_index | std::views::filter(avoid_satellites); + if (std::ranges::empty(not_satellites)) { return std::nullopt; } return *random.sample(not_satellites.begin(), not_satellites.end()); @@ -190,7 +190,7 @@ std::optional FindCluster::findSeed(Random& random) */ std::pair, bool> FindCluster::findCluster(size_t seed_index) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; assert(seed_index < spc.particles.size()); std::set pool(molecule_index.begin(), molecule_index.end()); // decreasing pool of candidate groups @@ -301,12 +301,12 @@ void Cluster::_from_json(const json& j) Point Cluster::clusterMassCenter(const std::vector& indices) const { assert(!indices.empty()); - namespace rv = ranges::cpp20::views; - auto groups = indices | rv::transform(index_to_group); - auto positions = groups | rv::transform(&Group::mass_center); - auto masses = groups | rv::transform(&Group::mass); + using std::views::transform; + auto groups = indices | transform(index_to_group); + auto positions = groups | transform(&Group::mass_center); + auto masses = groups | transform(&Group::mass); return Geometry::weightedCenter(positions, masses, spc.geometry.getBoundaryFunc(), - -groups.begin()->mass_center); + -((*groups.begin()).mass_center)); } void Cluster::_move(Change& change) @@ -318,14 +318,14 @@ void Cluster::_move(Change& change) } const auto [cluster, safe_to_rotate] = find_cluster->findCluster(seed_index.value()); const Point cluster_mass_center = clusterMassCenter(cluster); // cluster mass center - auto groups = cluster | ranges::cpp20::views::transform(index_to_group); + auto groups = cluster | std::views::transform(index_to_group); average_cluster_size += cluster.size(); // average cluster size if (number_of_attempted_moves % shape_analysis_interval == 0) { shape_analysis->sample(groups, cluster_mass_center, spc); } - using ranges::cpp20::for_each; + using std::ranges::for_each; auto boundary = spc.geometry.getBoundaryFunc(); if (safe_to_rotate) { for_each(groups, rotate->getLambda(boundary, cluster_mass_center, slump)); diff --git a/src/clustermove.h b/src/clustermove.h index 5d7f60e35..524fb061d 100644 --- a/src/clustermove.h +++ b/src/clustermove.h @@ -30,7 +30,7 @@ class ClusterShapeAnalysis const Range& groups, const Point& mass_center_of_groups, const Geometry::BoundaryFunction boundary = [](auto&) {}) { - using namespace ranges::cpp20::views; + using std::views::transform; auto positions = groups | transform(&Group::mass_center); auto masses = groups | transform(&Group::mass); return Geometry::gyration(positions.begin(), positions.end(), masses.begin(), diff --git a/src/core.cpp b/src/core.cpp index cb386302f..2140a1efa 100644 --- a/src/core.cpp +++ b/src/core.cpp @@ -10,8 +10,7 @@ #include #include #include -#include -#include +#include #include namespace Faunus { @@ -362,7 +361,7 @@ TEST_CASE("[Faunus] infinite/nan") TEST_CASE("[Faunus] distance") { std::vector v = {10, 20, 30, 40, 30}; - auto rng = v | ranges::cpp20::views::filter([](auto i) { return i == 30; }); + auto rng = v | std::views::filter([](auto i) { return i == 30; }); CHECK_EQ(Faunus::distance(v.begin(), rng.begin()), 2); auto it = rng.begin(); CHECK_EQ(Faunus::distance(v.begin(), ++it), 4); @@ -406,7 +405,7 @@ Electrolyte::Electrolyte(const double molarity, const std::vector& valencie : molarity(molarity) , valencies(valencies) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; const auto sum_positive = ranges::accumulate(valencies | rv::filter([](auto v) { return v > 0; }), 0); const auto sum_negative = diff --git a/src/core.h b/src/core.h index cdb664c11..4ba14f4ab 100644 --- a/src/core.h +++ b/src/core.h @@ -6,6 +6,7 @@ #include #include #include +#include // forward declare logger namespace spdlog { @@ -25,7 +26,7 @@ class Random; /** Concept for a range of points */ template concept RequirePoints = - ranges::cpp20::range && std::is_same_v, Point>; + std::ranges::range && std::is_same_v, Point>; /** Concept for an iterator to a `Point` */ template diff --git a/src/energy.cpp b/src/energy.cpp index 8c5423311..1eb21c704 100644 --- a/src/energy.cpp +++ b/src/energy.cpp @@ -4,8 +4,6 @@ #include "externalpotential.h" #include #include -#include -#include #include #include #include @@ -229,7 +227,7 @@ void PolicyIonIon::updateComplex(EwaldData& d, const Change& change, auto& g_new = groups.at(changed_group.group_index); auto& g_old = oldgroups.at(changed_group.group_index); const auto max_group_size = std::max(g_new.size(), g_old.size()); - auto indices = (changed_group.all) ? ranges::cpp20::views::iota(0u, max_group_size) | + auto indices = (changed_group.all) ? std::views::iota(0u, max_group_size) | ranges::to> : changed_group.relative_atom_indices; for (auto i : indices) { @@ -466,7 +464,7 @@ void PolicyIonIonIPBC::updateComplex(EwaldData& d, const Change& change, double PolicyIonIon::surfaceEnergy(const EwaldData& data, [[maybe_unused]] const Change& change, const Space::GroupVector& groups) { - namespace rv = ranges::cpp20::views; + using ranges::cpp20::views::join; if (data.const_inf < 0.5 || change.empty()) { return 0.0; } @@ -474,7 +472,7 @@ double PolicyIonIon::surfaceEnergy(const EwaldData& data, [[maybe_unused]] const auto charge_x_position = [](const Particle& particle) -> Point { return particle.charge * particle.pos; }; - auto qr_range = groups | rv::join | rv::transform(charge_x_position); + auto qr_range = groups | join | std::views::transform(charge_x_position); const auto qr_squared = ranges::accumulate(qr_range, Point(0, 0, 0)).squaredNorm(); return data.const_inf * 2.0 * pc::pi / ((2.0 * data.surface_dielectric_constant + 1.0) * volume) * qr_squared * @@ -817,7 +815,7 @@ double ContainerOverlap::energy(const Change& change) double ContainerOverlap::energyOfAllGroups() const { auto positions = - spc.groups | ranges::cpp20::views::join | ranges::cpp20::views::transform(&Particle::pos); + spc.groups | ranges::cpp20::views::join | std::views::transform(&Particle::pos); bool outside = std::any_of(positions.begin(), positions.end(), [&](const auto& position) { return spc.geometry.collision(position); }); @@ -890,8 +888,8 @@ double Isobaric::energy(const Change& change) auto count_particles = [](const auto& group) { return group.isAtomic() ? group.size() : 1; }; - auto particles_per_group = spc.groups | ranges::cpp20::views::filter(group_is_active) | - ranges::cpp20::views::transform(count_particles); + auto particles_per_group = spc.groups | std::views::filter(group_is_active) | + std::views::transform(count_particles); auto number_of_particles = std::accumulate(particles_per_group.begin(), particles_per_group.end(), 0); const auto volume = spc.geometry.getVolume(); @@ -1067,7 +1065,6 @@ double Bonded::energy(const Change& change) double Bonded::internalGroupEnergy(const Change::GroupChange& changed) { - using namespace ranges::cpp20::views; // @todo cpp20 --> std::ranges double energy = 0.0; const auto& group = spc.groups.at(changed.group_index); if (changed.internal && !group.empty()) { @@ -1077,9 +1074,10 @@ double Bonded::internalGroupEnergy(const Change::GroupChange& changed) } else { // only partial update of affected atoms const auto first_particle_index = spc.getFirstParticleIndex(group); - auto particle_indices = - changed.relative_atom_indices | - transform([first_particle_index](auto i) { return i + first_particle_index; }); + auto particle_indices = changed.relative_atom_indices | + std::views::transform([first_particle_index](auto i) { + return i + first_particle_index; + }); energy += sumEnergy(bonds, particle_indices); } } @@ -1215,8 +1213,8 @@ void Hamiltonian::checkBondedMolecules() const if (find() .empty()) { // no bond potential added? issue warning if molecules w. bonds auto molecules_with_bonds = - Faunus::molecules | ranges::cpp20::views::filter( - [](const auto& molecule) { return !molecule.bonds.empty(); }); + Faunus::molecules | + std::views::filter([](const auto& molecule) { return !molecule.bonds.empty(); }); for (const auto& molecule : molecules_with_bonds) { faunus_logger->warn("{} bonds specified in topology but missing in energy", molecule.name); @@ -1463,7 +1461,7 @@ void FreeSASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) const auto offset = spc.getFirstActiveParticleIndex(group); auto absolute_atom_index = group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); + std::views::transform([offset](auto i) { return i + offset; }); for (auto i : absolute_atom_index) { radii.at(i) = other->radii.at(i); for (size_t k = 0; k < 3; ++k) { @@ -1635,14 +1633,13 @@ double SASAEnergyReference::energy(const Change& change) std::vector target_indices; auto to_index = [this](const auto& particle) { return indexOf(particle); }; - target_indices = - particles | ranges::cpp20::views::transform(to_index) | ranges::to; + target_indices = particles | std::views::transform(to_index) | ranges::to; const auto neighbours = sasa->calcNeighbourData(spc, target_indices); sasa->updateSASA(neighbours, target_indices); const auto& new_areas = sasa->getAreas(); - ranges::cpp20::for_each(target_indices, [this, &new_areas](const auto index) { + std::ranges::for_each(target_indices, [this, &new_areas](const auto index) { areas.at(index) = new_areas.at(index); }); @@ -1650,7 +1647,7 @@ double SASAEnergyReference::energy(const Change& change) energy += areas.at(indexOf(particle)) * (particle.traits().tension + cosolute_molarity * particle.traits().tfe); }; - ranges::cpp20::for_each(particles, accumulate_energy); + std::ranges::for_each(particles, accumulate_energy); return energy; } @@ -1700,14 +1697,13 @@ void SASAEnergy::updateChangedIndices(const Change& change) }; if (group_change.relative_atom_indices.empty()) { - const auto indices = ranges::cpp20::views::iota(offset, group.size() + offset); - ranges::cpp20::for_each(indices, insert_changed); + const auto indices = std::views::iota(offset, group.size() + offset); + std::ranges::for_each(indices, insert_changed); } else { - const auto indices = - group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return offset + i; }); - ranges::cpp20::for_each(indices, insert_changed); + const auto indices = group_change.relative_atom_indices | + std::views::transform([offset](auto i) { return offset + i; }); + std::ranges::for_each(indices, insert_changed); } } changed_indices.assign(target_indices.begin(), target_indices.end()); @@ -1742,8 +1738,7 @@ double SASAEnergy::energy(const Change& change) changed_indices.clear(); if (change.everything) { //! all the active particles will be used for SASA calculation auto to_index = [this](const auto& particle) { return indexOf(particle); }; - changed_indices = - particles | ranges::cpp20::views::transform(to_index) | ranges::to; + changed_indices = particles | std::views::transform(to_index) | ranges::to; } else { updateChangedIndices(change); @@ -1765,7 +1760,7 @@ double SASAEnergy::energy(const Change& change) energy += areas.at(indexOf(particle)) * (particle.traits().tension + cosolute_molarity * particle.traits().tfe); }; - ranges::cpp20::for_each(particles, accumulate_energy); + std::ranges::for_each(particles, accumulate_energy); return energy; } @@ -1780,10 +1775,10 @@ void SASAEnergy::sync(EnergyTerm* energybase_ptr, const Change& change) if (state == MonteCarloState::TRIAL) { //! changed_indices get updated only in TRIAL state //! for speedup - ranges::for_each(this->changed_indices, sync_data); + std::ranges::for_each(this->changed_indices, sync_data); } else { - ranges::for_each(other->changed_indices, sync_data); + std::ranges::for_each(other->changed_indices, sync_data); } if (sasa->needs_syncing) { @@ -2092,10 +2087,10 @@ double CustomGroupGroup::energy([[maybe_unused]] const Change& change) }; // all indices matching either molid1 or molid2 - auto indices = spc.groups | ranges::cpp20::views::filter(match_groups) | - ranges::cpp20::views::transform( - [&](const auto& group) { return spc.getGroupIndex(group); }) | - ranges::to_vector; + auto indices = + spc.groups | std::views::filter(match_groups) | + std::views::transform([&](const auto& group) { return spc.getGroupIndex(group); }) | + ranges::to_vector; return for_each_unique_pair(indices.begin(), indices.end(), group_group_energy, std::plus<>()); } diff --git a/src/energy.h b/src/energy.h index 8e0f983b7..e1e22b4d4 100644 --- a/src/energy.h +++ b/src/energy.h @@ -9,11 +9,9 @@ #include "aux/pairmatrix.h" #include "smart_montecarlo.h" #include -#include -#include -#include #include #include +#include #include #include #include @@ -174,7 +172,7 @@ class EwaldPolicyBase auto is_partially_inactive = [](const Group& group) { return group.size() != group.capacity(); }; - if (ranges::cpp20::any_of(groups, is_partially_inactive)) { + if (std::ranges::any_of(groups, is_partially_inactive)) { throw std::runtime_error("Eigen optimized Ewald not available with inactive groups"); } auto first_particle = groups.front().begin(); @@ -191,7 +189,7 @@ class EwaldPolicyBase auto is_partially_inactive = [](const Group& group) { return group.size() != group.capacity(); }; - if (ranges::cpp20::any_of(groups, is_partially_inactive)) { + if (std::ranges::any_of(groups, is_partially_inactive)) { throw std::runtime_error("Eigen optimized Ewald not available with inactive groups"); } auto first_particle = groups.front().begin(); @@ -342,7 +340,7 @@ class Bonded : public EnergyTerm double sumBondEnergy(const BondVector& bonds) const; //!< sum energy in vector of BondData double internalGroupEnergy(const Change::GroupChange& changed); //!< Energy from internal bonds double sumEnergy(const BondVector& bonds, - const ranges::cpp20::range auto& particle_indices) const; + const std::ranges::range auto& particle_indices) const; void updateInternalBonds(); //!< finds and adds all intra-molecular bonds of active molecules public: @@ -363,15 +361,15 @@ class Bonded : public EnergyTerm * to simplistic search which scales as number_of_bonds x number_of_moved_particles */ double Bonded::sumEnergy(const Bonded::BondVector& bonds, - const ranges::cpp20::range auto& particle_indices) const + const std::ranges::range auto& particle_indices) const { assert(std::is_sorted(particle_indices.begin(), particle_indices.end())); auto index_is_included = [&](auto index) { return std::binary_search(particle_indices.begin(), particle_indices.end(), index); }; - auto affected_bonds = bonds | ranges::cpp20::views::filter([&](const auto& bond) { - return ranges::cpp20::any_of(bond->indices, index_is_included); + auto affected_bonds = bonds | std::views::filter([&](const auto& bond) { + return std::ranges::any_of(bond->indices, index_is_included); }); auto bond_energy = [dist = spc.geometry.getDistanceFunc()](const auto& bond) { return bond->energyFunc(dist); @@ -388,11 +386,11 @@ double Bonded::sumEnergy(const Bonded::BondVector& bonds, * @param range an original set of integers (must be sorted) * @return a set of ints complementary to the original set */ -auto indexComplement(std::integral auto size, const ranges::cpp20::range auto& range) +auto indexComplement(std::integral auto size, const std::ranges::range auto& range) { - namespace rv = ranges::cpp20::views; - return rv::iota(0, static_cast(size)) | - rv::filter([&](auto i) { return !std::binary_search(range.begin(), range.end(), i); }); + using namespace std::views; + return iota(0, static_cast(size)) | + filter([&](auto i) { return !std::binary_search(range.begin(), range.end(), i); }); } /** @@ -1400,7 +1398,7 @@ template class GroupPairing const auto fixed = indexComplement(spc.groups.size(), moved) | ranges::to; // index of static groups auto filter_active = [](int size) { - return ranges::views::filter([size](const auto i) { return i < size; }); + return std::views::filter([size](const auto i) { return i < size; }); }; // loop over all changed groups diff --git a/src/geometry.cpp b/src/geometry.cpp index 6bba9aa24..21e4060d4 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -1583,7 +1583,7 @@ std::vector TwobodyAngles::fibonacciSphere(const size_t samples) TwobodyAngles::TwobodyAngles(const double angle_resolution) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; const auto number_of_samples = size_t(std::round(4.0 * pc::pi / std::pow(angle_resolution, 2))); const auto points_on_sphere = fibonacciSphere(number_of_samples); diff --git a/src/geometry.h b/src/geometry.h index 187506717..dd1c01c16 100644 --- a/src/geometry.h +++ b/src/geometry.h @@ -9,10 +9,12 @@ #include #include #include +#include +#include #include +#ifndef __cpp_lib_ranges_join_with #include -#include -#include +#endif #include /** @brief Faunus main namespace */ @@ -548,7 +550,7 @@ enum class weight * @param boundary Used to remove periodic boundaries * @param shift Shift with this value before and after center calculation. To e.g. remove PBC */ -template +template Point weightedCenter( const Positions& positions, const Weights& weights, Geometry::BoundaryFunction boundary = [](auto&) {}, const Point& shift = Point::Zero()) @@ -589,8 +591,8 @@ Point weightedCenter(iterator begin, iterator end, BoundaryFunction boundary, std::function weight_function, const Point& shift = Point::Zero()) { - namespace rv = ranges::cpp20::views; - auto particles = ranges::make_subrange(begin, end); + namespace rv = std::views; + auto particles = std::ranges::subrange(begin, end); auto positions = particles | rv::transform(&Particle::pos); auto weights = particles | rv::transform(weight_function); return weightedCenter(positions, weights, boundary, shift); @@ -684,8 +686,13 @@ Point trigoCom(const Tspace& spc, const GroupIndex& indices, if (dir.empty() || dir.size() > 3) { throw std::out_of_range("invalid directions"); } - namespace rv = ranges::cpp20::views; - auto positions = indices | rv::transform([&](auto i) { return spc.groups.at(i); }) | rv::join | + namespace rv = std::views; +#ifdef __cpp_lib_ranges_join_with + using std::views::join; +#else + using ranges::cpp20::views::join; +#endif + auto positions = indices | rv::transform([&](auto i) { return spc.groups.at(i); }) | join | rv::transform(&Particle::pos); Point xhi(0, 0, 0); Point zeta(0, 0, 0); @@ -774,8 +781,8 @@ Tensor gyration( iterator begin, iterator end, const Point& mass_center, const BoundaryFunction boundary = [](auto&) {}) { - namespace rv = ranges::cpp20::views; - auto particles = ranges::make_subrange(begin, end); + namespace rv = std::views; + auto particles = std::ranges::subrange(begin, end); auto positions = particles | rv::transform(&Particle::pos); auto masses = particles | rv::transform(&Particle::traits) | rv::transform(&AtomData::mw); return gyration(positions.begin(), positions.end(), masses.begin(), mass_center, boundary); @@ -945,7 +952,7 @@ class TwobodyAngles return q_dihedral * q2; // noncommutative }; auto second_body_quaternions = - rv::cartesian_product(quaternions_2, dihedrals) | rv::transform(product); + rv::cartesian_product(quaternions_2, dihedrals) | std::views::transform(product); return rv::cartesian_product(quaternions_1, second_body_quaternions); } diff --git a/src/group.h b/src/group.h index 75cf1fa5e..69451c178 100644 --- a/src/group.h +++ b/src/group.h @@ -3,9 +3,7 @@ #include "particle.h" #include "molecule.h" #include "geometry.h" -#include -#include -#include +#include #include #include @@ -103,19 +101,14 @@ template class ElasticRange : public IterRange return (new_size >= 0 && new_size <= capacity()); } - /* - * @note On Apple Clang 12.0.5 (Nov. 2021) template deduction is unavailable, and we have to use - * `make_subrange()`. With GCC `cpp20::subrange()` can be used. More info here: - * https://stackoverflow.com/questions/58316189/in-ranges-v3-how-do-i-create-a-range-from-a-pair-of-iterators - */ inline auto all() { - return ranges::make_subrange(begin(), trueend()); + return std::ranges::subrange(begin(), trueend()); } //!< Active and inactive elements [[nodiscard]] inline auto all() const { - return ranges::make_subrange(begin(), trueend()); + return std::ranges::subrange(begin(), trueend()); } //!< Active and inactive elements }; @@ -299,15 +292,14 @@ class Group : public ElasticRange auto positions() { - return ranges::make_subrange(begin(), end()) | - ranges::cpp20::views::transform( - [&](Particle& particle) -> Point& { return particle.pos; }); + return std::ranges::subrange(begin(), end()) | + std::views::transform([&](Particle& particle) -> Point& { return particle.pos; }); } //!< Range of positions of active particles [[nodiscard]] auto positions() const { - return ranges::make_subrange(begin(), end()) | - ranges::cpp20::views::transform( + return std::ranges::subrange(begin(), end()) | + std::views::transform( [&](const Particle& particle) -> const Point& { return particle.pos; }); } //!< Range of positions of active particles @@ -317,8 +309,8 @@ class Group : public ElasticRange [[nodiscard]] auto findAtomID(AtomData::index_type atomid) const { - return *this | ranges::cpp20::views::filter( - [atomid](auto& particle) { return (particle.id == atomid); }); + return *this | + std::views::filter([atomid](auto& particle) { return (particle.id == atomid); }); } //!< Range of all (active) elements with matching particle id /** @@ -346,8 +338,8 @@ class Group : public ElasticRange assert(*std::max_element(indices.begin(), indices.end()) < size()); } #endif - return indices | ranges::cpp20::views::transform( - [this](auto i) -> Particle& { return *(begin() + i); }); + return indices | + std::views::transform([this](auto i) -> Particle& { return *(begin() + i); }); } /** @@ -436,6 +428,6 @@ template void load(Archive& archive, Group& group, std::uint32_t /** Concept for a range of groups */ template concept RequireGroups = - ranges::cpp20::range && std::is_convertible_v, Group>; + std::ranges::range && std::is_convertible_v, Group>; } // namespace Faunus diff --git a/src/io.h b/src/io.h index ba9e42074..1b3ce6d85 100644 --- a/src/io.h +++ b/src/io.h @@ -8,9 +8,8 @@ #include #include #include -#include -#include #include +#include namespace cereal { class BinaryOutputArchive; @@ -258,12 +257,12 @@ class StructureFileWriter particle_index = 0; box_dimensions = box_length; - auto group_sizes = groups | ranges::cpp20::views::transform(&Group::capacity); + auto group_sizes = groups | std::views::transform(&Group::capacity); const auto number_of_particles = std::accumulate(group_sizes.begin(), group_sizes.end(), 0u); saveHeader(stream, number_of_particles); - ranges::cpp20::for_each(groups, [&](const auto& group) { saveGroup(stream, group); }); + std::ranges::for_each(groups, [&](const auto& group) { saveGroup(stream, group); }); saveFooter(stream); } @@ -494,7 +493,7 @@ struct XTCTrajectoryFrame void importCoordinates(begin_iterator begin, end_iterator end, const Point& offset) const { int i = 0; - ranges::cpp20::for_each(begin, end, [&](const auto& position) { + std::ranges::for_each(begin, end, [&](const auto& position) { if (i >= number_of_atoms) { throw std::runtime_error("too many particles for XTC frame"); } @@ -704,7 +703,7 @@ class XTCWriter void writeNext(const Point& box, begin_iterator coordinates_begin, end_iterator coordinates_end) { if (!xtc_frame) { - auto number_of_atoms = ranges::cpp20::distance(coordinates_begin, coordinates_end); + auto number_of_atoms = std::ranges::distance(coordinates_begin, coordinates_end); faunus_logger->trace("preparing xtc output for {} particles", number_of_atoms); xtc_frame = std::make_unique(number_of_atoms); } diff --git a/src/molecule.cpp b/src/molecule.cpp index 8c7ee56de..760fa75bc 100644 --- a/src/molecule.cpp +++ b/src/molecule.cpp @@ -10,8 +10,6 @@ #include #include #include -#include -#include #include #include @@ -292,7 +290,7 @@ void NeighboursGenerator::generatePairs(AtomPairList& pairs, int bond_distance) paths | join | transform(make_ordered_pair) | filter(is_valid_pair) | ranges::to>; pairs.reserve(pairs.size() + excluded_pairs.size()); - std::copy(excluded_pairs.begin(), excluded_pairs.end(), std::back_inserter(pairs)); + std::ranges::copy(excluded_pairs, std::back_inserter(pairs)); } TEST_CASE("NeighboursGenerator") @@ -512,7 +510,7 @@ void MoleculeBuilder::readBonds(const json& j) bonds = j.value("bondlist", bonds); auto is_invalid_index = [size = particles.size()](auto& i) { return i >= size || i < 0; }; auto indices = bonds | views::transform(&pairpotential::BondData::indices) | views::join; - if (any_of(indices, is_invalid_index)) { + if (std::ranges::any_of(indices, is_invalid_index)) { throw ConfigurationError("bonded index out of range"); } } @@ -853,7 +851,7 @@ RandomInserter::operator()(const Geometry::GeometryBase& geo, MoleculeData& mole auto container_overlap = [&geo](const auto& particle) { return geo.collision(particle.pos); }; if (keep_positions) { // keep given positions - if (ranges::cpp20::none_of(particles, container_overlap)) { + if (std::ranges::none_of(particles, container_overlap)) { return particles; } throw std::runtime_error("inserted molecule does not fit in container"); @@ -866,7 +864,7 @@ RandomInserter::operator()(const Geometry::GeometryBase& geo, MoleculeData& mole else { translateRotateMolecularGroup(geo, rotator, particles); } - if (allow_overlap || ranges::cpp20::none_of(particles, container_overlap)) { + if (allow_overlap || std::ranges::none_of(particles, container_overlap)) { return particles; } } @@ -944,10 +942,10 @@ void Conformation::copyTo(ParticleVector& particles) const * Note how `std::ranges::transform` accepts data member pointers like `&Particle::pos` * which are internally accessed using `std::invoke`. */ - auto particle_positions = particles | ranges::cpp20::views::transform(&Particle::pos); + auto particle_positions = particles | std::views::transform(&Particle::pos); std::copy(positions.begin(), positions.end(), particle_positions.begin()); - auto particle_charges = particles | ranges::cpp20::views::transform(&Particle::charge); + auto particle_charges = particles | std::views::transform(&Particle::charge); std::copy(charges.begin(), charges.end(), particle_charges.begin()); } diff --git a/src/montecarlo.cpp b/src/montecarlo.cpp index aaaca8c71..a7b0feced 100644 --- a/src/montecarlo.cpp +++ b/src/montecarlo.cpp @@ -1,9 +1,7 @@ #include "montecarlo.h" -#include "speciation.h" #include "energy.h" #include "move.h" #include -#include namespace Faunus { @@ -221,8 +219,8 @@ void MetropolisMonteCarlo::sweep() assert(moves); number_of_sweeps++; auto perform_single_move = [&](auto& move) { performMove(*move); }; - ranges::cpp20::for_each(moves->repeatedStochasticMoves(), perform_single_move); - ranges::cpp20::for_each(moves->constantIntervalMoves(number_of_sweeps), perform_single_move); + std::ranges::for_each(moves->repeatedStochasticMoves(), perform_single_move); + std::ranges::for_each(moves->constantIntervalMoves(number_of_sweeps), perform_single_move); } Energy::Hamiltonian& MetropolisMonteCarlo::getHamiltonian() diff --git a/src/move.cpp b/src/move.cpp index 86a48bfea..c7e304935 100644 --- a/src/move.cpp +++ b/src/move.cpp @@ -9,11 +9,11 @@ #include "aux/eigensupport.h" #include #include -#include -#include -#include +#include +#include +#ifndef __cpp_lib_ranges_fold #include - +#endif namespace Faunus::move { Random Move::slump; // static instance of Random (shared for all moves) @@ -257,7 +257,7 @@ void AtomicTranslateRotate::groupToDisk(const Space::GroupType& group) const { if (auto stream = std::ofstream("mass-center-failure.pqr"); stream) { const auto group_iter = spc.groups.cbegin() + spc.getGroupIndex(group); - auto groups = ranges::cpp20::views::counted(group_iter, 1); // slice out single group + auto groups = std::views::counted(group_iter, 1); // slice out single group PQRWriter().save(stream, groups, spc.geometry.getLength()); } } @@ -572,11 +572,16 @@ double GibbsEnsembleHelper::exchange(const double value) const */ std::pair GibbsEnsembleHelper::currentNumParticles(const Space& spc) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto to_num_molecules = [&](auto molid) { return spc.numMolecules(molid); }; - const int n1 = ranges::fold_left(molids | rv::transform(to_num_molecules), 0, std::plus<>()); +#ifdef __cpp_lib_ranges_fold + using std::ranges::fold_left; +#else + using ranges::fold_left; +#endif + const int n1 = fold_left(molids | rv::transform(to_num_molecules), 0, std::plus<>()); const int n2 = total_num_particles - n1; return {n1, n2}; } @@ -842,12 +847,11 @@ void ParallelTempering::_to_json(json& j) const */ void ParallelTempering::exchangeGroupSizes(Space::GroupVector& groups, int partner_rank) { - std::vector sizes = - groups | ranges::cpp20::views::transform(&Group::size) | ranges::to_vector; + std::vector sizes = groups | std::views::transform(&Group::size) | ranges::to_vector; mpi.world.sendrecv_replace(sizes.begin(), sizes.end(), partner_rank, mpl::tag_t(0), partner_rank, mpl::tag_t(0)); auto it = sizes.begin(); - ranges::cpp20::for_each(groups, [&it](Group& group) { group.resize(*it++); }); + std::ranges::for_each(groups, [&it](Group& group) { group.resize(*it++); }); } /** @@ -1184,7 +1188,7 @@ void ChargeTransfer::_move(Change& change) { auto mollist1 = spc.findMolecules(mol1.id, Space::Selection::ACTIVE); auto mollist2 = spc.findMolecules(mol2.id, Space::Selection::ACTIVE); - if ((not ranges::cpp20::empty(mollist1)) and (not ranges::cpp20::empty(mollist2))) { + if ((not std::ranges::empty(mollist1)) and (not std::ranges::empty(mollist2))) { auto git1 = slump.sample(mollist1.begin(), mollist1.end()); // selecting a random molecule of type molecule1 auto git2 = slump.sample(mollist2.begin(), @@ -1392,7 +1396,7 @@ void QuadrantJump::_move(Change& change) // TODO: This can be slow -- implement look-up-table in Space auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); // list of molecules w. 'molid' - if (not ranges::cpp20::empty(mollist)) { + if (not std::ranges::empty(mollist)) { auto it = slump.sample(mollist.begin(), mollist.end()); if (not it->empty()) { assert(it->id == molid); @@ -1466,7 +1470,7 @@ ParticleVector::iterator AtomicSwapCharge::randomAtom() { assert(molid >= 0); auto mollist = spc.findMolecules(molid); // all `molid` groups - if (not ranges::cpp20::empty(mollist)) { + if (not std::ranges::empty(mollist)) { auto git = slump.sample(mollist.begin(), mollist.end()); // random molecule iterator if (!git->empty()) { auto p = slump.sample(git->begin(), git->end()); // random particle iterator @@ -1584,7 +1588,7 @@ void TranslateRotate::_from_json(const json& j) TranslateRotate::OptionalGroup TranslateRotate::findRandomMolecule() { if (auto mollist = spc.findMolecules(molid, Space::Selection::ACTIVE); - not ranges::cpp20::empty(mollist)) { + not std::ranges::empty(mollist)) { if (auto group_it = random.sample(mollist.begin(), mollist.end()); not group_it->empty()) { return *group_it; } @@ -1921,7 +1925,7 @@ void ConformationSwap::checkConformationSize() const size_t conformation_id = 0; for (const auto& conformation : Faunus::molecules.at(molid).conformations.data) { - const auto positions = conformation | ranges::cpp20::views::transform(&Particle::pos); + const auto positions = conformation | std::views::transform(&Particle::pos); const auto max_separation = find_max_distance(positions); if (max_separation > max_allowed_separation) { faunus_logger->warn( diff --git a/src/move.h b/src/move.h index 280071cf0..f7eccbf91 100644 --- a/src/move.h +++ b/src/move.h @@ -11,8 +11,8 @@ #include "aux/sparsehistogram.h" #include #include +#include #include -#include #include namespace Faunus { @@ -639,10 +639,10 @@ class MoveCollection auto is_valid_and_stochastic = [&](auto move) { return move < moves.end() && (*move)->isStochastic(); }; - return ranges::cpp20::views::iota(0U, number_of_moves_per_sweep) | - ranges::cpp20::views::transform( - [&]([[maybe_unused]] auto count) { return sample(); }) | - ranges::cpp20::views::filter(is_valid_and_stochastic) | + using namespace ranges::cpp20; + return views::iota(0U, number_of_moves_per_sweep) | + views::transform([&]([[maybe_unused]] auto count) { return sample(); }) | + views::filter(is_valid_and_stochastic) | ranges::views::indirect; // dereference iterator } @@ -664,7 +664,7 @@ class MoveCollection auto is_static_and_time_to_sample = [&, sweep_number](const auto& move) { return (!move->isStochastic()) && (sweep_number % move->sweep_interval == 0); }; - return moves | ranges::cpp20::views::filter(is_static_and_time_to_sample); + return moves | std::views::filter(is_static_and_time_to_sample); } }; diff --git a/src/mpicontroller.cpp b/src/mpicontroller.cpp index a4af97c74..7332e0b3b 100644 --- a/src/mpicontroller.cpp +++ b/src/mpicontroller.cpp @@ -1,5 +1,4 @@ #include "mpicontroller.h" -#include #include #include @@ -99,7 +98,7 @@ void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) case Format::XYZ: packet_size = 3; from_particle = [&](auto& particle, auto& destination) { - ranges::cpp20::copy(particle.pos, destination); + std::ranges::copy(particle.pos, destination); std::advance(destination, 3); }; to_particle = [&](auto& source, auto& particle) { @@ -110,7 +109,7 @@ void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) case Format::XYZQ: packet_size = 4; from_particle = [&](auto& particle, auto& destination) { - ranges::cpp20::copy(particle.pos, destination); + std::ranges::copy(particle.pos, destination); std::advance(destination, 3); *(destination++) = particle.charge; }; @@ -123,7 +122,7 @@ void ParticleBuffer::setFormat(ParticleBuffer::Format data_format) case Format::XYZQI: packet_size = 5; from_particle = [&](auto& particle, auto& destination) { - ranges::cpp20::copy(particle.pos, destination); + std::ranges::copy(particle.pos, destination); std::advance(destination, 3); *(destination++) = particle.charge; *(destination++) = static_cast(particle.id); @@ -144,8 +143,8 @@ void ParticleBuffer::copyToBuffer(const ParticleVector& particles) { buffer.resize(packet_size * particles.size()); auto destination = buffer.begin(); // set *after* buffer resize - ranges::cpp20::for_each(particles, - std::bind(from_particle, std::placeholders::_1, std::ref(destination))); + std::ranges::for_each(particles, + std::bind(from_particle, std::placeholders::_1, std::ref(destination))); if (destination != buffer.end()) { throw std::runtime_error("buffer mismatch"); } @@ -157,8 +156,8 @@ void ParticleBuffer::copyFromBuffer(ParticleVector& particles) throw std::out_of_range("particles out of range"); } auto source = buffer.begin(); - ranges::cpp20::for_each(particles, - std::bind(to_particle, std::ref(source), std::placeholders::_1)); + std::ranges::for_each(particles, + std::bind(to_particle, std::ref(source), std::placeholders::_1)); assert(source == buffer.end()); } diff --git a/src/mpicontroller.h b/src/mpicontroller.h index 376c1e265..e9d5fb40b 100644 --- a/src/mpicontroller.h +++ b/src/mpicontroller.h @@ -7,13 +7,10 @@ #include "core.h" #include "particle.h" #include "geometry.h" - #include #include -#include -#include -#include #include +#include // Expose classes for MPI serialization MPL_REFLECTION(Faunus::Point, x(), y(), z()) @@ -232,18 +229,17 @@ void avgTables(const mpl::communicator& communicator, Ttable& table, int& size) else { send_buffer = table.hist2buf(size); recv_buffer.resize(size); - auto slaves = - ranges::cpp20::views::iota(0, communicator.size()) | - ranges::cpp20::views::filter([&](auto rank) { return rank != mpi.master_rank; }); + auto slaves = std::views::iota(0, communicator.size()) | + std::views::filter([&](auto rank) { return rank != mpi.master_rank; }); - ranges::cpp20::for_each(slaves, [&](auto rank) { + std::ranges::for_each(slaves, [&](auto rank) { communicator.recv(recv_buffer, rank); send_buffer.insert(send_buffer.end(), recv_buffer.begin(), recv_buffer.end()); }); table.buf2hist(send_buffer); send_buffer = table.hist2buf(size); - ranges::cpp20::for_each(slaves, [&](auto rank) { communicator.send(send_buffer, rank); }); + std::ranges::for_each(slaves, [&](auto rank) { communicator.send(send_buffer, rank); }); } } #endif diff --git a/src/particle.h b/src/particle.h index 200a55bfa..7db15de72 100644 --- a/src/particle.h +++ b/src/particle.h @@ -4,7 +4,7 @@ #include "tensor.h" #include #include -#include +#include namespace Eigen { using Matrix3d = Matrix; @@ -284,7 +284,7 @@ using ParticleVector = std::vector; /** Concept for a range of particles */ template concept RequireParticles = - ranges::cpp20::range && std::is_convertible_v, Particle>; + std::ranges::range && std::is_convertible_v, Particle>; template concept RequireParticleIterator = std::is_convertible_v, Particle>; diff --git a/src/random.h b/src/random.h index 94090e0da..ca847908e 100644 --- a/src/random.h +++ b/src/random.h @@ -1,5 +1,4 @@ #pragma once -#include #include #include #include @@ -113,7 +112,10 @@ template class WeightedDistribution */ template void setWeight(Iterator begin, Iterator end) { - static_assert(std::is_convertible_v, double>); + // Disable assert fow now + // see + // https://stackoverflow.com/questions/74496713/how-to-get-the-type-of-the-values-in-a-c20-stdranges-range + // static_assert(std::is_convertible_v, double>); if (auto size = std::distance(begin, end); size == data.size()) { weights.resize(size); std::copy(begin, end, weights.begin()); diff --git a/src/reactioncoordinate.cpp b/src/reactioncoordinate.cpp index 8b60d23e0..aa27ea7f6 100644 --- a/src/reactioncoordinate.cpp +++ b/src/reactioncoordinate.cpp @@ -5,10 +5,10 @@ #include "multipole.h" #include #include "aux/eigensupport.h" -#include -#include -#include +#include +#ifndef __cpp_lib_ranges_join_with #include +#endif #include "spdlog/spdlog.h" namespace Faunus::ReactionCoordinate { @@ -106,7 +106,12 @@ void SystemProperty::_to_json(json& j) const SystemProperty::SystemProperty(const json& j, const Space& spc) : ReactionCoordinateBase(j) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; +#ifdef __cpp_lib_ranges_join_with + using std::views::join; +#else + using ranges::cpp20::views::join; +#endif name = "system"; property = j.at("property").get(); if (property == "V") { @@ -132,37 +137,37 @@ SystemProperty::SystemProperty(const json& j, const Space& spc) } else if (property == "Q") { // system net charge function = [&spc] { - auto charges = spc.groups | rv::join | rv::transform(&Particle::charge); + auto charges = spc.groups | join | rv::transform(&Particle::charge); return std::accumulate(charges.begin(), charges.end(), 0.0); }; } else if (property == "mu") { // system dipole moment function = [&spc]() { - auto particles = spc.groups | rv::join; + auto particles = spc.groups | join; return Faunus::dipoleMoment(particles.begin(), particles.end()).norm(); }; } else if (property == "mu_x") { // system dipole moment function = [&spc]() { - auto particles = spc.groups | rv::join; + auto particles = spc.groups | join; return Faunus::dipoleMoment(particles.begin(), particles.end()).x(); }; } else if (property == "mu_y") { // system dipole moment function = [&spc]() { - auto particles = spc.groups | rv::join; + auto particles = spc.groups | join; return Faunus::dipoleMoment(particles.begin(), particles.end()).y(); }; } else if (property == "mu_z") { // system dipole moment function = [&spc]() { - auto particles = spc.groups | rv::join; + auto particles = spc.groups | join; return Faunus::dipoleMoment(particles.begin(), particles.end()).z(); }; } else if (property == "N") { // number of particles function = [&spc]() { - auto sizes = spc.groups | rv::transform(&Space::GroupType::size); + auto sizes = spc.groups | std::views::transform(&Space::GroupType::size); return static_cast(std::accumulate(sizes.begin(), sizes.end(), size_t(0))); }; } @@ -213,9 +218,8 @@ AtomProperty::AtomProperty(const json& j, const Space& spc) else if (property == "N") { function = [&spc, id = index]() { return static_cast( - ranges::cpp20::count_if(spc.activeParticles(), [&](const Particle& particle) { - return particle.id == id; - })); + std::ranges::count_if(spc.activeParticles(), + [&](const Particle& particle) { return particle.id == id; })); }; } @@ -484,7 +488,7 @@ void MoleculeProperty::selectRinner(const json& j, const Space& spc) } function = [&spc, &dir = direction, i = indexes.at(0), j = indexes.at(1), k = indexes.at(2), l = indexes.at(3)]() { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto slicei = spc.findAtoms(i); const auto mass_center = Geometry::massCenter(slicei.begin(), slicei.end(), spc.geometry.getBoundaryFunc()); @@ -496,11 +500,11 @@ void MoleculeProperty::selectRinner(const json& j, const Space& spc) }; auto mean = [](auto& radii) { Average mean; - ranges::cpp20::for_each(radii, [&](auto radius) { mean += radius; }); + std::ranges::for_each(radii, [&](auto radius) { mean += radius; }); return mean.avg(); }; - auto radii_j = spc.findAtoms(j) | rv::transform(radius); + auto radii_j = spc.findAtoms(j) | std::views::transform(radius); auto mean_radii_j = mean(radii_j); auto radii = spc.activeParticles() | rv::filter(k_or_l) | rv::transform(radius) | diff --git a/src/regions.cpp b/src/regions.cpp index 008e38d26..4d2169b8f 100644 --- a/src/regions.cpp +++ b/src/regions.cpp @@ -1,6 +1,5 @@ #include "regions.h" #include "space.h" -#include #include #include @@ -29,7 +28,7 @@ bool RegionBase::inside(const Group& group) const return isInside(mass_center.value()); } } - return ranges::cpp20::any_of(group, [&](const Particle& particle) { return inside(particle); }); + return std::ranges::any_of(group, [&](const Particle& particle) { return inside(particle); }); } /** @@ -107,7 +106,7 @@ WithinMoleculeType::WithinMoleculeType(const Space& spc, const json& j) bool WithinMoleculeType::isInside(const Point& position) const { - using ranges::cpp20::any_of; + using std::ranges::any_of; auto has_position_inside = [&](const Group& group) { if (use_region_mass_center) { return within_threshold(position, *group.massCenter()); diff --git a/src/regions.h b/src/regions.h index f42134c02..f3045aa4a 100644 --- a/src/regions.h +++ b/src/regions.h @@ -3,8 +3,7 @@ #include "molecule.h" #include "group.h" #include "space.h" -#include -#include +#include namespace Faunus { class Space; @@ -55,7 +54,7 @@ class RegionBase /** Selects particles within the region */ template auto filterInside(const ParticleRange& particles) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; return particles | rv::transform(&Particle::pos) | rv::filter(&RegionBase::isInside); } }; diff --git a/src/sasa.cpp b/src/sasa.cpp index f5c13fa5f..1e13fad54 100644 --- a/src/sasa.cpp +++ b/src/sasa.cpp @@ -3,8 +3,6 @@ #include "space.h" #include #include -#include -#include #include namespace Faunus { @@ -172,8 +170,7 @@ void SASA::init(const Space& spc) auto sasa_radius_l = [this](const Particle& particle) { return 0.5 * particle.traits().sigma + probe_radius; }; - sasa_radii = - spc.particles | ranges::cpp20::views::transform(sasa_radius_l) | ranges::to; + sasa_radii = spc.particles | std::views::transform(sasa_radius_l) | ranges::to; areas.resize(spc.particles.size()); } @@ -219,7 +216,7 @@ std::vector SASA::calcNeighbourData(const Space& spc, const std::vector& target_indices) const { - return target_indices | ranges::views::transform([&](auto index) { + return target_indices | std::views::transform([&](auto index) { return calcNeighbourDataOfParticle(spc, index); }) | ranges::to; @@ -357,9 +354,8 @@ template void SASACellList::init(const Space& spc) } auto get_sasa_radius = [this](auto& i) { return 0.5 * i.traits().sigma + probe_radius; }; - sasa_radii = - spc.particles | ranges::cpp20::views::transform(get_sasa_radius) | ranges::to; - const auto max_sasa_radius = ranges::cpp20::max(sasa_radii); + sasa_radii = spc.particles | std::views::transform(get_sasa_radius) | ranges::to; + const double max_sasa_radius = std::ranges::max(sasa_radii); cell_length = 2.0 * (max_sasa_radius); const auto active_particles = spc.activeParticles(); @@ -422,7 +418,7 @@ std::vector SASACellList::calcNeighbourData(const Space& spc, const std::vector& target_indices) const { - return target_indices | ranges::views::transform([&](auto index) { + return target_indices | std::views::transform([&](auto index) { return calcNeighbourDataOfParticle(spc, index); }) | ranges::to; @@ -512,15 +508,14 @@ void SASACellList::updatePositionsChange(const Space& spc, const Chang }; if (group_change.relative_atom_indices.empty()) { - const auto changed_atom_indices = - ranges::cpp20::views::iota(offset, offset + group.size()); - ranges::cpp20::for_each(changed_atom_indices, update); + const auto changed_atom_indices = std::views::iota(offset, offset + group.size()); + std::ranges::for_each(changed_atom_indices, update); } else { const auto changed_atom_indices = group_change.relative_atom_indices | - ranges::cpp20::views::transform([offset](auto i) { return i + offset; }); - ranges::cpp20::for_each(changed_atom_indices, update); + std::views::transform([offset](auto i) { return i + offset; }); + std::ranges::for_each(changed_atom_indices, update); } } } diff --git a/src/scatter.h b/src/scatter.h index e611ed4da..add1b8b3f 100644 --- a/src/scatter.h +++ b/src/scatter.h @@ -338,8 +338,7 @@ class StructureFactorPBC : private TSamplingPolicy // Note January 2020: only GCC exploits this using libmvec library if --ffast-math is // enabled. auto dot_product = [q](const auto& pos) { return static_cast(q.dot(pos)); }; - auto qdotr = - positions | ranges::cpp20::views::transform(dot_product) | ranges::to; + auto qdotr = positions | std::views::transform(dot_product) | ranges::to; std::for_each(qdotr.begin(), qdotr.end(), [&](auto qr) { sum_cos += cos(qr); }); std::for_each(qdotr.begin(), qdotr.end(), [&](auto qr) { sum_sin += sin(qr); }); } diff --git a/src/smart_montecarlo.h b/src/smart_montecarlo.h index 25b758bdf..d01e196df 100644 --- a/src/smart_montecarlo.h +++ b/src/smart_montecarlo.h @@ -2,7 +2,7 @@ #include "regions.h" #include -#include +#include namespace Faunus::SmarterMonteCarlo { @@ -65,7 +65,7 @@ class RegionSampler const double outside_acceptance = 1.0; //!< Or "p" between ]0:1]; 1 --> uniform sampling (no regional preference) static BiasDirection getDirection(bool inside_before, bool inside_after); - template double getNumberInside(Range& range) const; + template double getNumberInside(Range& range) const; protected: const std::unique_ptr region; //!< This defines the smart MC region @@ -75,7 +75,7 @@ class RegionSampler virtual ~RegionSampler() = default; void to_json(json& j) const; //!< Serialise to json - template + template std::optional> select(Range& range, Random& random); template double bias(const Selection& selection); @@ -88,13 +88,13 @@ class RegionSampler * * @returns (mean) number of elements inside region */ -template double RegionSampler::getNumberInside(Range& range) const +template double RegionSampler::getNumberInside(Range& range) const { if (fixed_number_inside) { return fixed_number_inside.value(); } - return static_cast( - ranges::count_if(range, [&](const auto& i) { return region->inside(i); })); // expensive + return static_cast(std::ranges::count_if( + range, [&](const auto& i) { return region->inside(i); })); // expensive } /** @@ -109,10 +109,10 @@ template double RegionSampler::getNumberInside(Rang * without any selection, an empty object is returned and a warning issued. The maximum * number of attempts is currently set to 10x the size of the given range. */ -template +template std::optional> RegionSampler::select(Range& range, Random& random) { - const auto n_total = ranges::distance(range.begin(), range.end()); + const auto n_total = std::ranges::distance(range.begin(), range.end()); int max_selection_attempts = 10 * n_total; do { auto it = random.sample(range.begin(), range.end()); // random particle or group @@ -166,7 +166,7 @@ template class MoveSupport MoveSupport(const Space& spc, const json& j); double bias(); void to_json(json& j) const; - template OptionalElement select(Range& mollist, Random& random); + template OptionalElement select(Range& mollist, Random& random); }; template @@ -245,7 +245,7 @@ template void MoveSupport::to_json(json& j) const * @return Optional reference to selected element */ template -template +template typename MoveSupport::OptionalElement MoveSupport::select(Range& mollist, Random& random) { selection = region_sampler.select(mollist, random); diff --git a/src/space.cpp b/src/space.cpp index 4bb195234..010c2001d 100644 --- a/src/space.cpp +++ b/src/space.cpp @@ -2,9 +2,9 @@ #include "io.h" #include "aux/eigensupport.h" #include -#include -#include #include +#include +#include namespace Faunus { @@ -235,7 +235,7 @@ void Space::sync(const Space& other, const Change& change) } } // apply registered triggers - ranges::cpp20::for_each(onSyncTriggers, [&](auto& trigger) { trigger(*this, other, change); }); + std::ranges::for_each(onSyncTriggers, [&](auto& trigger) { trigger(*this, other, change); }); } /** @@ -330,7 +330,7 @@ Space::GroupVector::iterator Space::randomMolecule(MoleculeData::index_type moli Space::Selection selection) { auto found_molecules = findMolecules(molid, selection); - if (ranges::cpp20::empty(found_molecules)) { + if (std::ranges::empty(found_molecules)) { return groups.end(); } return groups.begin() + @@ -419,8 +419,8 @@ std::size_t Space::getFirstActiveParticleIndex(const GroupType& group) const size_t Space::countAtoms(AtomData::index_type atomid) const { - return ranges::cpp20::count_if(activeParticles(), - [&](auto& particle) { return particle.id == atomid; }); + return std::ranges::count_if(activeParticles(), + [&](auto& particle) { return particle.id == atomid; }); } void Space::updateInternalState(const Change& change) @@ -513,8 +513,8 @@ void from_json(const json& j, Space& spc) auto active_and_molecular = [](const auto& group) { return (!group.empty() && group.isMolecular()); }; - ranges::cpp20::for_each(spc.groups | ranges::cpp20::views::filter(active_and_molecular), - check_mass_center); + std::ranges::for_each(spc.groups | std::views::filter(active_and_molecular), + check_mass_center); } catch (const std::exception& e) { throw std::runtime_error("error building space -> "s + e.what()); @@ -613,7 +613,7 @@ TEST_CASE("[Faunus] Space::toIndices") Space spc; spc.particles.resize(3); - auto subrange = ranges::make_subrange(spc.particles.begin() + 1, spc.particles.end()); // p1, p2 + auto subrange = std::ranges::subrange(spc.particles.begin() + 1, spc.particles.end()); // p1, p2 auto indices = spc.toIndices(subrange); CHECK_EQ(indices.size(), 2); CHECK_EQ(indices.at(0), 1); diff --git a/src/space.h b/src/space.h index f7591dcae..f80b8a9fa 100644 --- a/src/space.h +++ b/src/space.h @@ -3,8 +3,9 @@ #include "geometry.h" #include "group.h" #include "molecule.h" +#include #include -#include +#include #include namespace Faunus { @@ -60,7 +61,7 @@ struct Change //! List of moved groups (index) [[nodiscard]] inline auto touchedGroupIndex() const { - return ranges::cpp20::views::transform(groups, &GroupChange::group_index); + return std::views::transform(groups, &GroupChange::group_index); } //! List of changed atom index relative to first particle in system @@ -203,7 +204,7 @@ class Space assert(size <= std::distance(destination, particles.end())); auto affected_groups = - groups | ranges::cpp20::views::filter([=](auto& group) { + groups | std::views::filter([=](auto& group) { return (group.begin() < destination + size) && (group.end() > destination); }); // filtered group with affected groups only. Note we copy in org. `destination` @@ -211,7 +212,7 @@ class Space std::for_each(begin, end, [&](const auto& source) { copy_function(source, *destination++); }); - std::for_each(affected_groups.begin(), affected_groups.end(), [&](Group& group) { + std::ranges::for_each(affected_groups, [&](Group& group) { group.updateMassCenter(geometry.getBoundaryFunc(), group.begin()->pos); }); } @@ -233,12 +234,12 @@ class Space //! Iterable range of all particle positions [[nodiscard]] auto positions() const { - return ranges::cpp20::views::transform( - particles, [](auto& particle) -> const Point& { return particle.pos; }); + return std::views::transform(particles, + [](auto& particle) -> const Point& { return particle.pos; }); } //! Mutable iterable range of all particle positions - auto positions() { return ranges::cpp20::views::transform(particles, &Particle::pos); } + auto positions() { return std::views::transform(particles, &Particle::pos); } static std::function getGroupFilter(MoleculeData::index_type molid, const Selection& selection) @@ -324,7 +325,7 @@ class Space } return static_cast(index); }; - return particle_range | ranges::cpp20::views::transform(to_index) | ranges::to_vector; + return particle_range | std::views::transform(to_index) | ranges::to_vector; } /** @@ -334,7 +335,7 @@ class Space */ auto findAtoms(AtomData::index_type atomid) { - return activeParticles() | ranges::cpp20::views::filter([atomid](const Particle& particle) { + return activeParticles() | std::views::filter([atomid](const Particle& particle) { return particle.id == atomid; }); } @@ -346,7 +347,7 @@ class Space */ [[nodiscard]] auto findAtoms(AtomData::index_type atomid) const { - return activeParticles() | ranges::cpp20::views::filter([atomid](const Particle& particle) { + return activeParticles() | std::views::filter([atomid](const Particle& particle) { return particle.id == atomid; }); } diff --git a/src/speciation.cpp b/src/speciation.cpp index a439b4f39..bae511bde 100644 --- a/src/speciation.cpp +++ b/src/speciation.cpp @@ -1,11 +1,10 @@ #include "bonds.h" #include "speciation.h" #include "aux/iteratorsupport.h" -#include #include -#include -#include +#include #include +#include #include namespace Faunus::Speciation { @@ -69,14 +68,14 @@ bool ReactionValidator::canReduceImplicitGroups(const ReactionData& reaction) co const auto& [molid, number_to_delete] = key_value; return spc.getImplicitReservoir().at(molid) >= number_to_delete; }; - auto implicit_reactants = reaction.getReactants().second | - ranges::cpp20::views::filter(ReactionData::is_implicit_group); - return ranges::cpp20::all_of(implicit_reactants, has_enough); + auto implicit_reactants = + reaction.getReactants().second | std::views::filter(ReactionData::is_implicit_group); + return std::ranges::all_of(implicit_reactants, has_enough); } bool ReactionValidator::canSwapAtoms(const ReactionData& reaction) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; if (reaction.containsAtomicSwap()) { auto reactive_atoms = reaction.getReactants().first | rv::filter(ReactionData::not_implicit_atom); @@ -92,7 +91,7 @@ bool ReactionValidator::canSwapAtoms(const ReactionData& reaction) const bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto selection = reaction.only_neutral_molecules ? Space::Selection::ACTIVE_NEUTRAL : Space::Selection::ACTIVE; @@ -116,12 +115,12 @@ bool ReactionValidator::canReduceMolecularGroups(const ReactionData& reaction) c rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_molecular_group); - return ranges::cpp20::all_of(molecular_groups, can_reduce); + return std::ranges::all_of(molecular_groups, can_reduce); } bool ReactionValidator::canReduceAtomicGroups(const ReactionData& reaction) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto can_reduce = [&](auto key_value) { const auto [molid, number_to_delete] = key_value; if (number_to_delete > 0) { @@ -143,12 +142,12 @@ bool ReactionValidator::canReduceAtomicGroups(const ReactionData& reaction) cons auto atomic_groups = reaction.getReactants().second | rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); - return ranges::cpp20::all_of(atomic_groups, can_reduce); + return std::ranges::all_of(atomic_groups, can_reduce); } bool ReactionValidator::canProduceMolecularGroups(const ReactionData& reaction) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto selection = reaction.only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL : Space::Selection::INACTIVE; @@ -169,12 +168,12 @@ bool ReactionValidator::canProduceMolecularGroups(const ReactionData& reaction) rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_molecular_group); - return ranges::cpp20::all_of(molecular_groups, can_create); + return std::ranges::all_of(molecular_groups, can_create); } bool ReactionValidator::canProduceAtomicGroups(const ReactionData& reaction) const { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto can_expand = [&](auto key_value) { const auto [molid, number_to_insert] = key_value; auto groups = spc.findMolecules(molid, Space::Selection::ALL) | @@ -195,7 +194,7 @@ bool ReactionValidator::canProduceAtomicGroups(const ReactionData& reaction) con rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); - return ranges::cpp20::all_of(atomic_groups, can_expand); + return std::ranges::all_of(atomic_groups, can_expand); } // ---------------------------------------------- @@ -293,9 +292,8 @@ double MolecularGroupDeActivator::getBondEnergy(const Group& group) const { double energy = 0.0; if (apply_bond_bias) { - auto bonds = - group.traits().bonds | ranges::views::transform(&pairpotential::BondData::clone); - ranges::cpp20::for_each(bonds, [&](auto bond) { + auto bonds = group.traits().bonds | std::views::transform(&pairpotential::BondData::clone); + std::ranges::for_each(bonds, [&](auto bond) { bond->shiftIndices(spc.getFirstParticleIndex(group)); bond->setEnergyFunction(spc.particles); energy += bond->energyFunc(spc.geometry.getDistanceFunc()); @@ -373,7 +371,7 @@ AtomicGroupDeActivator::deactivate(Group& group, GroupDeActivator::OptionalInt n group.deactivate(last_particle, group.end()); // deactivate one particle at the time }; - ranges::for_each(ranges::cpp20::views::iota(0, number_to_delete), delete_particle); + std::ranges::for_each(std::views::iota(0, number_to_delete), delete_particle); change_data.sort(); return {change_data, 0.0}; } @@ -465,7 +463,7 @@ void SpeciationMove::activateProducts(Change& change) void SpeciationMove::activateMolecularGroups(Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto selection = reaction->only_neutral_molecules ? Space::Selection::INACTIVE_NEUTRAL : Space::Selection::INACTIVE; @@ -478,7 +476,7 @@ void SpeciationMove::activateMolecularGroups(Change& change) ranges::views::sample(number_to_insert, random_internal.engine) | ranges::to>>; - ranges::for_each(inactive, [&](auto& group) { + std::ranges::for_each(inactive, [&](auto& group) { auto [change_data, bias] = molecular_group_bouncer->activate(group); change.groups.emplace_back(change_data); bias_energy += bias; @@ -488,7 +486,7 @@ void SpeciationMove::activateMolecularGroups(Change& change) void SpeciationMove::activateAtomicGroups(Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto atomic_products = reaction->getProducts().second | rv::filter(ReactionData::not_implicit_group) | rv::filter(ReactionData::is_atomic_group); @@ -513,7 +511,7 @@ void SpeciationMove::deactivateReactants(Change& change) void SpeciationMove::deactivateMolecularGroups(Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto nonzero_stoichiometric_coeff = [](auto key_value) { return key_value.second > 0; }; auto selection = (reaction->only_neutral_molecules) ? Space::Selection::ACTIVE_NEUTRAL : Space::Selection::ACTIVE; @@ -536,7 +534,7 @@ void SpeciationMove::deactivateMolecularGroups(Change& change) void SpeciationMove::deactivateAtomicGroups(Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; auto nonzero_stoichiometric_coeff = [](auto key_value) { return key_value.second > 0; }; auto atomic_reactants = @@ -555,11 +553,12 @@ void SpeciationMove::deactivateAtomicGroups(Change& change) TEST_CASE("[Faunus] Speciation - Ranges::sample") { + using ranges::views::sample; std::vector vec = {1, 2, 3, 4}; - auto take_nothing = vec | ranges::views::sample(0); - auto take_less = vec | ranges::views::sample(2); - auto take_all = vec | ranges::views::sample(4); - auto take_too_much = vec | ranges::views::sample(10); + auto take_nothing = vec | sample(0); + auto take_less = vec | sample(2); + auto take_all = vec | sample(4); + auto take_too_much = vec | sample(10); CHECK_EQ(range_size(take_nothing), 0); CHECK_EQ(range_size(take_less), 2); @@ -616,7 +615,7 @@ void SpeciationMove::updateGroupMassCenters(const Change& change) const auto groups = change.groups | rv::filter(atomic_or_swap) | rv::transform(to_group) | rv::filter(has_mass_center); - ranges::cpp20::for_each(groups, [&](Group& group) { + std::ranges::for_each(groups, [&](Group& group) { group.updateMassCenter(spc.geometry.getBoundaryFunc(), group.massCenter().value()); }); } @@ -633,7 +632,7 @@ double SpeciationMove::bias([[maybe_unused]] Change& change, [[maybe_unused]] do void SpeciationMove::_accept([[maybe_unused]] Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; direction_ratio[reaction].update(reaction->getDirection(), true); auto implicit_reactants = @@ -653,7 +652,7 @@ void SpeciationMove::_accept([[maybe_unused]] Change& change) void SpeciationMove::_reject([[maybe_unused]] Change& change) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; direction_ratio[reaction].update(reaction->getDirection(), false); auto implicit_reactants = diff --git a/src/voronota.cpp b/src/voronota.cpp index 814b7f787..f8e543e32 100644 --- a/src/voronota.cpp +++ b/src/voronota.cpp @@ -60,7 +60,7 @@ void Voronota::_to_disk() void Voronota::_sample() { using voronotalt::SimplePoint; - using namespace ranges::cpp20::views; + using namespace std::views; // Convert single `Particle` to Voronota's `SimpleSphere` auto to_sphere = [&](const Particle& p) -> voronotalt::SimpleSphere { From 4e2daf79a248c1a380a81b074b873cda9d06da61 Mon Sep 17 00:00:00 2001 From: Mikael Lund Date: Thu, 4 Jul 2024 13:25:20 +0200 Subject: [PATCH 12/12] Fix actions --- src/actions.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/actions.cpp b/src/actions.cpp index f2e1c9ebc..868b6a41d 100644 --- a/src/actions.cpp +++ b/src/actions.cpp @@ -4,6 +4,7 @@ #include "io.h" #include "aux/arange.h" #include +#include #include namespace Faunus { @@ -49,7 +50,7 @@ AngularScan::AngularScan(const json& j, const Space& spc) */ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int molecule_index) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; index = molecule_index; const auto& group = groups.at(index); if (group.isAtomic()) { @@ -66,7 +67,7 @@ void AngularScan::Molecule::initialize(const Space::GroupVector& groups, int mol ParticleVector AngularScan::Molecule::getRotatedReference(const Space::GroupVector& groups, const Eigen::Quaterniond& q) { - namespace rv = ranges::cpp20::views; + namespace rv = std::views; const auto& group = groups.at(index); auto particles = ParticleVector(group.begin(), group.end()); // copy particles from Space auto positions = @@ -95,7 +96,7 @@ void AngularScan::report(const Group& group1, const Group& group2, const Eigen:: << fmt::format("{:8.4f} {:>10.3E}\n", group2.mass_center.z(), energy / 1.0_kJmol); if (trajectory) { auto positions = ranges::views::concat(group1, group2) | - ranges::cpp20::views::transform(&Particle::pos); + std::views::transform(&Particle::pos); trajectory->writeNext({500, 500, 500}, positions.begin(), positions.end()); } } @@ -127,7 +128,7 @@ void AngularScan::operator()(Space& spc, Energy::Hamiltonian& hamiltonian) const auto q2 = q_dihedral * q_body2; // simultaneous rotations (non-commutative) auto particles2 = molecules.second.getRotatedReference(spc.groups, q2); - ranges::cpp20::for_each(particles2, translate); + std::ranges::for_each(particles2, translate); auto group2 = Group(0, particles2.begin(), particles2.end()); group2.mass_center = {0.0, 0.0, z_pos}; report(group1, group2, q1, q2, *nonbonded);