Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Widom insertions #101

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/source/guides/kwargs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -276,4 +276,23 @@ input file.
| **Description:** list of properties to write to the ``.prp`` file. Valid options include: ``energy_total``, ``energy_intra``, ``energy_bond``, ``energy_angle``, ``energy_diheral``, ``energy_improper``, ``energy_intravdw``, ``energy_intraq``, ``energy_inter``, ``energy_intervdw``, ``energy_lrc``, ``energy_interq``, ``energy_recip``, ``energy_self``, ``enthalpy``, ``pressure``, ``pressure_xx``, ``pressure_yy``, ``pressure_zz``, ``volume``, ``nmols``, ``density``, ``mass_density``.
| **Default:** ``["energy_total", "energy_intra", "energy_inter", "enthalpy", "pressure", "volume", "nmols", "mass_density"]``

``widom_insertions``
~~~~~~~~~~~~~~~~~~~~
| **Type:** ``list`` of ``dicts``
| **Description:** One ``dict`` per box. The dictionary keys are the species numbers of the Widom test particle species, and each dictionary entry is a list of two ``ints``: ``[n_ins, widom_freq, n_subgroups]``, where ``n_ins`` is the number of Widom insertions to be performed after every ``widom_freq`` MC steps (or MC ``sweeps`` if ``units="sweeps"``) and ``n_subgroups`` is the number of Widom insertion subgroups per Widom insertion frame.
| **Default:** ``None``
| **Notes:** units of ``widom_freq`` cannot be time units, so they default to ``steps`` if ``units="minutes"``.


``cell_list``
~~~~~~~~~~~~~~~~~~~~
| **Type:** ``bool`` or ``str``
| **Description:** ``True`` if cell list overlap detection is to be used for Widom insertions.
| **Default:** ``False``

``adaptive_rmin``
~~~~~~~~~~~~~~~~~~~~
| **Type:** ``bool``, ``int``, or ``float``
| **Description:** Maximum desired intermolecular nonbonded atom pair energy, normalized by kBT. Setting as ``True`` sets no value in the input file, leaving it as the Cassandra's default value (708.0).
| **Default:** ``False``

105 changes: 105 additions & 0 deletions mosdef_cassandra/tests/test_writers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1806,3 +1806,108 @@ def test_rst_twobox(self, twobox_system):
assert "run 1000" in contents
assert "# Start_Type\ncheckpoint gemc.out.chk\n\n!" in contents
assert "# Run_Name\ngemc.rst.001.out" in contents

def test_widom_onecomp(self, onecomp_system):
(system, moveset) = onecomp_system
widom_insertions = [{1: [100, 10]}]
with temporary_directory() as tmp_dir:
with temporary_cd(tmp_dir):
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
widom_insertions=widom_insertions,
cell_list=True,
adaptive_rmin=100,
)
assert "# Widom_Insertion\nTrue\ncbmc 100 10 \n" in inp_data
assert "# Cell_List_Overlap\nTrue\n" in inp_data
assert "# Rcutoff_Low\n1.0\nadaptive 100\n" in inp_data

def test_widom_twocomp(self, twocomp_system):
(system, moveset) = twocomp_system
with temporary_directory() as tmp_dir:
with temporary_cd(tmp_dir):
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
widom_insertions=[{1: [100, 10], 2: [300, 5]}],
)
assert (
"# Widom_Insertion\nTrue\ncbmc 100 10 \ncbmc 300 5 \n"
in inp_data
)
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
widom_insertions=[{2: [300, 5], 1: [100, 10]}],
)
assert (
"# Widom_Insertion\nTrue\ncbmc 100 10 \ncbmc 300 5 \n"
in inp_data
)
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
widom_insertions=[{2: [300, 5]}],
)
assert (
"# Widom_Insertion\nTrue\nnone \ncbmc 300 5 \n" in inp_data
)
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
widom_insertions=[{1: [10000000000, 100]}],
)
assert (
"# Widom_Insertion\nTrue\ncbmc 10000000000 100 \nnone \n"
in inp_data
)

def test_widom_twocomptwobox(self, twocomptwobox_system):
(system, moveset) = twocomptwobox_system
with temporary_directory() as tmp_dir:
with temporary_cd(tmp_dir):
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
pressure=1.0 * u.bar,
widom_insertions=[
{1: [100, 10], 2: [300, 5]},
{2: [35, 25], 1: [50, 15]},
],
)
assert (
"# Widom_Insertion\nTrue\ncbmc 100 10 cbmc 50 15 \ncbmc 300 5 cbmc 35 25 "
in inp_data
)
inp_data = generate_input(
system=system,
moveset=moveset,
run_type="equilibration",
run_length=500,
temperature=300 * u.K,
pressure=1.0 * u.bar,
widom_insertions=[{2: [300, 5]}, {1: [50, 15]}],
)
assert (
"# Widom_Insertion\nTrue\nnone cbmc 50 15 \ncbmc 300 5 none "
in inp_data
)
108 changes: 105 additions & 3 deletions mosdef_cassandra/writers/inp_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,11 @@ def generate_input(
rcut_min = kwargs["rcut_min"].to_value()
else:
rcut_min = 1.0
inp_data += get_minimum_cutoff(rcut_min)
if "adaptive_rmin" in kwargs:
adaptive_rmin = kwargs["adaptive_rmin"]
else:
adaptive_rmin = False
inp_data += get_minimum_cutoff(rcut_min, adaptive_rmin)

# Pair Energy
if "pair_energy" in kwargs:
Expand Down Expand Up @@ -472,6 +476,12 @@ def generate_input(
block_avg_freq,
)

# Widom_Insertion section
if "widom_insertions" in kwargs:
inp_data += get_widom_info(kwargs["widom_insertions"], nbr_species)
if "cell_list" in kwargs:
inp_data += get_cell_list_info(kwargs["cell_list"])

# Properties section
if "properties" in kwargs:
properties = kwargs["properties"]
Expand Down Expand Up @@ -792,16 +802,21 @@ def get_seed_info(seed1=None, seed2=None):
return inp_data


def get_minimum_cutoff(cutoff):
def get_minimum_cutoff(cutoff, adaptive_rmin):
if not isinstance(cutoff, (float, int)):
raise TypeError("rcut_min should be of type float")
if not isinstance(adaptive_rmin, (bool, float, int, np.int_)):
raise TypeError("adaptive_rmin should be of type bool, int, or float")

inp_data = """
# Rcutoff_Low
{cutoff}""".format(
cutoff=cutoff
)

if not (adaptive_rmin is False):
inp_data += "\nadaptive "
if not (adaptive_rmin is True):
inp_data += str(adaptive_rmin)
inp_data += """
!------------------------------------------------------------------------------
"""
Expand Down Expand Up @@ -1854,6 +1869,90 @@ def get_cbmc_info(n_insert, n_dihed, cutoffs):
return inp_data


def get_widom_info(widom_insertions, nbr_species):
"""Get the Widom_Insertion section of the input file.
Parameters
----------
widom_insertions : list of dictionaries
One dictionary per box. The dictionary keys are the
species numbers of the Widom test particle species, and
each dictionary entry is a list of two integers:
[n_ins, widom_freq], where n_ins is the number of Widom insertions
to be performed after every widom_freq MC steps (or MC sweeps if the
simulation length units are sweeps).
nbr_species : integer
number of species in the simulation
"""
param_names = (
"Number of Widom insertions per frame",
"Frequency of Widom insertions",
"Number of Widom insertion subgroups",
)
inp_data = """
# Widom_Insertion
"""
if not widom_insertions:
inp_data += "False"
inp_data += """
!------------------------------------------------------------------------------
"""
return inp_data
inp_data += "True"
for i in range(nbr_species):
inp_data += "\n"
for boxdict in widom_insertions:
if (i + 1) in boxdict:
parlist = boxdict[i + 1]
inp_data += "cbmc "
if len(parlist) > 3 or len(parlist) < 2:
raise ValueError(
f"Widom insertions parameter list for species {i+1} must have 2 or 3 elements"
)
for j, j_el in enumerate(parlist):
# Verify that parameters are integers. Should this allow numpy.int64?
if not (
isinstance(j_el, int) or isinstance(j_el, np.int64)
):
raise TypeError(param_names[j] + " must be an integer")
if j_el < 0:
raise ValueError(
param_names[j] + " must not be negative"
)
if j == 1 and j_el < 1:
raise ValueError(
"Freqency of Widom insertions must be positive"
)
if j == 2 and not j_el:
continue
if j == 2:
if j_el > parlist[0]:
raise ValueError(
param_names[j]
+ " must not exceed "
+ param_names[0]
)
inp_data += "{} ".format(j_el)
else:
inp_data += "none "
inp_data += """
!------------------------------------------------------------------------------
"""
return inp_data


def get_cell_list_info(cell_list):
"""Add the cell list section of the input file."""

if not isinstance(cell_list, (bool, str)):
raise TypeError("cell_list must be of type bool or str")
inp_data = """
# Cell_List_Overlap
{}""".format(
cell_list
)
return inp_data


def print_valid_kwargs():
"""Print the valid keyword arguments with a brief description"""

Expand Down Expand Up @@ -1900,6 +1999,9 @@ def _get_possible_kwargs(desc=False):
'"pressure", "volume", "nmols", "density", "mass_density"'
),
"angle_style": "list of str, angle style for each species",
"widom_insertions": "list of dicts, one dict per box, key=species number of test particle, value=list of two integer Widom insertion parameters",
"cell_list": "boolean, true if using cell list overlap detection",
"adaptive_rmin": "float, maximum desired intermolecular nonbonded single atom pair energy for Widom insertions",
}
if desc:
return valid_kwargs
Expand Down