def _validate_connection_members(connection_members): """Ensure all elements of entered connection_members are gmso.Site""" for partner in connection_members: if not isinstance(partner, Site): raise GMSOError("Supplied non-Site {}".format(partner)) if len(set(connection_members)) != len(connection_members): raise GMSOError("Error, cannot add connection between same sites.") return tuple(connection_members)
def _validate_two_member_type_names(types): """Ensure exactly 2 partners are involved in BondType""" if len(types) != 2 and len(types) != 0: raise GMSOError("Trying to create a BondType " "with {} constituent types".format(len(types))) if not all([isinstance(t, str) for t in types]): raise GMSOError("Types passed to BondType " "need to be strings corresponding to AtomType names") return types
def convert_ryckaert_to_opls(ryckaert_connection_type): """Convert Ryckaert-Bellemans dihedral to OPLS. NOTE: the conventions defining the dihedral angle are different for OPLS and RB torsions. OPLS torsions are defined with phi_cis = 0 while RB torsions are defined as phi_trans = 0. """ templates = PotentialTemplateLibrary() ryckaert_bellemans_torsion_potential = templates[ "RyckaertBellemansTorsionPotential"] opls_torsion_potential = templates["OPLSTorsionPotential"] valid_connection_type = False if (ryckaert_connection_type.independent_variables == ryckaert_bellemans_torsion_potential.independent_variables): if (sympy.simplify(ryckaert_connection_type.expression - ryckaert_bellemans_torsion_potential.expression) == 0): valid_connection_type = True if not valid_connection_type: raise GMSOError("Cannot use convert_ryckaert_to_opls " "function to convert a ConnectionType that is not an " "RyckaertBellemansTorsionPotential") c0 = ryckaert_connection_type.parameters["c0"] c1 = ryckaert_connection_type.parameters["c1"] c2 = ryckaert_connection_type.parameters["c2"] c3 = ryckaert_connection_type.parameters["c3"] c4 = ryckaert_connection_type.parameters["c4"] c5 = ryckaert_connection_type.parameters["c5"] if c5 != 0.0: raise GMSOError("Cannot convert Ryckaert-Bellemans dihedral " "to OPLS dihedral if c5 is not equal to zero.") converted_params = { "k0": 2.0 * (c0 + c1 + c2 + c3 + c4), "k1": (-2.0 * c1 - (3.0 / 2.0) * c3), "k2": (-c2 - c4), "k3": ((-1.0 / 2.0) * c3), "k4": ((-1.0 / 4.0) * c4), } name = opls_torsion_potential.name expression = opls_torsion_potential.expression variables = opls_torsion_potential.independent_variables opls_connection_type = gmso.DihedralType( name=name, expression=expression, independent_variables=variables, parameters=converted_params, ) return opls_connection_type
def convert_ryckaert_to_opls(ryckaert_connection_type): """Convert Ryckaert-Bellemans dihedral to OPLS NOTE: the conventions defining the dihedral angle are different for OPLS and RB torsions. OPLS torsions are defined with phi_cis = 0 while RB torsions are defined as phi_trans = 0. """ templates = PotentialTemplateLibrary() ryckaert_bellemans_torsion_potential = templates[ 'RyckaertBellemansTorsionPotential'] opls_torsion_potential = templates['OPLSTorsionPotential'] valid_connection_type = False if (ryckaert_connection_type.independent_variables == ryckaert_bellemans_torsion_potential.independent_variables): if sympy.simplify( ryckaert_connection_type.expression - ryckaert_bellemans_torsion_potential.expression) == 0: valid_connection_type = True if not valid_connection_type: raise GMSOError('Cannot use convert_ryckaert_to_opls ' 'function to convert a ConnectionType that is not an ' 'RyckaertBellemansTorsionPotential') c0 = ryckaert_connection_type.parameters['c0'] c1 = ryckaert_connection_type.parameters['c1'] c2 = ryckaert_connection_type.parameters['c2'] c3 = ryckaert_connection_type.parameters['c3'] c4 = ryckaert_connection_type.parameters['c4'] c5 = ryckaert_connection_type.parameters['c5'] if c5 != 0.0: raise GMSOError('Cannot convert Ryckaert-Bellemans dihedral ' 'to OPLS dihedral if c5 is not equal to zero.') converted_params = { 'k0': 2. * (c0 + c1 + c2 + c3 + c4), 'k1': (-2. * c1 - (3. / 2.) * c3), 'k2': (-c2 - c4), 'k3': ((-1. / 2.) * c3), 'k4': ((-1. / 4.) * c4) } name = opls_torsion_potential.name expression = opls_torsion_potential.expression variables = opls_torsion_potential.independent_variables opls_connection_type = gmso.DihedralType(name=name, expression=expression, independent_variables=variables, parameters=converted_params) return opls_connection_type
def _validate_dihedraltype(contype): """Ensure connection_type is a DihedralType """ if contype is None: warnings.warn("Non-parametrized Dihedral detected") elif not isinstance(contype, DihedralType): raise GMSOError("Supplied non-DihedralType {}".format(contype)) return contype
def _validate_four_partners(connection_members): """Ensure 4 partners are involved in Dihedral""" assert connection_members is not None, "connection_members is not given" if len(connection_members) != 4: raise GMSOError("Trying to create an Dihedral " "with {} connection members". format(len(connection_members))) return connection_members
def update_connection_types(self): """Update the connection types based on the connection collection in the topology. This method looks into all the connection objects (Bonds, Angles, Dihedrals) to check if any Potential object (BondType, AngleType, DihedralType) is not in the topology's respective collection and will add those objects there. See Also -------- gmso.Topology.update_atom_types : Update atom types in the topology. """ for c in self.connections: if c.connection_type is None: warnings.warn('Non-parametrized Connection {} detected'.format(c)) elif not isinstance(c.connection_type, Potential): raise GMSOError('Non-Potential {} found' 'in Connection {}'.format(c.connection_type, 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 if isinstance(c.connection_type, AngleType): self._angle_types[c.connection_type] = c.connection_type if isinstance(c.connection_type, DihedralType): self._dihedral_types[c.connection_type] = c.connection_type 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]
def _validate_impropertype(contype): """Ensure connection_type is a ImproperType """ if contype is None: warnings.warn("Non-parametrized Improper detected") elif not isinstance(contype, ImproperType): raise GMSOError("Supplied non-ImproperType {}".format(contype)) return contype
def _reindex_connection_types(self, ref): if ref not in self._index_refs: raise GMSOError(f'cannot reindex {ref}. It should be one of ' f'{ANGLE_TYPE_DICT}, {BOND_TYPE_DICT}, ' f'{ANGLE_TYPE_DICT}, {DIHEDRAL_TYPE_DICT}, {IMPROPER_TYPE_DICT}') for i, ref_member in enumerate(self._set_refs[ref].keys()): self._index_refs[ref][ref_member] = i
def _validate_two_partners(connection_members): """Ensure 2 partners are involved in Bond""" assert connection_members is not None, "connection_members is not given" if len(connection_members) != 2: raise GMSOError("Trying to create a Bond " "with {} connection members". format(len(connection_members))) return connection_members
def _validate_connection_type(c_type): """Ensure given connection_type is the gmso.Potential""" if c_type is None: warnings.warn("Non-parametrized Connection detected") elif not isinstance(c_type, Potential): raise GMSOError("Supplied non-Potential {}".format(c_type)) return c_type
def _verify_potential_template_keys(_dict, name): assert 'name' in _dict, f"Key name not found in the potential template {name}.json" assert 'expression' in _dict, f"Key expression not found in the potential template {name}.json" assert 'independent_variables' in _dict, f"Key independent_variables not found in the potential template {name}.json" if str(name) != _dict['name']: raise GMSOError( f'Mismatch between Potential name {name} and {_dict["name"]}')
def update_atom_types(self): """Update atom types in the topology This method checks all the sites in the topology which have an associated AtomType and if that AtomType is not in the topology's AtomTypes collection, it will add it there. See Also: --------- gmso.Topology.update_connection_types : Update the connection types based on the connection collection in the topology """ for site in self._sites: if site.atom_type is None: warnings.warn('Non-parametrized site detected {}'.format(site)) elif not isinstance(site.atom_type, AtomType): raise GMSOError( 'Non AtomType instance found in site {}'.format(site)) elif site.atom_type not in self._atom_types: site.atom_type.topology = self self._atom_types[site.atom_type] = site.atom_type self._atom_types_idx[site.atom_type] = len( self._atom_types) - 1 elif site.atom_type in self._atom_types: site.atom_type = self._atom_types[site.atom_type] self.is_typed(updated=True)
def _validate_angletype(contype): """Ensure connection_type is a AngleType """ if contype is None: warnings.warn("Non-parametrized Angle detected") elif not isinstance(contype, AngleType): raise GMSOError("Supplied non-AngleType {}".format(contype)) return contype
def from_template(cls, potential_template, parameters, topology=None): """Create a potential object from the potential_template Parameters ---------- potential_template : gmso.lib.potential_templates.PotentialTemplate, The potential template object parameters : dict, The parameters of the potential object to create topology : gmso.Topology, default=None The topology to which the created potential object belongs to Returns ------- gmso.ParametricPotential The potential object created Raises ------ GMSOError If potential_template is not of instance PotentialTemplate """ from gmso.lib.potential_templates import PotentialTemplate if not isinstance(potential_template, PotentialTemplate): raise GMSOError( f'Object {type(potential_template)} is not an instance of PotentialTemplate.' ) return cls( name=potential_template.name, expression=potential_template.expression, independent_variables=potential_template.independent_variables, parameters=parameters, topology=topology)
def element_by_atomic_number(atomic_number): """Search for an element by its atomic number Look up an element from a list of known elements by atomic number. Return None if no match found. Parameters ---------- atomic_number : int Element atomic number that need to look for if a string is provided, only numbers are considered during the search Returns ------- matched_element : element.Element Return an element from the periodic table if we find a match, otherwise raise GMSOError """ if isinstance(atomic_number, str): atomic_number_trimmed = int( sub('[a-z -]', '', atomic_number.lower()).lstrip('0')) msg = '''Letters and spaces are not considered when searching by element atomic number. \n {} became {}'.format(atomic_number, atomic_number_trimmed)''' warnings.warn(msg) else: atomic_number_trimmed = atomic_number matched_element = atomic_dict.get(atomic_number_trimmed) if matched_element is None: raise GMSOError( f'Failed to find an element with atomic number {atomic_number_trimmed}' ) return matched_element
def scaling_factors(self, scaling_factors): expected_items = [ "vdw_12", "vdw_13", "vdw_14", "coul_12", "coul_13", "coul_14" ] if not isinstance(scaling_factors, dict): raise GMSOError("Scaling factors should be a dictionary") for item in expected_items: if item not in scaling_factors.keys(): raise GMSOError( f"Expected {expected_items} as keys in the scaling factors" ) for val in scaling_factors.values(): if val < 0.0 or val > 1.0: raise GMSOError( "Scaling factors should be between 0.0 and 1.0") self._scaling_factors = scaling_factors
def _check_compatibility(top): """Check Topology object for compatibility with Cassandra MCF format""" if not isinstance(top, Topology): raise GMSOError("MCF writer requires a Topology object.") if not all([site.atom_type.name for site in top.sites]): raise GMSOError( "MCF writing not supported without parameterized forcefield.") accepted_potentials = [ potential_templates['LennardJonesPotential'], potential_templates['MiePotential'], potential_templates['HarmonicAnglePotential'], potential_templates['PeriodicTorsionPotential'], potential_templates['OPLSTorsionPotential'], potential_templates['RyckaertBellemansTorsionPotential'], ] check_compatibility(top, accepted_potentials)
def _reindex_connection_types(self, ref): """Re-generate the indices of the connection types in the topology.""" if ref not in self._index_refs: raise GMSOError( f"cannot reindex {ref}. It should be one of " f"{ANGLE_TYPE_DICT}, {BOND_TYPE_DICT}, " f"{ANGLE_TYPE_DICT}, {DIHEDRAL_TYPE_DICT}, {IMPROPER_TYPE_DICT}" ) for i, ref_member in enumerate(self._set_refs[ref].keys()): self._index_refs[ref][ref_member] = i
def _validate_connection(site, connection): if not isinstance(site, Site): raise ValueError("Passed value {} is not a site".format(site)) from gmso.core.connection import Connection if not isinstance(connection, Connection): raise ValueError("Passed value {} is not a Connection".format(connection)) if site not in connection.connection_members: raise GMSOError("Error: Site not in connection members. Cannot add the connection.") if connection in site.connections: return None return connection
def convert_opls_to_ryckaert(opls_connection_type): """Convert an OPLS dihedral to Ryckaert-Bellemans dihedral. Equations taken/modified from: http://manual.gromacs.org/documentation/2019/ reference-manual/functions/bonded-interactions.html NOTE: the conventions defining the dihedral angle are different for OPLS and RB torsions. OPLS torsions are defined with phi_cis = 0 while RB torsions are defined as phi_trans = 0. """ templates = PotentialTemplateLibrary() opls_torsion_potential = templates["OPLSTorsionPotential"] valid_connection_type = False if (opls_connection_type.independent_variables == opls_torsion_potential.independent_variables): if (sympy.simplify(opls_connection_type.expression - opls_torsion_potential.expression) == 0): valid_connection_type = True if not valid_connection_type: raise GMSOError("Cannot use convert_opls_to_ryckaert " "function to convert a ConnectionType that is not an " "OPLSTorsionPotential") f0 = opls_connection_type.parameters["k0"] f1 = opls_connection_type.parameters["k1"] f2 = opls_connection_type.parameters["k2"] f3 = opls_connection_type.parameters["k3"] f4 = opls_connection_type.parameters["k4"] converted_params = { "c0": (f2 + 0.5 * (f0 + f1 + f3)), "c1": (0.5 * (-f1 + 3.0 * f3)), "c2": (-f2 + 4.0 * f4), "c3": (-2.0 * f3), "c4": (-4.0 * f4), "c5": 0.0 * u.Unit("kJ/mol"), } ryckaert_bellemans_torsion_potential = templates[ "RyckaertBellemansTorsionPotential"] name = ryckaert_bellemans_torsion_potential.name expression = ryckaert_bellemans_torsion_potential.expression variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = gmso.DihedralType( name=name, expression=expression, independent_variables=variables, parameters=converted_params, ) return ryckaert_connection_type
def validate_fields(cls, values): connection_members = values.get('connection_members') if not all(isinstance(x, Site) for x in connection_members): raise TypeError( f'A non-site object provided to be a connection member') if len(set(connection_members)) != len(connection_members): raise GMSOError( f'Trying to create a {cls.__name__} between ' f'same sites. A {cls.__name__} between same ' f'{type(connection_members[0]).__name__}s is not allowed') if not values.get('name'): values['name'] = cls.__name__ return values
def convert_opls_to_ryckaert(opls_connection_type): """Convert an OPLS dihedral to Ryckaert-Bellemans dihedral Equations taken/modified from: http://manual.gromacs.org/documentation/2019/ reference-manual/functions/bonded-interactions.html NOTE: the conventions defining the dihedral angle are different for OPLS and RB torsions. OPLS torsions are defined with phi_cis = 0 while RB torsions are defined as phi_trans = 0. """ templates = PotentialTemplateLibrary() opls_torsion_potential = templates['OPLSTorsionPotential'] valid_connection_type = False if (opls_connection_type.independent_variables == opls_torsion_potential.independent_variables): if sympy.simplify(opls_connection_type.expression - opls_torsion_potential.expression) == 0: valid_connection_type = True if not valid_connection_type: raise GMSOError('Cannot use convert_opls_to_ryckaert ' 'function to convert a ConnectionType that is not an ' 'OPLSTorsionPotential') f0 = opls_connection_type.parameters['k0'] f1 = opls_connection_type.parameters['k1'] f2 = opls_connection_type.parameters['k2'] f3 = opls_connection_type.parameters['k3'] f4 = opls_connection_type.parameters['k4'] converted_params = { 'c0': (f2 + 0.5 * (f0 + f1 + f3)), 'c1': (0.5 * (-f1 + 3. * f3)), 'c2': (-f2 + 4. * f4), 'c3': (-2. * f3), 'c4': (-4. * f4), 'c5': 0. * u.Unit('kJ/mol') } ryckaert_bellemans_torsion_potential = templates[ 'RyckaertBellemansTorsionPotential'] name = ryckaert_bellemans_torsion_potential.name expression = ryckaert_bellemans_torsion_potential.expression variables = ryckaert_bellemans_torsion_potential.independent_variables ryckaert_connection_type = gmso.DihedralType( name=name, expression=expression, independent_variables=variables, parameters=converted_params) return ryckaert_connection_type
def element_by_smarts_string(smarts_string): """Search for an element by a given SMARTS string. Look up an element from a list of known elements by SMARTS string. Return None if no match found. Parameters ---------- smarts_string : str SMARTS string representation of an atom type or its local chemical context. The Foyer SMARTS parser will be used to find the central atom and look up an Element. Note that this means some SMARTS grammar may not be parsed properly. For details, see https://github.com/mosdef-hub/foyer/issues/63 Returns ------- matched_element : element.Element Return an element from the periodic table if we find a match Raises ------ GMSOError If no matching element is found for the provided smarts string """ from gmso.utils.io import import_ foyer = import_("foyer") SMARTS = foyer.smarts.SMARTS PARSER = SMARTS() symbols = PARSER.parse(smarts_string).iter_subtrees_topdown() first_symbol = None for symbol in symbols: if symbol.data == "atom_symbol": first_symbol = symbol.children[0] break matched_element = None if first_symbol is not None: matched_element = element_by_symbol(first_symbol) if matched_element is None: raise GMSOError( f"Failed to find an element from SMARTS string {smarts_string}. The " f"parser detected a central node with name {first_symbol}") return matched_element
def _verify_potential_template_keys(_dict, name): """Verify the potential template is properly formatted.""" assert ( "name" in _dict), f"Key name not found in the potential template {name}.json" assert ( "expression" in _dict ), f"Key expression not found in the potential template {name}.json" assert ( "independent_variables" in _dict ), f"Key independent_variables not found in the potential template {name}.json" if str(name) != _dict["name"]: raise GMSOError( f'Mismatch between Potential name {name} and {_dict["name"]}')
def validate_fields(cls, values): connection_members = values.get("connection_members") if all(isinstance(member, dict) for member in connection_members): connection_members = [ cls.__members_creator__(x) for x in connection_members ] if not all(isinstance(x, Site) for x in connection_members): raise TypeError( f"A non-site object provided to be a connection member") if len(set(connection_members)) != len(connection_members): raise GMSOError( f"Trying to create a {cls.__name__} between " f"same sites. A {cls.__name__} between same " f"{type(connection_members[0]).__name__}s is not allowed") if not values.get("name"): values["name"] = cls.__name__ return values
def element_by_smarts_string(smarts_string): """Search for an element by a given SMARTS string Look up an element from a list of known elements by SMARTS string. Return None if no match found. Parameters ---------- smarts_string : str SMARTS string representation of an atom type or its local chemical context. The Foyer SMARTS parser will be used to find the central atom and look up an Element. Note that this means some SMARTS grammar may not be parsed properly. For details, see https://github.com/mosdef-hub/foyer/issues/63 Returns ------- matched_element : element.Element or None Return an element from the periodict table if we find a match, otherwise return None """ from foyer.smarts import SMARTS PARSER = SMARTS() symbol = next( PARSER.parse(smarts_string).find_data('atom_symbol')).children[0] print(symbol) matched_element = element_by_symbol(symbol) if matched_element is None: raise GMSOError( f'' 'Failed to find an element from SMARTS string {smarts_string). The' 'parser detected a central node with name {symbol}') return matched_element
def element_by_atom_type(atom_type): """Search for an element by a given a gmso AtomType object Look up an element from a list of known elements by atom type. Return None if no match is found. Parameters ---------- atom_type : gmso.core.atom_type.AtomType AtomType object to be parsed for element information. Attributes are looked up in the order of mass, name, and finally definition (the SMARTS string). Because of the loose structure of this class, a successful lookup is not guaranteed. Returns ------- matched_element : element.Element or None Return an element from the periodict table if we find a match, otherwise return None """ matched_element = None if matched_element is None and atom_type.mass: matched_element = element_by_mass(atom_type.mass, exact=False) if matched_element is None and atom_type.name: matched_element = element_by_symbol(atom_type.name) if matched_element is None and atom_type.definition: matched_element = element_by_smarts_string(atom_type.definition) if matched_element is None: raise GMSOError(f'Failed to find an element from atom type' '{atom_type} with ' 'properties mass: {atom_type.mass}, name:' '{atom_type.name}, and ' 'definition: {atom_type.definition}') return matched_element
def is_valid_position(cls, position): """Validate attribute position.""" if position is None: return u.unyt_array([np.nan] * 3, u.nm) if not isinstance(position, u.unyt_array): try: position *= u.nm except InvalidUnitOperation as e: raise GMSOError( f"Converting object of type {type(position)} failed with following error: {e}" ) warnings.warn("Positions are assumed to be in nm") try: position = np.reshape(position, newshape=(3, ), order="C") position.convert_to_units(u.nm) except ValueError: raise ValueError( f"Position of shape {position.shape} is not valid. " "Accepted values: (a.) list-like of length 3" "(b.) np.array or unyt.unyt_array of shape (3,)") return position
def combining_rule(self, rule): if rule not in ['lorentz', 'geometric']: raise GMSOError('Combining rule must be `lorentz` or `geometric`') self._combining_rule = rule