Exemplo n.º 1
0
    def __init__(self, ff_file_name, debug=False):
        from foyer.forcefield import preprocess_forcefield_files

        try:
            preprocessed_ff_file_name = preprocess_forcefield_files(
                [ff_file_name])

            ff_tree = etree.parse(preprocessed_ff_file_name[0])
            self.validate_xsd(ff_tree)

            self.atom_type_names = ff_tree.xpath(
                "/ForceField/AtomTypes/Type/@name")
            self.atom_types = ff_tree.xpath("/ForceField/AtomTypes/Type")

            self.validate_class_type_exclusivity(ff_tree)

            # Loading forcefield should succeed, because XML can be parsed and
            # basics have been validated.
            from foyer.forcefield import Forcefield

            self.smarts_parser = Forcefield(preprocessed_ff_file_name,
                                            validation=False).parser
        finally:
            for ff_file_name in preprocessed_ff_file_name:
                os.remove(ff_file_name)

        self.validate_smarts(debug=debug)
        self.validate_overrides()
Exemplo n.º 2
0
    def test_structure_to_hoomdsimulation(self, ethane):
        import hoomd
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_simulation import create_hoomd_simulation

        ff = Forcefield(name="oplsaa")
        structure = ff.apply(ethane)
        sim = hoomd.context.SimulationContext()
        with sim:
            create_hoomd_simulation(structure, 2.5)

            sim_forces = hoomd.context.current.forces
            pair_force = import_("hoomd.md.pair")
            charge_force = import_("hoomd.md.charge")
            special_pair_force = import_("hoomd.md.special_pair")
            bond_force = import_("hoomd.md.bond")
            angle_force = import_("hoomd.md.angle")
            dihedral_force = import_("hoomd.md.dihedral")

            assert isinstance(sim_forces[0], pair_force.lj)
            assert isinstance(sim_forces[1], charge_force.pppm)
            assert isinstance(sim_forces[2], pair_force.ewald)
            assert isinstance(sim_forces[3], special_pair_force.lj)
            assert isinstance(sim_forces[4], special_pair_force.coulomb)
            assert isinstance(sim_forces[5], bond_force.harmonic)
            assert isinstance(sim_forces[6], angle_force.harmonic)
            assert isinstance(sim_forces[7], dihedral_force.opls)
Exemplo n.º 3
0
    def test_hoomdsimulation_restart(self):
        import gsd.hoomd
        import hoomd
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_simulation import create_hoomd_simulation

        box = mb.Compound()
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        ff = Forcefield(forcefield_files=get_fn("lj.xml"))
        structure = ff.apply(box)
        structure.box = [10, 10, 10, 90, 90, 90]
        sim = hoomd.context.SimulationContext()
        with sim:
            hoomd_obj, ref_vals = create_hoomd_simulation(
                structure, 2.5, restart=get_fn("restart.gsd"))
            sim_forces = hoomd.context.current.forces
            pair_force = import_("hoomd.md.pair")

            assert isinstance(sim_forces[0], pair_force.lj)

        snap = hoomd_obj[0]
        with gsd.hoomd.open(get_fn("restart.gsd")) as f:
            rsnap = f[0]
        assert np.array_equal(snap.particles.position,
                              rsnap.particles.position)
Exemplo n.º 4
0
    def test_param_structure_to_snapshot(self, ethane):
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_snapshot import to_hoomdsnapshot

        ff = Forcefield(name="oplsaa")
        structure = ff.apply(ethane)
        snap, _ = to_hoomdsnapshot(structure)

        assert snap.particles.N == 8
        assert snap.bonds.N == 7
        assert snap.angles.N == 12
        assert snap.dihedrals.N == 9
        assert snap.pairs.N == 9
Exemplo n.º 5
0
    def __init__(self, ff_file_name):

        from foyer.forcefield import preprocess_forcefield_files

        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name,
                                        validation=False).parser

        self.validate_smarts(ff_tree)
Exemplo n.º 6
0
    def test_lj_to_hoomd_forcefield(self):
        import hoomd
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_forcefield import create_hoomd_forcefield

        box = mb.Compound()
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        ff = Forcefield(forcefield_files=get_fn("lj.xml"))
        structure = ff.apply(box)
        structure.box = [10, 10, 10, 90, 90, 90]

        snapshot, forces, ref_values = create_hoomd_forcefield(structure, 2.5)

        assert isinstance(forces[0], hoomd.md.pair.LJ)
Exemplo n.º 7
0
def test_hexa_coordinated():
    ff = Forcefield(forcefield_files=get_fn('pf6.xml'))
    mol2 = pmd.load_file(get_fn('pf6.mol2'), structure=True)

    pf6 = ff.apply(mol2)

    types = [a.type for a in pf6.atoms]
    assert types.count('P') == 1
    assert types.count('F1') == 2
    assert types.count('F2') == 2
    assert types.count('F3') == 2

    assert len(pf6.bonds) == 6
    assert all(bond.type for bond in pf6.bonds)

    assert len(pf6.angles) == 15
    assert all(angle.type for angle in pf6.angles)
Exemplo n.º 8
0
    def test_hexa_coordinated(self):
        ff = Forcefield(forcefield_files=get_fn("pf6.xml"))
        mol2 = pmd.load_file(get_fn("pf6.mol2"), structure=True)

        pf6 = ff.apply(mol2)

        types = [a.type for a in pf6.atoms]
        assert types.count("P") == 1
        assert types.count("F1") == 2
        assert types.count("F2") == 2
        assert types.count("F3") == 2

        assert len(pf6.bonds) == 6
        assert all(bond.type for bond in pf6.bonds)

        assert len(pf6.angles) == 15
        assert all(angle.type for angle in pf6.angles)
Exemplo n.º 9
0
def test_hexa_coordinated():
    ff = Forcefield(forcefield_files=get_fn('pf6.xml'))
    mol2 = pmd.load_file(get_fn('pf6.mol2'), structure=True)

    pf6 = ff.apply(mol2)

    types = [a.type for a in pf6.atoms]
    assert types.count('P') == 1
    assert types.count('F1') == 2
    assert types.count('F2') == 2
    assert types.count('F3') == 2

    assert len(pf6.bonds) == 6
    assert all(bond.type for bond in pf6.bonds)

    assert len(pf6.angles) == 15
    assert all(angle.type for angle in pf6.angles)
Exemplo n.º 10
0
    def test_lj_to_hoomdsimulation(self):
        import hoomd
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_simulation import create_hoomd_simulation

        box = mb.Compound()
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        box.add(mb.Compound(name="Ar", pos=[1, 1, 1]))
        ff = Forcefield(forcefield_files=get_fn("lj.xml"))
        structure = ff.apply(box)
        structure.box = [10, 10, 10, 90, 90, 90]
        sim = hoomd.context.SimulationContext()
        with sim:
            create_hoomd_simulation(structure, 2.5)
            sim_forces = hoomd.context.current.forces
            pair_force = import_("hoomd.md.pair")

            assert isinstance(sim_forces[0], pair_force.lj)
Exemplo n.º 11
0
    def test_structure_to_hoomd_forcefield(self, ethane):
        import hoomd
        from foyer.forcefield import Forcefield

        from mbuild.formats.hoomd_forcefield import create_hoomd_forcefield

        ff = Forcefield(name="oplsaa")
        structure = ff.apply(ethane)

        snapshot, forces, ref_values = create_hoomd_forcefield(structure, 2.5)

        assert isinstance(forces[0], hoomd.md.pair.LJ)
        assert isinstance(forces[1], hoomd.md.pair.Ewald)
        assert isinstance(forces[2], hoomd.md.long_range.pppm.Coulomb)
        assert isinstance(forces[3], hoomd.md.special_pair.LJ)
        assert isinstance(forces[4], hoomd.md.special_pair.Coulomb)
        assert isinstance(forces[5], hoomd.md.bond.Harmonic)
        assert isinstance(forces[6], hoomd.md.angle.Harmonic)
        assert isinstance(forces[7], hoomd.md.dihedral.OPLS)
Exemplo n.º 12
0
 def test_atom_typing(
     self, openff_topology_graph, gmso_topology_graph, parmed_topology_graph
 ):
     # ToDo: More robust testing for atomtyping
     opls = Forcefield(name="oplsaa")
     openff_typemap = find_atomtypes(openff_topology_graph, forcefield=opls)
     gmso_typemap = find_atomtypes(gmso_topology_graph, forcefield=opls)
     parmed_typemap = find_atomtypes(parmed_topology_graph, forcefield=opls)
     assert openff_typemap
     assert gmso_typemap
     assert parmed_typemap
Exemplo n.º 13
0
    def __init__(self, ff_file_name):

        from foyer.forcefield import preprocess_forcefield_files

        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name, validation=False).parser

        self.validate_smarts(ff_tree)
Exemplo n.º 14
0
    def __init__(self, ff_file_name, debug=False):
        from foyer.forcefield import preprocess_forcefield_files
        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        self.atom_type_names = ff_tree.xpath('/ForceField/AtomTypes/Type/@name')
        self.atom_types = ff_tree.xpath('/ForceField/AtomTypes/Type')

        self.validate_class_type_exclusivity(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name, validation=False).parser

        self.validate_smarts(debug=debug)
        self.validate_overrides()
Exemplo n.º 15
0
class Validator(object):
    def __init__(self, ff_file_name):

        from foyer.forcefield import preprocess_forcefield_files

        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name, validation=False).parser

        self.validate_smarts(ff_tree)
        # TODO: figure out what exceptions are raised, and provide good error messages

    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

    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)

    def validate_overrides(self, ff_tree):
        results = ff_tree.xpath('/ForceField/AtomTypes/Type[@name]')

        for r in results:
            smarts_string = r.attrib['def']
            print(smarts_string)
            print(r.sourceline)
            # make sure smarts string can be parsed
            self.smarts.parse(smarts_string)
Exemplo n.º 16
0
class Validator(object):
    def __init__(self, ff_file_name, debug=False):
        from foyer.forcefield import preprocess_forcefield_files
        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        self.atom_type_names = ff_tree.xpath('/ForceField/AtomTypes/Type/@name')
        self.atom_types = ff_tree.xpath('/ForceField/AtomTypes/Type')

        self.validate_class_type_exclusivity(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name, validation=False).parser

        self.validate_smarts(debug=debug)
        self.validate_overrides()

    @staticmethod
    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

    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, 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_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)
Exemplo n.º 17
0
class Validator(object):
    """Verifies formatting of force field XML."""
    def __init__(self, ff_file_name, debug=False):
        from foyer.forcefield import preprocess_forcefield_files

        try:
            preprocessed_ff_file_name = preprocess_forcefield_files(
                [ff_file_name])

            ff_tree = etree.parse(preprocessed_ff_file_name[0])
            self.validate_xsd(ff_tree)

            self.atom_type_names = ff_tree.xpath(
                "/ForceField/AtomTypes/Type/@name")
            self.atom_types = ff_tree.xpath("/ForceField/AtomTypes/Type")

            self.validate_class_type_exclusivity(ff_tree)

            # Loading forcefield should succeed, because XML can be parsed and
            # basics have been validated.
            from foyer.forcefield import Forcefield

            self.smarts_parser = Forcefield(preprocessed_ff_file_name,
                                            validation=False).parser
        finally:
            for ff_file_name in preprocessed_ff_file_name:
                os.remove(ff_file_name)

        self.validate_smarts(debug=debug)
        self.validate_overrides()

    @staticmethod
    def validate_xsd(ff_tree, xsd_file=None):
        """Check consistency with forcefields/ff.xsd."""
        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

    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_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)
Exemplo n.º 18
0
class Validator(object):
    def __init__(self, ff_file_name):
        from foyer.forcefield import preprocess_forcefield_files
        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        self.atom_type_names = ff_tree.xpath('/ForceField/AtomTypes/Type/@name')
        self.atom_types = ff_tree.xpath('/ForceField/AtomTypes/Type')

        self.validate_class_type_exclusivity(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name, validation=False).parser

        self.validate_smarts()
        self.validate_overrides()

    @staticmethod
    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

    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):
        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)
Exemplo n.º 19
0
class Validator(object):
    def __init__(self, ff_file_name):

        from foyer.forcefield import preprocess_forcefield_files

        preprocessed_ff_file_name = preprocess_forcefield_files([ff_file_name])

        ff_tree = etree.parse(preprocessed_ff_file_name[0])
        self.validate_xsd(ff_tree)

        # Loading forcefield should succeed, because XML can be parsed and
        # basics have been validated.
        from foyer.forcefield import Forcefield
        self.smarts_parser = Forcefield(preprocessed_ff_file_name,
                                        validation=False).parser

        self.validate_smarts(ff_tree)
        # TODO: figure out what exceptions are raised, and provide good error messages

    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

    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)

    def validate_overrides(self, ff_tree):
        results = ff_tree.xpath('/ForceField/AtomTypes/Type[@name]')

        for r in results:
            smarts_string = r.attrib['def']
            print(smarts_string)
            print(r.sourceline)
            # make sure smarts string can be parsed
            self.smarts.parse(smarts_string)