diff --git a/gmso/core/forcefield.py b/gmso/core/forcefield.py index 9316abe2..fc506975 100644 --- a/gmso/core/forcefield.py +++ b/gmso/core/forcefield.py @@ -351,6 +351,33 @@ def _get_bond_type(self, atom_types, return_match_order=False, warn=False): if reverse in self.bond_types: match = self.bond_types[reverse], (1, 0) + if match: + if return_match_order: + return match + else: + return match[0] + + for i in range(1, 3): + forward_patterns = mask_with(atom_types, i) + reverse_patterns = mask_with(reversed(atom_types), i) + + for forward_pattern, reverse_pattern in zip( + forward_patterns, reverse_patterns + ): + forward_match_key = FF_TOKENS_SEPARATOR.join(forward_pattern) + reverse_match_key = FF_TOKENS_SEPARATOR.join(reverse_pattern) + + if forward_match_key in self.bond_types: + match = self.bond_types[forward_match_key], (0, 1) + break + + if reverse_match_key in self.bond_types: + match = self.bond_types[reverse_match_key], (1, 0) + break + + if match: + break + msg = ( f"BondType between atoms {atom_types[0]} and {atom_types[1]} " f"is missing from the ForceField" @@ -382,6 +409,33 @@ def _get_angle_type(self, atom_types, return_match_order=False, warn=False): if reverse in self.angle_types: match = self.angle_types[reverse], (2, 1, 0) + if match: + if return_match_order: + return match + else: + return match[0] + + for i in range(1, 4): + forward_patterns = mask_with(atom_types, i) + reverse_patterns = mask_with(reversed(atom_types), i) + + for forward_pattern, reverse_pattern in zip( + forward_patterns, reverse_patterns + ): + forward_match_key = FF_TOKENS_SEPARATOR.join(forward_pattern) + reverse_match_key = FF_TOKENS_SEPARATOR.join(reverse_pattern) + + if forward_match_key in self.angle_types: + match = self.angle_types[forward_match_key], (0, 1, 2) + break + + if reverse_match_key in self.angle_types: + match = self.angle_types[reverse_match_key], (2, 1, 0) + break + + if match: + break + msg = ( f"AngleType between atoms {atom_types[0]}, {atom_types[1]} " f"and {atom_types[2]} is missing from the ForceField" diff --git a/gmso/tests/files/alkanes_wildcards.xml b/gmso/tests/files/alkanes_wildcards.xml index 7cd7f3f2..393d7e87 100644 --- a/gmso/tests/files/alkanes_wildcards.xml +++ b/gmso/tests/files/alkanes_wildcards.xml @@ -46,38 +46,32 @@ - + - - + + - + - - + + - + - - + + - + - - - - - - - - + + @@ -88,24 +82,14 @@ - - - - - - - - - - - + - - - - - - + + + + + + diff --git a/gmso/tests/parameterization/test_opls_gmso.py b/gmso/tests/parameterization/test_parameterizations.py similarity index 62% rename from gmso/tests/parameterization/test_opls_gmso.py rename to gmso/tests/parameterization/test_parameterizations.py index 085ac2a2..8986a439 100644 --- a/gmso/tests/parameterization/test_opls_gmso.py +++ b/gmso/tests/parameterization/test_parameterizations.py @@ -4,11 +4,13 @@ import parmed as pmd import pytest +from gmso import ForceField from gmso.external.convert_parmed import from_parmed from gmso.parameterization.parameterize import apply from gmso.tests.parameterization.parameterization_base_test import ( ParameterizationBaseTest, ) +from gmso.tests.utils import get_path def get_foyer_opls_test_dirs(): @@ -56,3 +58,25 @@ def test_foyer_oplsaa_files( assert_same_connection_params(gmso_top, gmso_top_from_pmd) assert_same_connection_params(gmso_top, gmso_top_from_pmd, "angles") assert_same_connection_params(gmso_top, gmso_top_from_pmd, "dihedrals") + + +class TestGeneralParameterizations(ParameterizationBaseTest): + def test_wildcards(self, ethane_methane_top): + from gmso.core.views import PotentialFilters + + ff = ForceField(get_path("alkanes_wildcards.xml")) + ptop = apply(ethane_methane_top, ff, identify_connections=True) + assert ptop.is_fully_typed() + assert len(ptop.bond_types) == 11 + assert len(ptop.bond_types(PotentialFilters.UNIQUE_NAME_CLASS)) == 2 + assert ptop.bonds[0].bond_type.member_types == ("*", "opls_135") # ethane + assert ptop.bonds[8].bond_type.member_types == ("*", "*") # methane + + assert len(ptop.angle_types) == 12 + 6 # ethane + methane + assert len(ptop.angle_types(PotentialFilters.UNIQUE_NAME_CLASS)) == 2 + assert ptop.angles[0].angle_type.member_types == ("*", "*", "*") + assert ptop.angles[2].angle_type.member_types == ("*", "*", "opls_135") + + assert len(ptop.dihedral_types) == 9 + assert len(ptop.dihedral_types(PotentialFilters.UNIQUE_NAME_CLASS)) == 1 + assert ptop.dihedrals[0].dihedral_type.member_types == ("*", "*", "*", "*")