Skip to content

Commit

Permalink
tests: Improve code coverage
Browse files Browse the repository at this point in the history
Re-use existing utility functions in tests_common to reduce code
duplication. Replace unreachable exceptions by unittest assertions.
Fix tolerance of LBGPU test case up to single-precision accuracy.
  • Loading branch information
jngrad committed May 25, 2022
1 parent 7450767 commit a84e824
Show file tree
Hide file tree
Showing 9 changed files with 37 additions and 81 deletions.
5 changes: 3 additions & 2 deletions testsuite/python/cell_system.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ def test_cell_system(self):

@ut.skipIf(n_nodes == 1, "Skipping test: only runs for n_nodes >= 2")
def check_node_grid(self):
system = self.system
for i in range(3):
node_grid_ref = [1, 1, 1]
node_grid_ref[i] = self.n_nodes
self.system.cell_system.node_grid = node_grid_ref
node_grid = self.system.cell_system.get_state()['node_grid']
system.cell_system.node_grid = node_grid_ref
node_grid = system.cell_system.get_state()['node_grid']
np.testing.assert_array_equal(node_grid, node_grid_ref)

def test_exceptions(self):
Expand Down
14 changes: 8 additions & 6 deletions testsuite/python/collision_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,12 @@ def test_glue_to_surface_random(self):
# Bond type and partner type
# part_type_after_glueing can have a bond to a vs or to a
# non_virtual particle
allowed_types = (self.part_type_after_glueing,
self.part_type_to_attach_vs_to)
self.assertIn(
p.type,
allowed_types,
msg=f"Particle {p.id} of type {p.type} should not have bonds, yet has {p.bonds}.")
if p.type == self.part_type_after_glueing:
self.assertIn(bond[0], (self.H, self.H2))
# Bonds to virtual sites:
Expand All @@ -546,9 +552,6 @@ def test_glue_to_surface_random(self):
self.assertEqual(
system.part.by_id(bond[1]).type,
self.part_type_after_glueing)
else:
raise Exception(
f"Particle {p.id} of type {p.type} should not have bonds, yet has {p.bonds}.")

# Collect bonds
# Sort bond partners to make them unique independently of
Expand Down Expand Up @@ -689,16 +692,15 @@ def verify_triangle_binding(self, distance, first_bond, angle_res):
found_angle_bonds = []
for i in range(n):
for b in system.part.by_id(i).bonds:
self.assertIn(
len(b), (2, 3), msg="There should only be 2- and 3-particle bonds")
if len(b) == 2:
self.assertEqual(b[0]._bond_id, self.H._bond_id)
found_pairs.append(tuple(sorted((i, b[1]))))
elif len(b) == 3:
partners = sorted(b[1:])
found_angle_bonds.append(
(i, b[0]._bond_id, partners[0], partners[1]))
else:
raise Exception(
"There should be only 2- and 3-particle bonds")

# The order between expected and found bonds does not always match
# because collisions occur in random order. Sort stuff
Expand Down
52 changes: 5 additions & 47 deletions testsuite/python/hybrid_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#
import unittest as ut
import unittest_decorators as utx
import tests_common
import espressomd
import itertools
import numpy as np
Expand Down Expand Up @@ -155,48 +156,9 @@ def test_position_rounding(self):

@utx.skipIfMissingFeatures(["LENNARD_JONES"])
def test_non_bonded_loop_trace(self):
"""Validates that the distances used by the non-bonded loop
match with the minimum image distance accessible by Python,
checks that no pairs are lost or double-counted.
"""
self.prepare_hybrid_setup(n_part_small=50, n_part_large=50)
# regular cutoff used in preparation
cutoff_regular = 2.5

cs_pairs = self.system.cell_system.non_bonded_loop_trace()
distance_vec = self.system.distance_vec

# Distance for all pairs of particles obtained by Python
py_distances = {}
for p1, p2 in self.system.part.pairs():
py_distances[p1.id, p2.id] = np.copy(distance_vec(p1, p2))

# Go through pairs found by the non-bonded loop and check distance
for p in cs_pairs:
# p is a tuple with (id1, id2, pos1, pos2, vec21, mpi_node)
# Note that system.distance_vec uses the opposite sign convention
# as the minimum image distance in the core
self.assertTrue(
(p[0], p[1]) in py_distances or
(p[1], p[0]) in py_distances,
msg=f"Extra pair from core {p}")
if (p[0], p[1]) in py_distances:
np.testing.assert_allclose(
np.copy(p[4]), -py_distances[p[0], p[1]])
del py_distances[p[0], p[1]]
elif (p[1], p[0]) in py_distances:
np.testing.assert_allclose(
np.copy(p[4]), py_distances[p[1], p[0]])
del py_distances[p[1], p[0]]

# test for pairs from regular child decomposition with more than one
# cell
for ids, dist in py_distances.items():
self.assertGreaterEqual(
np.linalg.norm(dist),
cutoff_regular,
msg=f"Pair not found by the core {ids}")
cutoff = 2.5
tests_common.check_non_bonded_loop_trace(self, self.system, cutoff)

@utx.skipIfMissingFeatures(["LENNARD_JONES"])
def test_against_nsquare(self):
Expand Down Expand Up @@ -237,12 +199,8 @@ def test_sort_into_child_decs(self):
"""
n_parts = 3
parts = self.system.part.add(
pos=np.random.random(
(n_parts,
3)) * self.system.box_l[0],
type=np.random.randint(
2,
size=n_parts))
pos=np.random.random((n_parts, 3)) * self.system.box_l[0],
type=np.random.randint(2, size=n_parts))
for ndx, types in enumerate(itertools.product([0, 1], repeat=n_parts)):
parts.type = types
n_square_type = ndx % 2
Expand Down
7 changes: 2 additions & 5 deletions testsuite/python/lb_momentum_conservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,8 @@ def test(self):
np.testing.assert_allclose(np.copy(p.v), v_final, atol=2.2E-3)

# Make sure, the particle has crossed the periodic boundaries
self.assertGreater(
np.amax(
np.abs(v_final) *
self.system.time),
BOX_SIZE)
self.assertGreater(np.amax(np.abs(v_final) * self.system.time),
BOX_SIZE)


@utx.skipIfMissingGPU()
Expand Down
4 changes: 2 additions & 2 deletions testsuite/python/lees_edwards.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,15 +678,15 @@ def test_zz_lj(self):
system.lees_edwards.shear_direction = 2
system.lees_edwards.shear_plane_normal = 0
system.integrator.run(1, recalc_forces=True)
tests_common.check_non_bonded_loop_trace(system)
tests_common.check_non_bonded_loop_trace(self, system)

# Rewind the clock to get back the LE offset applied during force calc
system.time = system.time - system.time_step
tests_common.verify_lj_forces(system, 1E-7)

system.thermostat.set_langevin(kT=.1, gamma=5, seed=2)
system.integrator.run(50)
tests_common.check_non_bonded_loop_trace(system)
tests_common.check_non_bonded_loop_trace(self, system)


if __name__ == "__main__":
Expand Down
2 changes: 1 addition & 1 deletion testsuite/python/linear_momentum_lb.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ class LBGPULinearMomentum(LinearMomentumTest, ut.TestCase):
"""Test for the GPU implementation of the LB."""

lb_class = espressomd.lb.LBFluidGPU
atol = 1e-6
atol = 1e-5


if __name__ == '__main__':
Expand Down
7 changes: 2 additions & 5 deletions testsuite/python/oif_volume_conservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,8 @@ def test(self):

# Test that restoring forces net to zero and don't produce a torque
system.integrator.run(1)
np.testing.assert_allclose(
np.sum(
partcls.f, axis=0), [
0., 0., 0.], atol=1E-12)

total_force = np.sum(partcls.f, axis=0)
np.testing.assert_allclose(total_force, [0., 0., 0.], atol=1E-12)
total_torque = np.zeros(3)
for p in system.part:
total_torque += np.cross(p.pos, p.f)
Expand Down
5 changes: 2 additions & 3 deletions testsuite/python/random_pairs.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@
import numpy as np
import itertools
import collections

from tests_common import check_non_bonded_loop_trace
import tests_common


class RandomPairTest(ut.TestCase):
Expand Down Expand Up @@ -92,7 +91,7 @@ def check_n_squared(self, n2_pairs):
def test(self):
periods = [0, 1]
self.system.periodicity = [True, True, True]
check_non_bonded_loop_trace(self.system)
tests_common.check_non_bonded_loop_trace(self, self.system)

for periodicity in itertools.product(periods, periods, periods):
self.system.periodicity = periodicity
Expand Down
22 changes: 12 additions & 10 deletions testsuite/python/tests_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,8 @@ def get_histogram(pos, obs_params, coord_system, **kwargs):
Bins and bin edges.
"""
assert coord_system in ('cartesian', 'cylindrical'), \
f"Unknown coord system '{coord_system}'"
if coord_system == 'cartesian':
bins = (obs_params['n_x_bins'],
obs_params['n_y_bins'],
Expand All @@ -276,8 +278,6 @@ def get_histogram(pos, obs_params, coord_system, **kwargs):
extent = [(obs_params['min_r'], obs_params['max_r']),
(obs_params['min_phi'], obs_params['max_phi']),
(obs_params['min_z'], obs_params['max_z'])]
else:
raise ValueError(f"Unknown coord system '{coord_system}'")
return np.histogramdd(pos, bins=bins, range=extent, **kwargs)


Expand Down Expand Up @@ -348,7 +348,7 @@ def random_dipoles(n_particles):
return dip


def check_non_bonded_loop_trace(system):
def check_non_bonded_loop_trace(ut_obj, system, cutoff=None):
"""Validates that the distances used by the non-bonded loop
match with the minimum image distance accessible by Python,
checks that no pairs are lost or double-counted.
Expand All @@ -358,7 +358,8 @@ def check_non_bonded_loop_trace(system):
# format [id1, id2, pos1, pos2, vec2, mpi_node]

distance_vec = system.distance_vec
cutoff = system.cell_system.max_cut_nonbonded
if cutoff is None:
cutoff = system.cell_system.max_cut_nonbonded

# Distance for all pairs of particles obtained by Python
py_distances = {}
Expand All @@ -367,10 +368,13 @@ def check_non_bonded_loop_trace(system):

# Go through pairs found by the non-bonded loop and check distance
for p in cs_pairs:
# p is a tuple with (id1,id2,pos1,pos2,vec21)
# p is a tuple with (id1, id2, pos1, pos2, vec21, mpi_node)
# Note that system.distance_vec uses the opposite sign convention
# as the minimum image distance in the core

ut_obj.assertTrue(
(p[0], p[1]) in py_distances or
(p[1], p[0]) in py_distances,
msg=f"Extra pair from core {p}")
if (p[0], p[1]) in py_distances:
np.testing.assert_allclose(
np.copy(p[4]), -py_distances[p[0], p[1]])
Expand All @@ -379,9 +383,7 @@ def check_non_bonded_loop_trace(system):
np.testing.assert_allclose(
np.copy(p[4]), py_distances[p[1], p[0]])
del py_distances[p[1], p[0]]
else:
raise Exception("Extra pair from core", p)

for ids, dist in py_distances.items():
if np.linalg.norm(dist) < cutoff:
raise Exception("Pair not found by the core", ids)
ut_obj.assertGreaterEqual(np.linalg.norm(dist), cutoff,
msg=f"Pair not found by the core {ids}")

0 comments on commit a84e824

Please sign in to comment.