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

Lammps Writer Changes For Updated Topology Class #701

Merged
merged 44 commits into from
Jul 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
8f9fe12
Initial commit for lammpswriter testing
CalCraven Nov 21, 2022
6fd6ecc
LAMMPS writer refactor for updated topology class
CalCraven Nov 28, 2022
2f16b6d
Merge branch 'main' of https://github.com/mosdef-hub/gmso into 700-la…
CalCraven Nov 28, 2022
6119f1b
More changes to ParmEd comparisons for testing and connection convers…
CalCraven Feb 3, 2023
fe88d6b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Feb 3, 2023
ff69064
Overhaul of functions to use in testing lammps conversions and to tes…
CalCraven Apr 20, 2023
5c15c84
Conversion of parmed structure to GMSO should now properly identify u…
CalCraven Apr 26, 2023
fd65d8e
Clean up legacy functions necessary for mapping types to connections
CalCraven Apr 27, 2023
723cad4
Merge branch 'main' into 721-conv-param-pmd
daico007 Apr 28, 2023
85763c1
revert changes to parmed_conversion
CalCraven May 1, 2023
120ecff
Update gmso/external/convert_parmed.py
CalCraven May 1, 2023
80f4f35
Update gmso/external/convert_parmed.py
CalCraven May 1, 2023
6134376
Update gmso/external/convert_parmed.py
CalCraven May 1, 2023
511615a
Fix merge conflicts to main
CalCraven May 1, 2023
408ef1b
Modified ParmEd loading to sort new GMSO topology connections by site…
CalCraven May 2, 2023
5b1b8bc
Merge in changes from conversion to ParmEd pending PR
CalCraven May 2, 2023
fb1dae5
Unit styles, atom_styles, sorting of orders, and minor improvements w…
CalCraven May 12, 2023
53e67d6
Merge branch 'main' of https://github.com/mosdef-hub/gmso into 700-la…
CalCraven May 12, 2023
4fc3008
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 12, 2023
8eb5d65
Update unit conversions to write before writing out, not at initial s…
CalCraven May 16, 2023
bfea4f8
Fixed reading in dimensionless values for mass, box length, and charg…
CalCraven May 16, 2023
444a6d5
Only raise missing conversion error if types are found
CalCraven May 16, 2023
c9a977e
merge conflicts
CalCraven May 16, 2023
8ba6f17
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 16, 2023
85fbe99
Fixes to QL bugs for function formatting
CalCraven May 17, 2023
ec56db6
Fixes for docstrings
CalCraven May 17, 2023
f9c9c60
Merge branch 'main' of https://github.com/mosdef-hub/gmso into 700-la…
CalCraven May 17, 2023
ca4e227
remove unused parameters from topology box
CalCraven May 17, 2023
b500cdf
Fix merge conflicts
CalCraven May 17, 2023
3df21b0
Fix bug with default dict for lammpswriter lj values
CalCraven May 18, 2023
be63e33
merge main
CalCraven Jun 20, 2023
5e9229b
Address PR review for docstring formatting, minor syntax changes, imp…
CalCraven Jun 20, 2023
01471a7
Address units writing in lammpswriter, remove WIP tag, variable renam…
CalCraven Jun 20, 2023
c2cfb16
Fixes to code QL, remove angles from unit validation due to reliance …
CalCraven Jun 20, 2023
007a288
Reformatting for consistency across writers, addition of standard rou…
CalCraven Jun 26, 2023
8f4645f
Fixes to bug with * printed in connections instead of member indices
CalCraven Jun 26, 2023
a676a57
Added tests for charmm impropers
CalCraven Jun 29, 2023
47185f7
merge conflicts
CalCraven Jun 29, 2023
a666e99
Remove extraneous text
CalCraven Jun 29, 2023
93ba838
initialize test variable 'end'
CalCraven Jun 29, 2023
b43d9b8
pin pydantic to <2.0 to address tests failing
CalCraven Jul 5, 2023
83faf4b
tests for unit styles
CalCraven Jul 6, 2023
855fa32
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jul 6, 2023
27df258
Merge branch 'main' into 700-lammpswrite-updates
daico007 Jul 7, 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
3 changes: 2 additions & 1 deletion gmso/abc/abstract_site.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ def is_valid_position(cls, position):

try:
position = np.reshape(position, newshape=(3,), order="C")
position.convert_to_units(u.nm)
if position.units != u.dimensionless:
position.convert_to_units(u.nm)
except ValueError:
raise ValueError(
f"Position of shape {position.shape} is not valid. "
Expand Down
3 changes: 2 additions & 1 deletion gmso/core/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def _validate_lengths(lengths):
np.reshape(lengths, newshape=(3,), order="C")

lengths *= input_unit
lengths.convert_to_units(u.nm)
if input_unit != u.Unit("dimensionless"):
lengths.convert_to_units(u.nm)

if np.any(
np.less(
Expand Down
60 changes: 60 additions & 0 deletions gmso/core/topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
from gmso.utils.connectivity import (
identify_connections as _identify_connections,
)
from gmso.utils.conversions import (
convert_params_units,
convert_topology_expressions,
)
from gmso.utils.units import GMSO_UnitRegsitry as UnitReg

scaling_interaction_idxes = {"12": 0, "13": 1, "14": 2}
Expand Down Expand Up @@ -1693,6 +1697,62 @@ def load(cls, filename, **kwargs):
loader = LoadersRegistry.get_callable(filename.suffix)
return loader(filename, **kwargs)

def convert_potential_styles(self, expressionMap={}):
"""Convert from one parameter form to another.

Parameters
CalCraven marked this conversation as resolved.
Show resolved Hide resolved
----------
expressionMap : dict, default={}
Map where the keys represent the current potential
type and the corresponding values represent the desired
potential type. The desired potential style can be
either a string with the corresponding name, or
a gmso.utils.expression.PotentialExpression type.

Examples
________
# Convert from RB torsions to OPLS torsions
top.convert_potential_styles({"dihedrals": "OPLSTorsionPotential"})
# TODO: convert_potential_styles with PotentialExpression
"""
# TODO: raise warnings for improper values or keys in expressionMap

return convert_topology_expressions(self, expressionMap)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why make a method for convert_potential_styles instead of just using convert_topology_expressions. Regardless of whether this method remains, the import should be at the top of the module considering that it is an in-package import and not an optional external dependency.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After discussing with @daico007, all of this will get refactored so that the 3 engine writers are using similar methods. This method is analagous to what HOOMD and GROMACS Top writers are currently using locally, so that code will end up being extracted to this topology method.


def convert_unit_styles(self, unitsystem, exp_unitsDict):
"""Convert from one set of base units to another.

Parameters
CalCraven marked this conversation as resolved.
Show resolved Hide resolved
----------
unitsystem : unyt.UnitSystem
set of base units to use for all expressions of the topology
in `unyt package <https://unyt.readthedocs.io/en/stable/>_`
exp_unitsDict : dict
keys with topology attributes that should be converted and
values with dictionary of parameter: expected_dimension

Examples
________
top.convert_unit_styles(
u.UnitSystem(
"lammps_real", "Å", "amu", "fs", "K", "rad",
),
{"bond":{"k":"energy/length**2", "r_eq":"length"}},
)
"""

ref_values = {"energy": "kJ/mol", "length": "nm", "angle": "radians"}

# all potContainer ["atom", "bond", "angle", "dihedral", "improper"]
for potStr in exp_unitsDict:
potContainer = getattr(self, potStr + "_types")
Comment on lines +1747 to +1748
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this method only convert all the potentials. Should we also convert the atoms' position too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the initial goal for this method was just to be used on the parameters of the potential expressions, which is why it's missing things. Pairpotentials come to mind as an example of that. However, I ended up converting the expressions on writing out, instead of upfront in the lammpswriter, and mostly relying on methods that are living in lammpsdata.py because of that. I still think the best way to do the conversion would be:

  1. Grab the set of base UnitSystem. Presumably, this would be from a library, or passed directly to whatever function by the user. I can see a useful added input of also passing units specific to parameters in the topology, such as: {"angle_types":{"theta":u.degrees}}, which would override the value for that UnitSystem.
  2. Determine if the unit changes should happen in place or to a copy of the topology. This would be mostly for speedups, and prevent issues with overwriting whatever you pass to the function.
  3. Iterate through all components of the topology. Each component should be passed to a generic unit_conversion method with the base units and the conversion should be done. These components include: [box, sites, atomtypes, bondtypes, angletypes, dihedraltypes, impropertypes, pairpotentials]. Each of these may have their own specific methods, but should be accessible through a single api.
  4. Once the conversions are made, then the topology attributes can be written out directly as floats with the to_value() method. Rounding should also occur on writing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To summarize, I think this function is not used currently at all. I can either delete it, or leave it as a starting place for the next PR that looks to normalize and standardize all of the unit handling in GMSO.

convert_params_units(
potContainer,
expected_units_dim=exp_unitsDict[potStr],
base_units=unitsystem,
ref_values=ref_values,
)


def _return_float_for_unyt(unyt_quant, unyts_bool):
try:
Expand Down
15 changes: 14 additions & 1 deletion gmso/core/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,13 @@ def get_sorted_names(potential):
else:
return potential.member_types
elif isinstance(potential, ImproperType):
return (potential.member_types[0], *sorted(potential.member_types[1:]))
return (
potential.member_types[0],
*potential.member_types[1:],
) # could sort using `sorted`
return ValueError(
f"Potential {potential} not one of {potential_attribute_map.values()}"
)


def get_parameters(potential):
Expand Down Expand Up @@ -170,6 +176,13 @@ def index(self, item):
for j, potential in enumerate(self.yield_view()):
if potential is item:
return j
return None

def equality_index(self, item):
Fixed Show fixed Hide fixed
for j, potential in enumerate(self.yield_view()):
if potential == item:
return j
return None

def _collect_potentials(self):
"""Collect potentials from the iterator"""
Expand Down
4 changes: 2 additions & 2 deletions gmso/external/convert_mbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ def _parse_molecule_residue(site_map, compound):
if molecule_tag.name in molecule_tracker:
molecule_tracker[molecule_tag.name] += 1
else:
molecule_tracker[molecule_tag.name] = 0
molecule_tracker[molecule_tag.name] = 1
molecule_number = molecule_tracker[molecule_tag.name]
"""End of molecule parsing"""

Expand All @@ -329,7 +329,7 @@ def _parse_molecule_residue(site_map, compound):
residue_tracker[residue_tag.name]
)
else:
residue_tracker[residue_tag.name] = {residue_tag: 0}
residue_tracker[residue_tag.name] = {residue_tag: 1}

residue_number = residue_tracker[residue_tag.name][residue_tag]
site_map[particle]["residue"] = (residue_tag.name, residue_number)
Expand Down
61 changes: 43 additions & 18 deletions gmso/external/convert_parmed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Module support for converting to/from ParmEd objects."""
import copy
import warnings
from operator import attrgetter, itemgetter

Expand All @@ -7,7 +8,7 @@
from symengine import expand

import gmso
from gmso.core.element import element_by_atom_type, element_by_atomic_number
from gmso.core.element import element_by_atomic_number, element_by_symbol
from gmso.core.views import PotentialFilters, get_parameters

pfilter = PotentialFilters.UNIQUE_PARAMETERS
Expand Down Expand Up @@ -71,12 +72,12 @@ def from_parmed(structure, refer_type=True):
charge=atom.charge * u.elementary_charge,
position=[atom.xx, atom.xy, atom.xz] * u.angstrom,
atom_type=None,
residue=(residue.name, residue.idx),
residue=(residue.name, residue.idx + 1),
element=element,
)
site.molecule = (residue.name, residue.idx) if ind_res else None
site.molecule = (residue.name, residue.idx + 1) if ind_res else None
site.atom_type = (
pmd_top_atomtypes[atom.atom_type]
copy.deepcopy(pmd_top_atomtypes[atom.atom_type])
if refer_type and isinstance(atom.atom_type, pmd.AtomType)
else None
)
Expand All @@ -102,6 +103,7 @@ def from_parmed(structure, refer_type=True):
}
_add_conn_type_from_pmd(
connStr="BondType",
pmd_conn=bond,
gmso_conn=top_connection,
conn_params=conn_params,
name=name,
Expand All @@ -124,11 +126,12 @@ def from_parmed(structure, refer_type=True):
)
if refer_type and isinstance(angle.type, pmd.AngleType):
conn_params = {
"k": (2 * angle.type.k * u.Unit("kcal / (rad**2 * mol)")),
"k": (2 * angle.type.k * u.Unit("kcal / (radian**2 * mol)")),
"theta_eq": (angle.type.theteq * u.degree),
}
_add_conn_type_from_pmd(
connStr="AngleType",
pmd_conn=angle,
gmso_conn=top_connection,
conn_params=conn_params,
name=name,
Expand Down Expand Up @@ -164,6 +167,7 @@ def from_parmed(structure, refer_type=True):
}
_add_conn_type_from_pmd(
connStr="ImproperType",
pmd_conn=dihedral,
gmso_conn=top_connection,
conn_params=conn_params,
name=name_improper,
Expand All @@ -186,6 +190,7 @@ def from_parmed(structure, refer_type=True):
}
_add_conn_type_from_pmd(
connStr="DihedralType",
pmd_conn=dihedral,
gmso_conn=top_connection,
conn_params=conn_params,
name=name_proper,
Expand Down Expand Up @@ -221,6 +226,7 @@ def from_parmed(structure, refer_type=True):
}
_add_conn_type_from_pmd(
connStr="DihedralType",
pmd_conn=rb_torsion,
gmso_conn=top_connection,
conn_params=conn_params,
name=name,
Expand All @@ -240,7 +246,7 @@ def from_parmed(structure, refer_type=True):
connection_members=_sort_improper_members(
top,
site_map,
*attrgetter("atom1", "atom2", "atom3", "atom4")(improper),
*attrgetter("atom3", "atom2", "atom1", "atom4")(improper),
)
)
if refer_type and isinstance(improper.type, pmd.ImproperType):
Expand All @@ -250,6 +256,7 @@ def from_parmed(structure, refer_type=True):
}
_add_conn_type_from_pmd(
connStr="ImproperType",
pmd_conn=improper,
gmso_conn=top_connection,
conn_params=conn_params,
name=name,
Expand Down Expand Up @@ -281,11 +288,11 @@ def _atom_types_from_pmd(structure):
A dictionary linking a pmd.AtomType object to its
corresponding GMSO.AtomType object.
"""
unique_atom_types = set()
for atom in structure.atoms:
if isinstance(atom.atom_type, pmd.AtomType):
unique_atom_types.add(atom.atom_type)
unique_atom_types = list(unique_atom_types)
unique_atom_types = [
atom.atom_type
for atom in structure.atoms
if isinstance(atom.atom_type, pmd.AtomType)
]
pmd_top_atomtypes = {}
for atom_type in unique_atom_types:
if atom_type.atomic_number:
Expand All @@ -302,7 +309,7 @@ def _atom_types_from_pmd(structure):
"epsilon": atom_type.epsilon * u.Unit("kcal / mol"),
},
independent_variables={"r"},
mass=atom_type.mass,
mass=copy.deepcopy(atom_type.mass),
)
pmd_top_atomtypes[atom_type] = top_atomtype
return pmd_top_atomtypes
Expand Down Expand Up @@ -342,7 +349,7 @@ def _sort_improper_members(top, site_map, atom1, atom2, atom3, atom4):


def _add_conn_type_from_pmd(
connStr, gmso_conn, conn_params, name, expression, variables
connStr, pmd_conn, gmso_conn, conn_params, name, expression, variables
):
"""Convert ParmEd dihedral types to GMSO DihedralType.

Expand Down Expand Up @@ -408,6 +415,9 @@ def to_parmed(top, refer_type=True):
msg = "Provided argument is not a topology.Topology."
assert isinstance(top, gmso.Topology)

# Copy structure to not overwrite object in memory
top = copy.deepcopy(top)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use parmed copy method as described in Foyer PR#530

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is creating a copy of the gmso.Topology, not the parmed structure which is what I think you linked to, so this wouldn't apply here.


# Set up Parmed structure and define general properties
structure = pmd.Structure()
structure.title = top.name
Expand Down Expand Up @@ -450,7 +460,9 @@ def to_parmed(top, refer_type=True):
# Add atom to structure
if site.residue:
structure.add_atom(
pmd_atom, resname=site.residue.name, resnum=site.residue.number
pmd_atom,
resname=site.residue.name,
resnum=site.residue.number - 1,
)
else:
structure.add_atom(pmd_atom, resname="RES", resnum=-1)
Expand Down Expand Up @@ -561,14 +573,20 @@ def _atom_types_from_gmso(top, structure, atom_map):
atype_epsilon = float(
atom_type.parameters["epsilon"].to("kcal/mol").value
)
atype_element = element_by_atom_type(atom_type)
if atom_type.mass:
atype_mass = atom_type.mass.to("amu").value
else:
atype_mass = element_by_symbol(atom_type.name).mass.to("amu").value
atype_atomic_number = getattr(
element_by_symbol(atom_type.name), "atomic_number", None
)
atype_rmin = atype_sigma * 2 ** (1 / 6) / 2 # to rmin/2
# Create unique Parmed AtomType object
atype = pmd.AtomType(
atype_name,
None,
atype_mass,
atype_element.atomic_number,
atype_atomic_number,
atype_charge,
)
atype.set_lj_params(atype_epsilon, atype_rmin)
Expand Down Expand Up @@ -645,7 +663,7 @@ def _angle_types_from_gmso(top, structure, angle_map):
), msg
# Extract Topology angle_type information
agltype_k = 0.5 * float(
angle_type.parameters["k"].to("kcal / (rad**2 * mol)").value
angle_type.parameters["k"].to("kcal / (radian**2 * mol)").value
)
agltype_theta_eq = float(
angle_type.parameters["theta_eq"].to("degree").value
Expand Down Expand Up @@ -729,10 +747,17 @@ def _dihedral_types_from_gmso(top, structure, dihedral_map):
)
# Create unique DihedralType object
dtype = pmd.RBTorsionType(
dtype_c0, dtype_c1, dtype_c2, dtype_c3, dtype_c4, dtype_c5
dtype_c0,
dtype_c1,
dtype_c2,
dtype_c3,
dtype_c4,
dtype_c5,
list=structure.rb_torsion_types,
)
# Add RBTorsionType to structure.rb_torsion_types
structure.rb_torsion_types.append(dtype)
# dtype._idx = len(structure.rb_torsion_types) - 1
else:
raise GMSOError("msg")
dtype_map[get_parameters(dihedral_type)] = dtype
Expand Down
4 changes: 2 additions & 2 deletions gmso/formats/gro.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def read_gro(filename):

r = re.compile("([0-9]+)([a-zA-Z]+)")
m = r.match(res)
site.molecule = (m.group(2), int(m.group(1)) - 1)
site.residue = (m.group(2), int(m.group(1)) - 1)
site.molecule = (m.group(2), int(m.group(1)))
site.residue = (m.group(2), int(m.group(1)))
top.add_site(site, update_types=False)
top.update_topology()

Expand Down
Loading