def validate_class_type_exclusivity(self, ff_tree): """Assert unique bond/angle/dihedral definitions and class lengths.""" sections = { "HarmonicBondForce/Bond": 2, "HarmonicAngleForce/Angle": 3, "RBTorsionForce/Proper": 4, } errors = [] for element, num_atoms in sections.items(): valid_attribs = set() for n in range(1, num_atoms + 1): valid_attribs.add("class{}".format(n)) valid_attribs.add("type{}".format(n)) for entry in ff_tree.xpath("/ForceField/{}".format(element)): attribs = [ valid for valid in valid_attribs if entry.attrib.get(valid) is not None ] if num_atoms != len(attribs): error = ValidationError( 'Invalid number of "class" and/or "type" attributes for' " {} at line {}".format(element, entry.sourceline), None, entry.sourceline, ) errors.append(error) number_endings = Counter([a[-1] for a in attribs]) if not all(1 == x for x in number_endings.values()): error = ValidationError( 'Only one "class" or "type" attribute may be defined' " for each atom in a bonded force. See line {}".format( entry.sourceline), None, entry.sourceline, ) errors.append(error) referenced_types = [] for valid in valid_attribs: if valid.startswith("type"): atomtype = entry.attrib.get(valid) if atomtype: referenced_types.append(atomtype) for atomtype in referenced_types: if atomtype not in self.atom_type_names: error = ValidationError( "Atom type {} is found in {} at line {} but" " undefined in AtomTypes".format( atomtype, element.split("/")[0], entry.sourceline, ), None, entry.sourceline, ) errors.append(error) raise_collected(errors)
def validate_smarts(self, debug): """ Check SMARTS definitions for missing or non-parseable """ missing_smarts = [] errors = [] for entry in self.atom_types: smarts_string = entry.attrib.get('def') if not smarts_string: warn("You have empty smart definition(s)", ValidationWarning) continue name = entry.attrib['name'] if smarts_string is None: missing_smarts.append(name) continue # make sure smarts string can be parsed try: self.smarts_parser.parse(smarts_string) except lark.ParseError as ex: if " col " in ex.args[0]: column = ex.args[0][ex.args[0].find(" col ") + 5:].strip() column = " at character {} of {}".format( column, smarts_string) else: column = "" malformed = ValidationError( "Malformed SMARTS string{} on line {}".format( column, entry.sourceline), ex, entry.sourceline) errors.append(malformed) continue # make sure referenced labels exist smarts_graph = SMARTSGraph(smarts_string, parser=self.smarts_parser, name=name, overrides=entry.attrib.get('overrides')) for atom_expr in nx.get_node_attributes(smarts_graph, name='atom').values(): labels = atom_expr.find_data('has_label') for label in labels: atom_type = label.children[0][1:] if atom_type not in self.atom_type_names: undefined = ValidationError( "Reference to undefined atomtype '{}' in SMARTS " "string '{}' at line {}".format( atom_type, entry.attrib['def'], entry.sourceline), None, entry.sourceline) errors.append(undefined) raise_collected(errors) if missing_smarts and debug: warn( "The following atom types do not have smarts definitions: {}". format(', '.join(missing_smarts)), ValidationWarning) if missing_smarts and not debug: warn( "There are {} atom types that are missing a smarts definition. " "To view the missing atom types, re-run with debug=True when " "applying the forcefield.".format(len(missing_smarts)), ValidationWarning)
def validate_class_type_exclusivity(self, ff_tree): sections = { 'HarmonicBondForce/Bond': 2, 'HarmonicAngleForce/Angle': 3, 'RBTorsionForce/Proper': 4 } errors = [] for element, num_atoms in sections.items(): valid_attribs = set() for n in range(1, num_atoms + 1): valid_attribs.add('class{}'.format(n)) valid_attribs.add('type{}'.format(n)) for entry in ff_tree.xpath('/ForceField/{}'.format(element)): attribs = [ valid for valid in valid_attribs if entry.attrib.get(valid) is not None ] if num_atoms != len(attribs): error = ValidationError( 'Invalid number of "class" and/or "type" attributes for' ' {} at line {}'.format(element, entry.sourceline), None, entry.sourceline) errors.append(error) number_endings = Counter([a[-1] for a in attribs]) if not all(1 == x for x in number_endings.values()): error = ValidationError( 'Only one "class" or "type" attribute may be defined' ' for each atom in a bonded force. See line {}'.format( entry.sourceline), None, entry.sourceline) errors.append(error) referenced_types = [] for valid in valid_attribs: if valid.startswith('type'): atomtype = entry.attrib.get(valid) if atomtype: referenced_types.append(atomtype) for atomtype in referenced_types: if atomtype not in self.atom_type_names: error = ValidationError( 'Atom type {} is found in {} at line {} but' ' undefined in AtomTypes'.format( atomtype, element.split('/')[0], entry.sourceline), None, entry.sourceline) errors.append(error) raise_collected(errors)
def validate_overrides(self): errors = [] for entry in self.atom_types: overrides = entry.attrib.get('overrides') if not overrides: continue overridden_types = [at.strip() for at in overrides.split(',') if at] for atom_type in overridden_types: if atom_type not in self.atom_type_names: undefined = ValidationError( "Reference to undefined atomtype '{}' in 'overrides' " "'{}' at line {}".format(atom_type, entry.attrib['overrides'], entry.sourceline), None, entry.sourceline) errors.append(undefined) raise_collected(errors)
def validate_smarts(self, debug): missing_smarts = [] errors = [] for entry in self.atom_types: smarts_string = entry.attrib.get('def') name = entry.attrib['name'] if smarts_string is None: missing_smarts.append(name) continue # make sure smarts string can be parsed try: self.smarts_parser.parse(smarts_string) except lark.ParseError as ex: if " col " in ex.args[0]: column = ex.args[0][ex.args[0].find(" col ") + 5:].strip() column = " at character {} of {}".format(column, smarts_string) else: column = "" malformed = ValidationError( "Malformed SMARTS string{} on line {}".format(column, entry.sourceline), ex, entry.sourceline) errors.append(malformed) continue # make sure referenced labels exist smarts_graph = SMARTSGraph(smarts_string, parser=self.smarts_parser, name=name, overrides=entry.attrib.get('overrides')) for atom_expr in nx.get_node_attributes(smarts_graph, name='atom').values(): labels = atom_expr.find_data('has_label') for label in labels: atom_type = label.children[0][1:] if atom_type not in self.atom_type_names: undefined = ValidationError( "Reference to undefined atomtype '{}' in SMARTS " "string '{}' at line {}".format(atom_type, entry.attrib['def'], entry.sourceline), None, entry.sourceline) errors.append(undefined) raise_collected(errors) if missing_smarts and debug: warn("The following atom types do not have smarts definitions: {}".format( ', '.join(missing_smarts)), ValidationWarning) if missing_smarts and not debug: warn("There are {} atom types that are missing a smarts definition. " "To view the missing atom types, re-run with debug=True when " "applying the forcefield.".format(len(missing_smarts)), ValidationWarning)
def validate_class_type_exclusivity(self, ff_tree): sections = {'HarmonicBondForce/Bond': 2, 'HarmonicAngleForce/Angle': 3, 'RBTorsionForce/Proper': 4} errors = [] for element, num_atoms in sections.items(): valid_attribs = set() for n in range(1, num_atoms + 1): valid_attribs.add('class{}'.format(n)) valid_attribs.add('type{}'.format(n)) for entry in ff_tree.xpath('/ForceField/{}'.format(element)): attribs = [valid for valid in valid_attribs if entry.attrib.get(valid) is not None] if num_atoms != len(attribs): error = ValidationError( 'Invalid number of "class" and/or "type" attributes for' ' {} at line {}'.format(element, entry.sourceline), None, entry.sourceline) errors.append(error) number_endings = Counter([a[-1] for a in attribs]) if not all(1 == x for x in number_endings.values()): error = ValidationError( 'Only one "class" or "type" attribute may be defined' ' for each atom in a bonded force. See line {}'.format(entry.sourceline), None, entry.sourceline) errors.append(error) referenced_types = [] for valid in valid_attribs: if valid.startswith('type'): atomtype = entry.attrib.get(valid) if atomtype: referenced_types.append(atomtype) for atomtype in referenced_types: if atomtype not in self.atom_type_names: error = ValidationError( 'Atom type {} is found in {} at line {} but' ' undefined in AtomTypes'.format(atomtype, element.split('/')[0], entry.sourceline), None, entry.sourceline) errors.append(error) raise_collected(errors)
def validate_smarts(self): missing_smarts = [] errors = [] for entry in self.atom_types: smarts_string = entry.attrib.get('def') name = entry.attrib['name'] if smarts_string is None: missing_smarts.append(name) continue # make sure smarts string can be parsed try: self.smarts_parser.parse(smarts_string) except ParseError as ex: if " col " in ex.args[0]: column = ex.args[0][ex.args[0].find(" col ") + 5:].strip() column = " at character {} of {}".format(column, smarts_string) else: column = "" malformed = ValidationError( "Malformed SMARTS string{} on line {}".format(column, entry.sourceline), ex, entry.sourceline) errors.append(malformed) continue # make sure referenced labels exist smarts_graph = SMARTSGraph(smarts_string, parser=self.smarts_parser, name=name, overrides=entry.attrib.get('overrides')) for atom_expr in nx.get_node_attributes(smarts_graph, 'atom').values(): labels = atom_expr.select('has_label') for label in labels: atom_type = label.tail[0][1:] if atom_type not in self.atom_type_names: undefined = ValidationError( "Reference to undefined atomtype '{}' in SMARTS " "string '{}' at line {}".format(atom_type, entry.attrib['def'], entry.sourceline), None, entry.sourceline) errors.append(undefined) raise_collected(errors) if missing_smarts: warn("The following atom types do not have smarts definitions: {}".format( ', '.join(missing_smarts)), ValidationWarning)
def validate_overrides(self): """Assert all overrides are defined elsewhere in force field.""" errors = [] for entry in self.atom_types: overrides = entry.attrib.get("overrides") if not overrides: continue overridden_types = [ at.strip() for at in overrides.split(",") if at ] for atom_type in overridden_types: if atom_type not in self.atom_type_names: undefined = ValidationError( "Reference to undefined atomtype '{}' in 'overrides' " "'{}' at line {}".format( atom_type, entry.attrib["overrides"], entry.sourceline, ), None, entry.sourceline, ) errors.append(undefined) raise_collected(errors)