def test_minkowski(self): """ Test Minkowski functionals for a slab domain with periodic boundaries. """ mesh = fieldkit.Mesh().from_lattice( N=2, lattice=fieldkit.HOOMDLattice(L=4.0)) field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) field[:, :, 0] = 1 ## check that the right values are computed (reference values determined by hand) domain = fieldkit.domain.digitize(field, threshold=0.5) V, S, B, chi = fieldkit.measure.minkowski(domain) a = 2.0 self.assertAlmostEqual(V, 4 * a**3) self.assertAlmostEqual(S, 8 * a**2) self.assertAlmostEqual(B, 0 * a) self.assertEqual(chi, 0) ## ensure an error is rasied for non-cubic lattices # orthorhombic without extra mesh points mesh = fieldkit.Mesh().from_lattice( N=2, lattice=fieldkit.HOOMDLattice(L=(4.0, 6.0, 8.0))) field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) field[:, :, 0] = 1 domain = fieldkit.domain.digitize(field, threshold=0.5) with self.assertRaises(ValueError): V, S, B, chi = fieldkit.measure.minkowski(domain) # triclinic mesh = fieldkit.Mesh().from_lattice(N=2, lattice=fieldkit.HOOMDLattice( L=4.0, tilt=[0.5, 0., 0.])) field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) field[:, :, 0] = 1 domain = fieldkit.domain.digitize(field, threshold=0.5) with self.assertRaises(ValueError): V, S, B, chi = fieldkit.measure.minkowski(domain)
def test_multi(self): """ Test common multidimensional layouts for input arrays. """ # last index is density data = np.ones(np.append(self.mesh.shape, 2)) field = fieldkit.Field(self.mesh).from_array(data, index=0, axis=3) self.assertEqual(field.shape, self.mesh.shape) np.testing.assert_almost_equal(field.field, np.ones(self.mesh.shape)) # first index is density data = np.ones(np.insert(self.mesh.shape, 0, 2)) data[1] *= 2. field = fieldkit.Field(self.mesh).from_array(data, index=1, axis=0) self.assertEqual(field.shape, self.mesh.shape) np.testing.assert_almost_equal(field.field, np.full(self.mesh.shape,2.))
def test_interpolator(self): """ Test for creation of interpolator into field. """ field = fieldkit.Field(self.mesh) for n in np.ndindex(self.mesh.shape): field[n] = n[-2] np.testing.assert_almost_equal(field.field, ( ( (0,0,0,0),(1,1,1,1),(2,2,2,2) ), ( (0,0,0,0),(1,1,1,1),(2,2,2,2) ) ) ) # interpolator f = field.interpolator() # interpolate the mesh points themselves, which should have values almost the same as the field pts = self.mesh.grid.reshape((np.prod(self.mesh.shape),3)) fracs = self.mesh.lattice.as_fraction(pts) vals = f(fracs).reshape(self.mesh.shape) np.testing.assert_almost_equal(vals, ( ( (0,0,0,0),(1,1,1,1),(2,2,2,2) ), ( (0,0,0,0),(1,1,1,1),(2,2,2,2) ) ) ) # interpolate in between the mesh. last point gets 1 since interpolation is between 2 and 0 pts += 0.5 * self.mesh.step fracs = self.mesh.lattice.as_fraction(pts) vals = f(fracs).reshape(self.mesh.shape) np.testing.assert_almost_equal(vals, ( ( (0.5,0.5,0.5,0.5),(1.5,1.5,1.5,1.5),(1.0,1.0,1.0,1.0) ), ( (0.5,0.5,0.5,0.5),(1.5,1.5,1.5,1.5),(1.0,1.0,1.0,1.0) ) ) )
def test_from_array(self): """ Test for field creation for a simple array. """ data = np.ones(self.mesh.shape) field = fieldkit.Field(self.mesh).from_array(data) self.assertEqual(field.shape, self.mesh.shape) np.testing.assert_almost_equal(field.field, np.ones(self.mesh.shape))
def test_msd(self): """ Validate biased random walk with a short simulation, computing the MSD. This MSD is a little different than the usual random walk because the MSD tends to 1.0 at t->0, rather than 0.0, because any hop (even after a short time) moves the walker by one lattice site. """ mesh = fieldkit.Mesh().from_lattice( N=10, lattice=fieldkit.HOOMDLattice(L=10.0)) field = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) domain = fieldkit.domain.digitize(field, threshold=0.5) # displacement should be consistent with random walk with coeff. 1/2 rates = np.full(list(mesh.shape) + [6], 0.5) traj, _, _, _ = fieldkit.simulate.kmc(domain, rates, np.arange(2000), N=4000, steps=10000, seed=42) window = 3 msd = fieldkit.simulate.msd(traj, window=window) self.assertEqual(msd.shape, (window + 1, 3)) np.testing.assert_array_almost_equal(msd[0], (0., 0., 0.), decimal=3) np.testing.assert_array_almost_equal(msd[1], (1., 1., 1.), decimal=3) np.testing.assert_array_almost_equal(msd[2], (2., 2., 2.), decimal=2) np.testing.assert_array_almost_equal(msd[3], (3., 3., 3.), decimal=2)
def test_basic(self): """ Test basic biased random walk rules, using unbiased rates.""" mesh = fieldkit.Mesh().from_lattice( N=3, lattice=fieldkit.HOOMDLattice(L=3.0)) field = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) field[:, :, 0] = 0 domain = fieldkit.domain.digitize(field, threshold=0.5) # these are the hopping rates. # should really be zero to go in boundary, but this move will be rejected anyway. rates = np.full(list(mesh.shape) + [6], 1.) traj, x, im, t = fieldkit.simulate.kmc(domain, rates, np.arange(10), N=2, steps=100, seed=42) # check shape of output is OK self.assertEqual(traj.shape, (10, 2, 3)) self.assertEqual(x.shape, (2, 3)) self.assertEqual(im.shape, (2, 3)) self.assertEqual(t.shape, (2, )) # check that all coords are still in box self.assertTrue(np.all(x >= 0)) self.assertTrue(np.all(x < 3)) # walk cannot enter z = 0 self.assertTrue(np.all(traj[:, :, 2] != 0)) # with 10 steps, a particle cannot have traveled more than 3 images self.assertTrue(np.all(im >= -3)) self.assertTrue(np.all(im < 3))
def test_volume(self): """ Test for domain volume calculation by :py:meth:`~fieldkit.domain.volume`. """ mesh = fieldkit.Mesh().from_lattice( N=4, lattice=fieldkit.HOOMDLattice(L=4.0)) self.assertAlmostEqual(mesh.lattice.volume, 64.) # 25% covered field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) field[:, :, 0] = 1. vol = fieldkit.measure.volume(field, threshold=0.5, N=500000, seed=42) self.assertAlmostEqual(vol, 0.25 * mesh.lattice.volume, places=1) # 50% covered field[:, :, 2] = 1. vol = fieldkit.measure.volume(field, threshold=0.5, N=500000, seed=42) self.assertAlmostEqual(vol, 0.5 * mesh.lattice.volume, places=1) # 75% covered field[:, :, 1] = 1. vol = fieldkit.measure.volume(field, threshold=0.5, N=500000, seed=42) self.assertAlmostEqual(vol, 0.75 * mesh.lattice.volume, places=1) # 100% covered field[:, :, 3] = 1. vol = fieldkit.measure.volume(field, threshold=0.5, N=5e5, seed=42) self.assertAlmostEqual(vol, mesh.lattice.volume, places=1)
def test_msd(self): """ Validate biased random walk with a short simulation, computing the MSD. The simulation is constructed so that the MSD = 1 for each component after 1 run. """ mesh = fieldkit.Mesh().from_lattice( N=10, lattice=fieldkit.HOOMDLattice(L=10.0)) field = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) domain = fieldkit.domain.digitize(field, threshold=0.5) # these are the hopping rates, which we make a random walk for now probs = np.full(list(mesh.shape) + [6], 1. / 6.) # displacement should be consistent with random walk traj, _, _ = fieldkit.simulate.biased_walk(domain, probs, N=4000, steps=3, runs=1000, seed=42) window = 3 msd = np.zeros((window + 1, 3)) samples = np.zeros(window + 1, dtype=np.int32) for i, ri in enumerate(traj[:-1]): for dt in range(1, min(window + 1, traj.shape[0] - i)): rj = traj[i + dt] dr = rj - ri msd[dt] += np.mean(dr * dr, axis=0) samples[dt] += 1 flags = samples > 0 for ax in range(3): msd[flags, ax] /= samples[flags] np.testing.assert_array_almost_equal(msd[0], (0., 0., 0.), decimal=3) np.testing.assert_array_almost_equal(msd[1], (1., 1., 1.), decimal=3) np.testing.assert_array_almost_equal(msd[2], (2., 2., 2.), decimal=2) np.testing.assert_array_almost_equal(msd[3], (3., 3., 3.), decimal=2) # use compiled code to test farther out msd_2 = fieldkit.simulate.msd(traj, window=window) self.assertEqual(msd_2.shape, (window + 1, 3)) np.testing.assert_array_almost_equal(msd_2[0], (0., 0., 0.), decimal=3) np.testing.assert_array_almost_equal(msd_2[1], (1., 1., 1.), decimal=3) np.testing.assert_array_almost_equal(msd_2[2], (2., 2., 2.), decimal=2) np.testing.assert_array_almost_equal(msd_2[3], (3., 3., 3.), decimal=2) # both results should be essentially the same np.testing.assert_array_almost_equal(msd, msd_2) # use every 2nd origin with a looser tolerance due to lower stats msd_3 = fieldkit.simulate.msd(traj, window=window, every=2) self.assertEqual(msd_3.shape, (window + 1, 3)) np.testing.assert_array_almost_equal(msd_3[0], (0., 0., 0.), decimal=3) np.testing.assert_array_almost_equal(msd_3[1], (1., 1., 1.), decimal=2) np.testing.assert_array_almost_equal(msd_3[2], (2., 2., 2.), decimal=2) np.testing.assert_array_almost_equal(msd_3[3], (3., 3., 3.), decimal=2)
def test_one_step(self): """ Test simple random walk rules for one step. """ mesh = fieldkit.Mesh().from_lattice( N=3, lattice=fieldkit.HOOMDLattice(L=3.0)) field = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) field[:, :, 0] = 0 domain = fieldkit.domain.digitize(field, threshold=0.5) traj, x, im = fieldkit.simulate.random_walk(domain, N=2, steps=1, runs=10, seed=42) # check shape of output is OK self.assertEqual(traj.shape, (10, 2, 3)) self.assertEqual(x.shape, (2, 3)) self.assertEqual(im.shape, (2, 3)) # check that all coords are still in box self.assertTrue(np.all(x >= 0)) self.assertTrue(np.all(x < 3)) # walk cannot enter z = 0 self.assertTrue(np.all(traj[:, :, 2] != 0)) # with 10 steps, a particle cannot have traveled more than 3 images self.assertTrue(np.all(im >= -3)) self.assertTrue(np.all(im < 3)) # check that trajectory is continuous (no step is larger than 1) # 0->1 self.assertLessEqual(np.max(traj[1] - traj[0]), 1) self.assertGreaterEqual(np.min(traj[1] - traj[0]), -1) # 1->2 self.assertLessEqual(np.max(traj[2] - traj[1]), 1) self.assertGreaterEqual(np.min(traj[2] - traj[1]), -1) # 2->3 self.assertLessEqual(np.max(traj[3] - traj[2]), 1) self.assertGreaterEqual(np.min(traj[3] - traj[2]), -1) # try to restart from last state traj2, _, _ = fieldkit.simulate.random_walk(domain, N=2, steps=1, runs=1, coords=x, images=im, seed=24) # first frame should match old coordinates np.testing.assert_array_equal(traj2[0], x + im * mesh.shape) # difference between last old and first new should be 1 step at most self.assertLessEqual(np.max(traj2[0] - traj[-1]), 1) self.assertGreaterEqual(np.min(traj2[0] - traj[-1]), -1)
def test(self): """ Test for basic creation of an empty field. """ field = fieldkit.Field(self.mesh) np.testing.assert_almost_equal(field.field, np.zeros(self.mesh.shape)) self.assertAlmostEqual(field[0,0,0], 0.) self.assertEqual(field.shape, self.mesh.shape) # change field data via property field.field = np.ones(self.mesh.shape) np.testing.assert_almost_equal(field.field, np.ones(self.mesh.shape)) self.assertAlmostEqual(field[0,0,0], 1.)
def test_area(self): """ Test for domain surface triangulation and area calculation. """ mesh = fieldkit.Mesh().from_lattice( N=4, lattice=fieldkit.HOOMDLattice(L=4.0)) self.assertAlmostEqual(mesh.lattice.volume, 64.) # make a plane in the box field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) field[:, :, 0] = 1. surface = fieldkit.measure.triangulate(field, threshold=0.5) area = fieldkit.measure.surface_area(surface) self.assertAlmostEqual(area, 2 * mesh.lattice.L[0] * mesh.lattice.L[1])
def test_collide(self): mesh = fieldkit.Mesh().from_lattice( N=3, lattice=fieldkit.HOOMDLattice(L=4.)) field = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) field[:, :, 0] = 0 domain = fieldkit.domain.digitize(field, 0.5) burn, axis = fieldkit.domain.burn(domain) # check burning points np.testing.assert_equal(burn.shape, (3, 3, 3)) np.testing.assert_equal(burn[:, :, 0], 0) np.testing.assert_equal(burn[:, :, 1], 1) np.testing.assert_equal(burn[:, :, 2], 1) # check axis axis_nodes = np.asarray(axis.nodes) np.testing.assert_equal(axis_nodes.shape, (2 * 3 * 3, 3)) np.testing.assert_equal(axis_nodes[:, 2], [1, 2] * 9)
def test_sphere(self): """ Test for measuring properties of a sphere """ mesh = fieldkit.Mesh().from_lattice( N=32, lattice=fieldkit.HOOMDLattice(L=4.)) field = fieldkit.Field(mesh).from_array(np.zeros(mesh.shape)) # make a sphere R = 1. for n in np.ndindex(mesh.shape): pt = mesh[n] rsq = np.sum((pt - mesh.lattice.L / 2)**2) if rsq <= R**2: field[n] = 1. # use a loose tolerance due to inaccuracies of meshing and interpolating densities volume = fieldkit.measure.volume(field, threshold=0.5, N=5e5, seed=42) self.assertAlmostEqual(volume, 4 * np.pi * R**3 / 3, delta=0.1) # the surface should have a measured area greater than that of sphere surface = fieldkit.measure.triangulate(field, threshold=0.5) area = fieldkit.measure.surface_area(surface) self.assertTrue(area >= 4 * np.pi * R**2) self.assertAlmostEqual(area, 4 * np.pi * R**2, delta=1.)
def test_tortuosity(self): """ Test for tortuosity calculation for a domain. """ ## one straight line self.field[:, 0, 0] = 1. domain = fieldkit.domain.digitize(self.field, 0.5) # x tort, nodes = fieldkit.domain.tortuosity(domain, axis=0) self.assertAlmostEqual(tort[0], 1.0) self.assertEqual(nodes[0], (0, 0, 0)) # y tort, nodes = fieldkit.domain.tortuosity(domain, axis=1) self.assertEqual(len(tort), 0) # z tort, nodes = fieldkit.domain.tortuosity(domain, axis=1) self.assertEqual(len(tort), 0) ## three lines self.field[:, 0, 0] = 1. self.field[:, 2, 1] = 1. self.field[:, 1, 2] = 1. tort, nodes = fieldkit.domain.tortuosity(fieldkit.domain.digitize( self.field, 0.5), axis=0) np.testing.assert_array_equal(nodes, ((0, 0, 0), (0, 1, 2), (0, 2, 1))) np.testing.assert_almost_equal(tort, (1.0, 1.0, 1.0)) ## single plane self.field[:] = 0. self.field[:, :, 0] = 1. domain = fieldkit.domain.digitize(self.field, 0.5) # x tort, nodes = fieldkit.domain.tortuosity(domain, axis=0) self.assertEqual(len(tort), 4) np.testing.assert_array_equal(nodes, ((0, 0, 0), (0, 1, 0), (0, 2, 0), (0, 3, 0))) np.testing.assert_almost_equal(tort, (1.0, 1.0, 1.0, 1.0)) # y tort, nodes = fieldkit.domain.tortuosity(domain, axis=1) self.assertEqual(len(tort), 4) np.testing.assert_array_equal(nodes, ((0, 0, 0), (1, 0, 0), (2, 0, 0), (3, 0, 0))) np.testing.assert_almost_equal(tort, (1.0, 1.0, 1.0, 1.0)) # z tort, nodes = fieldkit.domain.tortuosity(domain, axis=2) self.assertEqual(len(tort), 0) ## twisted line # # Shape:: # -- # -| |- # # Total length = 6, tortuosity = 6/4 = 1.5 self.field[:] = 0. for pt in ((0, 0, 0), (1, 0, 0), (1, 1, 0), (2, 1, 0), (3, 1, 0), (3, 0, 0)): self.field[pt] = 1. domain = fieldkit.domain.digitize(self.field, 0.5) tort, nodes = fieldkit.domain.tortuosity(domain, axis=0) self.assertEqual(len(tort), 1) self.assertAlmostEqual(tort[0], 1.5) ## triclinic mesh with 45* tilt tri_mesh = fieldkit.Mesh().from_lattice(N=4, lattice=fieldkit.HOOMDLattice( L=4.0, tilt=(1.0, 0., 0.))) tri_field = fieldkit.Field(tri_mesh).from_array( np.zeros(tri_mesh.shape)) # x: not tilted, so tortuosity of a line is 1 tri_field[:, 0, 0] = 1. domain = fieldkit.domain.digitize(tri_field, 0.5) tort, nodes = fieldkit.domain.tortuosity(domain, axis=0) self.assertAlmostEqual(tort[0], 1.0) # y: tilted by 45*, so tortuosity is sqrt(2) to account for longer path tri_field[:] = 0. tri_field[0, :, 0] = 1. domain = fieldkit.domain.digitize(tri_field, 0.5) tort, nodes = fieldkit.domain.tortuosity(domain, axis=1) self.assertAlmostEqual(tort[0], np.sqrt(2.))
def setUp(self): self.mesh = fieldkit.Mesh().from_lattice( N=4, lattice=fieldkit.HOOMDLattice(L=4.0)) self.field = fieldkit.Field(self.mesh).from_array( np.zeros(self.mesh.shape))
def test_rates(self): """Test hopping rate calculator.""" mesh = fieldkit.Mesh().from_lattice( N=12, lattice=fieldkit.HOOMDLattice(L=12.0)) ## bias D in z flags = np.logical_or(mesh.grid[..., 2] < 1.0, mesh.grid[..., 2] > 10) D = fieldkit.Field(mesh).from_array(0.1 * (11.0 - mesh.grid[..., 2])) D[flags] = 0. rho = fieldkit.Field(mesh).from_array(np.ones(mesh.shape)) rho[flags] = 0. domain = fieldkit.domain.digitize(rho, threshold=1.e-6) # check rates by hand for simple case rates = fieldkit.simulate.compute_hopping_rates(domain, D, rho) self.assertEqual(rates.shape, (12, 12, 12, 6)) np.testing.assert_array_almost_equal(rates[0, 0, 0], (0., 0., 0., 0., 0., 0.)) np.testing.assert_array_almost_equal(rates[0, 0, 1], (1., 1., 1., 1., 0.95, 0.)) np.testing.assert_array_almost_equal(rates[0, 0, 2], (0.9, 0.9, 0.9, 0.9, 0.85, 0.95)) # ... np.testing.assert_array_almost_equal(rates[0, 0, 9], (0.2, 0.2, 0.2, 0.2, 0.15, 0.25)) np.testing.assert_array_almost_equal(rates[0, 0, 10], (0.1, 0.1, 0.1, 0.1, 0., 0.15)) np.testing.assert_array_almost_equal(rates[0, 0, 11], (0., 0., 0., 0., 0., 0.)) np.testing.assert_array_almost_equal(rates[0, 0], rates[1, 0]) np.testing.assert_array_almost_equal(rates[0, 0], rates[0, 1]) # run short simulation to verify density distribution traj, _, _, _ = fieldkit.simulate.kmc(domain, rates, 100 + np.arange(500), N=1000, steps=10000, seed=42) self.assertEqual(np.min(traj[..., 2]), 1) self.assertEqual(np.max(traj[..., 2]), 10) hist, _ = np.histogram(traj[..., 2], range=(0.5, 10.5), bins=10, density=True) np.testing.assert_array_almost_equal(hist, 0.1, decimal=2) ## also bias the density in z rho.field = 0.1 * (mesh.grid[..., 2]) rho[flags] = 0. domain = fieldkit.domain.digitize(rho, threshold=1.e-6) rates = fieldkit.simulate.compute_hopping_rates(domain, D, rho) np.testing.assert_array_almost_equal(rates[0, 0, 0, 0:4], 0.) np.testing.assert_array_almost_equal(rates[0, 0, 1, 0:4], 1.) np.testing.assert_array_almost_equal(rates[0, 0, 10, 0:4], 0.1) np.testing.assert_array_almost_equal(rates[0, 0, 11, 0:4], 0.) np.testing.assert_array_almost_equal(rates[0, 0], rates[1, 0]) np.testing.assert_array_almost_equal(rates[0, 0], rates[0, 1]) traj, _, _, _ = fieldkit.simulate.kmc(domain, rates, 100 + np.arange(500), N=1000, steps=10000, seed=42) self.assertEqual(np.min(traj[..., 2]), 1) self.assertEqual(np.max(traj[..., 2]), 10) hist, _ = np.histogram(traj[..., 2], range=(0.5, 10.5), bins=10, density=True) np.testing.assert_array_almost_equal(hist, rho.field[0, 0, 1:-1] / np.sum(rho.field[0, 0, 1:-1]), decimal=2) ## roll axis to y D.field = np.moveaxis(D.field, 2, 1) rho.field = np.moveaxis(rho.field, 2, 1) domain = fieldkit.domain.digitize(rho, threshold=1.e-6) rates = fieldkit.simulate.compute_hopping_rates(domain, D, rho) np.testing.assert_array_almost_equal(rates[0, 0, 0, 0:2], 0.) np.testing.assert_array_almost_equal(rates[0, 0, 0, 4:6], 0.) np.testing.assert_array_almost_equal(rates[0, 1, 0, 0:2], 1.) np.testing.assert_array_almost_equal(rates[0, 1, 0, 4:6], 1.) np.testing.assert_array_almost_equal(rates[0, 10, 0, 0:2], 0.1) np.testing.assert_array_almost_equal(rates[0, 10, 0, 4:6], 0.1) np.testing.assert_array_almost_equal(rates[0, 11, 0, 0:2], 0.) np.testing.assert_array_almost_equal(rates[0, 11, 0, 4:6], 0.) np.testing.assert_array_almost_equal(rates[0, :, 0], rates[1, :, 0]) np.testing.assert_array_almost_equal(rates[0, :, 0], rates[0, :, 1]) traj, _, _, _ = fieldkit.simulate.kmc(domain, rates, 100 + np.arange(500), N=1000, steps=10000, seed=42) self.assertEqual(np.min(traj[..., 1]), 1) self.assertEqual(np.max(traj[..., 1]), 10) hist, _ = np.histogram(traj[..., 1], range=(0.5, 10.5), bins=10, density=True) np.testing.assert_array_almost_equal(hist, rho.field[0, 1:-1, 0] / np.sum(rho.field[0, 1:-1, 0]), decimal=2) ## roll axis to z D.field = np.moveaxis(D.field, 1, 0) rho.field = np.moveaxis(rho.field, 1, 0) domain = fieldkit.domain.digitize(rho, threshold=1.e-6) rates = fieldkit.simulate.compute_hopping_rates(domain, D, rho) np.testing.assert_array_almost_equal(rates[0, 0, 0, 2:6], 0.) np.testing.assert_array_almost_equal(rates[1, 0, 0, 2:6], 1.) np.testing.assert_array_almost_equal(rates[10, 0, 0, 2:6], 0.1) np.testing.assert_array_almost_equal(rates[11, 0, 0, 2:6], 0.) np.testing.assert_array_almost_equal(rates[:, 0, 0], rates[:, 1, 0]) np.testing.assert_array_almost_equal(rates[:, 0, 0], rates[:, 0, 1]) traj, _, _, _ = fieldkit.simulate.kmc(domain, rates, 100 + np.arange(500), N=1000, steps=10000, seed=42) self.assertEqual(np.min(traj[..., 0]), 1) self.assertEqual(np.max(traj[..., 0]), 10) hist, _ = np.histogram(traj[..., 0], range=(0.5, 10.5), bins=10, density=True) np.testing.assert_array_almost_equal(hist, rho.field[1:-1, 0, 0] / np.sum(rho.field[1:-1, 0, 0]), decimal=2)