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 == ("*", "*", "*", "*")