def test_a_init_unit_cube(self): # instantiate a cubic lattice with unit-length vectors # and its reciprocal lattice, still cubic with 2π-length vectors d, r = make_dr(1, 1, 1) # creating a BrillouinZone objects requires that we pass the reciprocal # lattice object, and passing a Direct object is a TypeError: with self.assertRaises(TypeError): s.BrillouinZone(d) # its first Brillouin zone is a cube in reciprocal space bz = s.BrillouinZone(r) # with six faces, defined by: (00̄1),(0̄10),(̄100),(100),(010),(001) p = bz.points self.assertEqual(p.ndim, 2) self.assertEqual(p.shape[0], 6) # and there is one for each of the six faces self.assertEqual(p.shape[1], 3) # the face vectors are 3-vectors n = bz.normals self.assertEqual(n.ndim, 2) self.assertEqual(n.shape[0], 6) # and there is one for each of the six faces self.assertEqual(n.shape[1], 3) # the face normals are 3-vectors n_dot_p = np.array([np.dot(x/norm(x),y/norm(y)) for x,y in zip(n,p)]) self.assertTrue((n_dot_p == 1.0).all()) expected = np.array([[-1,0,0],[0,-1,0],[0,0,-1],[0,0,1],[0,1,0],[1,0,0]]) self.assertTrue(vector_lists_match(p, expected/2)) self.assertTrue(vector_lists_match(np.array([x/norm(x) for x in n]), expected)) # # the vertices of the first Brillouin zone are the 8 corners of the 1/2-unit cube: expected = np.array([[-1,-1,-1], [-1,-1, 1], [-1, 1,-1], [-1, 1, 1], [1,-1,-1], [1,-1, 1], [1, 1,-1], [1, 1, 1]])/2 verts = bz.vertices self.assertEqual(verts.ndim, 2) self.assertEqual(verts.shape[0], 8) self.assertEqual(verts.shape[1], 3) self.assertTrue(vector_lists_match(verts, expected))
def setup_grid(iscomplex=False, halfN=(2, 2, 2)): """Create a grid object for interpolating.""" rlat = s.Reciprocal((1, 1, 1), np.array([1, 1, 1])*np.pi/2) bz = s.BrillouinZone(rlat) if iscomplex: bzg = s.BZGridQcomplex(bz, halfN=halfN) else: bzg = s.BZGridQ(bz, halfN=halfN) return bzg
def test_c_moveinto_hexagonal(self): d, r = make_dr(3, 3, 9, np.pi/2, np.pi/2, np.pi*2/3) bz = s.BrillouinZone(r) Q = (np.random.rand(100, 3)-0.5) * 10 # this is a uniform distribution over [-5, 5) if bz.isinside(Q).all(): # this is vanishingly-unlikely Q += 100.0 self.assertFalse( bz.isinside(Q).all() ) (q, tau) = bz.moveinto(Q) self.assertTrue( bz.isinside(q).all() ) self.assertAlmostEqual( np.abs(Q-q-tau).sum(), 0)
def test_b_isinside_hexagonal(self): d, r = make_dr(3, 3, 9, np.pi/2, np.pi/2, np.pi*2/3) bz = s.BrillouinZone(r) # Q = (np.random.rand(1000, 3)-0.5) * 2 # this is a uniform distribution over [-1, 1) x=np.linspace(-1, 1, 100) X, Y, Z=np.meshgrid(x, x, 0) Q = np.stack( (X.flatten(), Y.flatten(), Z.flatten()), axis=-1) Qin = bz.isinside(Q) B = r.B X = np.stack( [ np.matmul(B, v) for v in Q[Qin,:] ] )
def test_i_iron_self_consistency(self): """Test with data as iron spinwaves, but test only *at* grid points.""" d = s.Direct((2.87, 2.87, 2.87), np.pi/2*np.array((1, 1, 1)), "Im-3m") r = d.star bz = s.BrillouinZone(r) bzg = s.BZGridQcomplex(bz, halfN=(1, 1, 1)) Q = bzg.rlu bzg.fill(fe_dispersion(Q),[1,]) intres = bzg.interpolate_at(Q, False, False) antres = fe_dispersion(Q) self.assertTrue(np.isclose(intres, antres).all())
def test_b_isinside_unit_cube(self): d, r = make_dr(1, 1, 1) bz = s.BrillouinZone(r) # the first Brillouin zone of this unit-cube is bounded by # {(00̄1),(0̄10),(̄100),(100),(010),(001)}/2 face_centres = np.array([ [-1, 0, 0], [0,-1, 0], [0, 0,-1], [0, 0, 1], [0, 1, 0], [1, 0, 0] ], dtype='double')/2 self.assertTrue( (bz.isinside(face_centres)).all() ) # including the vertices (since they are the corners of the zone) corners = np.array([[-1,-1,-1], [-1,-1, 1], [-1, 1,-1], [-1, 1, 1], [1,-1,-1], [1,-1, 1], [1, 1,-1], [1, 1, 1]], dtype='double')/2 self.assertTrue( (bz.isinside(corners)).all() ) # so all points with h, k, and l in the range [-0.5, 0.5] are in the zone Q = np.random.rand(100, 3) - 0.5 # this is a uniform distribution over [-0.5, 0.5) -- close enough self.assertTrue( bz.isinside(Q).all() ) self.assertFalse( bz.isinside(Q+5).all() )
def test_a_init_hexagonal(self): # instantiate a hexagonal lattice and its reciprocal lattice, still hexagonal d, r = make_dr(3, 3, 9, np.pi/2, np.pi/2, np.pi*2/3) # creating a BrillouinZone objects requires that we pass the reciprocal # lattice object, and passing a Direct object is a TypeError: with self.assertRaises(TypeError): s.BrillouinZone(d) # its first Brillouin zone is a cube in reciprocal space bz = s.BrillouinZone(r) # with eight faces, defined by: (̄100),(̄110),(0̄10),(001),(001),(010),(1̄10),(100) p = bz.points self.assertEqual(p.ndim, 2) self.assertEqual(p.shape[0], 8) # and there is one for each of the eight faces self.assertEqual(p.shape[1], 3) # the face vectors are 3-vectors n = bz.normals self.assertEqual(n.ndim, 2) self.assertEqual(n.shape[0], 8) # and there is one for each of the six faces self.assertEqual(n.shape[1], 3) # the face normals are 3-vectors n_dot_p = np.array([np.dot(x/norm(x),y/norm(y)) for x,y in zip(n,p)]) self.assertTrue(np.allclose(n_dot_p, 1.)) expected= np.array([ [-1, 0, 0],[-1, 1, 0],[0,-1, 0],[0, 0,-1],[0, 0, 1],[0, 1, 0],[1,-1, 0],[1, 0, 0] ]) self.assertTrue(vector_lists_match(p, expected/2)) n_compare = np.array([x/np.max(np.abs(x)) for x in n]) self.assertTrue(vector_lists_match(n_compare, expected)) self.assertTrue( (bz.isinside(expected/2)).all() ) # # the vertices of the first Brillouin zone are the 12 corners of the hexagonal-prism: expected = np.array([[-4, 2,-3],[-2,-2,-3],[-4, 2, 3],[-2,-2, 3],[-2, 4,-3],[-2, 4, 3], [ 2,-4,-3],[ 2,-4, 3],[ 2, 2,-3],[ 4,-2,-3],[ 2, 2, 3],[ 4,-2, 3]])/6 verts = bz.vertices self.assertEqual(verts.ndim, 2) self.assertEqual(verts.shape[0], 12) self.assertEqual(verts.shape[1], 3) self.assertTrue(vector_lists_match(verts, expected)) self.assertTrue( (bz.isinside(expected)).all() )
def test_aflow_crystaldatabase(): tested = 0 failed = 0 errored = 0 failed_afl = [] failed_ratio = [] errored_afl = [] errored_arg = [] hall_groups_passed = np.zeros(530, dtype='int') hall_groups_failed = np.zeros(530, dtype='int') for afl in get_aflow_lattices(): dlat = s.Direct(afl[1], afl[2], afl[0]) # use the pre-determined Hall number i = dlat.hall try: bz = s.BrillouinZone(dlat.star) vol_bz = bz.polyhedron.volume vol_ir = bz.ir_polyhedron.volume tested += 1 if not np.isclose(vol_ir, vol_bz / s.PointSymmetry(i).size): failed += 1 failed_afl.append(afl) failed_ratio.append(vol_ir / vol_bz * s.PointSymmetry(i).size) hall_groups_failed[i - 1] += 1 else: hall_groups_passed[i - 1] += 1 except Exception as err: errored += 1 errored_afl.append(afl) errored_arg.append(err.args) if failed > 0: print("\nFailed to find correct irreducible Brillouin zone for", failed, "out of", tested, "lattices") for file, rat in zip(failed_afl, failed_ratio): print(file, rat) if errored > 0: print("\nException raised for", errored, "out of", tested, "lattices") for file, arg in zip(errored_afl, errored_arg): print(file, arg) print("\nHall groups passed (total =", hall_groups_passed.sum(), "of", tested, "tested)") encoded_hgp = [n2chr(x) for x in hall_groups_passed] for x in [encoded_hgp[i * 53:(i + 1) * 53] for i in range(10)]: print(''.join(x))
def make_drbz(a, b, c, al=np.pi / 2, be=np.pi / 2, ga=np.pi / 2): """Make a Direct Reicprocal and BrillouinZone object.""" d = s.Direct(a, b, c, al, be, ga) r = d.star bz = s.BrillouinZone(r) return (d, r, bz)
def get_conventional_BrillouinZone(self): return b.BrillouinZone(self.get_conventional_Direct().star)
def get_input_BrillouinZone(self): return b.BrillouinZone(self.get_input_Direct().star)
Z[np.newaxis, -1] + dX[np.newaxis, -1]), axis=0) dY = np.diff(Z, axis=1) / 2 Z = np.concatenate( (Z[:, :-1] - dY, Z[:, np.newaxis, -1] - dY[:, np.newaxis, -1], Z[:, np.newaxis, -1] + dY[:, np.newaxis, -1]), axis=1) return Z def pcolormesh(X, Y, Z, *args, **kwargs): return pp.pcolormesh(cen2corner(X), cen2corner(Y), Z, *args, **kwargs) dlat = brille.Direct((5., 5., 5.), (90., 90., 90.), 525) bz = brille.BrillouinZone(dlat.star) nb = load_interpolation_data('nb') # Next investigate now choice of maximum tetrahedra size and trellis size # effect point-interpolation time qx, qy = np.mgrid[1 - 0.3:1 + 0.3:0.005, 1 - 0.3:1 + 0.3:0.005] inshape = qx.shape px = cen2corner(qx) py = cen2corner(qy) qx = qx.reshape(qx.size, 1) qy = qy.reshape(qy.size, 1) qxyz = np.concatenate((qx, qy, 0 * qx), axis=1) energy = 4.1 + np.zeros_like(qx)
def create_bz(*args, is_reciprocal=False, use_primitive=True, search_length=1, time_reversal_symmetry=False, wedge_search=True, **kwargs): """ Construct a BrillouinZone object. Parameters ---------- a, b, c : float Lattice parameters as separate floating point values lens : (3,) :py:class:`numpy.ndarray` or list Lattice parameters as a 3-element array or list alpha, beta, gamma : float Lattice angles in degrees or radians as separate floating point values Brille tries to determine if the input is in degrees or radians by looking at its magnitude. If the values are all less than PI it assumes the angles are in radians otherwise it assumes degrees angs : (3,) :py:class:`numpy.ndarray` or list Lattice angles in degrees or radians as a 3-element array or list lattice_vectors : (3, 3) :py:class:`numpy.ndarray` or list of list The lattice vectors as a 3x3 matrix, array or list of list spacegroup: str or int The spacegroup in either International Tables (Hermann-Mauguin) notation or a Hall symbol or an integer Hall number. is_reciprocal : bool, keyword-only optional (default: False) Whether the lattice parameters or lattice vectors refers to a reciprocal rather than direct lattice. If True, a/b/c/lens should be in reciprocal Angstrom, otherwise they should be in Angstrom use_primitive : bool, keyword-only optional (default: True) Whether the primitive (or conventional) lattice should be used search_length : int, keyword-only optional (default: 1) An integer to control how-far the vertex-finding algorithm should search in τ-index. The default indicates that (1̄1̄1̄), (1̄1̄0), (1̄1̄1), (1̄0̄1), ..., (111) are included. time_reversal_symmetry : bool, keyword-only optional (default: False) Whether to include time-reversal symmetry as an operation to determine the irreducible Brillouin zone wedge_search : bool, keyword-only optional (default: True) If true, return an irreducible first Brillouin zone, otherwise just return the first Brillouin zone Note ---- Note that the required lattice parameters must be specified as: - EITHER ``create_bz(a, b, c, alpha, beta, gamma, spacegroup, ...)`` - OR ``create_bz(lens, angs, spacegroup, ...)`` - OR ``create_bz(lattice_vectors, spacegroup, ...)`` E.g. you cannot mix specifing `a`, `b`, `c`, and `angs` etc. """ # Take keyword arguments in preference to positional ones a, b, c, alpha, beta, gamma, lens, angs = ( kwargs.pop(pname, None) for pname in ['a', 'b', 'c', 'alpha', 'beta', 'gamma', 'lens', 'angs']) no_lat_kw_s = any([v is None for v in [a, b, c, alpha, beta, gamma]]) no_lat_kw_v = any([v is None for v in [lens, angs]]) if no_lat_kw_v and not no_lat_kw_s: lens, angs = ([a, b, c], [alpha, beta, gamma]) lattice_vectors = kwargs.pop('lattice_vectors', None) spacegroup = kwargs.pop('spacegroup', None) # Parse positional arguments spg_id = 0 if no_lat_kw_s and no_lat_kw_v and lattice_vectors is None: if np.shape(args[0]) == (): lens, angs = (args[:3], args[3:6]) spg_id = 6 elif np.shape(args[0]) == (3, ): lens, angs = tuple(args[:2]) spg_id = 2 elif np.shape(args[0]) == (3, 1) or np.shape(args[0]) == (1, 3): lens, angs = tuple(args[:2]) lens = np.squeeze(np.array(lens)) angs = np.squeeze(np.array(angs)) spg_id = 2 elif np.shape(args[0]) == (3, 3): lattice_vectors = args[0] spg_id = 1 else: raise ValueError('No lattice parameters or vectors given') if spacegroup is None: if len(args) > spg_id: spacegroup = args[spg_id] else: raise ValueError('Spacegroup not given') if not isinstance(spacegroup, str): try: spacegroup = int(spacegroup) except TypeError as e0: e1 = ValueError( 'Invalid spacegroup input. It must be a string or number') e1.__suppress_context__ = True e1.__traceback__ = e0.__traceback__ raise e1 if is_reciprocal: if lattice_vectors is not None: lattice = brille.Reciprocal(lattice_vectors, spacegroup) else: lattice = brille.Reciprocal(lens, angs, spacegroup) else: if lattice_vectors is not None: lattice = brille.Direct(lattice_vectors, spacegroup) else: lattice = brille.Direct(lens, angs, spacegroup) lattice = lattice.star try: return brille.BrillouinZone( lattice, use_primitive=use_primitive, search_length=search_length, time_reversal_symmetry=time_reversal_symmetry, wedge_search=wedge_search) except RuntimeError as e0: # We set wedge_search=True by default so add a hint here. if 'Failed to find an irreducible Brillouin zone' in str(e0): e1 = RuntimeError(str(e0) + ' You can try again with wedge_search=False ' \ 'to calculate with just the first Brillouin zone') e1.__suppress_context__ = True e1.__traceback__ = e0.__traceback__ raise e1 else: raise e0
def test_d_all_hallgroups(self): tested = 0 failed = 0 errored = 0 failed_spg = [] failed_ptg = [] failed_lat = [] failed_ratio = [] errored_spg = [] errored_ptg = [] errored_lat = [] errored_arg = [] print() for i in range(1,531): spacegroup = s.Spacegroup(i) pointgroup = s.Pointgroup(spacegroup.pointgroup_number) a = 5; b = 5; c = 5; al = np.pi/2; be = np.pi/2; ga = np.pi/2 # nothing to do for cubic spacegroups if 'hexa' in pointgroup.holohedry: ga = 2*np.pi/3 elif 'trig' in pointgroup.holohedry: if 'R' in spacegroup.choice: al = be = ga = np.pi/3 else: # 'H' setting or normally hexagonal c = 10; ga = 2*np.pi/3 elif 'tetr' in pointgroup.holohedry: c = 10 elif 'orth' in pointgroup.holohedry: axperm = spacegroup.choice.replace('-','') if 'cab' in axperm: c = 5; a = 10; b = 15; elif 'cba' in axperm: c = 5; b = 10; a = 15; elif 'bca' in axperm: b = 5; c = 10; a = 15; elif 'bac' in axperm: b = 5; a = 10; c = 15; elif 'acb' in axperm: a = 5; c = 10; b = 15; else: a = 5; b = 10; c = 15; elif 'mono' in pointgroup.holohedry: # continue # skip all monoclinic pointgroups for now if 'a' in spacegroup.choice: a = 10; al = np.pi/180*(91+19*np.random.rand()) elif 'b' in spacegroup.choice: b = 10; be = np.pi/180*(91+19*np.random.rand()) elif 'c' in spacegroup.choice: c = 10; ga = np.pi/180*(91+19*np.random.rand()) else: print("Monoclinic without 'a', 'b', or 'c' choice?? ", spacegroup.choice) continue elif 'tric' in pointgroup.holohedry: a = 5; b = 10; c = 15; al = np.pi/3*(1 + np.random.rand()) be = np.pi/3*(1 + np.random.rand()) ga = np.pi/3*(1 + np.random.rand()) dlat, rlat = make_dr(a, b, c, al, be, ga, i) # print("Hall ", i, " ", dlat) # print(spacegroup,pointgroup) try: bz = s.BrillouinZone(rlat) vol_bz = bz.polyhedron.volume vol_ir = bz.ir_polyhedron.volume tested += 1 if not np.isclose(vol_ir, vol_bz/s.PointSymmetry(i).size): # print(dlat,": ",vol_ir," != ",vol_bz/s.PointSymmetry(i).size) failed += 1 failed_spg.append(spacegroup) failed_ptg.append(pointgroup) failed_lat.append(dlat) failed_ratio.append(vol_ir/vol_bz*s.PointSymmetry(i).size) except Exception as err: errored += 1 errored_spg.append(spacegroup) errored_ptg.append(pointgroup) errored_lat.append(dlat) errored_arg.append(err.args) if failed > 0: print("\nFailed to find irreducible Brillouin zone for",failed,"out of",tested,"(max 530) Hall groups") # for spg, ptg, lat, rat in zip(failed_spg, failed_ptg, failed_lat, failed_ratio): # print(spg,ptg,lat,rat) if errored > 0: print("\nException raised for",errored,"out of",tested,"(max 530) Hall Groups")
def make_rbz(lengths, angles=np.pi / 2 * np.array((1, 1, 1))): """Make a reciprocal lattice and Brillouin zone.""" r_lat = s.Reciprocal(lengths, angles) b_z = s.BrillouinZone(r_lat) return (r_lat, b_z)