-
Notifications
You must be signed in to change notification settings - Fork 35
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
Charmm parameters writer #848
base: main
Are you sure you want to change the base?
Changes from 2 commits
3327de2
25f893f
13e0d6e
2c7df9c
7bfbb3f
ea5ec28
2f10015
362f747
86b6d06
2f7657e
9b4c3e7
d107c21
d150a31
2fb6ffe
2fab3c0
c325111
a3c0d42
23976ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
"""CHARMM Par format.""" | ||
|
||
from gmso.formats.formats_registry import saves_as | ||
from gmso.utils.units import LAMMPS_UnitSystems | ||
|
||
|
||
@saves_as(".prm", ".par") | ||
def write_prm(topology, filename): | ||
"""Write CHARMM Parameter .prm file given a parametrized structure. | ||
|
||
Notes | ||
----- | ||
Follows format according to | ||
https://www.ks.uiuc.edu/Training/Tutorials/namd/namd-tutorial-unix-html/ | ||
node25.html | ||
Furthermore, ParmEd can support writing CHARMM par, rtf, str files | ||
by converting the parmed.Structure into parmed.CharmmParameterSet | ||
|
||
Parmed stores rmin/2 in "rmin" | ||
""" | ||
unit_system = LAMMPS_UnitSystems("real") # ang, kcal/mol, amu | ||
# ATOMS | ||
with open(filename, "w") as f: | ||
f.write("ATOMS\n") | ||
unique_atoms = set() | ||
for site in topology.sites: | ||
unique_atoms.add( | ||
( | ||
site.atom_type.name, | ||
unit_system.convert_parameter( | ||
site.atom_type.mass, n_decimals=6, name="mass" | ||
), | ||
) | ||
) | ||
for atom in unique_atoms: | ||
f.write("MASS -1 {:8s} {:8s}\n".format(atom[0], atom[1])) | ||
|
||
# TODO: Works for harmonic bonds | ||
f.write("\nBONDS\n") | ||
for bond in topology.bond_types: | ||
atom1, atom2 = bond.member_types | ||
f.write( | ||
"{:8s} {:8s} {:.5f} {:.5f}\n".format( | ||
atom1, atom2, bond.parameters["k"], bond.parameters["r_eq"] | ||
) | ||
) | ||
|
||
f.write("\nANGLES\n") | ||
for angle in topology.angle_types: | ||
atom1, atom2, atom3 = angle.member_types | ||
if angle.name == "UreyBradleyAnglePotential": | ||
f.write( | ||
"{:8s} {:8s} {:8s} {:.5f} {:.5f} {:.5f} {:.5f}\n".format( | ||
atom1, | ||
atom2, | ||
atom3, | ||
angle.parameters["k"], | ||
angle.parameters["theta_eq"], | ||
angle.parameters["kub"], | ||
angle.parameters["r_eq"], | ||
) | ||
) | ||
else: # assume harmonic style: | ||
f.write( | ||
"{:8s} {:8s} {:8s} {:.5f} {:.5f}\n".format( | ||
atom1, | ||
atom2, | ||
atom3, | ||
angle.parameters["k"], | ||
angle.parameters["theta_eq"], | ||
) | ||
) | ||
|
||
# These dihedrals need to be PeriodicTorsion Style (Charmm style) | ||
f.write("\nDIHEDRALS\n") | ||
for dihedral in topology.dihedral_types: | ||
# works for PeriodicTorsion | ||
atom1, atom2, atom3, atom4 = dihedral.member_types | ||
f.write( | ||
"{:8s} {:8s} {:8s} {:8s} {:.5f} {:5f} {:.5f}\n".format( | ||
atom1, | ||
atom2, | ||
atom3, | ||
atom4, | ||
dihedral.parameters["k"][0], | ||
dihedral.parameters["n"][0], | ||
dihedral.parameters["phi_eq"][0], | ||
) | ||
) | ||
|
||
# TODO: No support for harmonic impropers | ||
|
||
f.write("\nIMPROPER\n") | ||
for improper in topology.improper_types: | ||
atom1, atom2, atom3, atom4 = improper.member_types | ||
f.write( | ||
"{:8s} {:8s} {:8s} {:8s} {:.5f} {:5f} {:.5f}\n".format( | ||
atom1, | ||
atom2, | ||
atom3, | ||
atom4, | ||
improper.parameters["phi_k"][0], | ||
improper.parameters["n"][0], | ||
improper.parameters["phi_eq"][0], | ||
) | ||
) | ||
|
||
f.write("\nNONBONDED\n") | ||
nonbonded14 = topology.scaling_factors[0][2] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't need to be fixed here, but I feel like this attribute should be more readable rather than just knowing that 0th index is LJ and 1st index is electrostatics. Maybe a dict? I can open a separate issue. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Was thinking the same thing myself. I think it should also be {"electrostatics":{"1-4":0.5}}, instead of a fixed list. Could potentially be some odd 1-5 interactions at some point. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's sort of what I was thinking as well.
|
||
|
||
for atype in topology.atom_types: | ||
# atype, 0.0, epsilon, rmin/2, 0.0, epsilon(1-4), rmin/2 (1-4) | ||
f.write( | ||
"{:8s} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f} {:8.3f}\n".format( | ||
atype.name, | ||
0.0, # ignored, | ||
atype.parameters["epsilon"], | ||
atype.parameters["sigma"], | ||
0.0, | ||
atype.parameters["epsilon"] * nonbonded14, | ||
atype.parameters["sigma"] * nonbonded14, | ||
) | ||
) | ||
f.write("\nEND") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "UreyBradleyPotential", | ||
"expression": "0.5 * k * (theta-theta_eq)**2 + 0.5 * kub * (r-r_eq)**2", | ||
"independent_variables": ["theta", "r"], | ||
"expected_parameters_dimensions": { | ||
"k":"energy/angle**2", | ||
"theta_eq": "angle", | ||
"kub": "energy/length**2", | ||
"r_eq": "length" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from gmso import ForceField, Topology | ||
from gmso.parameterization import apply | ||
from gmso.tests.base_test import BaseTest | ||
from gmso.utils.io import get_fn | ||
|
||
# TODO: Sorting of atomtypes info in connection types | ||
# TODO: Test of correct potential forms | ||
# TODO: Handle iterating over unique types | ||
# TODO: Handle multiple values in charmm dihedral k as np array | ||
|
||
|
||
class TestPar(BaseTest): | ||
def test_save_charmm(self): | ||
top = Topology.load(get_fn("charmm_dihedral.mol2")) | ||
for site in top.sites: | ||
site.name = f"_{site.name}" | ||
|
||
ff = ForceField(get_fn("charmm_truncated.xml")) | ||
ptop = apply( | ||
top, ff, identify_connections=True, ignore_params=["dihedral", "improper"] | ||
) | ||
ptop.save("charmm_dihedral.prm") | ||
|
||
def test_par_parameters(self, ethane, oplsaa_forcefield): | ||
from gmso.parameterization import apply | ||
|
||
ptop = apply(ethane, oplsaa_forcefield) | ||
|
||
ptop.save(filename="ethane-opls.par") | ||
from parmed.charmm import CharmmParameterSet | ||
|
||
pset = CharmmParameterSet.load_set(pfile="ethane-opls.par") | ||
assert len(pset.bond_types) == 3 | ||
assert len(pset.angle_types) == 3 | ||
assert len(pset.atom_types) == 2 | ||
|
||
def test_parameter_forms(self, ethane): | ||
# test that the values are correct | ||
# write mbuild file and compare to gmso file | ||
pass | ||
|
||
def test_raise_errors(self): | ||
# only takes harmonic bonds | ||
# only takes harmonic of ub angles | ||
pass |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
@<TRIPOS>MOLECULE | ||
DPPC | ||
4 3 1 0 1 | ||
SMALL | ||
USER_CHARGES | ||
@<TRIPOS>CRYSIN | ||
13.9528 17.1622 31.1323 90.0000 90.0000 90.0000 1 1 | ||
@<TRIPOS>ATOM | ||
1 CTL1 -0.1558 6.4170 4.4541 CTL1 1 DPPC 1.500000 | ||
2 OSL -0.7278 7.6038 3.8783 OSL 1 DPPC -0.780000 | ||
3 PL -0.5108 6.0856 5.8561 PL 1 DPPC -0.780000 | ||
4 OSLP -0.2550 5.1063 3.5611 OSLP 1 DPPC -0.570000 | ||
|
||
@<TRIPOS>BOND | ||
1 1 2 1 | ||
2 2 3 1 | ||
3 1 4 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<ForceField><AtomTypes> | ||
<Type class="CTL1" element="_CTL1" mass="12.011" name="CTL1" def="[_CTL1]"/> | ||
<Type class="OSL" element="_OSL" mass="16.008" name="OSL" def="[_OSL]"/> | ||
<Type class="PL" element="_PL" mass="15" name="PL" def="[_PL]"/> | ||
<Type class="OSLP" element="_OSLP" mass="16.008" name="OSLP" def="[_OSLP]"/> | ||
</AtomTypes> | ||
<HarmonicBondForce> | ||
<Bond k="1.00000" length="1" type1="CTL1" type2="CTL1"/> | ||
<Bond k="2" length="2." type1="CTL1" type2="OSL"/> | ||
<Bond k="3.00000" length="3" type1="CTL1" type2="PL"/> | ||
<Bond k="4.00000" length="4" type1="CTL1" type2="OSLP"/> | ||
<Bond k="5.00000" length="5" type1="OSL" type2="PL"/> | ||
<Bond k="6.00000" length="6" type1="OSL" type2="OSL"/> | ||
<Bond k="7.00000" length="7" type1="OSL" type2="OSLP"/> | ||
<Bond k="8.00000" length="8" type1="PL" type2="OSLP"/> | ||
<Bond k="9.00000" length="9" type1="PL" type2="PL"/> | ||
<Bond k="10.00000" length="10." type1="OSLP" type2="OSLP"/> | ||
</HarmonicBondForce> | ||
<HarmonicAngleForce> | ||
<Angle angle="1.93732" k="418.40000" type1="CTL1" type2="OSL" type3="PL"/> | ||
<Angle angle="2.93732" k="518.40000" type1="CTL1" type2="OSL" type3="OSLP"/> | ||
<Angle angle="3.93732" k="618.40000" type1="OSLP" type2="OSL" type3="PL"/> | ||
<Angle angle="4.93732" k="718.40000" type1="OSLP" type2="CTL1" type3="OSL"/> | ||
</HarmonicAngleForce> | ||
<AmoebaUreyBradleyForce> | ||
<UreyBradley d="0.21400" k="20920.00000" class1="CTL1" class2="OSL" class3="PL"/> | ||
</AmoebaUreyBradleyForce> | ||
<PeriodicTorsionForce> | ||
<!--<Proper k1="69.00000" periodicity1="1" phase1="0.00000" type1="PL" type2="OSL" type3="CTL1" type4="OSLP"/>--> | ||
<Proper k1="69.00000" periodicity1="1" phase1="0.00000" type1="PL" type2="OSL" type3="CTL1" type4="OSLP" k2="138.00000" periodicity2="2" phase2="45.00000" k3="138.00000" periodicity3="3" phase3="90.00000"/> | ||
</PeriodicTorsionForce> | ||
<CustomTorsionForce energy="k*(theta-theta0)^2"> | ||
<PerTorsionParameter name="k"/> | ||
<PerTorsionParameter name="theta0"/> | ||
<Improper k="200.83200" theta0="0.00000" type1="PL" type2="OSL" type3="CTL1" type4="OSLP"/> | ||
<Improper k="300.83200" theta0="1.00000" type1="OSL" type2="PL" type3="CTL1" type4="OSLP"/> | ||
</CustomTorsionForce> | ||
<NonbondedForce coulomb14scale="1.0" lj14scale="1.0"> | ||
<Atom epsilon="1.192464" sigma="1.04000135244450124" type="CTL1" charge="0.0"/> | ||
<Atom epsilon="2.192464" sigma="2.04000135244450124" type="OSL" charge="0.0"/> | ||
<Atom epsilon="3.192464" sigma="3.04000135244450124" type="PL" charge="0.0"/> | ||
<Atom epsilon="4.192464" sigma="4.04000135244450124" type="OSLP" charge="0.0"/> | ||
</NonbondedForce> | ||
</ForceField> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to make sure we're using this everywhere we write out parameters? I see we use it for the atom mass, but not anywhere else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, for in order to handle any input unyts this will do the filtering. I was just too lazy to do it every time at first. Was wondering if you think we should make a system that isn't specific to LAMMPS for this one. It is the exact same as the real units, but maybe a touch confusing to copy them across to other engines. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if
unit_system
should be a parameter ofwrite_prm
that defaults toNone
. If nothing is passed, then all units are used as-is from the toplogy? If we setunit_system = topology.unit_system
as default behavior, would that work as well?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PRM file looks to me like it has default assumed units of kCal/mol, angstrom, amu, radians, etc for it's units. So it can only convert to one defined system.