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

Cassandra gmso #756

Merged
merged 52 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
665feb6
Add fixed length bonds and angles to potential templates library.
rsdefever Jun 24, 2021
bd6ad75
Working to make MCF writer feature complete. Tests in progress.
rsdefever Jun 24, 2021
a6ce48d
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 24, 2021
a97826c
Add dimensions to Fixed Bond and Angle templates
emarinri Aug 11, 2023
c9bd1d1
Update keys in OPLS dihedral parameters dict
emarinri Aug 11, 2023
014b7ae
Fix typed OPLS ethane test for MCF format
emarinri Aug 11, 2023
f41d5a9
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 11, 2023
f8e044c
Add typed ethane test.
emarinri Aug 13, 2023
059ee03
Fix code style
emarinri Aug 13, 2023
48fd600
Add fixture to parse mcf into sections
emarinri Aug 13, 2023
aed4c44
Support Pydantic v1 and v2 (#752)
mattwthompson Aug 11, 2023
76415a8
Update MCF with GMSO builtin top site sorting
emarinri Aug 15, 2023
f093262
Use PotentialFilters to check for unique atomtypes
emarinri Aug 15, 2023
44a0928
Add test to check charge neutrality
emarinri Aug 15, 2023
b9efc1b
Add test for incompatible expressions
emarinri Aug 15, 2023
f9f2759
Test full MCF for Mie-Xe and LJ-Ar
emarinri Aug 16, 2023
5442297
Check charge neutrality in each molecule test
emarinri Aug 16, 2023
86902e6
Add exception for not neutral system in MCF writer
emarinri Aug 16, 2023
00cc2bc
Move parsing and neutrality check as utils
emarinri Aug 17, 2023
73d24de
Merge branch 'main' into cassandra-gmso
daico007 Sep 2, 2023
6094aba
Merge branch 'main' into cassandra-gmso
daico007 Sep 6, 2023
51fdad8
minor docstring/comment fixes, swap out simplify with symengine expand
daico007 Sep 11, 2023
5f4ca3c
Merge branch 'main' of https://github.com/mosdef-hub/gmso into cassan…
daico007 Sep 11, 2023
1dc0a3e
[pre-commit.ci] pre-commit autoupdate (#763)
pre-commit-ci[bot] Sep 12, 2023
a1c3fb5
Test for 0.5 factor of OPLS dihedral potential
emarinri Sep 12, 2023
4578812
Test to account for 0.5 factor in harmonic angles
emarinri Sep 12, 2023
6668363
Test for rigid angles using the TIP3P model
emarinri Sep 12, 2023
80c9be3
MCF writer test with topology with 10 argon mols
emarinri Sep 13, 2023
2b0c7ee
Tentative output of multiple MCF from one Topology
emarinri Sep 13, 2023
8317de4
Merge branch 'main' into cassandra-gmso
daico007 Sep 13, 2023
3951954
Change psi to phi and consts for ethylene xml test
emarinri Sep 14, 2023
9579252
Test for 0.5 factor in OPLS dihedrals
emarinri Sep 14, 2023
ae69a8c
Add cassandra test for gmso
CalCraven Sep 18, 2023
750d755
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 18, 2023
a7df9bc
Add test for two atom fragment
emarinri Sep 16, 2023
685f8b8
Test for molecule with one ring
emarinri Sep 16, 2023
1252d2b
Change nitrogen test to ethane ua
emarinri Sep 19, 2023
ec3a5dd
Add ethane rigid reference xml
emarinri Sep 19, 2023
a30d7bd
Use Fourier dihedrals as MCF standard dihedrals.
emarinri Sep 19, 2023
64b5b8f
Fix dihedral type output fourier to opls
emarinri Sep 19, 2023
ad68575
Match MCF GMSO Angle header to mb Angle header
emarinri Sep 20, 2023
0759365
Add a test comparing gmso and mbuild mcf writers
emarinri Sep 20, 2023
cf4e0c0
Use the Fourier converter for ethylene dihedrals
emarinri Sep 20, 2023
f462d5b
Write atom type masses instead of atom masses to account for UA beads
emarinri Sep 20, 2023
2a40bbf
Output correct case for dihedral styles in MCFs
emarinri Sep 20, 2023
c12a120
Merge branch 'main' into cassandra-gmso
daico007 Sep 20, 2023
6ff41e2
Merge branch 'cassandra-gmso' of https://github.com/emarinri/gmso int…
emarinri Sep 20, 2023
c1b9c19
Run an energy calculation to compare GMSO and mBuild MCF writers
emarinri Sep 20, 2023
dcdc342
Merge branch 'main' into cassandra-gmso
daico007 Sep 21, 2023
3c12947
Update gmso/tests/test_mcf.py
daico007 Sep 21, 2023
a1f4853
Update gmso/tests/test_mcf.py
daico007 Sep 21, 2023
f38cfb6
Merge branch 'main' into cassandra-gmso
daico007 Sep 22, 2023
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
220 changes: 124 additions & 96 deletions gmso/formats/mcf.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,16 @@ def write_mcf(top, filename):
"!Molecular connectivity file\n"
"!***************************************"
"****************************************\n"
"!File {} written by gmso {} at {}\n\n".format(
filename, __version__, str(datetime.datetime.now())
)
f"!File {filename} written by gmso {__version__} "
f"at {str(datetime.datetime.now())}\n\n"
)

mcf.write(header)
_write_atom_information(mcf, top, in_ring)
_write_bond_information(mcf, top)
_write_angle_information(mcf, top)
_write_dihedral_information(mcf, top)
# TODO: Add improper information
# _write_improper_information(mcf, top)
_write_improper_information(mcf, top)
_write_fragment_information(mcf, top, frag_list, frag_conn)
_write_intrascaling_information(mcf, top)

Expand Down Expand Up @@ -107,7 +105,13 @@ def _id_rings_fragments(top):
# Identify atoms in rings
bond_graph = nx.Graph()
bond_graph.add_edges_from(
[[bond.atom1.idx, bond.atom2.idx] for bond in top.bonds]
[
[
top.get_index(bond.connection_members[0]),
top.get_index(bond.connection_members[1]),
]
for bond in top.bonds
]
)
if len(top.bonds) == 0:
warnings.warn(
Expand All @@ -119,7 +123,7 @@ def _id_rings_fragments(top):
return in_ring, frag_list, frag_conn

# Check if entire molecule is connected. Warn if not.
if nx.is_connected(bond_graph) == False:
if not nx.is_connected(bond_graph):
raise ValueError(
"Not all components of the molecule are connected. "
"MCF files are for a single molecule and thus "
Expand All @@ -143,21 +147,24 @@ def _id_rings_fragments(top):
i: list(bond_graph.neighbors(i))
for i in range(bond_graph.number_of_nodes())
}
# First ID fused rings
fused_rings = []
rings_to_remove = []
for i in range(len(all_rings)):
ring1 = all_rings[i]
for j in range(i + 1, len(all_rings)):
ring2 = all_rings[j]
shared_atoms = list(set(ring1) & set(ring2))
if len(shared_atoms) == 2:
fused_rings.append(list(set(ring1 + ring2)))
rings_to_remove.append(ring1)
rings_to_remove.append(ring2)
for ring in rings_to_remove:
all_rings.remove(ring)
all_rings = all_rings + fused_rings

# Handle fused/adjoining rings
rings_changed = True
while rings_changed:
rings_changed = False
for ring1 in all_rings:
if rings_changed:
break
for ring2 in all_rings:
if ring1 == ring2:
continue
if len(set(ring1) & set(ring2)) > 0:
all_rings.remove(ring1)
all_rings.remove(ring2)
all_rings.append(list(set(ring1 + ring2)))
rings_changed = True
break

# ID fragments which contain a ring
for ring in all_rings:
adjacent_atoms = []
Expand Down Expand Up @@ -209,53 +216,54 @@ def _write_atom_information(mcf, top, in_ring):
Topology object
in_ring : list
Boolean for each atom idx True if atom belongs to a ring

"""
names = [site.name for site in top.sites]
types = [site.atom_type.name for site in top.sites]
# Based upon Cassandra; updated following 1.2.2 release
max_element_length = 6
max_atomtype_length = 20

# Sort to make sure the atom order matches the topology indexing
sorted_sites = [site for site in top.sites]
sorted_sites.sort(key=lambda site: top.get_index(site))
names = [site.name for site in sorted_sites]
types = [site.atom_type.name for site in sorted_sites]
daico007 marked this conversation as resolved.
Show resolved Hide resolved

# Check constraints on atom type length and element name length
# TODO: Update these following Cassandra release
# to be more reasonable values
n_unique_names = len(set(names))
for name in names:
if len(name) > 2:
if len(name) > max_element_length:
warnings.warn(
"Warning, name {} will be shortened "
"to two characters. Please confirm your final "
"MCF.".format(name)
f"Name: {name} will be shortened to {max_element_length}"
"characters. Please confirm your final MCF."
)

# Confirm that shortening names to two characters does not
# cause two previously unique atom names to become identical.
names = [name[:2] for name in names]
names = [name[:max_element_length] for name in names]
if len(set(names)) < n_unique_names:
warnings.warn(
"Warning, the number of unique names has been "
"reduced due to shortening the name to two "
"characters."
"The number of unique names has been reduced due"
f"to shortening the name to {max_element_length} characters."
)

n_unique_types = len(set(types))
for itype in types:
if len(itype) > 6:
for type_ in types:
daico007 marked this conversation as resolved.
Show resolved Hide resolved
if len(type_) > max_atomtype_length:
warnings.warn(
"Warning, type name {} will be shortened to six "
"characters as {}. Please confirm your final "
"MCF.".format(itype, itype[-6:])
f"Type name: {type_} will be shortened to "
f"{max_atomtype_length} characters as "
f"{type[-max_atomtype_length:]}. Please confirm your final MCF."
)
types = [itype[-6:] for itype in types]
types = [itype[-max_atomtype_length:] for itype in types]
if len(set(types)) < n_unique_types:
warnings.warn(
"Warning, the number of unique atomtypes has been "
"reduced due to shortening the atomtype name to six "
"characters."
"The number of unique atomtypes has been reduced due to "
f"shortening the atomtype name to {max_atomtype_length} characters."
)

# Detect VDW style
vdw_styles = set()
for site in top.sites:
vdw_styles.add(_get_vdw_style(site.atom_type))
for site in sorted_sites:
vdw_styles.add(_get_vdw_style(site))
if len(vdw_styles) > 1:
raise GMSOError(
"More than one vdw_style detected. "
Expand Down Expand Up @@ -283,8 +291,8 @@ def _write_atom_information(mcf, top, in_ring):
"{:<4d} "
"{:<6s} "
"{:<2s} "
"{:7.3f} "
"{:7.3f} ".format(
"{:8.4f} "
"{:12.8f} ".format(
idx + 1,
types[idx],
names[idx],
Expand Down Expand Up @@ -354,8 +362,9 @@ def _write_bond_information(mcf, top):
"{:s} "
"{:10.5f}\n".format(
idx + 1,
bond.connection_members[0].idx + 1, # TODO: Confirm the +1 here
bond.connection_members[1].idx + 1,
top.get_index(bond.connection_members[0])
+ 1, # TODO: Confirm the +1 here
top.get_index(bond.connection_members[1]) + 1,
"fixed",
bond.connection_type.parameters["r_eq"]
.in_units(u.Angstrom)
Expand All @@ -374,8 +383,6 @@ def _write_angle_information(mcf, top):
top : Topology
Topology object
"""
# TODO: Add support for fixed angles
angle_style = "harmonic"
header = (
"\n!Angle Format\n"
"!index i j k type parameters\n"
Expand All @@ -387,27 +394,38 @@ def _write_angle_information(mcf, top):
mcf.write("{:d}\n".format(len(top.angles)))
for idx, angle in enumerate(top.angles):
mcf.write(
"{:<4d} "
"{:<4d} "
"{:<4d} "
"{:<4d} "
"{:s} "
"{:10.5f} "
"{:10.5f}\n".format(
idx + 1,
angle.connection_members[0].idx + 1,
angle.connection_members[1].idx
+ 1, # TODO: Confirm order for angles i-j-k
angle.connection_members[2].idx + 1,
angle_style,
(0.5 * angle.connection_type.parameters["k"] / u.kb)
.in_units("K/rad**2")
.value, # TODO: k vs. k/2. conversion
angle.connection_type.parameters["theta_eq"]
.in_units(u.degree)
.value,
)
f"{idx + 1:<4d} "
f"{top.get_index(angle.connection_members[0]) + 1:<4d} "
f"{top.get_index(angle.connection_members[1]) + 1:<4d} "
f"{top.get_index(angle.connection_members[2]) + 1:<4d} "
)
angle_style = _get_angle_style(angle)
if angle_style == "fixed":
mcf.write(
"{:s} "
"{:10.5f}\n".format(
angle_style,
angle.connection_type.parameters["theta_eq"]
.in_units(u.degree)
.value,
)
)
elif angle_style == "harmonic":
mcf.write(
"{:s} "
"{:10.5f} "
"{:10.5f}\n".format(
angle_style,
(0.5 * angle.connection_type.parameters["k"] / u.kb)
.in_units("K/rad**2")
.value, # TODO: k vs. k/2. conversion
angle.connection_type.parameters["theta_eq"]
.in_units(u.degree)
.value,
)
)
else:
raise GMSOError("Unsupported angle style for Cassandra MCF writer")


def _write_dihedral_information(mcf, top):
Expand All @@ -419,8 +437,6 @@ def _write_dihedral_information(mcf, top):
The file object of the Cassandra mcf being written
top : Topology
Topology object
dihedral_style : string
Dihedral style for Cassandra to use
"""
# Dihedral info
header = (
Expand All @@ -435,7 +451,6 @@ def _write_dihedral_information(mcf, top):

mcf.write(header)

# TODO: Are impropers buried in dihedrals?
mcf.write("{:d}\n".format(len(top.dihedrals)))
for idx, dihedral in enumerate(top.dihedrals):
mcf.write(
Expand All @@ -445,10 +460,10 @@ def _write_dihedral_information(mcf, top):
"{:<4d} "
"{:<4d} ".format(
idx + 1,
dihedral.connection_members[0].idx + 1,
dihedral.connection_members[1].idx + 1,
dihedral.connection_members[2].idx + 1,
dihedral.connection_members[3].idx + 1,
top.get_index(dihedral.connection_members[0]) + 1,
top.get_index(dihedral.connection_members[1]) + 1,
top.get_index(dihedral.connection_members[2]) + 1,
top.get_index(dihedral.connection_members[3]) + 1,
)
)
dihedral_style = _get_dihedral_style(dihedral)
Expand All @@ -467,10 +482,6 @@ def _write_dihedral_information(mcf, top):
"{:10.5f}\n".format(
dihedral_style,
0.5
* dihedral.connection_type.parameters["k0"]
.in_units("kJ/mol")
.value,
0.5
* dihedral.connection_type.parameters["k1"]
.in_units("kJ/mol")
.value,
Expand All @@ -482,6 +493,10 @@ def _write_dihedral_information(mcf, top):
* dihedral.connection_type.parameters["k3"]
.in_units("kJ/mol")
.value,
0.5
* dihedral.connection_type.parameters["k4"]
.in_units("kJ/mol")
.value,
)
)
elif dihedral_style == "charmm":
Expand Down Expand Up @@ -542,19 +557,19 @@ def _write_improper_information(mcf, top):
mcf.write(header)
mcf.write("{:d}\n".format(len(top.impropers)))

improper_type = "harmonic"
improper_style = "harmonic"
for i, improper in enumerate(top.impropers):
mcf.write(
"{:<4d} {:<4d} {:<4d} {:<4d} {:<4d}"
" {:s} {:8.3f} {:8.3f}\n".format(
" {:s} {:10.5f} {:10.5f}\n".format(
i + 1,
improper.atom1.idx + 1,
improper.atom2.idx + 1,
improper.atom3.idx + 1,
improper.atom4.idx + 1,
improper_type,
improper.type.psi_k * KCAL_TO_KJ,
improper.type.psi_eq,
top.get_index(improper.connection_members[0]) + 1,
top.get_index(improper.connection_members[1]) + 1,
top.get_index(improper.connection_members[2]) + 1,
top.get_index(improper.connection_members[3]) + 1,
improper_style,
0.5 * improper.connection_type.parameters["k"],
improper.connection_type.parameters["phi_eq"],
)
)

Expand Down Expand Up @@ -649,25 +664,38 @@ def _check_compatibility(top):
accepted_potentials = (
potential_templates["LennardJonesPotential"],
potential_templates["MiePotential"],
potential_templates["FixedBondPotential"],
potential_templates["HarmonicBondPotential"],
potential_templates["HarmonicAnglePotential"],
potential_templates["FixedAnglePotential"],
potential_templates["PeriodicTorsionPotential"],
potential_templates["OPLSTorsionPotential"],
potential_templates["RyckaertBellemansTorsionPotential"],
)
check_compatibility(top, accepted_potentials)


def _get_vdw_style(atom_type):
def _get_vdw_style(site):
"""Return the vdw style."""
vdw_styles = {
"LJ": potential_templates["LennardJonesPotential"],
"Mie": potential_templates["MiePotential"],
}

return _get_potential_style(vdw_styles, atom_type)
return _get_potential_style(vdw_styles, site.atom_type)


def _get_angle_style(angle):
"""Return the angle style."""
angle_styles = {
"harmonic": potential_templates["HarmonicAnglePotential"],
"fixed": potential_templates["FixedAnglePotential"],
}

return _get_potential_style(angle_styles, angle.connection_type)


def _get_dihedral_style(dihedral_type):
def _get_dihedral_style(dihedral):
"""Return the dihedral style."""
dihedral_styles = {
"charmm": potential_templates["PeriodicTorsionPotential"],
Expand All @@ -676,7 +704,7 @@ def _get_dihedral_style(dihedral_type):
"ryckaert": potential_templates["RyckaertBellemansTorsionPotential"],
}

return _get_potential_style(dihedral_styles, dihedral_type)
return _get_potential_style(dihedral_styles, dihedral.connection_type)


def _get_potential_style(styles, potential):
Expand Down
8 changes: 8 additions & 0 deletions gmso/lib/jsons/FixedAnglePotential.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "FixedAnglePotential",
"expression": "DiracDelta(theta-theta_eq)",
"independent_variables": "theta",
"expected_parameters_dimensions": {
"theta_eq": "angle"
}
}
Loading