Source code for openff.toolkit.typing.engines.smirnoff.io

"""
XML I/O parser for the SMIRNOFF (SMIRKS Native Open Force Field) format.

.. codeauthor:: John D. Chodera <john.chodera@choderalab.org>
.. codeauthor:: David L. Mobley <dmobley@mobleylab.org>
.. codeauthor:: Peter K. Eastman <peastman@stanford.edu>

"""

__all__ = [
    "ParameterIOHandler",
    "XMLParameterIOHandler",
]
import logging
from typing import Iterable, Optional

import xmltodict

logger = logging.getLogger(__name__)


[docs]class ParameterIOHandler: """ Base class for handling serialization/deserialization of SMIRNOFF ForceField objects """ _FORMAT: str
[docs] def __init__(self): """ Create a new ParameterIOHandler. """ pass
[docs] def parse_file(self, file_path): """ Parameters ---------- file_path Returns ------- """ pass
[docs] def parse_string(self, data): """ Parse a SMIRNOFF force field definition in a seriaized format Parameters ---------- data Returns ------- """ pass
[docs] def to_file(self, file_path, smirnoff_data): """ Write the current force field parameter set to a file. Parameters ---------- file_path The path to the file to write to. smirnoff_data A dictionary structured in compliance with the SMIRNOFF spec Returns ------- """ pass
[docs] def to_string(self, smirnoff_data): """ Render the force field parameter set to a string Parameters ---------- smirnoff_data A dictionary structured in compliance with the SMIRNOFF spec Returns ------- str """ pass
[docs]class XMLParameterIOHandler(ParameterIOHandler): """ Handles serialization/deserialization of SMIRNOFF ForceField objects from OFFXML format. """ # TODO: Come up with a better keyword for format _FORMAT = "XML"
[docs] def parse_file(self, source): """Parse a SMIRNOFF force field definition in XML format, read from a file. Parameters ---------- source File path of file-like object implementing a ``read()`` method specifying a SMIRNOFF force field definition in `the SMIRNOFF XML format <https://openforcefield.github.io/standards/standards/smirnoff/#xml-representation>`_. Raises ------ SMIRNOFFParseError If the XML cannot be processed. FileNotFoundError If the file could not found. """ # If this is a file-like object, we should be able to read it. try: raw_data = source.read() except AttributeError: # This raises FileNotFoundError if the file doesn't exist. with open(source) as source_obj: raw_data = source_obj.read() # Parse the data in string format. return self.parse_string(raw_data)
[docs] def parse_string(self, data: str) -> dict: """Parse a SMIRNOFF force field definition in XML format. A ``SMIRNOFFParseError`` is raised if the XML cannot be processed. Parameters ---------- data A SMIRNOFF force field definition in `the SMIRNOFF XML format <https://openforcefield.github.io/standards/standards/smirnoff/#xml-representation>`_. """ from pyexpat import ExpatError from openff.toolkit.utils.exceptions import SMIRNOFFParseError # Parse XML file try: smirnoff_data = xmltodict.parse(data, attr_prefix="") return smirnoff_data except ExpatError as e: raise SMIRNOFFParseError(str(e))
[docs] def to_file(self, file_path, smirnoff_data): """Write the current force field parameter set to a file. Parameters ---------- file_path The path to the file to be written. The `.offxml` or `.xml` file extension must be present. smirnoff_data A dict structured in compliance with the SMIRNOFF data spec. """ xml_string = self.to_string(smirnoff_data) with open(file_path, "w") as of: of.write(xml_string)
[docs] def to_string(self, smirnoff_data: dict) -> str: """ Write the current force field parameter set to an XML string. Parameters ---------- smirnoff_data A dictionary structured in compliance with the SMIRNOFF spec Returns ------- serialized_forcefield XML String representation of this force field. """ def prepend_all_keys( d: dict, char: Optional[str] = "@", ignore_keys: Iterable[str] = frozenset(), ): """ Modify a dictionary in-place, prepending a specified string to each key that doesn't refer to a value that is list or dict. Parameters ---------- d Hierarchical dictionary to traverse and modify keys char String to prepend onto each applicable dictionary key ignore_keys A set or list of strings, indicating keys not to prepend in the data structure """ if isinstance(d, dict): for key in list(d.keys()): if key in ignore_keys: continue if isinstance(d[key], list) or isinstance(d[key], dict): prepend_all_keys(d[key], char=char, ignore_keys=ignore_keys) else: new_key = char + key d[new_key] = d[key] del d[key] prepend_all_keys(d[new_key], char=char, ignore_keys=ignore_keys) elif isinstance(d, list): for item in d: prepend_all_keys(item, char=char, ignore_keys=ignore_keys) # the "xmltodict" library defaults to print out all element attributes on separate lines # unless they're prepended by "@" prepend_all_keys(smirnoff_data["SMIRNOFF"], ignore_keys=["Author", "Date"]) # Reorder parameter sections to put Author and Date at the top (this is the only # way to change the order of items in a dict, as far as I can tell) for key, value in list(smirnoff_data["SMIRNOFF"].items()): if key in ["Author", "Date"]: continue del smirnoff_data["SMIRNOFF"][key] smirnoff_data["SMIRNOFF"][key] = value return xmltodict.unparse(smirnoff_data, pretty=True, indent=" " * 4)