def test_coordinates_y(): r""" /\ /\ | | | | 2| 3| /\ /\ / | | | Hexagon size 1. | 0| 1| Cells 2 x 2. \/ \/ """ size = 1 mesh = HexagonalMesh(size, 2, 1) width = size * 2.0 # height refers to the y-distance between two hexagons # centres in consecutive rows height = sqrt(3) * size # This is the total hexagon height that we use as the base hex_height = 2.0 * width / sqrt(3) mesh = HexagonalMesh(size, 2, 2) assert allclose(mesh.coordinates, np.array(( (width / 2.0, hex_height / 2.0, 0), (width * 3.0 / 2.0, hex_height / 2.0, 0), (width, height + hex_height / 2.0, 0), (width * 2, height + hex_height / 2.0, 0))))
def test_nearest_neighbours_multiple(): r""" /\ /\ /\ | | | | | 6| 7| 8| /\ /\ /\ / | | | | | 3| 4| 5| /\ /\ /\ / | | | | Hexagon size 1. | 0| 1| 2| Cells 3 x 3. \/ \/ \/ """ mesh = HexagonalMesh(1, 3, 3) print(mesh.neighbours) # assert to_sets(mesh.neighbours) == [{1, 3}, {0, 2, 3, 4}, # cells 0, 1 # {1, 4, 5}, {0, 1, 4, 6}, # cells 2, 3 # {1, 2, 3, 5, 6, 7}, {2, 4, 7, 8}, # cells 4, 5 # {3, 4, 7}, {4, 5, 6, 8}, # cells 6, 7 # {5, 7}] # cell 8 assert (mesh.neighbours == [[1, -1, 3, -1, -1, -1], # cell 0 [2, 0, 4, -1, 3, -1], # cell 1 [-1, 1, 5, -1, 4, -1], # cell 2 [4, -1, 6, 0, -1, 1], # cell 3 [5, 3, 7, 1, 6, 2], # cell 4 [-1, 4, 8, 2, 7, -1], # cell 5 [7, -1, -1, 3, -1, 4], # ... [8, 6, -1, 4, -1, 5], [-1, 7, -1, 5, -1, -1] ] ).all()
def test_nearest_neighbours_multiple_square(): r""" /\ /\ /\ | | | | | 6| 7| 8| /\ /\ /\ / | | | | | 3| 4| 5| \ /\ /\ /\ | | | | | 0| 1| 2| Hexagon size 1. \/ \/ \/ Cells 3 x 3. """ mesh = HexagonalMesh(1, 3, 3, alignment='square') print(mesh.neighbours) assert (mesh.neighbours == [[1, -1, 4, -1, 3, -1], # cell 0 [2, 0, 5, -1, 4, -1], # cell 1 [-1, 1, -1, -1, 5, -1], # cell 2 [4, -1, 6, -1, -1, 0], # ... [5, 3, 7, 0, 6, 1], [-1, 4, 8, 1, 7, 2], [7, -1, -1, 3, -1, 4], [8, 6, -1, 4, -1, 5], [-1, 7, -1, 5, -1, -1] ] ).all()
def generate_mesh(self): nx = int(np.around(2 * self.R / self.lattice_a)) + 1 ny = int(np.around(2 * self.R * np.cos(np.pi / 6) / (self.lattice_a * np.sqrt(3) * 0.5) )) + 1 self.mesh = HexagonalMesh(self.lattice_a * 0.5, nx, ny, alignment='square', unit_length=1e-9, shells=self.shells )
def test_neighbours_x_periodic(): r""" /\ /\ | | | Hexagon size 1. | 0| 1| Cells 2 x 1. \/ \/ """ mesh = HexagonalMesh(1, 2, 1, periodicity=(True, False)) assert (mesh.neighbours == [[1, 1, -1, -1, -1, -1], [0, 0, -1, -1, -1, -1]]).all()
def test_neighbours_x(): r""" /\ /\ | | | Hexagon size 1. | 0| 1| Cells 2 x 1. \/ \/ """ mesh = HexagonalMesh(1, 2, 1) # assert to_sets(mesh.neighbours) == [{1}, {0}] assert (mesh.neighbours == [[1, -1, -1, -1, -1, -1], [-1, 0, -1, -1, -1, -1]]).all()
def test_neighbours_y_square(): r""" /\ | | | 1| \/\ | | Hexagon size 1. | 0| Cells 1 x 2. \/ """ mesh = HexagonalMesh(1, 1, 2, alignment='square') assert (mesh.neighbours == [[-1, -1, -1, -1, 1, -1], [-1, -1, -1, -1, -1, 0]]).all()
def test_neighbours_y_periodic(): r""" /\ | | | 1| /\ / | | Hexagon size 1. | 0| Cells 1 x 2. \/ """ mesh = HexagonalMesh(1, 1, 2, periodicity=(False, True)) assert (mesh.neighbours == [[-1, -1, 1, 1, -1, -1], [-1, -1, 0, 0, -1, -1]]).all()
def test_neighbours_y(): r""" /\ | | | 1| /\ / | | Hexagon size 1. | 0| Cells 1 x 2. \/ """ mesh = HexagonalMesh(1, 1, 2) # assert to_sets(mesh.neighbours) == [{1}, {0}] assert (mesh.neighbours == [[-1, -1, 1, -1, -1, -1], [-1, -1, -1, 0, -1, -1]]).all()
def test_hexagonal_demags_2D(): """ Comparison of the FFT approach for hexagonal meshes, named DemagHexagonal, where it is used a system with the double number of nodes along the x direction (i.e. a mesh with twice the number of nodes of the original mesh), against the full calculation of the Demag field """ # Number of atoms N = 15 a = 0.4 mesh = HexagonalMesh(a * 0.5, N, N, unit_length=1e-9, alignment='square') # Centre xc = (mesh.Lx * 0.5) yc = (mesh.Ly * 0.5) mu_s = 2 * const.mu_B sim = Sim(mesh) sim.mu_s = mu_s sim.set_m(lambda pos: m_init_2Dvortex(pos, (xc, yc))) # Brute force demag calculation sim.add(DemagFull()) sim.get_interaction('DemagFull').compute_field() sim.get_interaction('DemagFull').field DemagFull_energy = sim.compute_energy() / const.meV # Demag using the FFT approach and a larger mesh sim2 = Sim(mesh) sim2.mu_s = mu_s sim2.set_m(lambda pos: m_init_2Dvortex(pos, (xc, yc))) sim2.add(DemagHexagonal()) sim2.get_interaction('DemagHexagonal').compute_field() sim2.compute_energy() demag_2fft_energy = sim2.compute_energy() / const.meV # We compare both energies scaled in meV assert (DemagFull_energy - demag_2fft_energy) < 1e-10
def test_coordinates_x(): r""" /\ /\ | | | Hexagon size 1. | | | Cells 2 x 1. \/ \/ """ size = 1 mesh = HexagonalMesh(size, 2, 1) width = size * 2.0 height = 2.0 * width / sqrt(3) assert allclose(mesh.coordinates, np.array(((width / 2.0, height / 2.0, 0), (width * 3.0 / 2.0, height / 2.0, 0))))
def generate_mesh(self): # The width of the mesh will be the width of the triangle less # the offsets (that's why we shifted the material to the left) # We add 2 to avoid cropping the mesh if the integer approximation # is not optimal nx = int(np.around((self.L - (self.offsets[0] + self.offsets[2]) * 0.5) / self.lattice_a)) + 2 # Height of the triangle divided by the lattice hexagons height # +1 is to avoid cropping the triangle ny = int(np.around((self.L - self.offsets[1]) * np.sin(np.pi / 3) / (self.lattice_a * np.sqrt(3) * 0.5)) ) + 1 self.mesh = HexagonalMesh(self.lattice_a * 0.5, nx, ny, alignment='square', unit_length=1e-9, shells=self.shells )
def test_neighbours_9shells_square_even_row(): """ Just as test_neighbours_9shells_square_odd_row but checking a site in an even row in a 12x12 hexagonal lattice """ mesh = HexagonalMesh(1, 12, 12, alignment='square', shells=9) assert (mesh.neighbours[77] == [78, 76, 90, 65, 89, 66, # 1st shell 91, 64, 101, 53, 88, 67, # 2nd shell 79, 75, 102, 52, 100, 54, # 3rd shell 92, 63, 103, 51, 114, 41, # 4th shell 113, 42, 99, 55, 87, 68, 80, 74, 115, 40, 112, 43, # 5th shell 104, 50, 125, 29, 98, 56, # 6th shell 93, 62, 116, 39, 126, 28, # 7th shell 124, 30, 111, 44, 86, 69, 81, 73, 127, 27, 123, 31, # 8th shell 105, 49, 117, 38, 138, 17, # 9th shell 137, 18, 110, 45, 97, 57 ] ).all()
sk_npy_path = glob.glob(args.sk_folder + '*.npy')[0] fm_npy_path = glob.glob(args.fm_folder + '*.npy')[0] # Number of images to be generated number_images = 19 # ----------------------------------------------------------------------------- mu_s = 0.846 * const.mu_B a = 0.125 nx = 320 ny = 185 # MESH mesh = HexagonalMesh(a, nx, ny, alignment='square', unit_length=1e-9) vtk_saver = SaveVTK(mesh, name='sk_displ') centre_x = (np.max(mesh.coordinates[:, 0]) + np.min(mesh.coordinates[:, 0])) * 0.5 centre_y = (np.max(mesh.coordinates[:, 1]) + np.min(mesh.coordinates[:, 1])) * 0.5 lx = (np.max(mesh.coordinates[:, 0]) - np.min(mesh.coordinates[:, 0])) ly = (np.max(mesh.coordinates[:, 1]) - np.min(mesh.coordinates[:, 1])) # Create a simulation for the FM state and one for the skyrmion sim = Sim(mesh, name='sk_displ') sim.mu_s = mu_s
'blue': ((0.0, 0.0, 0.0), (0.5, 0.753402, 0.753402), (1.0, 73 / 255., 73 / 255.)) } # Transform to colormaps BlOr = mpl.colors.LinearSegmentedColormap('BlOr', cdict_BlOr, 256) BlOr_r = cm.revcmap(BlOr._segmentdata) BlOr_r = mpl.colors.LinearSegmentedColormap('BlOr', BlOr_r, 256) # mesh = CuboidMesh(nx=21, ny=21, # dx=0.5, dy=0.5, # unit_length=1e-9, # periodicity=(True, True, False) # ) mesh = HexagonalMesh(.5, 41, 41, periodicity=(True, True)) # Initialise a simulation object and load the skyrmion # or ferromagnetic states sim = Sim(mesh, name='neb_21x21-spins_fm-sk_atomic') sk = '../hexagonal_system/relaxation/npys/2Dhex_41x41_FePd-Ir_B-15e-1_J588e-2_sk-up_npys/m_689.npy' fm = '../../npys/neb_21x21-spins_fm-sk_atomic_k1e10_216/image_17.npy' ds = '../../npys/neb_21x21-spins_fm-sk_atomic_k1e10_216/image_11.npy' # For now we will just generate a skyrmion states = { 'skyrmion': sk, # 'ferromagnetic': fm, # 'destruction': ds }
def generate_simulation(self, do_precession=False, gamma=1.76e11, load_mu_s=False): """ Generates a simulation according to the self.simulation option gamma :: Default is the free electron gyromagnetic ratio load_mu_s :: A file path to a NPY file with the mu_s information (only for the experimental_sample option) """ # Mesh ---------------------------------------------------------------- if self.simulation == 'experimental_sample': self.sim_from_image = sfi.sim_from_image(sfi.default_image) if not load_mu_s: self.sim_from_image.generate_magnetic_moments(mu_s=self.mu_s) else: self.sim_from_image.generate_magnetic_moments( load_file=load_mu_s) self.sim = self.sim_from_image.sim elif self.simulation == '2D_square': # A square sized hexagonal mesh mesh = HexagonalMesh( self.mesh_a * 0.5, self.mesh_nx, self.mesh_ny, # periodicity=(True, True), alignment='square', unit_length=1e-9) self.sim = Sim(mesh) # If we use polygon mesh tools, we can use a hexagon shaped mesh # self.sim.mu_s = self.mu_s_in_hexagon self.sim.mu_s = self.mu_s elif self.simulation == '1D_chain': # A 1D chain using a cuboid mesh mesh = CuboidMesh( dx=self.mesh_a, nx=self.mesh_nx, ny=1, nz=1, # periodicity=(True, True), unit_length=1e-9) self.sim = Sim(mesh) # If we use polygon mesh tools, we can use a hexagon shaped mesh # self.sim.mu_s = self.mu_s_in_hexagon self.sim.mu_s = self.mu_s self.sim.driver.do_precession = do_precession self.sim.driver.gamma = gamma # Interactions -------------------------------------------------------- exch = UniformExchange(self.J) self.sim.add(exch) dmi = DMI(D=(self.D), dmi_type='interfacial') self.sim.add(dmi) zeeman = Zeeman((self.B[0], self.B[1], self.B[2])) self.sim.add(zeeman, save_field=True) if self.ku: # Uniaxial anisotropy along + z-axis self.sim.add(Anisotropy(self.ku, axis=[0, 0, 1])) if self.Demag: print('Using Demag!') self.sim.add(DemagHexagonal()) # --------------------------------------------------------------------- self.hls = np.ones_like(self.sim.spin.reshape(-1, 3)) self.rgbs = np.ones((self.sim.spin.reshape(-1, 3).shape[0], 4))
def test_hexagonal_mesh_creation_periodic_square_y(): mesh = HexagonalMesh(1, 2, 2, alignment='square', periodicity=(False, True, False))
def test_iterate_over_cells_and_neighbours(): mesh = HexagonalMesh(1, 2, 2) for c_i in mesh.cells(): print("I am cell #{}.".format(c_i)) for c_j in mesh.neighbours[c_i]: print("\tAnd I am its neighbour, cell #{}!".format(c_j))
def test_iterate_over_cells(): mesh = HexagonalMesh(1, 2, 2) for c_i in mesh.cells(): print("This is cell #{}, I have neighbours {}.".format(c_i, mesh.neighbours[c_i]))
def test_save_scalar_field_hexagonal_mesh(): mesh = HexagonalMesh(1, 3, 3) s = scalar_field(mesh, lambda r: r[0] + r[1]) vtk = VTK(mesh, directory=OUTPUT_DIR, filename="scalar_hexagonal") vtk.save_scalar(s, name="s")
def test_neighbours_9shells_square_odd_row(): r""" Testing neighbour indexes manually, for a 11x11 hexagonal mesh with square alignment. In this test we check the neighbours of the 60th lattice site, which is in an even row (we could check an odd row as well in the future) /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ | | | | |..|..| | | | | | | | | | |14|15| | | | | | /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ / | | | |;;|~~|``|~~|;;| | | | | | | |02|03|04|05|06| | | | \ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ | |..|~~|##|@@|@@|##|~~|..| | | | |89|90|91|92|93|94|95|96| | | /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ / | |..|``|@@|**|--|**|@@|``|..| | | |78|79|80|81|82|83|84|85|86| | \ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ | |~~|@@|--|xx|xx|--|@@|~~| | | OO i-th site | |67|68|69|70|71|72|73|74| | | xx 1st neighbours (nearest) /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ / -- 2nd neighbours (next nearest) | |;;|##|**|xx| |xx|**|##|;;| | ** 3rd neighbours | |56|57|58|59|60|61|62|63|64| | @@ 4th neighbours \ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ ## 5th neighnbours | |~~|@@|--|xx|xx|--|@@|~~| | | `` 6th neighbours | |45|46|47|48|49|50|51|52| | | ~~ 7th neighbours /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ / ;; 8th neighbours | |..|``|@@|**|--|**|@@|``|..| | | |34|35|36|37|38|39|40|41|42| | \ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ | |..|~~|##|@@|@@|##|~~|..| | | |22|23|24|25|26|27|28|29|30| | | /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ / | | | |;;|~~|``|~~|;;| | | | |11| |13|14|15|16|17|18| | | | \ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ /\ | | | | |..|..| | | | | | | 0| | 2| | 4| 5| | | | | | Hexagon size 1. \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ \/ Cells 9 x 6. """ mesh = HexagonalMesh(1, 11, 11, alignment='square', shells=9) print(mesh.neighbours) assert (mesh.neighbours[60] == [61, 59, 71, 48, 70, 49, # 1st shell 72, 47, 82, 38, 69, 50, # 2nd shell 62, 58, 83, 37, 81, 39, # 3rd shell 73, 46, 84, 36, 93, 26, # 4th shell 92, 27, 80, 40, 68, 51, 63, 57, 94, 25, 91, 28, # 5th shell 85, 35, 104, 16, 79, 41, # 6th shell 74, 45, 95, 24, 105, 15, # 7th shell 103, 17, 90, 29, 67, 52, 64, 56, 106, 14, 102, 18, # 8th shell 86, 34, 96, 23, 115, 4, # 9th shell 114, 5, 89, 30, 78, 42 ] ).all() # Second neighbours of the 0th position: assert (mesh.neighbours[0][6:12] == [13, -1, 22, -1, -1, -1] # 2nd shell ).all()
def test_save_scalar_field_hexagonal_mesh(tmpdir): mesh = HexagonalMesh(1, 3, 3) s = scalar_field(mesh, lambda r: r[0] + r[1]) vtk = VTK(mesh, directory=str(tmpdir), filename="scalar_hexagonal") vtk.save_scalar(s, name="s") assert same_as_ref(vtk.write_file(), REF_DIR)
nebm_data = {} # Return a simulation folder according to the specified method def methods(method, D): if method == 'boundary': return base_folder_ba(D) elif method == 'linear_interpolations': return base_folder(D) elif method == 'climbing': return base_folder_ci(D, climbing_image[D]) # Data ------------------------------------------------------------------------ mesh = HexagonalMesh(0.125, 320, 185, unit_length=1e-9, alignment='square') # Generate the linear interpolations of the NEBM band from every DMI value from # the arguments for D in args.D_list: sim = Sim(mesh) sim.set_mu_s(0.846 * const.mu_B) sim.add(DemagHexagonal()) sim.add(UniformExchange(J=27.026 * const.meV)) sim.add(DMI(DMIc[D] * const.meV, dmi_type='interfacial')) sim.add(Anisotropy(0.0676 * const.meV, axis=[0, 0, 1])) images = [ np.load(os.path.join(methods(args.method, D), _file)) for _file in sorted(os.listdir(methods(args.method, D)),
# Mesh and Simulation --------------------------------------------------------- # Check if multiple exchange constants were passed and set the number of # neighbour shells according to the number of constants if len(args.J) > 1: shells = len(args.J) else: shells = 1 if args.hexagonal_mesh: if not args.PBC_2D: mesh = HexagonalMesh(args.hexagonal_mesh[2] * 0.5, int(args.hexagonal_mesh[0]), int(args.hexagonal_mesh[1]), alignment=args.alignment, unit_length=args.unit_length, shells=shells ) else: mesh = HexagonalMesh(args.hex_a, args.hex_nx, args.hex_ny, periodicity=(True, True), alignment=args.alignment, unit_length=args.unit_length, shells=shells ) sim = Sim(mesh, name=args.sim_name, driver=args.driver) elif args.mesh_from_image: sim_from_image = sfi.sim_from_image(args.mesh_from_image[0], image_range=[float(args.mesh_from_image[1]),
def test_skx_num_atomistic_hexagonal(): """ Test the topological charge or skyrmion number for a discrete spins simulation in a two dimensional hexagonal lattice, using Berg and Luscher definition in [Nucl Phys B 190, 412 (1981)] and simplified in [PRB 93, 174403 (2016)], which maps a triangulated lattice (using triangles of neighbouring spins) area into a unit sphere. The areas of two triangles per lattice site cover a unit cell, thus the sum cover the whole area of the atomic lattice This test generates a skyrmion pointing down and two skyrmions pointing up in a PdFe sample using magnetic parameters from: PRL 114, 177203 (2015) """ mesh = HexagonalMesh(0.2715, 41, 41, periodicity=(True, True)) sim = Sim(mesh, name='skx_number_hexagonal') sim.driver.set_tols(rtol=1e-6, atol=1e-6) sim.driver.alpha = 1.0 sim.driver.gamma = 1.0 sim.mu_s = 3 * const.mu_B sim.set_m(lambda pos: init_m(pos, 16.1, 10, 2)) sim.driver.do_precession = False J = 5.881 * const.meV exch = UniformExchange(J) sim.add(exch) D = 1.557 * const.meV dmi = DMI(D, dmi_type='interfacial') sim.add(dmi) sim.add(Anisotropy(0.406 * const.meV, axis=[0, 0, 1])) zeeman = Zeeman([0, 0, 2.5]) sim.add(zeeman) sim.relax(dt=1e-13, stopping_dmdt=1e-2, max_steps=2000, save_m_steps=None, save_vtk_steps=100) skn_single = sim.skyrmion_number(method='BergLuscher') print('skx_number_hexagonal', skn_single) # Now we generate two skyrmions pointing up sim.driver.reset_integrator() sim.set_m( lambda pos: init_m_multiple_sks(pos, 1, sk_pos=[(9, 6), (18, 12)])) sim.get_interaction('Zeeman').update_field([0, 0, -2.5]) sim.relax(dt=1e-13, stopping_dmdt=1e-2, max_steps=2000, save_m_steps=None, save_vtk_steps=None) skn_two = sim.skyrmion_number(method='BergLuscher') print('skx_number_hexagonal_two', skn_two) # Check that we get a right sk number assert np.abs(skn_single - (-1)) < 1e-4 and np.sign(skn_single) < 0 assert np.abs(skn_two - (2)) < 1e-4 and np.sign(skn_two) > 0
from fidimag.common.nebm_spherical import NEBM_Spherical as NEBM_Spherical import os import re import glob mu0 = 4 * np.pi * 1e-7 # ----------------------------------------------------------------------------- # Mesh ------------------------------------------------------------------------ # ----------------------------------------------------------------------------- if not args.PBC_2D: mesh = HexagonalMesh(args.hex_a, args.hex_nx, args.hex_ny, alignment=args.alignment, unit_length=args.unit_length) else: mesh = HexagonalMesh(args.hex_a, args.hex_nx, args.hex_ny, periodicity=(True, True), alignment=args.alignment, unit_length=args.unit_length) # Initiate Fidimag simulation ------------------------------------------------- sim = Sim(mesh, name=args.sim_name) # sim.set_tols(rtol=1e-10, atol=1e-14) if args.alpha:
def __init__(self, image_path, image_range=[0, 21.03, 0, 17.79], mesh_a=0.2715, shells=1, # mesh_nx=79, # mesh_ny=78 sim_name='unnamed', bg_colour=(1., 1., 1.), driver='llg' ): """ Generate an object on top of the fidimag simulation class, to generate an atomistic simulation using a hexagonal mesh, where the magnetic moments are populated in the mesh according to the pixels in a PNG image, located in *image_path* The mesh size is generated automatically from the dimensions provided for the image (the *image_range* array), so that the mesh covers the whole picture. It is recommended that the image has white borders since mesh points away from the picture range will take the values from the closest boundary point (see the populate_mesh function) image_range :: An array [xmin, xmax, ymin, ymax] with the ranges of the image, given in nm """ # print 'Mesh spacings are: nx = {}, ny = {}'.format(mesh_a, # mesh_a * np.sqrt(3) * 0.5) self.image_path = image_path self.image_range = image_range mesh_nx = int(np.around(np.abs(image_range[1] - image_range[0]) / mesh_a)) mesh_ny = int(np.around( np.abs(image_range[3] - image_range[2]) / (mesh_a * np.sqrt(3) * 0.5) )) self.mesh = HexagonalMesh(mesh_a * 0.5, mesh_nx, mesh_ny, # periodicity=(True, True), alignment='square', unit_length=1e-9, shells=shells ) self.sim = Sim(self.mesh, name=sim_name, driver=driver) # Generate the 2D matrix with the image # If the image is B/W, every entry will have either [1., 1., 1.] # or [0., 0., 0.] representing the RGB data from every pixel self.image_data = mpl.image.imread(self.image_path) # We will create two new matrices, with the X and Y coordinates # of every point in the image matrix, using the range # provided in the arguments. For doing this, we use a meshgrid, which # automatically generates a 2D mesh of coordinates. The range # in the Y coordinates matrix is reversed since the image matrix # starts from top to bottom to generate the picture. # The shape is [n_rows, n_columns] --> (ny, nx) self.image_coords_x = np.linspace(self.image_range[0], self.image_range[1], self.image_data.shape[1]) self.image_coords_y = np.linspace(self.image_range[3], self.image_range[2], self.image_data.shape[0]) (self.image_coords_x, self.image_coords_y) = np.meshgrid(self.image_coords_x, self.image_coords_y) self.bg_colour = bg_colour
def hysteresis_loop(config_file, D=1.56, J=5.88, k_u=0.41, mu_s=3, B=(0, 0, 0), Demag=None, ): """ The config file must have the following parameters: D J k_u mu_s :: Magnitude in Bohr magneton units. A file path can be specified to load a NPY file with the mu_s values, when using the mesh_from_image option Demag :: Set to True for Demag sim_name :: Simulation name initial_state :: A function or a npy file hysteresis_steps :: mesh_from_image :: [IMAGE_PATH, xmin, xmax, ymin, ymax] hexagonal_mesh :: [nx, ny, a] PBC_2D :: Set to True for Periodic boundaries pin_boundaries :: Set to True to pin the spins at the boundaries. Their orientations are already given from the initial_state NPY file llg_dt :: llg_stopping_dmdt :: llg_max_steps :: llg_do_precession :: False as default llg_alpha :: 0.01 as default """ # Parameters -------------------------------------------------------------- cf = {} # execfile(config_file, cf) # Python3: exec(open(config_file).read(), cf) D = cf["D"] * const.meV J = cf["J"] * const.meV k_u = cf["k_u"] * const.meV if isinstance(cf["mu_s"], int) or isinstance(cf["mu_s"], float): mu_s = cf["mu_s"] * const.mu_B if isinstance(cf["initial_state"], str): init_state = np.load(cf["initial_state"]) elif isinstance(cf["initial_state"], types.FunctionType): init_state = cf["initial_state"] # Set up default arguments default_args = {"mesh_alignment": 'diagonal', "mesh_unit_length": 1e-9, "llg_dt": 1e-11, "llg_stopping_dmdt": 1e-2, "llg_max_steps": 4000, "llg_do_precession": False, "llg_alpha": 0.01 } for key in default_args.keys(): if not cf.get(key): print(default_args[key]) cf[key] = default_args[key] # Simulation object ------------------------------------------------------- if cf.get("hexagonal_mesh"): if not cf["PBC_2D"]: mesh = HexagonalMesh(cf["hexagonal_mesh"][2] * 0.5, int(cf["hexagonal_mesh"][0]), int(cf["hexagonal_mesh"][1]), alignment=cf["mesh_alignment"], unit_length=cf["mesh_unit_length"] ) else: mesh = HexagonalMesh(cf["hexagonal_mesh"][2] * 0.5, int(cf["hexagonal_mesh"][0]), int(cf["hexagonal_mesh"][1]), periodicity=(True, True), alignment=cf["mesh_alignment"], unit_length=cf["mesh_unit_length"] ) sim = Sim(mesh, name=cf["sim_name"]) elif cf.get("mesh_from_image"): sim_from_image = sfi.sim_from_image( cf["mesh_from_image"][0], image_range=[float(cf["mesh_from_image"][1]), float(cf["mesh_from_image"][2]), float(cf["mesh_from_image"][3]), float(cf["mesh_from_image"][4]) ], sim_name=cf["sim_name"] ) if isinstance(cf["mu_s"], str): sim_from_image.generate_magnetic_moments(load_file=cf["mu_s"]) else: sim_from_image.generate_magnetic_moments(mu_s=(mu_s)) sim = sim_from_image.sim elif cf.get("truncated_triangle"): if len(cf["truncated_triangle"]) == 3: sim_triangle = TruncatedTriangleSim( cf["truncated_triangle"][0], # L cf["truncated_triangle"][1], # offset cf["truncated_triangle"][2], # a cf["mu_s"], # mu_s name=cf["sim_name"] ) elif len(cf["truncated_triangle"]) == 5: sim_triangle = TruncatedTriangleSim( cf["truncated_triangle"][0], # L [float(offs) for offs in cf["truncated_triangle"][1:4]], # offsets cf["truncated_triangle"][4], # a cf["mu_s"], # mu_s name=cf["sim_name"] ) sim = sim_triangle.sim elif cf.get("hexagon"): sim_hexagon = HexagonSim(cf["hexagon"][0], # R cf["hexagon"][1], # a cf["mu_s"], # mu_s name=cf["sim_name"] ) sim = sim_hexagon.sim # Initial state sim.set_m(init_state) sim.driver.do_precession = cf["llg_do_precession"] sim.driver.alpha = cf["llg_alpha"] # Material parameters ----------------------------------------------------- if cf.get("hexagonal_mesh"): sim.mu_s = mu_s exch = UniformExchange(J) sim.add(exch) dmi = DMI(D=(D), dmi_type='interfacial') sim.add(dmi) zeeman = Zeeman((0, 0, 0)) sim.add(zeeman, save_field=True) if k_u: # Uniaxial anisotropy along + z-axis sim.add(Anisotropy(k_u, axis=[0, 0, 1])) if cf.get("Demag"): print('Using Demag!') sim.add(DemagHexagonal()) # Pin boundaries ---------------------------------------------------------- # We will correct the spin directions according to the specified argument, # in case the spins at the boundaries are pinned if cf.get('pin_boundaries'): ngbs_filter = np.zeros(sim.pins.shape[0]) # Filter rows by checking if any of the elements is less than zero # This means that if any of the neighbours of the i-th lattice site is # -1, we pin the spin direction at that site ngbs_filter = np.any(sim.mesh.neighbours < 0, axis=1, out=ngbs_filter) sim.set_pins(ngbs_filter) # Hysteresis -------------------------------------------------------------- for ext in ['npys', 'vtks']: if not os.path.exists('{}/{}'.format(ext, cf["sim_name"])): os.makedirs('{}/{}'.format(ext, cf["sim_name"])) for ext in ['txts', 'dats']: if not os.path.exists('{}/'.format(ext)): os.makedirs('{}'.format(ext)) Brange = cf["hysteresis_steps"] print('Computing for Fields:', Brange) # We will save the hysteresis steps on this file with every row as: # step_number field_in_Tesla hystfile = '{}_hyst_steps.dat'.format(cf["sim_name"]) # If the file already exists, we will append the new steps, otherwise # we just create a new file (useful for restarting simulations) if not os.path.exists(hystfile): nsteps = 0 fstate = 'w' else: # Move old txt file from the previous simulation, appending an _r # everytime a simulation with the same name is started txtfile = [f for f in os.listdir('.') if f.endswith('txt')][0] txtfile = re.search(r'.*(?=\.txt)', txtfile).group(0) shutil.move(txtfile + '.txt', txtfile + '_r.txt') nsteps = len(np.loadtxt(hystfile)) fstate = 'a' f = open(hystfile, fstate) for i, B in enumerate(Brange): sim.get_interaction('Zeeman').update_field(B) sim.driver.relax(dt=cf["llg_dt"], stopping_dmdt=cf["llg_stopping_dmdt"], max_steps=cf["llg_max_steps"], save_m_steps=None, save_vtk_steps=None ) print('Saving NPY for B = {}'.format(B)) np.save('npys/{0}/step_{1}.npy'.format(cf["sim_name"], i + nsteps), sim.spin) sim.driver.save_vtk() shutil.move('{}_vtks/m_{}.vtk'.format(cf["sim_name"], str(sim.driver.step).zfill(6) ), 'vtks/{0}/step_{1}.vtk'.format(cf["sim_name"], i + nsteps) ) f.write('{} {} {} {}\n'.format(i + nsteps, B[0], B[1], B[2], ) ) f.flush() sim.driver.reset_integrator() os.rmdir('{}_vtks'.format(cf["sim_name"])) shutil.move('{}.txt'.format(cf["sim_name"]), 'txts/') shutil.move(hystfile, 'dats/') f.close()