Skip to content

Commit

Permalink
Parallel Temper fix and example (#333)
Browse files Browse the repository at this point in the history
This fixes several problems with random number
states that would cause MPI deadlocks. Support
for fluctuating particles is temporarily disabled.

Test notebook added for running an 1D system with
and without tempering. Still needs to be added as
a test target.

Partial refactoring of ParallelTempering but still
plenty of things to improve in terms of safety and
clarity.

* Add temperature scale to 2D example energy

* Resurrect parallel tempering move

* Add missing macro guard for MPI

* Update mass centers

* Add particle update function to Space

* Add unit test to Space::updateParticle()

* Adds `SpaceFactory::makeWater()` used in the unittest.

* Change order of volume/particle update

* Rearrange updateParticles test case

* Fix hexagonal prism failing test

* Add 'temper' to test suite w. statistical test
  • Loading branch information
mlund authored Oct 26, 2020
1 parent 8415ccb commit ddaaec6
Show file tree
Hide file tree
Showing 20 changed files with 607 additions and 162 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ out.cereal
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.idea/deployment.xml

# Generated files
.idea/**/contentModel.xml
Expand Down
3 changes: 2 additions & 1 deletion docs/_docs/moves.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,8 @@ replica prefixes input and output files with `mpi0.`, `mpi1.`,
etc. and only exchange between neighboring processes is performed.

Parallel tempering is currently limited to systems with
constant number of particles, $N$.
constant number of particles, $N$, and the move is performed exactly
once per Monte Carlo cycle.


## Volume Move
Expand Down
11 changes: 11 additions & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ add_test(
set_property(TEST penalty APPEND PROPERTY ENVIRONMENT FAUNUS_EXECUTABLE=$<TARGET_FILE:faunus>)
set_property(TEST penalty APPEND PROPERTY ENVIRONMENT YASON_EXECUTABLE=${YASON})

if (ENABLE_MPI)
add_test(
NAME temper
COMMAND sh -c "jupyter nbconvert --execute temper.ipynb --ExecutePreprocessor.timeout=None"
WORKING_DIRECTORY ${EXAMPLES_DIR}/temper)
set_property(TEST temper APPEND PROPERTY ENVIRONMENT FAUNUS_EXECUTABLE=$<TARGET_FILE:faunus>)
set_property(TEST temper APPEND PROPERTY ENVIRONMENT MPIEXEC=${MPIEXEC_EXECUTABLE})
endif()

add_test(
NAME multipole
COMMAND sh -c "jupyter nbconvert --execute multipole.ipynb --ExecutePreprocessor.timeout=None"
Expand Down Expand Up @@ -254,6 +263,8 @@ install(FILES
${EXAMPLES_DIR}/swapconf/swapconf.state.json
${EXAMPLES_DIR}/swapconf/swapconf.conformations.pqr
${EXAMPLES_DIR}/swapconf/swapconf.weights.dat
${EXAMPLES_DIR}/temper/temper.yml
${EXAMPLES_DIR}/temper/temper.ipynb
${EXAMPLES_DIR}/titration/titration.yml
${EXAMPLES_DIR}/titration/titration.out.json
${EXAMPLES_DIR}/titration/titration.state.json
Expand Down
154 changes: 154 additions & 0 deletions examples/temper/temper.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Parallel Tempering\n",
"\n",
"This is a simple example of how to use parallel tempering in Faunus. The example is a particle moving in one dimension\n",
"and exposed to an osciallating potential. The example is taken from the book of Frenkel and Smit for parallel tempering."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import matplotlib\n",
"import matplotlib.cm as cm\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import jinja2, json, yaml, sys\n",
"from math import log, fabs, pi, cos, sin\n",
"from scipy.stats import ks_2samp"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"number_of_replicas = 6\n",
"scale_array = np.geomspace(1, 0.1, number_of_replicas)\n",
"temper = True # run with parallel tempering or not?\n",
"\n",
"# generate input files from the template file; one for each replica\n",
"with open(\"temper.yml\") as template_file:\n",
" template = jinja2.Template(template_file.read())\n",
" for replica, scale in enumerate(scale_array):\n",
" contents = template.render(scale=scale, micro=20000)\n",
" yml = yaml.safe_load(contents) # parse contents as YAML\n",
" if not temper: \n",
" del yml[\"moves\"][1]\n",
" file = open(\"mpi{}.input.json\".format(replica), 'w')\n",
" json.dump(yml, file, indent=4) # save JSON file\n",
" file.close()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Run simulation"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%bash\n",
"if [[ -z \"${FAUNUS_EXECUTABLE}\" ]]; then\n",
" mpirun -np 6 faunus -i input.json\n",
"else\n",
" echo \"Seems we're running CTest - use Faunus target from CMake\"\n",
" \"${MPIEXEC}\" -np 6 \"${FAUNUS_EXECUTABLE}\" -i input.json --nobar\n",
"fi"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Plot sampled histogram against expected result"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def energy(x):\n",
" s = 1 + sin(2*pi*x)\n",
" if x>=-2.00 and x<=-1.25: return 1*s\n",
" if x>=-1.25 and x<=-0.25: return 2*s\n",
" if x>=-0.25 and x<= 0.75: return 3*s\n",
" if x>= 0.75 and x<= 1.75: return 4*s\n",
" if x>= 1.75 and x<= 2.00: return 5*s\n",
" return 10000000\n",
"\n",
"u_vec = np.vectorize(energy)\n",
"offset = 1e10 # used to offset pmf to match above energy function\n",
"for replica in range(number_of_replicas):\n",
" if (replica==0):\n",
" x = np.loadtxt(\"mpi{}.x.dat.gz\".format(replica), usecols=[1])\n",
" hist, bins = np.histogram(x, bins=150)\n",
" x = (bins[:-1] + bins[1:]) / 2\n",
" pmf = -np.log(hist)\n",
" if (pmf.min() < offset):\n",
" offset = pmf.min();\n",
" plt.plot(x, pmf, label='{}'.format(replica))\n",
" \n",
"plt.legend(loc=0, frameon=False, title='replica')\n",
"plt.plot(x, u_vec(x) + offset, 'k--', alpha=0.6)\n",
"plt.xlabel(r'x')\n",
"plt.ylabel(r'PMF ($k_BT$)');"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Statistical test of output against expected result"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"statistic, pvalue = ks_2samp(pmf, u_vec(x)+offset)\n",
"if (pvalue < 0.95):\n",
" sys.exit(1)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.7"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
24 changes: 24 additions & 0 deletions examples/temper/temper.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Run with:
#
# $ yason.py penalty.yml | mpirun --np 4 --stdin all faunus
#
temperature: 300
random: { seed: fixed }
mcloop: { macro: 10, micro: {{micro}} }
geometry: {type: cuboid, length: [4,4,4]}
atomlist:
- A: {dp: 0.1}
moleculelist:
- mygroup: {atoms: [A], atomic: true, insdir: [1,1,0]}
insertmolecules:
- mygroup: {N: 1}
energy:
- example2d: {scale: {{scale}}, 2D: false } # this is hard-coded 2d potential, see Frenkel+Smit
moves:
- transrot: {molecule: mygroup, dir: [1,1,0], repeat: 10}
- temper: {format: xyz}
analysis:
- reactioncoordinate: {type: atom, property: x, file: x.dat.gz, index: 0, nstep: 1}
- savestate: {file: state.json}
- sanity: {nstep: 1000}

2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ target_link_libraries(libfaunus PRIVATE
# ========== faunus (main executable) ==========

add_executable(faunus faunus.cpp)
target_link_libraries(faunus PRIVATE libfaunus docopt progresstracker)
target_link_libraries(faunus PRIVATE libfaunus docopt progresstracker project_options)
target_compile_definitions(faunus PRIVATE SPDLOG_COMPILED_LIB)
set_target_properties(faunus PROPERTIES
POSITION_INDEPENDENT_CODE ON
Expand Down
25 changes: 18 additions & 7 deletions src/energy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,20 +555,31 @@ void Ewald::sync(Energybase *energybase_pointer, Change &change) {
void Ewald::to_json(json &j) const { j = data; }

double Example2D::energy(Change &) {
double s = 1 + std::sin(2 * pc::pi * i.x()) + std::cos(2 * pc::pi * i.y());
if (i.x() >= -2.00 && i.x() <= -1.25)
double s =
1 + std::sin(2.0 * pc::pi * particle.x()) + std::cos(2.0 * pc::pi * particle.y()) * static_cast<double>(use_2d);
s *= scale_energy;
if (particle.x() >= -2.00 && particle.x() <= -1.25)
return 1 * s;
if (i.x() >= -1.25 && i.x() <= -0.25)
if (particle.x() >= -1.25 && particle.x() <= -0.25)
return 2 * s;
if (i.x() >= -0.25 && i.x() <= 0.75)
if (particle.x() >= -0.25 && particle.x() <= 0.75)
return 3 * s;
if (i.x() >= 0.75 && i.x() <= 1.75)
if (particle.x() >= 0.75 && particle.x() <= 1.75)
return 4 * s;
if (i.x() >= 1.75 && i.x() <= 2.00)
if (particle.x() >= 1.75 && particle.x() <= 2.00)
return 5 * s;
return 1e10;
}
Example2D::Example2D(const json &, Space &spc) : i(spc.p.at(0).pos) { name = "Example2D"; }

Example2D::Example2D(const json &j, Space &spc) : particle(spc.p.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 {
j["scale"] = scale_energy;
j["2D"] = use_2d;
}

double ContainerOverlap::energy(Change &change) {
// if (spc.geo.type not_eq Geometry::CUBOID) // cuboid have PBC in all directions
Expand Down
19 changes: 16 additions & 3 deletions src/energy.h
Original file line number Diff line number Diff line change
Expand Up @@ -1213,9 +1213,22 @@ class SASAEnergy : public Energybase {
}; //!< SASA energy from transfer free energies
#endif

struct Example2D : public Energybase {
Point &i; // reference to 1st particle in the system
Example2D(const json &, Space &spc);
/**
* @brief Oscillating energy on a single particle
*
* This is 2D version of the oscillating potential used
* to illustrate parallel tempering in the book
* "Understanding Molecular Simulation" by D. Frenkel.
*/
class Example2D : public Energybase {
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;

public:
Example2D(const json &j, Space &spc);
double energy(Change &change) override;
};

Expand Down
2 changes: 1 addition & 1 deletion src/forcemove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ void ForceMoveBase::_move(Change &change) {
integrator->step(velocities, forces);
}
for (auto &group : spc.groups) { // update mass centers before returning to Monte Carlo
group.updateMassCenter(spc.geo.getBoundaryFunc());
group.updateMassCenter(spc.geo.getBoundaryFunc(), group.cm);
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1094,7 +1094,7 @@ TEST_CASE("[Faunus] Chameleon") {
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
Cuboid box(box_size);
HexagonalPrism geo(edge);
HexagonalPrism geo(edge, height);
Chameleon chameleon(geo, HEXAGONAL);
compare_boundary(chameleon, geo, box);
compare_vdist(chameleon, geo, box);
Expand Down
14 changes: 7 additions & 7 deletions src/group.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,17 @@ template <class T> void Group<T>::translate(const Point &d, Geometry::BoundaryFu

/**
* @param boundary_function Function to apply periodic boundaries
* @param original_mass_center Original or appriximate mass center used for PBC removal
*
* Only active, molecular groups are affected. Before the mass center can
* be calculated, the molecule is translated towards the center of the simulation
* Only active, molecular groups are affected. Before the mass center is
* calculated, the molecule is translated towards the center of the simulation
* box to remove possible periodic boundary conditions; then translated back again.
* The translation is done by subtracting / adding the initial mass center position.
* Control the shift by setting `cm` just before calling this function, or disable
* it by setting `cm={0,0,0}`.
* The translation is done by subtracting / adding `original_mass_center`.
*/
template <class T> void Group<T>::updateMassCenter(Geometry::BoundaryFunction boundary_function) {
template <class T>
void Group<T>::updateMassCenter(Geometry::BoundaryFunction boundary_function, const Point &original_mass_center) {
if (isMolecular() && !empty()) {
cm = Geometry::massCenter(begin(), end(), boundary_function, -cm);
cm = Geometry::massCenter(begin(), end(), boundary_function, -original_mass_center);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/group.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,8 @@ namespace Faunus {

void rotate(const Eigen::Quaterniond&, Geometry::BoundaryFunction); //!< Rotate all particles in group incl. internal coordinates (dipole moment etc.)

void updateMassCenter(Geometry::BoundaryFunction); //!< Calculates mass center
void updateMassCenter(Geometry::BoundaryFunction,
const Point &original_mass_center); //!< Calculates mass center
}; //!< End of Group class

// Group<Particle> is instantiated elsewhere (group.cpp)
Expand Down
Loading

0 comments on commit ddaaec6

Please sign in to comment.