|
3 | 3 | #include "energy.h" |
4 | 4 | #include "reactioncoordinate.h" |
5 | 5 | #include "multipole.h" |
| 6 | +#include "potentials.h" |
6 | 7 | #include "aux/iteratorsupport.h" |
7 | 8 | #include "aux/eigensupport.h" |
8 | 9 | #include <range/v3/numeric/accumulate.hpp> |
|
11 | 12 | #include <zstr.hpp> |
12 | 13 | #include <cereal/types/memory.hpp> |
13 | 14 | #include <cereal/archives/binary.hpp> |
| 15 | +#include <range/v3/numeric/accumulate.hpp> |
14 | 16 |
|
15 | 17 | #include <iomanip> |
16 | 18 | #include <iostream> |
@@ -114,6 +116,8 @@ std::shared_ptr<Analysisbase> createAnalysis(const std::string& name, const json |
114 | 116 | return std::make_shared<AtomDipDipCorr>(j, spc); |
115 | 117 | } else if (name == "density") { |
116 | 118 | return std::make_shared<Density>(j, spc); |
| 119 | + } else if (name == "electricpotential") { |
| 120 | + return std::make_shared<ElectricPotential>(j, spc); |
117 | 121 | } else if (name == "chargefluctuations") { |
118 | 122 | return std::make_shared<ChargeFluctuations>(j, spc); |
119 | 123 | } else if (name == "molrdf") { |
@@ -1776,4 +1780,145 @@ void SpaceTrajectory::_sample() { |
1776 | 1780 | void SpaceTrajectory::_to_json(json& j) const { j = {{"file", filename}}; } |
1777 | 1781 |
|
1778 | 1782 | void SpaceTrajectory::_to_disk() { stream->flush(); } |
| 1783 | + |
| 1784 | +// ----------------------------- |
| 1785 | + |
| 1786 | +ElectricPotential::ElectricPotential(const json& j, Space& spc) |
| 1787 | + : Analysisbase(spc, "electricpotential"), potential_correlation_histogram(histogram_resolution) { |
| 1788 | + from_json(j); |
| 1789 | + coulomb = std::make_shared<Potential::NewCoulombGalore>(); |
| 1790 | + coulomb->from_json(j); |
| 1791 | + getTargets(j); |
| 1792 | + setPolicy(j); |
| 1793 | + calculations_per_sample_event = j.value("ncalc", 1); |
| 1794 | +} |
| 1795 | + |
| 1796 | +void ElectricPotential::setPolicy(const json& j) { |
| 1797 | + output_information.clear(); |
| 1798 | + policy = j.value("policy", FIXED); |
| 1799 | + auto stride = 0.0; |
| 1800 | + switch (policy) { |
| 1801 | + case FIXED: |
| 1802 | + applyPolicy = []() {}; |
| 1803 | + break; |
| 1804 | + case RANDOM_WALK: |
| 1805 | + stride = j.at("stride").get<double>(); |
| 1806 | + output_information["stride"] = stride; |
| 1807 | + applyPolicy = [&, stride] { |
| 1808 | + auto origin = targets.begin(); |
| 1809 | + spc.geo.randompos(origin->position, random); |
| 1810 | + std::for_each(std::next(origin), targets.end(), [&](Target& target) { |
| 1811 | + target.position = origin->position + stride * ranunit(random); |
| 1812 | + std::advance(origin, 1); |
| 1813 | + }); |
| 1814 | + }; |
| 1815 | + break; |
| 1816 | + case RANDOM_WALK_NO_OVERLAP: |
| 1817 | + stride = j.at("stride").get<double>(); |
| 1818 | + output_information["stride"] = stride; |
| 1819 | + applyPolicy = [&, stride] { |
| 1820 | + auto origin = targets.begin(); |
| 1821 | + do { |
| 1822 | + spc.geo.randompos(origin->position, random); |
| 1823 | + } while (overlapWithParticles(origin->position)); |
| 1824 | + std::for_each(std::next(origin), targets.end(), [&](Target& target) { |
| 1825 | + do { |
| 1826 | + target.position = origin->position + stride * ranunit(random); |
| 1827 | + } while (overlapWithParticles(target.position)); |
| 1828 | + std::advance(origin, 1); |
| 1829 | + }); |
| 1830 | + }; |
| 1831 | + break; |
| 1832 | + default: |
| 1833 | + throw ConfigurationError("unknown policy"); |
| 1834 | + } |
| 1835 | +} |
| 1836 | + |
| 1837 | +void ElectricPotential::getTargets(const json& j) { |
| 1838 | + if (const auto& structure = j.find("structure"); structure == j.end()) { |
| 1839 | + throw ConfigurationError("missing structure"); |
| 1840 | + } else { |
| 1841 | + PointVector positions; |
| 1842 | + if (structure->is_string()) { // load positions from chemical structure file |
| 1843 | + auto particles = loadStructure(structure->get<std::string>(), false); |
| 1844 | + positions = particles | ranges::cpp20::views::transform(&Particle::pos) | ranges::to_vector; |
| 1845 | + } else if (structure->is_array()) { // list of positions |
| 1846 | + positions = structure->get<PointVector>(); |
| 1847 | + } |
| 1848 | + std::transform(positions.begin(), positions.end(), std::back_inserter(targets), [&](auto& position) { |
| 1849 | + Target target; |
| 1850 | + target.position = position; |
| 1851 | + target.potential_histogram = std::make_shared<SparseHistogram<double>>(histogram_resolution); |
| 1852 | + return target; |
| 1853 | + }); |
| 1854 | + if (targets.empty()) { |
| 1855 | + throw ConfigurationError("no targets defined"); |
| 1856 | + } |
| 1857 | + } |
| 1858 | +} |
| 1859 | + |
| 1860 | +void ElectricPotential::_sample() { |
| 1861 | + for (unsigned int i = 0; i < calculations_per_sample_event; i++) { |
| 1862 | + applyPolicy(); |
| 1863 | + auto potential_correlation = 1.0; // phi1 * phi2 * ... |
| 1864 | + for (auto& target : targets) { // loop over each target point |
| 1865 | + auto potential = calcPotentialOnTarget(target); |
| 1866 | + target.potential_histogram->add(potential); |
| 1867 | + target.mean_potential += potential; |
| 1868 | + potential_correlation *= potential; |
| 1869 | + } |
| 1870 | + mean_potential_correlation += potential_correlation; // <phi1 * phi2 * ...> |
| 1871 | + potential_correlation_histogram.add(potential_correlation); // P(<phi1 * phi2 * ...>) |
| 1872 | + } |
| 1873 | +} |
| 1874 | + |
| 1875 | +double ElectricPotential::calcPotentialOnTarget(const ElectricPotential::Target& target) { |
| 1876 | + auto potential_from_particle = [&](const Particle& particle) { |
| 1877 | + const auto distance_to_target = std::sqrt(spc.geo.sqdist(particle.pos, target.position)); |
| 1878 | + return coulomb->getCoulombGalore().ion_potential(particle.charge, distance_to_target); |
| 1879 | + }; |
| 1880 | + auto potentials = spc.activeParticles() | ranges::cpp20::views::transform(potential_from_particle); |
| 1881 | + return std::accumulate(potentials.begin(), potentials.end(), 0.0); |
| 1882 | +} |
| 1883 | + |
| 1884 | +void ElectricPotential::_to_json(json& j) const { |
| 1885 | + j = output_information; |
| 1886 | + coulomb->to_json(j["coulomb"]); |
| 1887 | + j["policy"] = policy; |
| 1888 | + j["number of targets"] = targets.size(); |
| 1889 | + j["calculations per sample"] = calculations_per_sample_event; |
| 1890 | + if (number_of_samples > 0) { |
| 1891 | + j["correlation (βe)ⁱ⟨ϕ₁ϕ₂...ϕᵢ⟩"] = mean_potential_correlation.avg(); |
| 1892 | + auto& mean_potential_j = j["mean potentials βe⟨ϕᵢ⟩"] = json::array(); |
| 1893 | + std::transform(targets.begin(), targets.end(), std::back_inserter(mean_potential_j), |
| 1894 | + [](const auto& target) { return target.mean_potential.avg(); }); |
| 1895 | + } |
| 1896 | +} |
| 1897 | +void ElectricPotential::_to_disk() { |
| 1898 | + if (auto stream = std::ofstream(MPI::prefix + "potential_correlation_histogram.dat")) { |
| 1899 | + stream << potential_correlation_histogram; |
| 1900 | + } |
| 1901 | + int filenumber = 1; |
| 1902 | + for (const auto& target : targets) { |
| 1903 | + if (auto stream = std::ofstream(fmt::format("{}potential_histogram{}.dat", MPI::prefix, filenumber++))) { |
| 1904 | + stream << *(target.potential_histogram); |
| 1905 | + } |
| 1906 | + } |
| 1907 | +} |
| 1908 | + |
| 1909 | +/** |
| 1910 | + * Checks if position lies within the spheres of diameters `sigma` defined |
| 1911 | + * by each active particle in the system. Complexity: N |
| 1912 | + * |
| 1913 | + * @return True if overlap with any particle |
| 1914 | + */ |
| 1915 | +bool ElectricPotential::overlapWithParticles(const Point& position) const { |
| 1916 | + auto overlap = [&position, &geometry = spc.geo](const Particle& particle) { |
| 1917 | + const auto radius = 0.5 * particle.traits().sigma; |
| 1918 | + return geometry.sqdist(particle.pos, position) < radius * radius; |
| 1919 | + }; |
| 1920 | + auto particles = spc.activeParticles(); |
| 1921 | + return std::any_of(particles.begin(), particles.end(), overlap); |
| 1922 | +} |
| 1923 | + |
1779 | 1924 | } // namespace Faunus::Analysis |
0 commit comments