def test_exceptions(self): """Check exceptions, the new ones specific to PeriodicGrid.""" # init with pytest.raises(ValueError): PeriodicGrid(self._ref_points, self._ref_weights, np.ones( (1, 100))) with pytest.raises(ValueError): if self._ref_points.ndim == 1: PeriodicGrid(self._ref_points, self._ref_weights, np.ones(100)) else: PeriodicGrid( self._ref_points, self._ref_weights, np.ones((100, self._ref_points.shape[1])), ) if self._ref_realvecs is not None and self._ref_realvecs.shape[0] > 1: with pytest.raises(ValueError): realvecs = self._ref_realvecs.copy() realvecs[0] = realvecs[1] PeriodicGrid(self._ref_points, self._ref_weights, realvecs) # get_localgrid with pytest.raises(ValueError): self.grid.get_localgrid(np.zeros(100), 3.0) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], np.inf) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], -np.nan) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], -1.0)
def setup(self): """Initialize an unwrapped and a wrapped version of the grid.""" self.define_reference_data() with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): self.grid = PeriodicGrid(self._ref_points, self._ref_weights, self._ref_realvecs) with pytest.warns(None) as record: self.wrapped_grid = PeriodicGrid(self._ref_points, self._ref_weights, self._ref_realvecs, True) assert len(record) == 0
def test_tutorial_periodic_repetition(delta_p, delta_c): """Test example of the periodic repetition of a local non-periodic function. The 1D grid has a periodicity of a=0.5. On this grid, a periodic repetition of a Gaussian function is evaluated and tested for consistency. """ a = 0.5276 # Our grid covers multiple primitive cells, which is not great for # efficiency, but it still works. Due to the efficiency issue, a warning # is raised. To fix this issue, any may simply add the ``wrap=True`` # argument to the constructor. (This modifies the grid points.) with pytest.warns(PeriodicGridWarning): grid = PeriodicGrid( np.linspace(-1, 1, 21) + delta_p, np.full(21, 0.1), np.array([a]) ) # The local grid is wider than one primitive cell, such that there will be # more points in the local grid than in the periodic grid. The test is repeated # with two displacements of the center by an integer multiple of the # periodic unit. center = 1.8362 - delta_c radius = 2.1876 fn_old = None for _ in range(3): localgrid = grid.get_localgrid(center, radius) assert localgrid.size > grid.size # Compute one Gaussian, which is to be periodically repeated. localfn = np.exp(-20 * (localgrid.points - center) ** 2) # Construct the periodic model on grid points of one primitive cell. # Mind the ``np.add.at`` line. This is explained in the documentation of # PeriodicGrid.__init__. fn = np.zeros(grid.size) np.add.at(fn, localgrid.indices, localfn) # Compare the periodic function to the result with the previous value # of center. It should be the same because the center was translated by # exactly one periodic unit. if fn_old is not None: assert_allclose(fn, fn_old) fn_old = fn # The integral over one primitive cell should be the same as the integral # over one isolated Gaussian. assert_allclose(localgrid.integrate(localfn), grid.integrate(fn)) # Manually construct the periodic repetition and compare. fn2 = np.zeros(grid.size) jmin = np.ceil((grid.points.min() - center - radius) / a).astype(int) jmax = np.floor((grid.points.max() - center + radius) / a).astype(int) assert jmax >= jmin for j in range(jmin, jmax + 1): center_translated = center + a * j fn2 += np.exp(-20 * (grid.points - center_translated) ** 2) assert_allclose(fn2, fn) # Modify the center for the next iteration. center += a
def test_get_item(self): """Test the __getitem__ method.""" # test index grid_index = self.grid[10] ref_grid_index = PeriodicGrid(self._ref_points[10:11], self._ref_weights[10:11], self._ref_realvecs) assert_allclose(grid_index.points, ref_grid_index.points) assert_allclose(grid_index.weights, ref_grid_index.weights) assert_allclose(grid_index.realvecs, self.grid.realvecs) assert_allclose(grid_index.recivecs, self.grid.recivecs) assert_allclose(grid_index.spacings, self.grid.spacings) assert_allclose(grid_index.frac_intvls, ref_grid_index.frac_intvls) assert isinstance(grid_index, PeriodicGrid) # test slice with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): ref_grid_slice = PeriodicGrid(self._ref_points[:15], self._ref_weights[:15], self._ref_realvecs) with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): grid_slice = self.grid[:15] assert_allclose(grid_slice.points, ref_grid_slice.points) assert_allclose(grid_slice.weights, ref_grid_slice.weights) assert_allclose(grid_slice.realvecs, self.grid.realvecs) assert_allclose(grid_slice.recivecs, self.grid.recivecs) assert_allclose(grid_slice.spacings, self.grid.spacings) assert_allclose(grid_slice.frac_intvls, ref_grid_slice.frac_intvls) assert isinstance(grid_slice, PeriodicGrid) # test take with integers indices = np.array([1, 3, 5]) grid_itake = self.grid[indices] assert_allclose(grid_itake.points, self._ref_points[indices]) assert_allclose(grid_itake.weights, self._ref_weights[indices]) assert_allclose(grid_itake.realvecs, self.grid.realvecs) assert_allclose(grid_itake.recivecs, self.grid.recivecs) assert_allclose(grid_itake.spacings, self.grid.spacings) assert isinstance(grid_itake, PeriodicGrid) # test take with boolean mask mask = np.zeros(self.grid.size, dtype=bool) mask[1] = True mask[3] = True mask[5] = True grid_mtake = self.grid[mask] assert_allclose(grid_mtake.points, grid_itake.points) assert_allclose(grid_mtake.weights, grid_itake.weights) assert_allclose(grid_mtake.realvecs, self.grid.realvecs) assert_allclose(grid_mtake.recivecs, self.grid.recivecs) assert_allclose(grid_mtake.spacings, self.grid.spacings) assert isinstance(grid_mtake, PeriodicGrid)
def setup_equidistant_grid(origin, realvecs, npts): """Define a periodic grid with equally spaced grid points. The relative vectors between neigbouring grid points are constant and equal to the real-space lattice vectors divided by the number of grid points along one lattice vector. At the moment, this function is just used for testing, but this might be useful to generate grids for cube files and for other types of visualization. Parameters ---------- origin: np.array(shape=(ndim, ), dtype=float) The position of the first grid point. realvecs: np.array(shape=(ndim, ndim), dtype=float) The real-space lattice vectors. ntps: np.array(shape=ndim, dtype=int) The number of grid points along each lattice vector. Returns ------- PeriodicGrid The periodic grid with equidistant points. """ # First build grid points in fractional coordinates and then transform # to real space. fractional_tuple = np.meshgrid(*[np.arange(npt) / npt for npt in npts]) points = ( sum( [ np.outer(fractional.ravel(), realvec) for fractional, realvec in zip(fractional_tuple, realvecs) ] ) + origin ) # The weights take into account the Jacobian of the affine transformation # from fractional to Cartesian grid points. npt_total = np.product(npts) weights = np.full(npt_total, abs(np.linalg.det(realvecs)) / npt_total) return PeriodicGrid(points, weights, realvecs)
class PeriodicGridTester: """Base class for PeriodicGrid test cases. Subclasses should override the define_reference_data method, which sets the following attributes: - ``self._ref_points``: grid points - ``self._ref_weights``: grid weights - ``self._ref_realvecs``: cell vectors - ``self._ref_recivecs``: the expected reciprocal cell vectors - ``self._ref_spacings``: the expected spacings between crystal planes - ``self._ref_frac_intvls``: the expected intervals of fractional coordinates. """ def setup(self): """Initialize an unwrapped and a wrapped version of the grid.""" self.define_reference_data() with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): self.grid = PeriodicGrid(self._ref_points, self._ref_weights, self._ref_realvecs) with pytest.warns(None) as record: self.wrapped_grid = PeriodicGrid(self._ref_points, self._ref_weights, self._ref_realvecs, True) assert len(record) == 0 def define_reference_data(self): """Define reference data for the test.""" raise NotImplementedError def test_init_grid(self): """Test PeriodicGrid init.""" assert_allclose(self.grid.points, self._ref_points, atol=1e-7) assert_allclose(self.grid.frac_intvls, self._ref_frac_intvls) assert (self.wrapped_grid.frac_intvls[:, 0] >= 0).all() assert (self.wrapped_grid.frac_intvls[:, 1] <= 1).all() if self._ref_points.ndim == 2: frac_points = self.wrapped_grid.points @ self.wrapped_grid.recivecs.T else: frac_points = self.wrapped_grid.points * self.wrapped_grid.recivecs assert (frac_points >= 0).all assert (frac_points <= 1).all for grid in self.grid, self.wrapped_grid: assert isinstance(grid, PeriodicGrid) assert_allclose(grid.weights, self._ref_weights, atol=1e-7) if self._ref_realvecs is None: assert_allclose(grid.realvecs, self._ref_recivecs, atol=1e-10) else: assert_allclose(grid.realvecs, self._ref_realvecs, atol=1e-10) assert_allclose(grid.recivecs, self._ref_recivecs, atol=1e-10) assert_allclose(grid.spacings, self._ref_spacings, atol=1e-10) assert grid.size == self._ref_weights.size def test_get_item(self): """Test the __getitem__ method.""" # test index grid_index = self.grid[10] ref_grid_index = PeriodicGrid(self._ref_points[10:11], self._ref_weights[10:11], self._ref_realvecs) assert_allclose(grid_index.points, ref_grid_index.points) assert_allclose(grid_index.weights, ref_grid_index.weights) assert_allclose(grid_index.realvecs, self.grid.realvecs) assert_allclose(grid_index.recivecs, self.grid.recivecs) assert_allclose(grid_index.spacings, self.grid.spacings) assert_allclose(grid_index.frac_intvls, ref_grid_index.frac_intvls) assert isinstance(grid_index, PeriodicGrid) # test slice with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): ref_grid_slice = PeriodicGrid(self._ref_points[:15], self._ref_weights[:15], self._ref_realvecs) with pytest.warns( None if self._ref_realvecs is None else PeriodicGridWarning): grid_slice = self.grid[:15] assert_allclose(grid_slice.points, ref_grid_slice.points) assert_allclose(grid_slice.weights, ref_grid_slice.weights) assert_allclose(grid_slice.realvecs, self.grid.realvecs) assert_allclose(grid_slice.recivecs, self.grid.recivecs) assert_allclose(grid_slice.spacings, self.grid.spacings) assert_allclose(grid_slice.frac_intvls, ref_grid_slice.frac_intvls) assert isinstance(grid_slice, PeriodicGrid) # test take with integers indices = np.array([1, 3, 5]) grid_itake = self.grid[indices] assert_allclose(grid_itake.points, self._ref_points[indices]) assert_allclose(grid_itake.weights, self._ref_weights[indices]) assert_allclose(grid_itake.realvecs, self.grid.realvecs) assert_allclose(grid_itake.recivecs, self.grid.recivecs) assert_allclose(grid_itake.spacings, self.grid.spacings) assert isinstance(grid_itake, PeriodicGrid) # test take with boolean mask mask = np.zeros(self.grid.size, dtype=bool) mask[1] = True mask[3] = True mask[5] = True grid_mtake = self.grid[mask] assert_allclose(grid_mtake.points, grid_itake.points) assert_allclose(grid_mtake.weights, grid_itake.weights) assert_allclose(grid_mtake.realvecs, self.grid.realvecs) assert_allclose(grid_mtake.recivecs, self.grid.recivecs) assert_allclose(grid_mtake.spacings, self.grid.spacings) assert isinstance(grid_mtake, PeriodicGrid) def test_get_localgrid_small_radius(self): """Basic checks for the get_localgrid method. In this unit test, the cutoff sphere fits inside a primitive cell, such that each grid point from the parent periodic grid will at most appear once in the local grid. """ center = self.grid.points[3] radius = 0.19475 # Check that the sphere fits inside the primitive cell: assert (2 * radius < self.grid.spacings).all() # Build local grids. localgrids = [ self.grid.get_localgrid(center, radius), self.wrapped_grid.get_localgrid(center, radius), ] # When there are no lattice vectors, the local grid from the base class # should also be the same. if self._ref_realvecs is None: aperiodic_grid = Grid(self._ref_points, self._ref_weights) localgrids.append(aperiodic_grid.get_localgrid(center, radius)) # One should get the same local grid with or without wrapping, possibly # with a different ordering of the points. We can perform a relatively # simple check here because each point appears at most once. order0 = localgrids[0].indices.argsort() for localgrid in localgrids[1:]: assert_allclose(localgrids[0].center, localgrid.center) order = localgrid.indices.argsort() assert_allclose(localgrids[0].points[order0], localgrid.points[order]) assert_allclose(localgrids[0].weights[order0], localgrid.weights[order]) # Other sanity checks on the grids. for localgrid in localgrids: # Just make sure we are testing with an actual local grid with at least # some points. assert localgrid.size > 0 assert localgrid.size <= self.grid.size assert len(localgrid.indices) == len(set(localgrid.indices)) # Test that the localgrid contains sensible results. assert_allclose(localgrid.center, center) assert localgrid.points.ndim == self.grid.points.ndim assert localgrid.weights.ndim == self.grid.weights.ndim if self._ref_points.ndim == 2: assert (np.linalg.norm(localgrid.points - center, axis=1) <= radius).all() else: assert (abs(localgrid.points - center) <= radius).all() def test_get_localgrid_large_radius(self): """Basic checks for the get_localgrid method. In this unit test, the cutoff sphere fits inside a primitive cell, such that each grid point from the parent periodic grid will at most appear once in the local grid. """ center = self.grid.points[3] radius = 2.51235 # Check that the sphere flows over the primitive cell, when there are # some lattice vectors. if self._ref_realvecs is not None: assert (2 * radius > self.grid.spacings).any() # Build local grids. localgrids = [ self.grid.get_localgrid(center, radius), self.wrapped_grid.get_localgrid(center, radius), ] # When there are no lattice vectors, the local grid from the base class # should also be the same. if self._ref_realvecs is None: aperiodic_grid = Grid(self._ref_points, self._ref_weights) localgrids.append(aperiodic_grid.get_localgrid(center, radius)) # One should get the same local grid with or without wrapping, possibly # with a different ordering of the points. for localgrid in localgrids[1:]: assert_equal_localgrids(localgrids[0], localgrid) # Other sanity checks. for localgrid in localgrids: # With a large radius there will never be less points in the local grid. if self._ref_realvecs is None: assert localgrid.size == self.grid.size else: assert localgrid.size > self.grid.size # Test that the local grid contains sensible results. assert_allclose(localgrid.center, center) assert localgrid.points.ndim == self.grid.points.ndim assert localgrid.weights.ndim == self.grid.weights.ndim if self._ref_points.ndim == 2: assert (np.linalg.norm(localgrid.points - center, axis=1) <= radius).all() else: assert (abs(localgrid.points - center) <= radius).all() def test_exceptions(self): """Check exceptions, the new ones specific to PeriodicGrid.""" # init with pytest.raises(ValueError): PeriodicGrid(self._ref_points, self._ref_weights, np.ones( (1, 100))) with pytest.raises(ValueError): if self._ref_points.ndim == 1: PeriodicGrid(self._ref_points, self._ref_weights, np.ones(100)) else: PeriodicGrid( self._ref_points, self._ref_weights, np.ones((100, self._ref_points.shape[1])), ) if self._ref_realvecs is not None and self._ref_realvecs.shape[0] > 1: with pytest.raises(ValueError): realvecs = self._ref_realvecs.copy() realvecs[0] = realvecs[1] PeriodicGrid(self._ref_points, self._ref_weights, realvecs) # get_localgrid with pytest.raises(ValueError): self.grid.get_localgrid(np.zeros(100), 3.0) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], np.inf) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], -np.nan) with pytest.raises(ValueError): self.grid.get_localgrid(self.grid.points[4], -1.0)