From 6add25dea4c3d6531b085c9bb2edf23754d244df Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 13 Jul 2021 14:10:36 -0500 Subject: [PATCH 01/19] WIP- Add LayeredDihedral and LayeredImproper classes --- gmso/__init__.py | 4 +-- gmso/core/dihedral.py | 78 +++++++++++++++++++++++++++++++---------- gmso/core/improper.py | 81 ++++++++++++++++++++++++++++++++----------- 3 files changed, 121 insertions(+), 42 deletions(-) diff --git a/gmso/__init__.py b/gmso/__init__.py index 569d5dba4..46c499043 100644 --- a/gmso/__init__.py +++ b/gmso/__init__.py @@ -6,11 +6,11 @@ from .core.bond import Bond from .core.bond_type import BondType from .core.box import Box -from .core.dihedral import Dihedral +from .core.dihedral import Dihedral, LayeredDihedral from .core.dihedral_type import DihedralType from .core.element import Element from .core.forcefield import ForceField -from .core.improper import Improper +from .core.improper import Improper, LayeredImproper from .core.improper_type import ImproperType from .core.pairpotential_type import PairPotentialType from .core.subtopology import SubTopology diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index 6d0910652..401f9e40f 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -1,4 +1,4 @@ -from typing import Callable, ClassVar, Optional, Tuple +from typing import Callable, ClassVar, List, Optional, Tuple from pydantic import Field @@ -7,7 +7,7 @@ from gmso.core.dihedral_type import DihedralType -class Dihedral(Connection): +class BaseDihedral(Connection): __base_doc__ = """A 4-partner connection between sites. This is a subclass of the gmso.Connection superclass. @@ -31,19 +31,6 @@ class Dihedral(Connection): ..., description="The 4 atoms involved in the dihedral." ) - dihedral_type_: Optional[DihedralType] = Field( - default=None, description="DihedralType of this dihedral." - ) - - @property - def dihedral_type(self): - return self.__dict__.get("dihedral_type_") - - @property - def connection_type(self): - # ToDo: Deprecate this? - return self.__dict__.get("dihedral_type_") - def equivalent_members(self): """Get a set of the equivalent connection member tuples @@ -96,18 +83,71 @@ def _equivalent_members_hash(self): ) ) + def is_layered(self): + return hasattr(self, "dihedral_types_") + + class Config: + fields = { + "connection_members_": "connection_members", + } + alias_to_fields = { + "connection_members": "connection_members_", + } + + +class Dihedral(BaseDihedral): + dihedral_type_: Optional[DihedralType] = Field( + default=None, description="DihedralType of this dihedral." + ) + + @property + def dihedral_type(self): + return self.__dict__.get("dihedral_type_") + + @property + def connection_type(self): + # ToDo: Deprecate this? + return self.__dict__.get("dihedral_type_") + def __setattr__(self, key, value): if key == "connection_type": - super(Dihedral, self).__setattr__("dihedral_type", value) + super().__setattr__("dihedral_type", value) else: - super(Dihedral, self).__setattr__(key, value) + super().__setattr__(key, value) class Config: fields = { "dihedral_type_": "dihedral_type", - "connection_members_": "connection_members", } alias_to_fields = { "dihedral_type": "dihedral_type_", - "connection_members": "connection_members_", + } + + +class LayeredDihedral(BaseDihedral): + dihedral_types_: Optional[List[DihedralType]] = Field( + default=None, description="DihedralTypes of this dihedral." + ) + + @property + def dihedral_types(self): + return self.__dict__.get("dihedral_types_") + + @property + def connection_types(self): + # ToDo: Deprecate this? + return self.__dict__.get("dihedral_types_") + + def __setattr__(self, key, value): + if key == "connection_types": + super().__setattr__("dihedral_types", value) + else: + super().__setattr__(key, value) + + class Config: + fields = { + "dihedral_types_": "dihedral_types", + } + alias_to_fields = { + "dihedral_types": "dihedral_types_", } diff --git a/gmso/core/improper.py b/gmso/core/improper.py index d27d77617..5c2922e09 100644 --- a/gmso/core/improper.py +++ b/gmso/core/improper.py @@ -1,5 +1,5 @@ """Support for improper style connections (4-member connection).""" -from typing import Callable, ClassVar, Optional, Tuple +from typing import Callable, ClassVar, List, Optional, Tuple from pydantic import Field @@ -8,7 +8,7 @@ from gmso.core.improper_type import ImproperType -class Improper(Connection): +class BaseImproper(Connection): __base_doc__ = """sA 4-partner connection between sites. This is a subclass of the gmso.Connection superclass. @@ -39,21 +39,6 @@ class Improper(Connection): "then the three atoms connected to the central site.", ) - improper_type_: Optional[ImproperType] = Field( - default=None, description="ImproperType of this improper." - ) - - @property - def improper_type(self): - """Return Potential object for this connection if it exists.""" - return self.__dict__.get("improper_type_") - - @property - def connection_type(self): - """Return Potential object for this connection if it exists.""" - # ToDo: Deprecate this? - return self.__dict__.get("improper_type_") - def equivalent_members(self): """Get a set of the equivalent connection member tuples. @@ -106,21 +91,75 @@ def _equivalent_members_hash(self): ) ) + class Config: + """Pydantic configuration to link fields to their public attribute.""" + + fields = { + "connection_members_": "connection_members", + } + alias_to_fields = { + "connection_members": "connection_members_", + } + + +class Improper(BaseImproper): + improper_type_: Optional[ImproperType] = Field( + default=None, description="ImproperType of this improper." + ) + + @property + def improper_type(self): + """Return Potential object for this connection if it exists.""" + return self.__dict__.get("improper_type_") + + @property + def connection_type(self): + """Return Potential object for this connection if it exists.""" + # ToDo: Deprecate this? + return self.__dict__.get("improper_type_") + def __setattr__(self, key, value): """Set attribute override to support connection_type key.""" if key == "connection_type": - super(Improper, self).__setattr__("improper_type", value) + super().__setattr__("improper_type", value) else: - super(Improper, self).__setattr__(key, value) + super().__setattr__(key, value) class Config: """Pydantic configuration to link fields to their public attribute.""" fields = { "improper_type_": "improper_type", - "connection_members_": "connection_members", } alias_to_fields = { "improper_type": "improper_type_", - "connection_members": "connection_members_", + } + + +class LayeredImproper(BaseImproper): + improper_types_: Optional[List[Improper]] = Field( + default=None, description="ImproperTypes of this improper." + ) + + @property + def improper_types(self): + return self.__dict__.get("improper_types_") + + @property + def connection_types(self): + # ToDo: Deprecate this? + return self.__dict__.get("improper_types_") + + def __setattr__(self, key, value): + if key == "connection_types": + super().__setattr__("improper_types_", value) + else: + super().__setattr__(key, value) + + class Config: + fields = { + "improper_types_": "improper_types", + } + alias_to_fields = { + "improper_types": "improper_types_", } From 461a50948e67a1a8e4160e73c63784f7457589a3 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Jul 2021 09:58:10 -0500 Subject: [PATCH 02/19] WIP- Make existing API work after layered classes addition --- gmso/core/topology.py | 90 ++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 35 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index bad9374e9..4f4370623 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -542,48 +542,68 @@ def update_connection_types(self): -------- gmso.Topology.update_atom_types : Update atom types in the topology. """ + # Here an alternative could be using checking instances of LayeredConnection classes + # But to make it generic, this approach works best + get_connection_type = ( + lambda conn: conn.connection_types + if hasattr(conn, "connection_types") + else conn.connection_type + ) for c in self.connections: - if c.connection_type is None: + connection_type_or_types = get_connection_type(c) + if connection_type_or_types is None: warnings.warn( "Non-parametrized Connection {} detected".format(c) ) - elif not isinstance(c.connection_type, ParametricPotential): + continue + elif not isinstance( + connection_type_or_types, (ParametricPotential, list) + ): raise GMSOError( "Non-Potential {} found" - "in Connection {}".format(c.connection_type, c) + "in Connection {}".format(connection_type_or_types, c) ) - elif c.connection_type not in self._connection_types: - c.connection_type.topology = self - self._connection_types[c.connection_type] = c.connection_type - if isinstance(c.connection_type, BondType): - self._bond_types[c.connection_type] = c.connection_type - self._bond_types_idx[c.connection_type] = ( - len(self._bond_types) - 1 - ) - if isinstance(c.connection_type, AngleType): - self._angle_types[c.connection_type] = c.connection_type - self._angle_types_idx[c.connection_type] = ( - len(self._angle_types) - 1 - ) - if isinstance(c.connection_type, DihedralType): - self._dihedral_types[c.connection_type] = c.connection_type - self._dihedral_types_idx[c.connection_type] = ( - len(self._dihedral_types) - 1 - ) - if isinstance(c.connection_type, ImproperType): - self._improper_types[c.connection_type] = c.connection_type - self._improper_types_idx[c.connection_type] = ( - len(self._improper_types) - 1 - ) - elif c.connection_type in self.connection_types: - if isinstance(c.connection_type, BondType): - c.connection_type = self._bond_types[c.connection_type] - if isinstance(c.connection_type, AngleType): - c.connection_type = self._angle_types[c.connection_type] - if isinstance(c.connection_type, DihedralType): - c.connection_type = self._dihedral_types[c.connection_type] - if isinstance(c.connection_type, ImproperType): - c.connection_type = self._improper_types[c.connection_type] + if not isinstance(connection_type_or_types, list): + connection_type_or_types = [connection_type_or_types] + for connection_type in connection_type_or_types: + if connection_type not in self._connection_types: + connection_type.topology = self + self._connection_types[connection_type] = connection_type + if isinstance(connection_type, BondType): + self._bond_types[connection_type] = connection_type + self._bond_types_idx[connection_type] = ( + len(self._bond_types) - 1 + ) + if isinstance(connection_type, AngleType): + self._angle_types[connection_type] = connection_type + self._angle_types_idx[connection_type] = ( + len(self._angle_types) - 1 + ) + if isinstance(connection_type, DihedralType): + self._dihedral_types[connection_type] = connection_type + self._dihedral_types_idx[connection_type] = ( + len(self._dihedral_types) - 1 + ) + if isinstance(connection_type, ImproperType): + self._improper_types[ + connection_type + ] = c.connection_type + self._improper_types_idx[connection_type] = ( + len(self._improper_types) - 1 + ) + elif connection_type in self.connection_types: + if isinstance(connection_type, BondType): + c.connection_type = self._bond_types[connection_type] + if isinstance(c.connection_type, AngleType): + c.connection_type = self._angle_types[connection_type] + if isinstance(c.connection_type, DihedralType): + c.connection_type = self._dihedral_types[ + connection_type + ] + if isinstance(connection_type, ImproperType): + c.connection_type = self._improper_types[ + connection_type + ] def add_pairpotentialtype(self, pairpotentialtype, update=True): """add a PairPotentialType to the topology From 2cff78adc8c55df47c86cd887625a789ac4b10d0 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Jul 2021 11:09:13 -0500 Subject: [PATCH 03/19] WIP- Add layered dihedral and layered improper attributes --- gmso/core/topology.py | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 8f5350ae2..91c73f7d2 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -11,9 +11,9 @@ from gmso.core.atom_type import AtomType from gmso.core.bond import Bond from gmso.core.bond_type import BondType -from gmso.core.dihedral import Dihedral +from gmso.core.dihedral import Dihedral, LayeredDihedral from gmso.core.dihedral_type import DihedralType -from gmso.core.improper import Improper +from gmso.core.improper import Improper, LayeredImproper from gmso.core.improper_type import ImproperType from gmso.core.pairpotential_type import PairPotentialType from gmso.core.parametric_potential import ParametricPotential @@ -154,7 +154,9 @@ def __init__(self, name="Topology", box=None): self._bonds = IndexedSet() self._angles = IndexedSet() self._dihedrals = IndexedSet() + self._layered_dihedrals = IndexedSet() self._impropers = IndexedSet() + self._layered_impropers = IndexedSet() self._subtops = IndexedSet() self._atom_types = {} self._atom_types_idx = {} @@ -342,11 +344,20 @@ def dihedrals(self): """Return all dihedrals in the topology.""" return tuple(self._dihedrals) + @property + def layered_dihedrals(self): + return tuple(self._layered_dihedrals) + @property def impropers(self): """Return all impropers in the topology.""" return tuple(self._impropers) + @property + def layered_impropers(self): + """Return all layered impropers in the topology.""" + return tuple(self._layered_impropers) + @property def atom_types(self): """Return all atom_types in the topology.""" @@ -520,8 +531,14 @@ def add_connection(self, connection, update_types=True): self._angles.add(connection) if isinstance(connection, Dihedral): self._dihedrals.add(connection) + if isinstance(connection, LayeredDihedral): + self._dihedrals.add(connection) + self._layered_dihedrals.add(connection) if isinstance(connection, Improper): self._impropers.add(connection) + if isinstance(connection, LayeredImproper): + self._impropers.add(connection) + self._layered_impropers.add(connection) if update_types: self.update_connection_types() @@ -764,10 +781,14 @@ def is_fully_typed(self, updated=False, group="topology"): angle.angle_type for angle in top._angles ), "dihedrals": lambda top: all( - dihedral.dihedral_type for dihedral in top._dihedrals + hasattr(dihedral, "dihedral_types") + or hasattr(dihedral, "dihedral_type") + for dihedral in top._dihedrals ), "impropers": lambda top: all( - improper.improper_type for improper in top._impropers + hasattr(improper, "improper_types") + or hasattr(improper, "improper_type") + for improper in top._impropers ), } @@ -828,7 +849,7 @@ def _get_untyped_sites(self): untyped = {"sites": list()} for site in self._sites: if not site.atom_type: - untyped[sites].append(site) + untyped["sites"].append(site) return untyped def _get_untyped_bonds(self): @@ -851,7 +872,11 @@ def _get_untyped_dihedrals(self): "Return a list of untyped dihedrals" untyped = {"dihedrals": list()} for dihedral in self._dihedrals: - if not dihedral.dihedral_type: + if getattr( + dihedral, + "dihedral_type", + getattr(dihedral, "dihedral_types", None), + ): untyped["dihedrals"].append(dihedral) return untyped @@ -859,7 +884,11 @@ def _get_untyped_impropers(self): "Return a list of untyped impropers" untyped = {"impropers": list()} for improper in self._impropers: - if not improper.improper_type: + if getattr( + improper, + "improper_type", + getattr(improper, "improper_types", None), + ): untyped["impropers"].append(improper) return untyped From 6e9ad3c76f3654a9fca1a72d549b7f4305f82f90 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Jul 2021 11:11:17 -0500 Subject: [PATCH 04/19] WIP- fix negation in typed check predicates --- gmso/core/topology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 91c73f7d2..6a3b99dad 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -872,7 +872,7 @@ def _get_untyped_dihedrals(self): "Return a list of untyped dihedrals" untyped = {"dihedrals": list()} for dihedral in self._dihedrals: - if getattr( + if not getattr( dihedral, "dihedral_type", getattr(dihedral, "dihedral_types", None), @@ -884,7 +884,7 @@ def _get_untyped_impropers(self): "Return a list of untyped impropers" untyped = {"impropers": list()} for improper in self._impropers: - if getattr( + if not getattr( improper, "improper_type", getattr(improper, "improper_types", None), From 2693f89425b9313fd03c8f458bf21bf57fc26f72 Mon Sep 17 00:00:00 2001 From: "Ryan S. DeFever" Date: Fri, 16 Jul 2021 14:51:59 -0400 Subject: [PATCH 05/19] Add simple unit test for layered dihedrals --- gmso/tests/test_dihedral.py | 57 ++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/gmso/tests/test_dihedral.py b/gmso/tests/test_dihedral.py index b2aefb721..8556fd885 100644 --- a/gmso/tests/test_dihedral.py +++ b/gmso/tests/test_dihedral.py @@ -1,9 +1,10 @@ import pytest +import unyt as u from pydantic import ValidationError from gmso.core.atom import Atom from gmso.core.atom_type import AtomType -from gmso.core.dihedral import Dihedral +from gmso.core.dihedral import Dihedral, LayeredDihedral from gmso.core.dihedral_type import DihedralType from gmso.core.topology import Topology from gmso.tests.base_test import BaseTest @@ -153,3 +154,57 @@ def test_equivalent_members_set(self): tuple(dihedral.connection_members) in dihedral_not_eq.equivalent_members() ) + + def test_layered_dihedrals(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + dihedral_type1 = DihedralType( + name=f"layer1", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 1 * u.dimensionless, + "a0": 30.0 * u.degree, + + } + ) + dihedral_type2 = DihedralType( + name=f"layer2", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 2 * u.dimensionless, + "a0": 30.0 * u.degree, + + } + ) + dihedral_type3 = DihedralType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + + } + ) + + connect = LayeredDihedral( + connection_members=[atom1, atom2, atom3, atom4], + dihedral_types=[dihedral_type1, dihedral_type2, dihedral_type3], + name="dihedral_name", + ) + + assert dihedral_type1 in connect.dihedral_types + assert dihedral_type2 in connect.dihedral_types + assert dihedral_type3 in connect.dihedral_types + + assert connect.dihedral_types[0].parameters["n"] == 1 + assert connect.dihedral_types[1].parameters["n"] == 2 + assert connect.dihedral_types[2].parameters["n"] == 3 From 60ec0d81af7a1f8e3c2219fb5346c900901ca8fb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 16 Jul 2021 18:52:23 +0000 Subject: [PATCH 06/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- gmso/tests/test_dihedral.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/gmso/tests/test_dihedral.py b/gmso/tests/test_dihedral.py index 8556fd885..3e82e1aee 100644 --- a/gmso/tests/test_dihedral.py +++ b/gmso/tests/test_dihedral.py @@ -169,8 +169,7 @@ def test_layered_dihedrals(self): "kn": 1.0 * u.K * u.kb, "n": 1 * u.dimensionless, "a0": 30.0 * u.degree, - - } + }, ) dihedral_type2 = DihedralType( name=f"layer2", @@ -180,8 +179,7 @@ def test_layered_dihedrals(self): "kn": 1.0 * u.K * u.kb, "n": 2 * u.dimensionless, "a0": 30.0 * u.degree, - - } + }, ) dihedral_type3 = DihedralType( name=f"layer3", @@ -191,8 +189,7 @@ def test_layered_dihedrals(self): "kn": 1.0 * u.K * u.kb, "n": 3 * u.dimensionless, "a0": 30.0 * u.degree, - - } + }, ) connect = LayeredDihedral( From cfd912535c3e4e75a9099d3a72009b1b4676c592 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Fri, 16 Jul 2021 15:57:16 -0500 Subject: [PATCH 07/19] WIP- add is_layered check while updating --- gmso/core/topology.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 6a3b99dad..df5c36cba 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -611,16 +611,28 @@ def update_connection_types(self): elif connection_type in self.connection_types: if isinstance(connection_type, BondType): c.connection_type = self._bond_types[connection_type] - if isinstance(c.connection_type, AngleType): + if isinstance(connection_type, AngleType): c.connection_type = self._angle_types[connection_type] - if isinstance(c.connection_type, DihedralType): - c.connection_type = self._dihedral_types[ - connection_type - ] + if isinstance(connection_type, DihedralType): + if c.is_layered(): + c.connection_types = c.connection_types or [] + c.connection_types.append( + self.dihedral_types[connection_type] + ) + else: + c.connection_type = self._dihedral_types[ + connection_type + ] if isinstance(connection_type, ImproperType): - c.connection_type = self._improper_types[ - connection_type - ] + if c.is_layered(): + c.connection_types = c.connection_types or [] + c.connection_types.append( + self.improper_types[connection_type] + ) + else: + c.connection_type = self.improper_types[ + connection_type + ] def add_pairpotentialtype(self, pairpotentialtype, update=True): """add a PairPotentialType to the topology From 39dacc3cab47f85ea1820d58be5a08a09704a580 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 20 Jul 2021 09:57:05 -0500 Subject: [PATCH 08/19] WIP- Use indexedset for layered dihedral/improper potentials --- gmso/core/dihedral.py | 16 +++++++++--- gmso/core/improper.py | 14 +++++++++-- gmso/tests/test_dihedral.py | 50 +++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 5 deletions(-) diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index 401f9e40f..9224c7921 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -1,10 +1,12 @@ -from typing import Callable, ClassVar, List, Optional, Tuple +from typing import Callable, ClassVar, Iterable, Optional, Tuple -from pydantic import Field +from boltons.setutils import IndexedSet +from pydantic import Field, ValidationError, validator from gmso.abc.abstract_connection import Connection from gmso.core.atom import Atom from gmso.core.dihedral_type import DihedralType +from gmso.utils.misc import validate_type class BaseDihedral(Connection): @@ -125,7 +127,7 @@ class Config: class LayeredDihedral(BaseDihedral): - dihedral_types_: Optional[List[DihedralType]] = Field( + dihedral_types_: Optional[IndexedSet] = Field( default=None, description="DihedralTypes of this dihedral." ) @@ -144,6 +146,14 @@ def __setattr__(self, key, value): else: super().__setattr__(key, value) + @validator("dihedral_types_", pre=True, always=True) + def validate_dihedral_types(cls, dihedral_types): + if not isinstance(dihedral_types, Iterable): + raise ValidationError("DihedralTypes should be iterable", cls) + + validate_type(dihedral_types, DihedralType) + return IndexedSet(dihedral_types) + class Config: fields = { "dihedral_types_": "dihedral_types", diff --git a/gmso/core/improper.py b/gmso/core/improper.py index 5c2922e09..76c915f0e 100644 --- a/gmso/core/improper.py +++ b/gmso/core/improper.py @@ -1,11 +1,13 @@ """Support for improper style connections (4-member connection).""" -from typing import Callable, ClassVar, List, Optional, Tuple +from typing import Callable, ClassVar, Iterable, List, Optional, Tuple -from pydantic import Field +from boltons.setutils import IndexedSet +from pydantic import Field, ValidationError, validator from gmso.abc.abstract_connection import Connection from gmso.core.atom import Atom from gmso.core.improper_type import ImproperType +from gmso.utils.misc import validate_type class BaseImproper(Connection): @@ -156,6 +158,14 @@ def __setattr__(self, key, value): else: super().__setattr__(key, value) + @validator("improper_types_", pre=True, always=True) + def validate_dihedral_types(cls, improper_types): + if not isinstance(improper_types, Iterable): + raise ValidationError("ImproperTypes should be iterable", cls) + + validate_type(improper_types, ImproperType) + return IndexedSet(improper_types) + class Config: fields = { "improper_types_": "improper_types", diff --git a/gmso/tests/test_dihedral.py b/gmso/tests/test_dihedral.py index 3e82e1aee..169557d23 100644 --- a/gmso/tests/test_dihedral.py +++ b/gmso/tests/test_dihedral.py @@ -205,3 +205,53 @@ def test_layered_dihedrals(self): assert connect.dihedral_types[0].parameters["n"] == 1 assert connect.dihedral_types[1].parameters["n"] == 2 assert connect.dihedral_types[2].parameters["n"] == 3 + + def test_layered_dihedral_duplicate(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + dihedral_type1 = DihedralType( + name=f"layer1", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 1 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + dihedral_type2 = DihedralType( + name=f"layer2", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 2 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + dihedral_type3 = DihedralType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + + connect = LayeredDihedral( + connection_members=[atom1, atom2, atom3, atom4], + dihedral_types=[ + dihedral_type1, + dihedral_type2, + dihedral_type3, + dihedral_type3, + ], + name="dihedral_name", + ) + + assert len(connect.dihedral_types) == 3 From baec981c9fd74b669de05df6a97972c4e9b54688 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 20 Jul 2021 10:10:35 -0500 Subject: [PATCH 09/19] WIP- Add test for layered impropers --- gmso/core/improper.py | 4 +- .../{test_dihedral.py => test_dihedrals.py} | 32 ++++- .../{test_improper.py => test_impropers.py} | 136 +++++++++++++++++- 3 files changed, 167 insertions(+), 5 deletions(-) rename gmso/tests/{test_dihedral.py => test_dihedrals.py} (89%) rename gmso/tests/{test_improper.py => test_impropers.py} (53%) diff --git a/gmso/core/improper.py b/gmso/core/improper.py index 76c915f0e..d2152a68f 100644 --- a/gmso/core/improper.py +++ b/gmso/core/improper.py @@ -1,5 +1,5 @@ """Support for improper style connections (4-member connection).""" -from typing import Callable, ClassVar, Iterable, List, Optional, Tuple +from typing import Callable, ClassVar, Iterable, Optional, Tuple from boltons.setutils import IndexedSet from pydantic import Field, ValidationError, validator @@ -139,7 +139,7 @@ class Config: class LayeredImproper(BaseImproper): - improper_types_: Optional[List[Improper]] = Field( + improper_types_: Optional[IndexedSet] = Field( default=None, description="ImproperTypes of this improper." ) diff --git a/gmso/tests/test_dihedral.py b/gmso/tests/test_dihedrals.py similarity index 89% rename from gmso/tests/test_dihedral.py rename to gmso/tests/test_dihedrals.py index 169557d23..fc4c4b3d7 100644 --- a/gmso/tests/test_dihedral.py +++ b/gmso/tests/test_dihedrals.py @@ -10,7 +10,7 @@ from gmso.tests.base_test import BaseTest -class TestDihedral(BaseTest): +class TestDihedrals(BaseTest): def test_dihedral_nonparametrized(self): atom1 = Atom(name="atom1") atom2 = Atom(name="atom1") @@ -255,3 +255,33 @@ def test_layered_dihedral_duplicate(self): ) assert len(connect.dihedral_types) == 3 + + def test_layered_dihedral_validation_error(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + dihedral_type = DihedralType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + with pytest.raises(ValidationError): + LayeredDihedral( + connection_members=[atom1, atom2, atom3, atom4], + dihedral_types_=["a1", "a2", "a3", "a4"], + name="dh1", + ) + + with pytest.raises(ValidationError): + LayeredDihedral( + connection_members=[atom1, atom2, atom3, atom4], + dihedral_types=dihedral_type, + name="dh1", + ) diff --git a/gmso/tests/test_improper.py b/gmso/tests/test_impropers.py similarity index 53% rename from gmso/tests/test_improper.py rename to gmso/tests/test_impropers.py index a853391d5..900535e4e 100644 --- a/gmso/tests/test_improper.py +++ b/gmso/tests/test_impropers.py @@ -1,15 +1,16 @@ import pytest +import unyt as u from pydantic import ValidationError from gmso.core.atom import Atom from gmso.core.atom_type import AtomType -from gmso.core.improper import Improper +from gmso.core.improper import Improper, LayeredImproper from gmso.core.improper_type import ImproperType from gmso.core.topology import Topology from gmso.tests.base_test import BaseTest -class TestImproper(BaseTest): +class TestImpropers(BaseTest): def test_improper_nonparametrized(self): atom1 = Atom(name="atom1") atom2 = Atom(name="atom2") @@ -152,3 +153,134 @@ def test_equivalent_members_set(self): tuple(improper.connection_members) in improper_not_eq.equivalent_members() ) + + def test_layered_impropers(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + improper_type1 = ImproperType( + name=f"layer1", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 1 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + improper_type2 = ImproperType( + name=f"layer2", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 2 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + improper_type3 = ImproperType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + + connect = LayeredImproper( + connection_members=[atom1, atom2, atom3, atom4], + improper_types=[improper_type1, improper_type2, improper_type3], + name="improper_name", + ) + + assert improper_type1 in connect.improper_types + assert improper_type2 in connect.improper_types + assert improper_type3 in connect.improper_types + + assert connect.improper_types[0].parameters["n"] == 1 + assert connect.improper_types[1].parameters["n"] == 2 + assert connect.improper_types[2].parameters["n"] == 3 + + def test_layered_improper_duplicate(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + improper_type1 = ImproperType( + name=f"layer1", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 1 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + improper_type2 = ImproperType( + name=f"layer2", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 2 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + improper_type3 = ImproperType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + + connect = LayeredImproper( + connection_members=[atom1, atom2, atom3, atom4], + improper_types=[ + improper_type1, + improper_type2, + improper_type3, + improper_type3, + ], + name="improper_name", + ) + + assert len(connect.improper_types) == 3 + + def test_layered_improper_validation_error(self): + atom1 = Atom(name="atom1") + atom2 = Atom(name="atom2") + atom3 = Atom(name="atom3") + atom4 = Atom(name="atom4") + + improper_type = ImproperType( + name=f"layer3", + expression="kn * (1 + cos(n * a - a0))", + independent_variables="a", + parameters={ + "kn": 1.0 * u.K * u.kb, + "n": 3 * u.dimensionless, + "a0": 30.0 * u.degree, + }, + ) + with pytest.raises(ValidationError): + LayeredImproper( + connection_members=[atom1, atom2, atom3, atom4], + improper_types_=["a1", "a2", "a3", "a4"], + name="dh1", + ) + + with pytest.raises(ValidationError): + LayeredImproper( + connection_members=[atom1, atom2, atom3, atom4], + improper_types=improper_type, + name="dh1", + ) From 76fa76b9319b7ed3a62a7b4c012e52d31310d2c4 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 27 Jul 2021 10:05:54 -0500 Subject: [PATCH 10/19] WIP- Add _get_types function in topology.py for dihedral/impropers --- gmso/core/topology.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 92d951337..71bc43797 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -11,9 +11,9 @@ from gmso.core.atom_type import AtomType from gmso.core.bond import Bond from gmso.core.bond_type import BondType -from gmso.core.dihedral import Dihedral, LayeredDihedral +from gmso.core.dihedral import BaseDihedral, Dihedral, LayeredDihedral from gmso.core.dihedral_type import DihedralType -from gmso.core.improper import Improper, LayeredImproper +from gmso.core.improper import BaseImproper, Improper, LayeredImproper from gmso.core.improper_type import ImproperType from gmso.core.pairpotential_type import PairPotentialType from gmso.core.parametric_potential import ParametricPotential @@ -884,11 +884,7 @@ def _get_untyped_dihedrals(self): "Return a list of untyped dihedrals" untyped = {"dihedrals": list()} for dihedral in self._dihedrals: - if not getattr( - dihedral, - "dihedral_type", - getattr(dihedral, "dihedral_types", None), - ): + if not self._get_types(dihedral): untyped["dihedrals"].append(dihedral) return untyped @@ -896,14 +892,23 @@ def _get_untyped_impropers(self): "Return a list of untyped impropers" untyped = {"impropers": list()} for improper in self._impropers: - if not getattr( - improper, - "improper_type", - getattr(improper, "improper_types", None), - ): + if not self._get_types(improper): untyped["impropers"].append(improper) return untyped + def _get_types(self, dihedral_or_improper): + """Get the dihedral/impropertypes for a dihedral/improper in this topology.""" + if not isinstance(dihedral_or_improper, (BaseDihedral, BaseImproper)): + raise TypeError( + f"Expected `dihedral_or_improper` to be either Dihedral or Improper. " + f"Got {type(dihedral_or_improper).__name__} instead." + ) + return ( + dihedral_or_improper.connection_types + if dihedral_or_improper.is_layered() + else dihedral_or_improper.connection_type + ) + def update_angle_types(self): """Use gmso.Topology.update_connection_types to update AngleTypes in the topology. From da62893aa6e3a153b1650ae4c536b1d58a71d35d Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 27 Jul 2021 10:41:04 -0500 Subject: [PATCH 11/19] WIP- use _get_types function in is_fully_typed --- gmso/core/topology.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 71bc43797..d39e2ff23 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -793,14 +793,10 @@ def is_fully_typed(self, updated=False, group="topology"): angle.angle_type for angle in top._angles ), "dihedrals": lambda top: all( - hasattr(dihedral, "dihedral_types") - or hasattr(dihedral, "dihedral_type") - for dihedral in top._dihedrals + self._get_types(dihedral) for dihedral in top._dihedrals ), "impropers": lambda top: all( - hasattr(improper, "improper_types") - or hasattr(improper, "improper_type") - for improper in top._impropers + self._get_types(improper) for improper in top._impropers ), } From fa751fa3b3417ccabd54c5025f075048efbc6c1b Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 27 Jul 2021 10:43:36 -0500 Subject: [PATCH 12/19] WIP- Remove layered_dihedral property --- gmso/core/topology.py | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index d39e2ff23..a6f181718 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -154,9 +154,7 @@ def __init__(self, name="Topology", box=None): self._bonds = IndexedSet() self._angles = IndexedSet() self._dihedrals = IndexedSet() - self._layered_dihedrals = IndexedSet() self._impropers = IndexedSet() - self._layered_impropers = IndexedSet() self._subtops = IndexedSet() self._atom_types = {} self._atom_types_idx = {} @@ -344,20 +342,11 @@ def dihedrals(self): """Return all dihedrals in the topology.""" return tuple(self._dihedrals) - @property - def layered_dihedrals(self): - return tuple(self._layered_dihedrals) - @property def impropers(self): """Return all impropers in the topology.""" return tuple(self._impropers) - @property - def layered_impropers(self): - """Return all layered impropers in the topology.""" - return tuple(self._layered_impropers) - @property def atom_types(self): """Return all atom_types in the topology.""" @@ -529,16 +518,10 @@ def add_connection(self, connection, update_types=True): self._bonds.add(connection) if isinstance(connection, Angle): self._angles.add(connection) - if isinstance(connection, Dihedral): - self._dihedrals.add(connection) - if isinstance(connection, LayeredDihedral): + if isinstance(connection, BaseDihedral): self._dihedrals.add(connection) - self._layered_dihedrals.add(connection) - if isinstance(connection, Improper): - self._impropers.add(connection) - if isinstance(connection, LayeredImproper): + if isinstance(connection, BaseImproper): self._impropers.add(connection) - self._layered_impropers.add(connection) if update_types: self.update_connection_types() From 8e987e63b9ff4a2f16a041f2a2febd39f9b35e4a Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 27 Jul 2021 10:51:44 -0500 Subject: [PATCH 13/19] WIP- Remove unused import --- gmso/core/topology.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index a6f181718..6c240b75f 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -11,9 +11,9 @@ from gmso.core.atom_type import AtomType from gmso.core.bond import Bond from gmso.core.bond_type import BondType -from gmso.core.dihedral import BaseDihedral, Dihedral, LayeredDihedral +from gmso.core.dihedral import BaseDihedral, Dihedral from gmso.core.dihedral_type import DihedralType -from gmso.core.improper import BaseImproper, Improper, LayeredImproper +from gmso.core.improper import BaseImproper, Improper from gmso.core.improper_type import ImproperType from gmso.core.pairpotential_type import PairPotentialType from gmso.core.parametric_potential import ParametricPotential From e28ba6efc7b8fdf150c8fdfa27ad621d07a14cf7 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Mon, 2 Aug 2021 00:12:18 -0500 Subject: [PATCH 14/19] WIP- fix issues with object construction; add tests --- gmso/abc/abstract_connection.py | 4 +-- gmso/abc/gmso_base.py | 1 + gmso/core/dihedral.py | 4 +-- gmso/core/topology.py | 30 ++++++++++++------- gmso/tests/base_test.py | 51 ++++++++++++++++++++++++++++++++- gmso/tests/test_dihedrals.py | 2 +- gmso/tests/test_topology.py | 11 ++++++- 7 files changed, 85 insertions(+), 18 deletions(-) diff --git a/gmso/abc/abstract_connection.py b/gmso/abc/abstract_connection.py index 51da44da6..819e33060 100644 --- a/gmso/abc/abstract_connection.py +++ b/gmso/abc/abstract_connection.py @@ -33,7 +33,7 @@ def name(self): @root_validator(pre=True) def validate_fields(cls, values): - connection_members = values.get("connection_members") + connection_members = values.get("connection_members", []) if all(isinstance(member, dict) for member in connection_members): connection_members = [ @@ -60,7 +60,7 @@ def __repr__(self): return ( f"<{self.__class__.__name__} {self.name},\n " f"connection_members: {self.connection_members},\n " - f"potential: {str(self.connection_type)},\n " + f"potential: {str(getattr(self, 'connection_type', getattr(self, 'connection_types')))},\n " f"id: {id(self)}>" ) diff --git a/gmso/abc/gmso_base.py b/gmso/abc/gmso_base.py index 957e704da..96d39238b 100644 --- a/gmso/abc/gmso_base.py +++ b/gmso/abc/gmso_base.py @@ -120,3 +120,4 @@ class Config: extra = "forbid" json_encoders = GMSOJSONHandler.json_encoders allow_population_by_field_name = True + validate_assignment = True diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index 9224c7921..a4a00cba4 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -146,9 +146,9 @@ def __setattr__(self, key, value): else: super().__setattr__(key, value) - @validator("dihedral_types_", pre=True, always=True) + @validator("dihedral_types_", pre=True) def validate_dihedral_types(cls, dihedral_types): - if not isinstance(dihedral_types, Iterable): + if not isinstance(dihedral_types, Iterable) or isinstance(dihedral_types, str): raise ValidationError("DihedralTypes should be iterable", cls) validate_type(dihedral_types, DihedralType) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 370163065..45142180e 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -558,13 +558,13 @@ def update_connection_types(self): ) continue elif not isinstance( - connection_type_or_types, (ParametricPotential, list) + connection_type_or_types, (ParametricPotential, IndexedSet) ): raise GMSOError( "Non-Potential {} found" "in Connection {}".format(connection_type_or_types, c) ) - if not isinstance(connection_type_or_types, list): + if not isinstance(connection_type_or_types, IndexedSet): connection_type_or_types = [connection_type_or_types] for connection_type in connection_type_or_types: if connection_type not in self._connection_types: @@ -599,22 +599,30 @@ def update_connection_types(self): c.connection_type = self._angle_types[connection_type] if isinstance(connection_type, DihedralType): if c.is_layered(): - c.connection_types = c.connection_types or [] - c.connection_types.append( - self.dihedral_types[connection_type] - ) + if c.connection_types is None: + c.connection_types = IndexedSet([ + self._dihedrals_types[connection_type] + ]) + else: + c.connection_types.add( + self._dihedral_types[connection_type] + ) else: c.connection_type = self._dihedral_types[ connection_type ] if isinstance(connection_type, ImproperType): if c.is_layered(): - c.connection_types = c.connection_types or [] - c.connection_types.append( - self.improper_types[connection_type] - ) + if c.connection_types is None: + c.connection_types = IndexedSet([ + self._improper_types[connection_type] + ]) + else: + c.connection_types.add( + self._improper_types[connection_type] + ) else: - c.connection_type = self.improper_types[ + c.connection_type = self._improper_types[ connection_type ] diff --git a/gmso/tests/base_test.py b/gmso/tests/base_test.py index 561da1b4b..5507cc87d 100644 --- a/gmso/tests/base_test.py +++ b/gmso/tests/base_test.py @@ -10,7 +10,8 @@ from gmso.core.atom_type import AtomType from gmso.core.bond import Bond from gmso.core.box import Box -from gmso.core.dihedral import Dihedral +from gmso.core.dihedral import Dihedral, LayeredDihedral +from gmso.core.dihedral_type import DihedralType from gmso.core.element import Hydrogen, Oxygen from gmso.core.forcefield import ForceField from gmso.core.improper import Improper @@ -18,6 +19,7 @@ from gmso.core.topology import Topology from gmso.external import from_mbuild, from_parmed from gmso.external.convert_foyer_xml import from_foyer_xml +from gmso.lib.potential_templates import PotentialTemplateLibrary from gmso.tests.utils import get_path from gmso.utils.io import get_fn, has_foyer @@ -484,3 +486,50 @@ def pairpotentialtype_top(self): top.add_pairpotentialtype(pptype12) return top + + @pytest.fixture + def ld_top(self): + lib = PotentialTemplateLibrary() + rb_type = DihedralType.from_template( + potential_template=lib["RyckaertBellemansTorsionPotential"], + parameters={ + 'c0': 9.28 * u.kJ / u.mol, + 'c1': 12.16 * u.kJ / u.mol, + 'c2': -13.12 * u.kJ / u.mol, + 'c3': -3.06 * u.kJ / u.mol, + 'c4': 26.24 * u.kJ / u.mol, + 'c5': -31.5 * u.kJ / u.mol, + } + ) + + periodic_type = DihedralType.from_template( + lib["PeriodicTorsionPotential"], + parameters={ + "k": 1.25 * u.nm, + "phi_eq": 3.14159 * u.rad, + "n": 2 * u.dimensionless + } + ) + + top = Topology(name="Topology") + atoms = [Atom(name=f'Atom{i+1}') for i in range(0, 100)] + dihedrals_group = [ + (atoms[i], atoms[i+1], atoms[i+2], atoms[i+3]) + for i in range(0, 100, 4) + ] + + for j, atom_groups in enumerate(dihedrals_group): + if j % 2 == 0: + dh = Dihedral( + connection_members=atom_groups, + ) + dh.connection_type = rb_type + + else: + dh = LayeredDihedral( + connection_members=atom_groups + ) + dh.connection_types = [rb_type, periodic_type] + top.add_connection(connection=dh) + top.update_topology() + return top diff --git a/gmso/tests/test_dihedrals.py b/gmso/tests/test_dihedrals.py index fc4c4b3d7..583cccbd7 100644 --- a/gmso/tests/test_dihedrals.py +++ b/gmso/tests/test_dihedrals.py @@ -155,7 +155,7 @@ def test_equivalent_members_set(self): in dihedral_not_eq.equivalent_members() ) - def test_layered_dihedrals(self): + def test_layered_dihedrals(self, layered_dihedrals_top): atom1 = Atom(name="atom1") atom2 = Atom(name="atom2") atom3 = Atom(name="atom3") diff --git a/gmso/tests/test_topology.py b/gmso/tests/test_topology.py index 9c37bc231..e28cdda11 100644 --- a/gmso/tests/test_topology.py +++ b/gmso/tests/test_topology.py @@ -12,7 +12,7 @@ from gmso.core.bond import Bond from gmso.core.bond_type import BondType from gmso.core.box import Box -from gmso.core.dihedral import Dihedral +from gmso.core.dihedral import Dihedral, LayeredDihedral from gmso.core.dihedral_type import DihedralType from gmso.core.improper import Improper from gmso.core.improper_type import ImproperType @@ -744,3 +744,12 @@ def test_cget_untyped(self, typed_chloroethanol): with pytest.raises(ValueError): clone.get_untyped(group="foo") + + def test_top_with_layered_dihedrals(self, ld_top): + assert len(ld_top.dihedral_types) == 2 + assert len(ld_top.dihedrals) == 25 + for j in range(ld_top.n_dihedrals): + if j % 2 == 0: + assert isinstance(ld_top._dihedrals[j], Dihedral) + else: + assert isinstance(ld_top._dihedrals[j], LayeredDihedral) From 9e2f7dcbd9d77d5bd1011eb136e1f6c00f585580 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Aug 2021 05:13:34 +0000 Subject: [PATCH 15/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- gmso/core/dihedral.py | 4 +++- gmso/core/topology.py | 12 ++++++------ gmso/tests/base_test.py | 26 ++++++++++++-------------- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index a4a00cba4..9d4859de2 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -148,7 +148,9 @@ def __setattr__(self, key, value): @validator("dihedral_types_", pre=True) def validate_dihedral_types(cls, dihedral_types): - if not isinstance(dihedral_types, Iterable) or isinstance(dihedral_types, str): + if not isinstance(dihedral_types, Iterable) or isinstance( + dihedral_types, str + ): raise ValidationError("DihedralTypes should be iterable", cls) validate_type(dihedral_types, DihedralType) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 45142180e..7c26a3772 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -600,9 +600,9 @@ def update_connection_types(self): if isinstance(connection_type, DihedralType): if c.is_layered(): if c.connection_types is None: - c.connection_types = IndexedSet([ - self._dihedrals_types[connection_type] - ]) + c.connection_types = IndexedSet( + [self._dihedrals_types[connection_type]] + ) else: c.connection_types.add( self._dihedral_types[connection_type] @@ -614,9 +614,9 @@ def update_connection_types(self): if isinstance(connection_type, ImproperType): if c.is_layered(): if c.connection_types is None: - c.connection_types = IndexedSet([ - self._improper_types[connection_type] - ]) + c.connection_types = IndexedSet( + [self._improper_types[connection_type]] + ) else: c.connection_types.add( self._improper_types[connection_type] diff --git a/gmso/tests/base_test.py b/gmso/tests/base_test.py index 5507cc87d..3789d87c2 100644 --- a/gmso/tests/base_test.py +++ b/gmso/tests/base_test.py @@ -493,13 +493,13 @@ def ld_top(self): rb_type = DihedralType.from_template( potential_template=lib["RyckaertBellemansTorsionPotential"], parameters={ - 'c0': 9.28 * u.kJ / u.mol, - 'c1': 12.16 * u.kJ / u.mol, - 'c2': -13.12 * u.kJ / u.mol, - 'c3': -3.06 * u.kJ / u.mol, - 'c4': 26.24 * u.kJ / u.mol, - 'c5': -31.5 * u.kJ / u.mol, - } + "c0": 9.28 * u.kJ / u.mol, + "c1": 12.16 * u.kJ / u.mol, + "c2": -13.12 * u.kJ / u.mol, + "c3": -3.06 * u.kJ / u.mol, + "c4": 26.24 * u.kJ / u.mol, + "c5": -31.5 * u.kJ / u.mol, + }, ) periodic_type = DihedralType.from_template( @@ -507,14 +507,14 @@ def ld_top(self): parameters={ "k": 1.25 * u.nm, "phi_eq": 3.14159 * u.rad, - "n": 2 * u.dimensionless - } + "n": 2 * u.dimensionless, + }, ) top = Topology(name="Topology") - atoms = [Atom(name=f'Atom{i+1}') for i in range(0, 100)] + atoms = [Atom(name=f"Atom{i+1}") for i in range(0, 100)] dihedrals_group = [ - (atoms[i], atoms[i+1], atoms[i+2], atoms[i+3]) + (atoms[i], atoms[i + 1], atoms[i + 2], atoms[i + 3]) for i in range(0, 100, 4) ] @@ -526,9 +526,7 @@ def ld_top(self): dh.connection_type = rb_type else: - dh = LayeredDihedral( - connection_members=atom_groups - ) + dh = LayeredDihedral(connection_members=atom_groups) dh.connection_types = [rb_type, periodic_type] top.add_connection(connection=dh) top.update_topology() From 6cb028957b69546c174a3196127b47bc15a1d5e1 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Mon, 2 Aug 2021 10:03:53 -0500 Subject: [PATCH 16/19] WIP- Add documentation, fix object creation errors --- gmso/abc/abstract_connection.py | 2 +- gmso/core/dihedral.py | 29 ++++++++++++++++++++++++++--- gmso/tests/test_dihedrals.py | 2 +- gmso/tests/test_top.py | 4 ++-- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/gmso/abc/abstract_connection.py b/gmso/abc/abstract_connection.py index 819e33060..a55b6af61 100644 --- a/gmso/abc/abstract_connection.py +++ b/gmso/abc/abstract_connection.py @@ -60,7 +60,7 @@ def __repr__(self): return ( f"<{self.__class__.__name__} {self.name},\n " f"connection_members: {self.connection_members},\n " - f"potential: {str(getattr(self, 'connection_type', getattr(self, 'connection_types')))},\n " + f"potential: {str(self.connection_type)},\n " f"id: {id(self)}>" ) diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index 9d4859de2..decee6fba 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -12,8 +12,10 @@ class BaseDihedral(Connection): __base_doc__ = """A 4-partner connection between sites. - This is a subclass of the gmso.Connection superclass. - This class has strictly 4 members in its connection_members. + This is a subclass of the gmso.Connection superclass. This class + has strictly 4 members in its connection_members and used as + a base class to define many different forms of a Dihedral. + The connection_type in this class corresponds to gmso.DihedralType. The connectivity of a dihedral is: m1–m2–m3–m4 @@ -23,7 +25,7 @@ class BaseDihedral(Connection): Notes ----- Inherits some methods from Connection: - __eq__, __repr__, _validate methods + __eq__, _validate methods Additional _validate methods are presented """ @@ -88,6 +90,14 @@ def _equivalent_members_hash(self): def is_layered(self): return hasattr(self, "dihedral_types_") + def __repr__(self): + return ( + f"<{self.__class__.__name__} {self.name},\n " + f"connection_members: {self.connection_members},\n " + f"potential: {str(self.dihedral_types if self.is_layered() else self.dihedral_type)},\n " + f"id: {id(self)}>" + ) + class Config: fields = { "connection_members_": "connection_members", @@ -98,6 +108,12 @@ class Config: class Dihedral(BaseDihedral): + __base_doc__ = """A 4-Partner connection between 2 sites with a single dihedral type association + + Notes + ----- + This class inherits from BaseDihedral. + """ dihedral_type_: Optional[DihedralType] = Field( default=None, description="DihedralType of this dihedral." ) @@ -127,6 +143,13 @@ class Config: class LayeredDihedral(BaseDihedral): + __base_doc__ = """A 4-Partner connection between 2 sites with a multiple dihedral type associations + + Notes + ----- + This class inherits from BaseDihedral. + """ + dihedral_types_: Optional[IndexedSet] = Field( default=None, description="DihedralTypes of this dihedral." ) diff --git a/gmso/tests/test_dihedrals.py b/gmso/tests/test_dihedrals.py index 583cccbd7..fc4c4b3d7 100644 --- a/gmso/tests/test_dihedrals.py +++ b/gmso/tests/test_dihedrals.py @@ -155,7 +155,7 @@ def test_equivalent_members_set(self): in dihedral_not_eq.equivalent_members() ) - def test_layered_dihedrals(self, layered_dihedrals_top): + def test_layered_dihedrals(self): atom1 = Atom(name="atom1") atom2 = Atom(name="atom2") atom3 = Atom(name="atom3") diff --git a/gmso/tests/test_top.py b/gmso/tests/test_top.py index 27c114219..2eb830de9 100644 --- a/gmso/tests/test_top.py +++ b/gmso/tests/test_top.py @@ -74,7 +74,7 @@ def test_water_top(self, water_system): write_top(top, "water.top") def test_ethane_periodic(self, typed_ethane): - from gmso.core.parametric_potential import ParametricPotential + from gmso.core.dihedral_type import DihedralType from gmso.lib.potential_templates import PotentialTemplateLibrary per_torsion = PotentialTemplateLibrary()["PeriodicTorsionPotential"] @@ -83,7 +83,7 @@ def test_ethane_periodic(self, typed_ethane): "phi_eq": 15 * u.Unit("degree"), "n": 3 * u.Unit("dimensionless"), } - periodic_dihedral_type = ParametricPotential.from_template( + periodic_dihedral_type = DihedralType.from_template( potential_template=per_torsion, parameters=params ) for dihedral in typed_ethane.dihedrals: From 2dd6fee310fc5cd9a3eb1c79f290ef11bb0e6704 Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Tue, 31 Aug 2021 10:52:58 -0500 Subject: [PATCH 17/19] WIP- Address PR review comments --- gmso/core/dihedral.py | 4 ++-- gmso/core/improper.py | 4 ++-- gmso/core/topology.py | 22 ++++++---------------- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index decee6fba..8489a2a58 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -108,7 +108,7 @@ class Config: class Dihedral(BaseDihedral): - __base_doc__ = """A 4-Partner connection between 2 sites with a single dihedral type association + __base_doc__ = """A 4-Partner connection between 4 sites with a **single** dihedral type association Notes ----- @@ -143,7 +143,7 @@ class Config: class LayeredDihedral(BaseDihedral): - __base_doc__ = """A 4-Partner connection between 2 sites with a multiple dihedral type associations + __base_doc__ = """A 4-Partner connection between 4 sites with a **multiple** dihedral type associations Notes ----- diff --git a/gmso/core/improper.py b/gmso/core/improper.py index d2152a68f..86dc3e0ce 100644 --- a/gmso/core/improper.py +++ b/gmso/core/improper.py @@ -11,7 +11,7 @@ class BaseImproper(Connection): - __base_doc__ = """sA 4-partner connection between sites. + __base_doc__ = """A 4-partner connection between sites. This is a subclass of the gmso.Connection superclass. This class has strictly 4 members in its connection_members. @@ -159,7 +159,7 @@ def __setattr__(self, key, value): super().__setattr__(key, value) @validator("improper_types_", pre=True, always=True) - def validate_dihedral_types(cls, improper_types): + def validate_improper_types(cls, improper_types): if not isinstance(improper_types, Iterable): raise ValidationError("ImproperTypes should be iterable", cls) diff --git a/gmso/core/topology.py b/gmso/core/topology.py index 7c26a3772..11e631370 100644 --- a/gmso/core/topology.py +++ b/gmso/core/topology.py @@ -599,28 +599,18 @@ def update_connection_types(self): c.connection_type = self._angle_types[connection_type] if isinstance(connection_type, DihedralType): if c.is_layered(): - if c.connection_types is None: - c.connection_types = IndexedSet( - [self._dihedrals_types[connection_type]] - ) - else: - c.connection_types.add( - self._dihedral_types[connection_type] - ) + c.connection_types.add( + self._dihedral_types[connection_type] + ) else: c.connection_type = self._dihedral_types[ connection_type ] if isinstance(connection_type, ImproperType): if c.is_layered(): - if c.connection_types is None: - c.connection_types = IndexedSet( - [self._improper_types[connection_type]] - ) - else: - c.connection_types.add( - self._improper_types[connection_type] - ) + c.connection_types.add( + self._improper_types[connection_type] + ) else: c.connection_type = self._improper_types[ connection_type From 580b7df1c0e9600209084e851dc6a9c9017d5aff Mon Sep 17 00:00:00 2001 From: Umesh Timalsina Date: Thu, 24 Feb 2022 13:05:50 -0600 Subject: [PATCH 18/19] Update gmso/core/dihedral.py Co-authored-by: Justin Gilmer --- gmso/core/dihedral.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmso/core/dihedral.py b/gmso/core/dihedral.py index 8489a2a58..7e470e28f 100644 --- a/gmso/core/dihedral.py +++ b/gmso/core/dihedral.py @@ -143,7 +143,7 @@ class Config: class LayeredDihedral(BaseDihedral): - __base_doc__ = """A 4-Partner connection between 4 sites with a **multiple** dihedral type associations + __base_doc__ = """A 4-Partner connection between 4 sites with **multiple** dihedral type associations Notes ----- From 6140f01590305f6d1fd8cf3174d8855fa6ae6153 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 9 May 2022 04:42:44 +0000 Subject: [PATCH 19/19] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- gmso/tests/base_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gmso/tests/base_test.py b/gmso/tests/base_test.py index 57e0095ab..4b0562958 100644 --- a/gmso/tests/base_test.py +++ b/gmso/tests/base_test.py @@ -592,4 +592,4 @@ def pentane_ua_parmed(self, pentane_ua_mbuild): @pytest.fixture(scope="session") def pentane_ua_gmso(self, pentane_ua_mbuild): - return from_mbuild(pentane_ua_mbuild) \ No newline at end of file + return from_mbuild(pentane_ua_mbuild)