Example #1
0
    def validate_xsd(self, ff_tree, xsd_file=None):
        if xsd_file is None:
            xsd_file = join(
                split(abspath(__file__))[0], 'forcefields', 'ff.xsd')

        xmlschema_doc = etree.parse(xsd_file)
        xmlschema = etree.XMLSchema(xmlschema_doc)

        try:
            xmlschema.assertValid(ff_tree)
        except DocumentInvalid as ex:
            message = ex.error_log.last_error.message
            line = ex.error_log.last_error.line

            # rewrite error message for constraint violation
            if ex.error_log.last_error.type_name == "SCHEMAV_CVC_IDC":
                if "missing_atom_type_in_nonbonded" in message:
                    atomtype = message[message.find("[") + 1:message.find("]")]
                    raise ValidationError(
                        "Atom type {} is found in NonbondedForce at line {} but"
                        " undefined in AtomTypes".format(atomtype, line), ex,
                        line)
                elif "nonunique_atomtype_name" in message:
                    atomtype = message[message.find("[") + 1:message.find("]")]
                    raise ValidationError(
                        "Atom type {} is defined a second time at line {}".
                        format(atomtype, line), ex, line)
            raise
Example #2
0
    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)
Example #3
0
    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)
Example #4
0
    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)

        #TODO: helper func to raise 1 or more erros
        if len(errors) > 1:
            raise MultipleValidationError(errors)
        elif len(errors) == 1:
            raise errors[0]
Example #5
0
    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)
        if len(errors) > 1:
            raise MultipleValidationError(errors)
        elif len(errors) == 1:
            raise errors[0]
        if missing_smarts:
            warn(
                "The following atom types do not have smarts definitions: {}".
                format(', '.join(missing_smarts)), ValidationWarning)
Example #6
0
    def validate_xsd(ff_tree, xsd_file=None):
        if xsd_file is None:
            xsd_file = join(split(abspath(__file__))[0], 'forcefields', 'ff.xsd')

        xmlschema_doc = etree.parse(xsd_file)
        xmlschema = etree.XMLSchema(xmlschema_doc)

        error_texts = {'missing_atom_type_in_nonbonded':
                           ("Atom type {} is found in NonbondedForce at line {}"
                            " but undefined in AtomTypes"),
                       'nonunique_atomtype_name':
                           "Atom type {} is defined a second time at line {}",
                       'atomtype_name_key':
                           "Atom type {} is defined a second time at line {}"
        }

        def create_error(keyword, message, line):
            atomtype = message[message.find("[") + 1:message.find("]")]
            error_text = error_texts[keyword].format(atomtype, line)
            return ValidationError(error_text, ex, line)

        try:
            xmlschema.assertValid(ff_tree)
        except DocumentInvalid as ex:
            message = ex.error_log.last_error.message
            line = ex.error_log.last_error.line
            # rewrite error message for constraint violation
            if ex.error_log.last_error.type_name == "SCHEMAV_CVC_IDC":
                for keyword in error_texts:
                    if keyword in message:
                        raise create_error(keyword, message, line)
                else:
                    raise ValidationError('Unhandled XML validation error. '
                                          'Please consider submitting a bug report.', ex, line)
            raise
Example #7
0
    def validate_smarts(self, ff_tree):
        results = ff_tree.xpath('/ForceField/AtomTypes/Type')
        atom_types = ff_tree.xpath('/ForceField/AtomTypes/Type/@name')

        missing_smarts = []
        for r in results:
            smarts_string = r.attrib.get('def')
            name = r.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 = ""

                raise ValidationError(
                    "Malformed SMARTS string{} on line {}".format(
                        column, r.sourceline), ex, r.sourceline)

            # make sure referenced labels exist
            smarts_graph = SMARTSGraph(smarts_string,
                                       parser=self.smarts_parser,
                                       name=name,
                                       overrides=r.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 atom_types:
                        raise ValidationError(
                            "Reference to undefined atomtype {} in SMARTS string"
                            " '{}' at line {}".format(atom_type,
                                                      r.attrib['def'],
                                                      r.sourceline), None,
                            r.sourceline)

        warn(
            "The following atom types do not have smarts definitions: {}".
            format(', '.join(missing_smarts)), ValidationWarning)
Example #8
0
 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)
Example #9
0
 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)
Example #10
0
 def create_error(keyword, message, line):
     atomtype = message[message.find("[") + 1:message.find("]")]
     error_text = error_texts[keyword].format(atomtype, line)
     return ValidationError(error_text, ex, line)