Skip to content

Commit

Permalink
Merge branch 'master' into deregister-toolkit
Browse files Browse the repository at this point in the history
  • Loading branch information
j-wags committed Jul 7, 2020
2 parents 3fa8d0f + e1b6e74 commit d78e111
Show file tree
Hide file tree
Showing 8 changed files with 109 additions and 5 deletions.
22 changes: 22 additions & 0 deletions docs/releasehistory.rst
Expand Up @@ -7,6 +7,28 @@ Releases follow the ``major.minor.micro`` scheme recommended by `PEP440 <https:/
* ``minor`` increments add features but do not break API compatibility
* ``micro`` increments represent bugfix releases or improvements in documentation

0.7.1 - Current development
---------------------------

Bugfixes
""""""""
- `PR #634 <https://github.com/openforcefield/openforcefield/pull/634>`_: Fixes a bug in which calling
:py:meth:`RDKitToolkitWrapper.from_file <openforcefield.utils.toolkits.RDKitToolkitWrapper.from_file>` directly
would not load files correctly if passed lowercase `file_format`. Note that this bug did not occur when calling
:`Molecule.from_file` <openforcefield.topology.molecule.Molecule.from_file>`.
- `PR #631 <https://github.com/openforcefield/openforcefield/pull/631>`_: Fixes a bug in which calling
:py:meth:`openforcefield.utils.utils.utils.unit_to_string <openforcefield.utils.utils.unit_to_string>` returned
``None`` when the unit is dimensionless. Now ``"dimensionless"`` is returned.
- `PR #630 <https://github.com/openforcefield/openforcefield/pull/630>`_: Closes issue `Issue #629
<https://github.com/openforcefield/openforcefield/issues/629>`_ in which the wrong exception is raised when
attempting to instantiate a ``ForceField`` from an unparsable string.

New features
""""""""""""
- `PR #632 <https://github.com/openforcefield/openforcefield/pull/632>`_: Adds
:py:meth:`ForceField.registered_parameter_handlers
<openforcefield.typing.engines.smirnoff.forcefield.ForceField.registered_parameter_handlers>`

0.7.0 - Charge Increment Model, Proper Torsion interpolation, and new Molecule methods
--------------------------------------------------------------------------------------

Expand Down
26 changes: 26 additions & 0 deletions openforcefield/tests/test_forcefield.py
Expand Up @@ -617,6 +617,12 @@ def test_create_forcefield_from_file(self):
assert len(forcefield._parameter_handlers['ImproperTorsions']._parameters) == 4
assert len(forcefield._parameter_handlers['vdW']._parameters) == 35

def test_load_bad_string(self):
with pytest.raises(IOError) as exception_info:
ForceField('1234')
assert 'Source 1234 could not be read.' in str(exception_info.value)
assert 'syntax error' in str(exception_info.value)

@pytest.mark.skip(reason='Needs to be updated for 0.2.0 syntax')
def test_create_forcefield_from_file_list(self):
# These offxml files are located in package data path, which is automatically installed and searched
Expand Down Expand Up @@ -1211,6 +1217,26 @@ def test_nonbonded_method_resolution(self,
forcefield.get_parameter_handler('Electrostatics', {}).method = electrostatics_method
omm_system = forcefield.create_openmm_system(topology)

def test_registered_parameter_handlers(self):
"""Test registered_parameter_handlers property"""
forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml')
registered_handlers = forcefield.registered_parameter_handlers

expected_handlers = [
'Bonds',
'Angles',
'ProperTorsions',
'ImproperTorsions',
'vdW',
'Electrostatics',
'ToolkitAM1BCC',
]

for expected_handler in expected_handlers:
assert expected_handler in registered_handlers

assert 'LibraryChrages' not in registered_handlers

def test_parameter_handler_lookup(self):
"""Ensure __getitem__ lookups work"""
forcefield = ForceField('test_forcefields/smirnoff99Frosst.offxml')
Expand Down
32 changes: 31 additions & 1 deletion openforcefield/tests/test_toolkits.py
Expand Up @@ -469,6 +469,21 @@ def test_load_multiconformer_sdf_as_separate_molecules_properties(self):
np.testing.assert_allclose(molecules[1].partial_charges / unit.elementary_charge,
[0.027170, 0.027170, 0.027170, 0.027170, -0.108680])

@pytest.mark.skipif(not OpenEyeToolkitWrapper.is_available(), reason='OpenEye Toolkit not available')
def test_file_extension_case(self):
"""
Test round-trips of some file extensions when called directly from the toolkit wrappers,
including lower- and uppercase file extensions. Note that this test does not ensure
accuracy, it only tests that reading/writing without raising an exception.
"""
mols_in = OpenEyeToolkitWrapper().from_file(file_path=get_data_file_path('molecules/ethanol.sdf'), file_format='sdf')

assert len(mols_in) > 0

mols_in = OpenEyeToolkitWrapper().from_file(file_path=get_data_file_path('molecules/ethanol.sdf'), file_format='SDF')

assert len(mols_in) > 0

@pytest.mark.skipif(not OpenEyeToolkitWrapper.is_available(), reason='OpenEye Toolkit not available')
def test_write_sdf_charges(self):
"""Test OpenEyeToolkitWrapper for writing partial charges to a sdf file"""
Expand Down Expand Up @@ -1397,7 +1412,22 @@ def test_to_from_rdkit_core_props_unset(self):
assert molecule2.partial_charges is None

assert molecule2.to_smiles(toolkit_registry=toolkit_wrapper) == expected_output_smiles


@pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available')
def test_file_extension_case(self):
"""
Test round-trips of some file extensions when called directly from the toolkit wrappers,
including lower- and uppercase file extensions. Note that this test does not ensure
accuracy, it only tests that reading/writing without raising an exception.
"""
mols_in = RDKitToolkitWrapper().from_file(file_path=get_data_file_path('molecules/ethanol.sdf'), file_format='sdf')

assert len(mols_in) > 0

mols_in = RDKitToolkitWrapper().from_file(file_path=get_data_file_path('molecules/ethanol.sdf'), file_format='SDF')

assert len(mols_in) > 0

@pytest.mark.skipif(not RDKitToolkitWrapper.is_available(), reason='RDKit Toolkit not available')
def test_get_sdf_coordinates(self):
"""Test RDKitToolkitWrapper for importing a single set of coordinates from a sdf file"""
Expand Down
8 changes: 8 additions & 0 deletions openforcefield/tests/test_utils.py
Expand Up @@ -71,3 +71,11 @@ def test_ast_eval(unit_string, expected_unit):
ast_root_node = ast.parse(unit_string, mode='eval').body
parsed_units = _ast_eval(ast_root_node)
assert parsed_units == expected_unit

def test_dimensionless_units():
assert utils.string_to_unit('dimensionless') == unit.dimensionless

unit_string = utils.unit_to_string(unit.dimensionless)
unit_value = utils.string_to_unit(unit_string)

assert unit_value == unit.dimensionless
17 changes: 15 additions & 2 deletions openforcefield/typing/engines/smirnoff/forcefield.py
Expand Up @@ -556,6 +556,19 @@ def register_parameter_io_handler(self, parameter_io_handler):
self._parameter_io_handlers[io_format]))
self._parameter_io_handlers[io_format] = parameter_io_handler

@property
def registered_parameter_handlers(self):
"""
Return the list of registered parameter handlers by name
.. warning :: This API is experimental and subject to change.
Returns
-------
registered_parameter_handlers: iterable of names of ParameterHandler objects in this ForceField
"""
return [*self._parameter_handlers.keys()]

# TODO: Do we want to make this optional?

Expand Down Expand Up @@ -970,14 +983,14 @@ def parse_smirnoff_from_source(self, source):
smirnoff_data = parameter_io_handler.parse_file(source)
return smirnoff_data
except ParseError as e:
exception_msg = str(e)
exception_msg = e.msg
except (FileNotFoundError, OSError):
# If this is not a file path or a file handle, attempt parsing as a string.
try:
smirnoff_data = parameter_io_handler.parse_string(source)
return smirnoff_data
except ParseError as e:
exception_msg = str(e)
exception_msg = e.msg

# If we haven't returned by now, the parsing was unsuccessful
valid_formats = [
Expand Down
2 changes: 1 addition & 1 deletion openforcefield/typing/engines/smirnoff/io.py
Expand Up @@ -204,7 +204,7 @@ def parse_string(self, data):
smirnoff_data = xmltodict.parse(data, attr_prefix='')
return smirnoff_data
except ExpatError as e:
raise ParseError(e)
raise ParseError(str(e))

def to_file(self, file_path, smirnoff_data):
"""Write the current forcefield parameter set to a file.
Expand Down
5 changes: 4 additions & 1 deletion openforcefield/utils/toolkits.py
Expand Up @@ -677,7 +677,7 @@ def to_file(self, molecule, file_path, file_format):
from openeye import oechem
oemol = self.to_openeye(molecule)
ofs = oechem.oemolostream(file_path)
openeye_format = getattr(oechem, 'OEFormat_' + file_format)
openeye_format = getattr(oechem, 'OEFormat_' + file_format.upper())
ofs.SetFormat(openeye_format)

# OFFTK strictly treats SDF as a single-conformer format.
Expand Down Expand Up @@ -2364,6 +2364,9 @@ def from_file(self,
"""
from rdkit import Chem

file_format = file_format.upper()

mols = list()
if (file_format == 'MOL') or (file_format == 'SDF'):
for rdmol in Chem.SupplierFromFilename(file_path, removeHs=False, sanitize=False, strictParsing=True):
Expand Down
2 changes: 2 additions & 0 deletions openforcefield/utils/utils.py
Expand Up @@ -146,6 +146,8 @@ def unit_to_string(input_unit):
The serialized unit.
"""

if input_unit == unit.dimensionless:
return "dimensionless"

# Decompose output_unit into a tuples of (base_dimension_unit, exponent)
unit_string = None
Expand Down

0 comments on commit d78e111

Please sign in to comment.