Skip to content

Commit

Permalink
samples: New Gibbs Ensemble scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
jhossbach authored and jngrad committed Jun 3, 2021
1 parent ae3094f commit 7fe8f51
Show file tree
Hide file tree
Showing 9 changed files with 1,062 additions and 649 deletions.
17 changes: 17 additions & 0 deletions samples/gibbs_ensemble/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Gibbs ensemble simulations using ESPResSo

This sample code shows how to implement the Gibbs ensemble into ESPResSo using
the `multiprocessing` module to create two separate systems and communicate to
them through the main script.

The program consists of 3 files: In the `run_sim.py` script, the client boxes
are started and given instructions. The clients use the `Gibbs_Client` subclass
in `client_system.py` which inherits the necessary methods for communication and
for the trial moves from the `Client` class in `gibbs_ensemble.py`. This subclass
allows for energy correction terms as is seen in this sample. Because ESPResSo
only allows one instance of a system in one process, the system is created using
the `set_up_system()` function and initialized once the `run()` method in the
`Client` class is called.

All equations are taken from Frenkel, Smit: *Understanding Molecular Simulation* 2002,
doi:[10.1016/B978-0-12-267351-1.X5000-7](https://doi.org/10.1016/B978-0-12-267351-1.X5000-7).
87 changes: 87 additions & 0 deletions samples/gibbs_ensemble/client_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#
# Copyright (C) 2013-2021 The ESPResSo project
#
# This file is part of ESPResSo.
#
# ESPResSo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ESPResSo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
This script uses the System class from the :file:`gibbs_ensemble.py`
script and creates a subclass in which you can create your system, add
energy corrections etc.
"""

import espressomd
import gibbs_ensemble
import numpy as np

espressomd.assert_features("LENNARD_JONES")

# Lennard Jones parameters
LJ_EPSILON = 1.0
LJ_SIGMA = 1.0
LJ_CUTOFF = 2.5 * LJ_SIGMA
LJ_SHIFT = - (np.power(LJ_SIGMA / LJ_CUTOFF, 12) -
np.power(LJ_SIGMA / LJ_CUTOFF, 6))


def calc_shift_correction(density):
""" Shift correction by integrating the constant part from 0 to the cutoff
radius """
return 4. / 3. * np.pi * LJ_CUTOFF**3 * LJ_SHIFT * 4 * LJ_EPSILON * density


def calc_tail_correction(density):
""" Lennard-Jones truncation correction (See Frenkel, Smit, Understanding
molecular simulation, eq. (3.2.5)) """
return 8. / 3. * np.pi * density * LJ_EPSILON * LJ_SIGMA**3 * \
(1. / 3. * np.power(LJ_SIGMA / LJ_EPSILON, 9) -
np.power(LJ_SIGMA / LJ_EPSILON, 3))


class Gibbs_Client(gibbs_ensemble.Client):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

@property
def energy(self):
""" Additional correction terms """
return super().energy # + \
# calc_tail_correction(self.density) + \
# calc_shift_correction(self.density)

def init_system(self):
""" Initialize the system (this is executed only when the run
method of the process is executed) """
self.system = set_up_system(self.init_box_length, self.init_num_part)


def set_up_system(box_length, num_part):
""" Use this function to create the system """

system = espressomd.System(box_l=3 * [box_length])

system.cell_system.set_n_square(use_verlet_lists=False)
system.cell_system.skin = 0
system.time_step = 0.01

system.part.add(pos=np.random.random((num_part, 3)) * box_length)

system.non_bonded_inter[0, 0].lennard_jones.set_params(
epsilon=LJ_EPSILON,
sigma=LJ_SIGMA,
cutoff=LJ_CUTOFF,
shift=LJ_SHIFT)

return system
232 changes: 232 additions & 0 deletions samples/gibbs_ensemble/create_fits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
#
# Copyright (C) 2021 The ESPResSo project
#
# This file is part of ESPResSo.
#
# ESPResSo is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ESPResSo is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Take in all simulation data, compute equilibrium values for every
datafile and take means for simulations with same temperature.
Plot the phase diagram and the fitted critical point.
"""

import numpy as np
from scipy.optimize import curve_fit
from scipy import signal
import pickle
import gzip
import matplotlib.pyplot as plt
import argparse
import logging


parser = argparse.ArgumentParser(
description="""Computes mean values for every file given as input,
computes the mean values for same temperatures and different seeds,
tries to compute the critical point using rectilinear and scaling
law and plots the phase diagram.""")
parser.add_argument(
'files',
nargs='+',
help="(All) files of the simulated system")
args = parser.parse_args()

# Set the logging level
logging.basicConfig(
format='%(asctime)s %(levelname)s: %(message)s',
datefmt='%H:%M:%S',
level=logging.INFO
)

# Warmup length to skip at beginning
WARMUP_LENGTH = int(3e3)

# Lists for (unsorted) densities and temperature
dens_liquid = []
dens_gas = []
errors_liquid = []
errors_gas = []
kTs = []

# 3D critical exponent (See Frenkel, Smit: Understanding Molecular
# Simulation 2002, p.217)
BETA = 0.32


def autocorr(x):
""" Compute the normalized autocorrelation function """
_x = x - np.mean(x)
return (signal.convolve(
_x, _x[::-1], mode='full', method='auto')[(np.size(_x) - 1):]) / (np.sum(_x * _x))


def relaxation_time(x):
""" Compute the relaxation time """
corr = autocorr(x)
popt, _ = curve_fit(lambda x, a: np.exp(-a * x),
range(np.size(corr)), corr, p0=(1. / 15000))
return popt[0]


def std_error_mean(x):
""" Compute the standard error of the mean with correlated samples """
autocorr_time = relaxation_time(x)
N_eff = np.size(x) / (2 * autocorr_time)
return np.sqrt(np.var(x) / N_eff)


def scaling_law(kT, B, kT_c):
""" Scaling law, see Frenkel, Smit, eq. (8.3.6) """
return B * np.abs(kT - kT_c)**BETA


def rectilinear_law(kT, p_c, A, kT_c):
""" Rectilinear law, see Frenkel, Smit, eq. (8.3.7) """
return p_c + A * np.abs(kT - kT_c)


# Load data
for f in args.files:

logging.info("Loading {}...".format(f))
try:
with gzip.open(f, 'rb') as _f:
data = pickle.load(_f)
except FileNotFoundError:
raise FileNotFoundError("File {} not found.".format(f))

kT = data["temperature"]

# Calculate densities of both phases
density1 = np.mean(data["densities_box01"][WARMUP_LENGTH:])
density2 = np.mean(data["densities_box02"][WARMUP_LENGTH:])

dens_liquid.append(np.max((density1, density2)))
dens_gas.append(np.min((density1, density2)))

# Calculate errors
errors_liquid.append(
std_error_mean(data["densities_box01"]) if density1 > density2 else
std_error_mean(data["densities_box02"]))
errors_gas.append(
std_error_mean(data["densities_box01"]) if density1 < density2 else
std_error_mean(data["densities_box02"]))
kTs.append(kT)

# Sort temperatures and densities respectively
order = np.argsort(kTs)
kTs = np.array(kTs)[order]
dens_liquid = np.array(dens_liquid)[order]
dens_gas = np.array(dens_gas)[order]
errors_liquid = np.array(errors_liquid)[order]
errors_gas = np.array(errors_gas)[order]

# Average over simulation results with different seeds
dens_liquid = [np.mean([dens_liquid[i] for (i, T) in enumerate(
kTs) if T == kT]) for kT in np.unique(kTs)]
dens_gas = [np.mean([dens_gas[i] for (i, T) in enumerate(kTs) if T == kT])
for kT in np.unique(kTs)]

# Compute Errors as maximum of each simulation error
errors_liquid = [np.max([errors_liquid[i] for (i, T) in enumerate(
kTs) if T == kT]) for kT in np.unique(kTs)]
errors_gas = [np.max([errors_gas[i] for (i, T) in enumerate(kTs) if T == kT])
for kT in np.unique(kTs)]
kTs = np.unique(kTs)

# Convert to arrays to do math
dens_liquid = np.array(dens_liquid)
dens_gas = np.array(dens_gas)

# Needed for scaling law and rectilinear law
y_scaling = dens_liquid - dens_gas
y_rectilinear = 0.5 * (dens_liquid + dens_gas)

# Fit using scaling law
fit_scaling, p_cov_scaling = curve_fit(
scaling_law, kTs, y_scaling, p0=(1., 1.), maxfev=6000)

# Print fit values
logging.info("Fits using scaling law: B, T_c: {}".format(fit_scaling))
logging.info("Fit uncertainty: {}".format(np.diag(p_cov_scaling)))

# Critical temperature obtained via scaling law
kT_c_scaling = fit_scaling[1]

# Fit using rectilinear law
fit_rectilinear, p_cov_rectilinear = curve_fit(
rectilinear_law, kTs, y_rectilinear, p0=(
0.3, 0.2, kT_c_scaling), maxfev=6000)

# Print fit values
logging.info(
"Fits using rectilinear law: p_c, A, T_c: {}".format(fit_rectilinear))
logging.info("Fit uncertainty: {}".format(np.diag(p_cov_rectilinear)))

# Critical point obtained via rectilinear law
p_c = fit_rectilinear[0]
kT_c = fit_rectilinear[2]

# Save results
with gzip.open('result.dat.gz', 'wb') as f:
pickle.dump(
{"dens_liquid": dens_liquid,
"dens_gas": dens_gas,
"errors_liquid": errors_liquid,
"errors_gas": errors_gas,
"kTs": kTs,
"kT_c_scaling": kT_c,
"p_c": p_c},
f)

# Plot results
ms = 40

plt.errorbar(dens_liquid,
kTs,
xerr=errors_liquid,
fmt='o',
c='red')

plt.errorbar(dens_gas,
kTs,
xerr=errors_gas,
fmt='o',
c='red',
label='simulation data')

plt.scatter(p_c,
kT_c,
s=ms, c='black',
marker='o',
label='crit. point')

plt.scatter(0.5 * (dens_liquid + dens_gas),
kTs,
s=ms - 10, c='black',
marker='x')

plt.plot(rectilinear_law(kTs,
p_c,
fit_rectilinear[1],
fit_rectilinear[2]),
kTs)

plt.xlabel(r"Particle density $\rho*$")
plt.ylabel(r"Temperature $T*$")
plt.legend()

plt.savefig("phase_diagram.pdf")
plt.show()
Loading

0 comments on commit 7fe8f51

Please sign in to comment.