diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 280ea828..6970a635 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -94,8 +94,8 @@ jobs: - name: Run tests shell: bash -l {0} run: | - # pytest -v --cov=openmmtools --cov-report=xml --color=yes openmmtools/tests/ - nosetests openmmtools/tests --nocapture --cover-tests --with-coverage --cover-package=openmmtools --cover-xml --cover-xml-file=coverage.xml --verbosity=2 --with-timer --with-doctest -a '!slow' + pytest -v --cov=openmmtools --cov-report=xml --color=yes openmmtools/tests/ + # nosetests openmmtools/tests --nocapture --cover-tests --with-coverage --cover-package=openmmtools --cover-xml --cover-xml-file=coverage.xml --verbosity=2 --with-timer --with-doctest -a '!slow' - name: CodeCov uses: codecov/codecov-action@v3 diff --git a/openmmtools/tests/conftest.py b/openmmtools/tests/conftest.py new file mode 100644 index 00000000..bffae85e --- /dev/null +++ b/openmmtools/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + +def pytest_configure(config): + config.addinivalue_line("markers", "slow: mark test as slow to run") + config.addinivalue_line("markers", "gpu_ci: mark test as useful to run on GPU") + config.addinivalue_line("markers", "gpu_needed: mark test as GPU required") \ No newline at end of file diff --git a/openmmtools/tests/test_alchemy.py b/openmmtools/tests/test_alchemy.py index bb5010f7..022bbfe9 100644 --- a/openmmtools/tests/test_alchemy.py +++ b/openmmtools/tests/test_alchemy.py @@ -22,13 +22,11 @@ import sys import zlib import pickle -import itertools from functools import partial -import nose +import pytest import scipy import numpy as np -from nose.plugins.attrib import attr import openmm from openmm import unit @@ -1175,104 +1173,6 @@ def overlap_check(reference_system, alchemical_system, positions, nsteps=50, nsa if dDeltaF > MAX_DEVIATION: raise Exception(report) - -def rstyle(ax): - """Styles x,y axes to appear like ggplot2 - - Must be called after all plot and axis manipulation operations have been - carried out (needs to know final tick spacing) - - From: - http://nbviewer.ipython.org/github/wrobstory/climatic/blob/master/examples/ggplot_styling_for_matplotlib.ipynb - - """ - import pylab - import matplotlib - import matplotlib.pyplot as plt - - #Set the style of the major and minor grid lines, filled blocks - ax.grid(True, 'major', color='w', linestyle='-', linewidth=1.4) - ax.grid(True, 'minor', color='0.99', linestyle='-', linewidth=0.7) - ax.patch.set_facecolor('0.90') - ax.set_axisbelow(True) - - #Set minor tick spacing to 1/2 of the major ticks - ax.xaxis.set_minor_locator((pylab.MultipleLocator((plt.xticks()[0][1] - plt.xticks()[0][0]) / 2.0))) - ax.yaxis.set_minor_locator((pylab.MultipleLocator((plt.yticks()[0][1] - plt.yticks()[0][0]) / 2.0))) - - #Remove axis border - for child in ax.get_children(): - if isinstance(child, matplotlib.spines.Spine): - child.set_alpha(0) - - #Restyle the tick lines - for line in ax.get_xticklines() + ax.get_yticklines(): - line.set_markersize(5) - line.set_color("gray") - line.set_markeredgewidth(1.4) - - #Remove the minor tick lines - for line in (ax.xaxis.get_ticklines(minor=True) + - ax.yaxis.get_ticklines(minor=True)): - line.set_markersize(0) - - #Only show bottom left ticks, pointing out of axis - plt.rcParams['xtick.direction'] = 'out' - plt.rcParams['ytick.direction'] = 'out' - ax.xaxis.set_ticks_position('bottom') - ax.yaxis.set_ticks_position('left') - - -def lambda_trace(reference_system, alchemical_regions, positions, nsteps=100): - """ - Compute potential energy as a function of lambda. - - """ - - # Create a factory to produce alchemical intermediates. - factory = AbsoluteAlchemicalFactory() - alchemical_system = factory.create_alchemical_system(reference_system, alchemical_regions) - alchemical_state = AlchemicalState.from_system(alchemical_system) - - # Take equally-sized steps. - delta = 1.0 / nsteps - - # Compute unmodified energy. - u_original = compute_energy(reference_system, positions) - - # Scan through lambda values. - lambda_i = np.zeros([nsteps+1], np.float64) # lambda values for u_i - - # u_i[i] is the potential energy for lambda_i[i] - u_i = unit.Quantity(np.zeros([nsteps+1], np.float64), unit.kilocalories_per_mole) - for i in range(nsteps+1): - lambda_i[i] = 1.0-i*delta - alchemical_state.set_alchemical_parameters(lambda_i[i]) - alchemical_state.apply_to_system(alchemical_system) - u_i[i] = compute_energy(alchemical_system, positions) - logger.info("{:12.9f} {:24.8f} kcal/mol".format(lambda_i[i], u_i[i] / GLOBAL_ENERGY_UNIT)) - - # Write figure as PDF. - from matplotlib.backends.backend_pdf import PdfPages - import matplotlib.pyplot as plt - with PdfPages('lambda-trace.pdf') as pdf: - fig = plt.figure(figsize=(10, 5)) - ax = fig.add_subplot(111) - plt.plot(1, u_original / unit.kilocalories_per_mole, 'ro', label='unmodified') - plt.plot(lambda_i, u_i / unit.kilocalories_per_mole, 'k.', label='alchemical') - plt.title('T4 lysozyme L99A + p-xylene : AMBER96 + OBC GBSA') - plt.ylabel('potential (kcal/mol)') - plt.xlabel('lambda') - ax.legend() - rstyle(ax) - pdf.savefig() # saves the current figure into a pdf page - plt.close() - - -def generate_trace(test_system): - lambda_trace(test_system['test'].system, test_system['test'].positions, test_system['receptor_atoms'], test_system['ligand_atoms']) - - # ============================================================================= # TEST ALCHEMICAL FACTORY SUITE # ============================================================================= @@ -1312,12 +1212,12 @@ def test_resolve_alchemical_region(): # An exception is if indices are not part of the system. alchemical_region = AlchemicalRegion(alchemical_atoms=[10000000]) - with nose.tools.assert_raises(ValueError): + with pytest.raises(ValueError): AbsoluteAlchemicalFactory._resolve_alchemical_region(system, alchemical_region) # An exception is raised if nothing is defined. alchemical_region = AlchemicalRegion() - with nose.tools.assert_raises(ValueError): + with pytest.raises(ValueError): AbsoluteAlchemicalFactory._resolve_alchemical_region(system, alchemical_region) class TestAbsoluteAlchemicalFactory(object): @@ -1469,27 +1369,20 @@ def test_split_force_groups(self): test_cases.update(self.filter_cases(lambda x: 'Explicit ' in x and 'exact PME' in x, max_number=1)) test_cases.update(self.filter_cases(lambda x: 'Explicit ' in x and 'exact PME' not in x, max_number=1)) for test_name, (test_system, alchemical_system, alchemical_region) in test_cases.items(): - f = partial(check_split_force_groups, alchemical_system) - f.description = "Testing force splitting among groups of {}".format(test_name) - yield f + check_split_force_groups(alchemical_system) def test_fully_interacting_energy(self): """Compare the energies of reference and fully interacting alchemical system.""" for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): - f = partial(compare_system_energies, test_system.system, - alchemical_system, alchemical_region, test_system.positions) - f.description = "Testing fully interacting energy of {}".format(test_name) - yield f + compare_system_energies(test_system.system, alchemical_system, alchemical_region, test_system.positions) def test_noninteracting_energy_components(self): """Check all forces annihilated/decoupled when their lambda variables are zero.""" for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): - f = partial(check_noninteracting_energy_components, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Testing non-interacting energy of {}".format(test_name) - yield f + check_noninteracting_energy_components(test_system.system, alchemical_system, alchemical_region, + test_system.positions) - @attr('slow') + @pytest.mark.slow def test_fully_interacting_energy_components(self): """Test interacting state energy by force component.""" # This is a very expensive but very informative test. We can @@ -1501,7 +1394,7 @@ def test_fully_interacting_energy_components(self): f.description = "Testing energy components of %s..." % test_name yield f - @attr('slow') + @pytest.mark.slow def test_platforms(self): """Test interacting and noninteracting energies on all platforms.""" global GLOBAL_ALCHEMY_PLATFORM @@ -1519,29 +1412,22 @@ def test_platforms(self): for platform in platforms: GLOBAL_ALCHEMY_PLATFORM = platform for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): - f = partial(compare_system_energies, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Test fully interacting energy of {} on {}".format(test_name, platform.getName()) - yield f - f = partial(check_noninteracting_energy_components, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Test non-interacting energy of {} on {}".format(test_name, platform.getName()) - yield f + compare_system_energies(test_system.system, alchemical_system, alchemical_region, + test_system.positions) + check_noninteracting_energy_components(test_system.system, alchemical_system, alchemical_region, + test_system.positions) # Restore global platform GLOBAL_ALCHEMY_PLATFORM = old_global_platform - @attr('slow') + @pytest.mark.slow def test_overlap(self): """Tests overlap between reference and alchemical systems.""" for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): #cached_trajectory_filename = os.path.join(os.environ['HOME'], '.cache', 'alchemy', 'tests', # test_name + '.pickle') cached_trajectory_filename = None - f = partial(overlap_check, test_system.system, alchemical_system, test_system.positions, - cached_trajectory_filename=cached_trajectory_filename, name=test_name) - f.description = "Testing reference/alchemical overlap for {}".format(test_name) - yield f + overlap_check(test_system.system, alchemical_system, test_system.positions) class TestMultiRegionAbsoluteAlchemicalFactory(TestAbsoluteAlchemicalFactory): """Test AbsoluteAlchemicalFactory class using multiple regions.""" @@ -1691,19 +1577,15 @@ def test_split_force_groups(self): region_names = [] for region in alchemical_region: region_names.append(region.name) - f = partial(check_split_force_groups, alchemical_system, region_names) - f.description = "Testing force splitting among groups of {}".format(test_name) - yield f + check_split_force_groups(alchemical_system, region_names) def test_noninteracting_energy_components(self): """Check all forces annihilated/decoupled when their lambda variables are zero.""" for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): - f = partial(check_multi_noninteracting_energy_components, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Testing non-interacting energy of {}".format(test_name) - yield f + check_multi_noninteracting_energy_components(test_system.system, alchemical_system, alchemical_region, + test_system.positions) - @attr('slow') + @pytest.mark.slow def test_platforms(self): """Test interacting and noninteracting energies on all platforms.""" global GLOBAL_ALCHEMY_PLATFORM @@ -1721,19 +1603,14 @@ def test_platforms(self): for platform in platforms: GLOBAL_ALCHEMY_PLATFORM = platform for test_name, (test_system, alchemical_system, alchemical_region) in self.test_cases.items(): - f = partial(compare_system_energies, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Test fully interacting energy of {} on {}".format(test_name, platform.getName()) - yield f - f = partial(check_multi_noninteracting_energy_components, test_system.system, alchemical_system, - alchemical_region, test_system.positions) - f.description = "Test non-interacting energy of {} on {}".format(test_name, platform.getName()) - yield f + compare_system_energies(test_system.system, alchemical_system, alchemical_region, test_system.positions) + check_multi_noninteracting_energy_components(test_system.system, alchemical_system, alchemical_region, + test_system.positions) # Restore global platform GLOBAL_ALCHEMY_PLATFORM = old_global_platform - @attr('slow') + @pytest.mark.slow def test_fully_interacting_energy_components(self): """Test interacting state energy by force component.""" # This is a very expensive but very informative test. We can @@ -1812,13 +1689,11 @@ def test_overlap(self): #cached_trajectory_filename = os.path.join(os.environ['HOME'], '.cache', 'alchemy', 'tests', # test_name + '.pickle') cached_trajectory_filename = None - f = partial(overlap_check, test_system.system, alchemical_system, test_system.positions, - cached_trajectory_filename=cached_trajectory_filename, name=test_name) - f.description = "Testing reference/alchemical overlap for no alchemical dispersion {}".format(test_name) - yield f + overlap_check(test_system.system, alchemical_system, test_system.positions, + cached_trajectory_filename=cached_trajectory_filename, name=test_name) -@attr('slow') +@pytest.mark.slow class TestAbsoluteAlchemicalFactorySlow(TestAbsoluteAlchemicalFactory): """Test AbsoluteAlchemicalFactory class with a more comprehensive set of systems.""" @@ -1908,7 +1783,7 @@ def setup_class(cls): def test_constructor(): """Test AlchemicalState constructor behave as expected.""" # Raise an exception if parameter is not recognized. - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): AlchemicalState(lambda_electro=1.0) # Properties are initialized correctly. @@ -1926,7 +1801,7 @@ def test_constructor(): def test_from_system_constructor(self): """Test AlchemicalState.from_system constructor.""" # A non-alchemical system raises an error. - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): AlchemicalState.from_system(testsystems.AlanineDipeptideVacuum().system) # Valid parameters are 1.0 by default in AbsoluteAlchemicalFactory, @@ -1977,7 +1852,7 @@ def test_apply_to_system(self): defined_lambdas.pop() # Remove one element. kwargs = dict.fromkeys(defined_lambdas, 1.0) alchemical_state = AlchemicalState(**kwargs) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.apply_to_system(state.system) # Raise an error if an extra parameter is defined in the state. @@ -1988,7 +1863,7 @@ def test_apply_to_system(self): defined_lambdas.add('lambda_bonds') # Add extra parameter. kwargs = dict.fromkeys(defined_lambdas, 1.0) alchemical_state = AlchemicalState(**kwargs) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.apply_to_system(state.system) def test_check_system_consistency(self): @@ -1998,17 +1873,17 @@ def test_check_system_consistency(self): alchemical_state.check_system_consistency(self.alanine_state.system) # Raise error if system has MORE lambda parameters. - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.check_system_consistency(self.full_alanine_state.system) # Raise error if system has LESS lambda parameters. alchemical_state = AlchemicalState.from_system(self.full_alanine_state.system) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.check_system_consistency(self.alanine_state.system) # Raise error if system has different lambda values. alchemical_state.lambda_bonds = 0.5 - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.check_system_consistency(self.full_alanine_state.system) def test_apply_to_context(self): @@ -2018,14 +1893,14 @@ def test_apply_to_context(self): # Raise error if Context has more parameters than AlchemicalState. alchemical_state = AlchemicalState.from_system(self.alanine_state.system) context = self.full_alanine_state.create_context(copy.deepcopy(integrator)) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.apply_to_context(context) del context # Raise error if AlchemicalState is applied to a Context with missing parameters. alchemical_state = AlchemicalState.from_system(self.full_alanine_state.system) context = self.alanine_state.create_context(copy.deepcopy(integrator)) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.apply_to_context(context) del context @@ -2109,7 +1984,7 @@ def test_alchemical_functions(self): assert alchemical_state.get_function_variable('lambda2') == 0.5 # Cannot call an alchemical variable as a supported parameter. - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): alchemical_state.set_function_variable('lambda_sterics', 0.5) # Assign string alchemical functions to parameters. @@ -2192,11 +2067,11 @@ def test_set_system_compound_state(self): # Setting an inconsistent alchemical system raise an error. system = compound_state.system incompatible_state.apply_to_system(system) - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): compound_state.system = system # Same for set_system when called with default arguments. - with nose.tools.assert_raises(AlchemicalStateError): + with pytest.raises(AlchemicalStateError): compound_state.set_system(system) # This doesn't happen if we fix the state. diff --git a/openmmtools/tests/test_cache.py b/openmmtools/tests/test_cache.py index 6cbe8286..945f1821 100644 --- a/openmmtools/tests/test_cache.py +++ b/openmmtools/tests/test_cache.py @@ -14,8 +14,8 @@ # ============================================================================= import itertools +import pytest -import nose try: from openmm import unit except ImportError: # OpenMM < 7.6 @@ -358,45 +358,45 @@ def test_platform_property(self): cache.platform = platforms[1] integrator = copy.deepcopy(self.compatible_integrators[0]) cache.get_context(self.compatible_states[0], integrator) - with nose.tools.assert_raises(RuntimeError): + with pytest.raises(RuntimeError): cache.platform = platforms[0] def test_platform_properties(self): # Failure tests # no platform specified platform_properties = {"CpuThreads": "2"} - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: ContextCache(platform=None, platform_properties=platform_properties) # non-string value in properties cpu_platform = openmm.Platform.getPlatformByName("CPU") ref_platform = openmm.Platform.getPlatformByName("Reference") - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: ContextCache(platform=cpu_platform, platform_properties={"CpuThreads": 2}) - assert "All platform properties must be strings." in str(cm.exception) + assert "All platform properties must be strings." in str(cm.value) # non-dict properties - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: ContextCache(platform=cpu_platform, platform_properties="jambalaya") - assert str(cm.exception) == "platform_properties must be a dictionary" + assert str(cm.value) == "platform_properties must be a dictionary" # invalid property - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: ContextCache(platform=cpu_platform, platform_properties={"jambalaya": "2"}) - assert "Invalid platform property for this platform." in str(cm.exception) + assert "Invalid platform property for this platform." in str(cm.value) # setter cache = ContextCache( platform=cpu_platform, platform_properties=platform_properties ) - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: cache.platform = ref_platform - assert "Invalid platform property for this platform." in str(cm.exception) + assert "Invalid platform property for this platform." in str(cm.value) # this should work cache.set_platform(ref_platform) assert cache.platform == ref_platform # assert errors are checked in set_platform - with nose.tools.assert_raises(ValueError) as cm: + with pytest.raises(ValueError) as cm: cache.set_platform(cpu_platform, platform_properties={"jambalaya": "2"}) - assert "Invalid platform property for this platform." in str(cm.exception) + assert "Invalid platform property for this platform." in str(cm.value) # assert that resetting the platform resets the properties cache = ContextCache( platform=cpu_platform, diff --git a/openmmtools/tests/test_forcefactories.py b/openmmtools/tests/test_forcefactories.py index 021ad7a2..2dc88c83 100644 --- a/openmmtools/tests/test_forcefactories.py +++ b/openmmtools/tests/test_forcefactories.py @@ -177,10 +177,8 @@ def test_replace_reaction_field(): positions = generate_new_positions(test_system.system, test_system.positions) # Test forces. - f = partial(compare_system_forces, test_system.system, modified_rf_system, positions, - name=test_name, platform=platform) - f.description = "Testing replace_reaction_field on system {}".format(test_name) - yield f + compare_system_forces(test_system.system, modified_rf_system, positions, + name=test_name, platform=platform) for test_system in test_cases: test_name = test_system.__class__.__name__ @@ -192,7 +190,5 @@ def test_replace_reaction_field(): positions = generate_new_positions(test_system.system, test_system.positions) # Test forces. - f = partial(compare_system_forces, test_system.system, modified_rf_system, positions, - name=test_name, platform=platform) - f.description = "Testing replace_reaction_field on system {} with shifted=True".format(test_name) - yield f + compare_system_forces(test_system.system, modified_rf_system, positions, + name=test_name, platform=platform) diff --git a/openmmtools/tests/test_forces.py b/openmmtools/tests/test_forces.py index fde9269d..31b96228 100644 --- a/openmmtools/tests/test_forces.py +++ b/openmmtools/tests/test_forces.py @@ -15,7 +15,7 @@ import pickle -import nose.tools +import pytest from openmmtools import testsystems, states from openmmtools.forces import * @@ -39,12 +39,6 @@ def assert_quantity_almost_equal(object1, object2): assert utils.is_quantity_close(object1, object2), '{} != {}'.format(object1, object2) -def assert_equal(*args, **kwargs): - """Python 2 work-around to be able to yield nose.tools.assert_equal""" - # TODO: Just yield nose.tools.assert_equal after we have dropped Python2 support. - nose.tools.assert_equal(*args, **kwargs) - - # ============================================================================= # UTILITY FUNCTIONS TESTS # ============================================================================= @@ -63,44 +57,46 @@ def assert_forces_equal(found_forces, expected_force_classes): # Forces should be ordered by their index. assert list(found_forces.keys()) == sorted(found_forces.keys()) found_forces = {(i, force.__class__) for i, force in found_forces.items()} - nose.tools.assert_equal(found_forces, set(expected_force_classes)) + assert found_forces == set(expected_force_classes) # Test find force without including subclasses. found_forces = find_forces(system, openmm.CustomBondForce) - yield assert_forces_equal, found_forces, [(6, openmm.CustomBondForce)] + assert_forces_equal(found_forces, [(6, openmm.CustomBondForce)]) # Test find force and include subclasses. found_forces = find_forces(system, openmm.CustomBondForce, include_subclasses=True) - yield assert_forces_equal, found_forces, [(5, HarmonicRestraintBondForce), - (6, openmm.CustomBondForce)] + assert_forces_equal(found_forces, [(5, HarmonicRestraintBondForce), + (6, openmm.CustomBondForce)]) found_forces = find_forces(system, RadiallySymmetricRestraintForce, include_subclasses=True) - yield assert_forces_equal, found_forces, [(5, HarmonicRestraintBondForce)] + assert_forces_equal(found_forces, [(5, HarmonicRestraintBondForce)]) # Test exact name matching. found_forces = find_forces(system, 'HarmonicBondForce') - yield assert_forces_equal, found_forces, [(0, openmm.HarmonicBondForce)] + assert_forces_equal(found_forces, [(0, openmm.HarmonicBondForce)]) # Find all forces containing the word "Harmonic". found_forces = find_forces(system, '.*Harmonic.*') - yield assert_forces_equal, found_forces, [(0, openmm.HarmonicBondForce), - (1, openmm.HarmonicAngleForce), - (5, HarmonicRestraintBondForce)] + assert_forces_equal(found_forces, [(0, openmm.HarmonicBondForce), + (1, openmm.HarmonicAngleForce), + (5, HarmonicRestraintBondForce)]) # Find all forces from the name including the subclasses. # Test find force and include subclasses. found_forces = find_forces(system, 'CustomBond.*', include_subclasses=True) - yield assert_forces_equal, found_forces, [(5, HarmonicRestraintBondForce), - (6, openmm.CustomBondForce)] + assert_forces_equal(found_forces, [(5, HarmonicRestraintBondForce), + (6, openmm.CustomBondForce)]) # With check_multiple=True only one force is returned. force_idx, force = find_forces(system, openmm.NonbondedForce, only_one=True) - yield assert_forces_equal, {force_idx: force}, [(3, openmm.NonbondedForce)] + assert_forces_equal({force_idx: force}, [(3, openmm.NonbondedForce)]) # An exception is raised with "only_one" if multiple forces are found. - yield nose.tools.assert_raises, MultipleForcesError, find_forces, system, 'CustomBondForce', True, True + with pytest.raises(MultipleForcesError): + find_forces(system, 'CustomBondForce', True, True) # An exception is raised with "only_one" if the force wasn't found. - yield nose.tools.assert_raises, NoForceFoundError, find_forces, system, 'NonExistentForce', True + with pytest.raises(NoForceFoundError): + find_forces(system, 'NonExistentForce', True) # ============================================================================= @@ -153,17 +149,17 @@ def test_restorable_forces(self): def test_restraint_properties(self): """Test that properties work as expected.""" for restraint in self.restraints: - yield assert_quantity_almost_equal, restraint.spring_constant, self.spring_constant + assert restraint.spring_constant == pytest.approx(self.spring_constant, abs=1e-7) if isinstance(restraint, FlatBottomRestraintForceMixIn): - yield assert_quantity_almost_equal, restraint.well_radius, self.well_radius + assert_quantity_almost_equal(restraint.well_radius, self.well_radius) if isinstance(restraint, RadiallySymmetricCentroidRestraintForce): - yield assert_equal, restraint.restrained_atom_indices1, self.restrained_atom_indices1 - yield assert_equal, restraint.restrained_atom_indices2, self.restrained_atom_indices2 + assert restraint.restrained_atom_indices1 == self.restrained_atom_indices1 + assert restraint.restrained_atom_indices2 == self.restrained_atom_indices2 else: assert isinstance(restraint, RadiallySymmetricBondRestraintForce) - yield assert_equal, restraint.restrained_atom_indices1, [self.restrained_atom_index1] - yield assert_equal, restraint.restrained_atom_indices2, [self.restrained_atom_index2] + assert restraint.restrained_atom_indices1 == [self.restrained_atom_index1] + assert restraint.restrained_atom_indices2 == [self.restrained_atom_index2] def test_controlling_parameter_name(self): """Test that the controlling parameter name enters the energy function correctly.""" @@ -251,7 +247,7 @@ def assert_equal_ssc(expected_restraint_volume, restraint, thermodynamic_state, ssc = restraint.compute_standard_state_correction(thermodynamic_state, square_well, radius_cutoff, energy_cutoff, max_volume) err_msg = '{} computed SSC != expected SSC'.format(restraint.__class__.__name__) - nose.tools.assert_equal(ssc, expected_ssc, msg=err_msg) + assert ssc == expected_ssc, err_msg for restraint in self.restraints: # In NPT ensemble, an exception is thrown if max_volume is not provided.