def create_tablewriter(self): entities = { 'step': {'unit': '<>', 'get': lambda sim: sim.step, 'header': 'step'}, 'm': {'unit': '<>', 'get': lambda sim: sim.compute_average(), 'header': ('m_x', 'm_y', 'm_z')}, 'skx_num':{'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} } self.saver = DataSaver(self, self.name + '.txt', entities=entities) self.saver.update_entity_order()
def __init__(self, mesh, name="unnamed", use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + ".txt") self.saver.entities["E_total"] = {"unit": "<J>", "get": lambda sim: sim.compute_energy(), "header": "E_total"} self.saver.entities["m_error"] = { "unit": "<>", "get": lambda sim: sim.compute_spin_error(), "header": "m_error", } self.saver.entities["skx_num"] = {"unit": "<>", "get": lambda sim: sim.skyrmion_number(), "header": "skx_num"} self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols()
def __init__(self, mesh, name): self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._magnetisation = np.zeros(self.n, dtype=np.float) # Inverse magnetisation self._magnetisation_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] # This is for old C files codes using the xperiodic variables try: self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity except ValueError: self.xperiodic, self.yperiodic = mesh.periodicity # To save the simulation data: ---------------------------------------- self.data_saver = DataSaver(self, name + '.txt') self.data_saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: self.compute_energy(), 'header': 'E_total' } self.data_saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: self.compute_spin_error(), 'header': 'm_error' } self.data_saver.update_entity_order()
def __init__(self, mesh, name): self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._magnetisation = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] # This is for old C files codes using the xperiodic variables try: self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity except ValueError: self.xperiodic, self.yperiodic = mesh.periodicity # To save the simulation data: ---------------------------------------- self.data_saver = DataSaver(self, name + '.txt') self.data_saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: self.compute_energy(), 'header': 'E_total'} self.data_saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: self.compute_spin_error(), 'header': 'm_error'} self.data_saver.update_entity_order()
class LLG(object): def __init__(self, mesh, name="unnamed", use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + ".txt") self.saver.entities["E_total"] = {"unit": "<J>", "get": lambda sim: sim.compute_energy(), "header": "E_total"} self.saver.entities["m_error"] = { "unit": "<>", "get": lambda sim: sim.compute_spin_error(), "header": "m_error", } self.saver.entities["skx_num"] = {"unit": "<>", "get": lambda sim: sim.skyrmion_number(), "header": "skx_num"} self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols() def set_default_options(self, gamma=1, mu_s=1, alpha=0.1): self.default_c = -1 self._alpha[:] = alpha self._mu_s[:] = mu_s self.gamma = gamma self.do_procession = True def set_tols(self, rtol=1e-8, atol=1e-10): self.vode.set_options(rtol, atol) def set_options(self, rtol=1e-8, atol=1e-10): self.set_tols(rtol, atol) def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._mu_s[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) self.vode.set_initial_value(self.spin, self.t) def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_mu_s(self): return self._mu_s def set_mu_s(self, value): self._mu_s[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._mu_s[i] > 0.0: self._mu_s_inv[i] = 1.0 / self._mu_s[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 mu_s = property(get_mu_s, set_mu_s) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, self._mu_s) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + "_2" self.interactions.append(interaction) energy_name = "E_{0}".format(interaction.name) self.saver.entities[energy_name] = { "unit": "<J>", "get": lambda sim: sim.get_interaction(interaction.name).compute_energy(), "header": energy_name, } if save_field: fn = "{0}".format(interaction.name) self.saver.entities[fn] = { "unit": "<>", "get": lambda sim: sim.get_interaction(interaction.name).average_field(), "header": ("%s_x" % fn, "%s_y" % fn, "%s_z" % fn), } self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError( "Failed to find the interaction with name '{0}', " "available interactions: {1}.".format(name, [x.name for x in self.interactions]) ) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return ode = self.vode self.spin_last[:] = self.spin[:] flag = ode.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = ode.y[:] self.t = t self.step += 1 # update field before saving data self.compute_effective_field(t) self.saver.save() def compute_effective_field(self, t): # self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def compute_effective_field_jac(self, t, spin): # self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: if obj.jac is True: self.field += obj.compute_field(t, spin=spin) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs( ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c ) # ydot[:] = self.dm_dt[:] return 0 def sundials_jtn(self, mp, Jmp, t, m, fy): # we can not copy mp to self.spin since m and self.spin is one object. # self.spin[:] = mp[:] print "NO jac..........." self.compute_effective_field_jac(t, mp) clib.compute_llg_jtimes( Jmp, m, fy, mp, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c ) return 0 def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (-1,) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skymrion_number(self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): """ Returns the spin components of the spin at i-th position in the z direction, j-th position in the y direction and k-th position in x direction """ nxyz = self.mesh.n index = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[index], self.spin[index + 1], self.spin[index + 2]]) def add_monitor_at(self, i, j, k, name="p"): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { "unit": "<>", "get": lambda sim: sim.spin_at(i, j, k), "header": (name + "_x", name + "_y", name + "_z"), } self.saver.update_entity_order() def save_vtk(self): self.vtk.save_vtk(self.spin.reshape(-1, 3), self._mu_s, step=self.step) def save_m(self): if not os.path.exists("%s_npys" % self.name): os.makedirs("%s_npys" % self.name) name = "%s_npys/m_%g.npy" % (self.name, self.step) np.save(name, self.spin) def save_skx(self, vtk=False): if not os.path.exists("%s_skx_npys" % self.name): os.makedirs("%s_skx_npys" % self.name) name = "%s_skx_npys/m_%g.npy" % (self.name, self.step) np.save(name, self._skx_number) if vtk is True: self.vtk.save_vtk_scalar(self._skx_number, self.step) def stat(self): return self.vode.stat() def spin_length(self): self.spin.shape = (-1, 3) length = np.sqrt(np.sum(self.spin ** 2, axis=1)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((-1, 3)) max_dm = np.max(np.sqrt(np.sum(dm ** 2, axis=1))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): for i in range(0, max_steps + 1): cvode_dt = self.vode.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print "step=%d, time=%g, max_dmdt=%g ode_step=%g" % (self.step, self.t, dmdt, cvode_dt) if dmdt < stopping_dmdt: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk()
class MonteCarlo(object): def __init__(self, mesh, name='unnamed'): self.mesh = mesh self.name = name self.n = mesh.n self.n_nonzero = self.n self.ngbs = mesh.neighbours self._mu_s = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self.random_spin = np.zeros(3 * self.n, dtype=np.float) self._H = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.create_tablewriter() self.vtk = SaveVTK(self.mesh, name=name) self.hexagonal_mesh = False if mesh.mesh_type == 'hexagonal': self.hexagonal_mesh = True #FIX ME !!!! self.nngbs = np.copy(mesh.neighbours) else: self.nngbs = mesh.next_neighbours self.step = 0 self.skx_num = 0 self.mc = clib.monte_carlo() self.set_options() def set_options(self, J=50.0 * const.k_B, J1=0, D=0, D1=0, Kc=0, H=None, seed=100, T=10.0, S=1): """ J, D and Kc in units of Joule H in units of Tesla. S is the spin length """ self.mc.set_seed(seed) self.J = J / const.k_B self.J1 = J1 / const.k_B self.D = D / const.k_B self.D1 = D1 / const.k_B self.T = T self.Kc = Kc / const.k_B self.mu_s = 1.0 if H is not None: self._H[:] = helper.init_vector(H, self.mesh) self._H[:] = self._H[:] * const.mu_s_1 * S / const.k_B def create_tablewriter(self): entities = { 'step': { 'unit': '<>', 'get': lambda sim: sim.step, 'header': 'step' }, 'm': { 'unit': '<>', 'get': lambda sim: sim.compute_average(), 'header': ('m_x', 'm_y', 'm_z') }, 'skx_num': { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num' } } self.saver = DataSaver(self, self.name + '.txt', entities=entities) self.saver.update_entity_order() def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._mu_s[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1, ) def get_mu_s(self): return self._mu_s def set_mu_s(self, value): self._mu_s[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._mu_s[i] > 0.0: nonzero += 1 self.n_nonzero = nonzero mu_s = property(get_mu_s, set_mu_s) def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (-1, ) return average def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skyrmion_number(self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours, self.mesh.n_ngbs) self.skx_num = number return number def save_vtk(self): """ Save a VTK file with the magnetisation vector field and magnetic moments as cell data. Magnetic moments are saved in units of Bohr magnetons NOTE: It is recommended to use a *cell to point data* filter in Paraview or Mayavi to plot the vector field """ self.vtk.save_vtk(self.spin.reshape(-1, 3), self._mu_s, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def run(self, steps=1000, save_m_steps=100, save_vtk_steps=100, save_data_steps=1): if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk() for step in range(1, steps + 1): self.step = step self.mc.run_step(self.spin, self.random_spin, self.ngbs, self.nngbs, self.mesh.n_ngbs, self.J, self.J1, self.D, self.D1, self._H, self.Kc, self.n, self.T, self.hexagonal_mesh) if save_data_steps is not None: if step % save_data_steps == 0: self.saver.save() print("step=%d, skyrmion number=%0.9g" % (self.step, self.skx_num)) if save_vtk_steps is not None: if step % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if step % save_m_steps == 0: self.save_m()
def __init__(self, mesh, name='unnamed', use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total' } self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error' } self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num' } self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[ 1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols() # When initialising the integrator in the self.vode call, the CVOde # class calls the set_initial_value function (with flag_m=0), which # initialises a new integrator and allocates memory in this process. # Now, when we set the magnetisation, we will use the same memory # setting this flag_m to 1, so instead of calling CVodeInit we call # CVodeReInit. If don't, memory is allocated in every call of set_m self.flag_m = 1
class LLG(object): def __init__(self, mesh, name='unnamed', use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total' } self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error' } self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num' } self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[ 1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols() # When initialising the integrator in the self.vode call, the CVOde # class calls the set_initial_value function (with flag_m=0), which # initialises a new integrator and allocates memory in this process. # Now, when we set the magnetisation, we will use the same memory # setting this flag_m to 1, so instead of calling CVodeInit we call # CVodeReInit. If don't, memory is allocated in every call of set_m self.flag_m = 1 def set_default_options(self, gamma=1, mu_s=1, alpha=0.1): self.default_c = -1 self._alpha[:] = alpha self._mu_s[:] = mu_s self.gamma = gamma self.do_precession = True def reset_integrator(self, t=0): self.vode.reset(self.spin, t) self.t = t # also reinitialise the simulation time and step self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10): self.vode.set_options(rtol, atol) def set_options(self, rtol=1e-8, atol=1e-10): self.set_tols(rtol, atol) def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._mu_s[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1, ) self.vode.set_initial_value(self.spin, self.t) if not self.flag_m: self.flag_m = 1 def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_mu_s(self): return self._mu_s def set_mu_s(self, value): self._mu_s[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._mu_s[i] > 0.0: self._mu_s_inv[i] = 1.0 / self._mu_s[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 mu_s = property(get_mu_s, set_mu_s) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, self._mu_s) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name } if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name). average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn) } self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return ode = self.vode self.spin_last[:] = self.spin[:] flag = ode.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = ode.y[:] self.t = t self.step += 1 # update field before saving data self.compute_effective_field(t) self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def compute_effective_field_jac(self, t, spin): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: if obj.jac is True: self.field += obj.compute_field(t, spin=spin) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def sundials_jtn(self, mp, Jmp, t, m, fy): # we can not copy mp to self.spin since m and self.spin is one object. #self.spin[:] = mp[:] print('NO jac...........') self.compute_effective_field_jac(t, mp) clib.compute_llg_jtimes(Jmp, m, fy, mp, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) return 0 def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (-1, ) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skyrmion_number(self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): """ Returns the spin components of the spin at i-th position in the z direction, j-th position in the y direction and k-th position in x direction """ nxyz = self.mesh.n index = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array( [self.spin[index], self.spin[index + 1], self.spin[index + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z') } self.saver.update_entity_order() def save_vtk(self): """ Save a VTK file with the magnetisation vector field and magnetic moments as cell data. Magnetic moments are saved in units of Bohr magnetons NOTE: It is recommended to use a *cell to point data* filter in Paraview or Mayavi to plot the vector field """ self.vtk.save_vtk(self.spin.reshape(-1, 3), self._mu_s / const.mu_B, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self, vtk=False): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) if vtk is True: self.vtk.save_vtk_scalar(self._skx_number, self.step) def stat(self): return self.vode.stat() def spin_length(self): self.spin.shape = (-1, 3) length = np.sqrt(np.sum(self.spin**2, axis=1)) self.spin.shape = (-1, ) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((-1, 3)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=1))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): for i in range(0, max_steps + 1): cvode_dt = self.vode.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print('step=%d, time=%0.3g, max_dmdt=%0.3g ode_step=%0.3g' % (self.step, self.t, dmdt, cvode_dt)) if dmdt < stopping_dmdt: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk()
class SimBase(object): """ A class with common methods and definitions for both micromagnetic and atomistic simulations """ def __init__(self, mesh, name): self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._magnetisation = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] # This is for old C files codes using the xperiodic variables try: self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity except ValueError: self.xperiodic, self.yperiodic = mesh.periodicity # To save the simulation data: ---------------------------------------- self.data_saver = DataSaver(self, name + '.txt') self.data_saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: self.compute_energy(), 'header': 'E_total'} self.data_saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: self.compute_spin_error(), 'header': 'm_error'} self.data_saver.update_entity_order() # --------------------------------------------------------------------- def set_m(self, m0=(1, 0, 0), normalise=True): """ Set the magnetisation/spin three dimensional vector field profile. ARGUMENTS: m0 :: * To set every spin with the same direction, set this value as a 3 elements tuple or list. * For a spatially dependent vector field, you can specify a function that returns a 3 element list depending on the spatial coordinates. For example, a magnetisation field that depends on the x position: def m_profile(r): for r[0] > 2: return (0, 0, 1) else: return (0, 0, -1) * You can also manually specify an array with (3 * n) elements with the spins directions in the following order: [mx_0 my_0 mz_0 mx_1 my_1 ... mx_n, my_n, mz_n] where n is the number of mesh nodes and the order of the magnetisation vectors follow the same order than the mesh coordinates array. * Alternatively, if you previously saved the magnetisation field array to a numpy file, you can load it using numpy.load(my_array) """ self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first # Set the magnetisation/spin direction to (0, 0, 0) for sites # with no material, i.e. M_s = 0 or mu_s = 0 # TODO: Check for atomistic and micromagnetic cases self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._magnetisation[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) # Set the initial state for the Sundials integrator using the # spins array self.driver.integrator.set_initial_value(self.spin, self.driver.t) def get_pins(self): """ Returns the array with pinned spins in the sample: sites with 0 are unpinned and sites with 1 are pinned. The order of the array follows the same order than the mesh coordinates """ return self._pins def set_pins(self, pin): """ An scalar field with values 1 or 0 to specify mesh/lattice sites with pinned or unpinned magnetic moments, respectively ARGUMENTS: pin :: * You can specify a function that returns 1 or 0 depending on the spatial coordinates. For example, to pin the spins in a range in the x direction: def pin_profile(r): for r[0] > 2 and r[0] < 4: return 1 else: return 0 * You can also manually specify an array with n elements (1 or 0) with the pinned/unpinned values in the same order than the mesh coordinates array. * Alternatively, if you previously saved the pin field array to a numpy file, you can load it using numpy.load(my_array) """ self._pins[:] = helper.init_scalar(pin, self.mesh) # Sites with no material, i.e. Mu_s or mu_s equal to zero, # will be pinned for i in range(len(self._magnetisation)): if self._magnetisation[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def add(self, interaction, save_field=False): """ Add an interaction from one of the Energy subclasses. By default, the average energy of the added interaction is saved to the data file when relaxing the system OPTIONAL ARGUMENTS: save_field :: Set True to save the average values of this interaction field when relaxing the system """ # magnetisation is Ms for the micromagnetic Sim class, and it is # mu_s for the atomistic Sim class interaction.setup(self.mesh, self.spin, self._magnetisation ) # TODO: FIX --> ?? # When adding an interaction that was previously added, using # the same name, append a '_2' to the new interaction name (?) for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) # Specify a name for the energy of the interaction, which will # appear in a file with saved values # When saving the energy values, we call the compute_energy() method # from the (micromagnetic/atomistic) Energy class (overhead?) energy_name = 'E_{0}'.format(interaction.name) self.data_saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} # Save the average values of the interaction vector field components if save_field: fn = '{0}'.format(interaction.name) self.data_saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.data_saver.update_entity_order() def get_interaction(self, name): """ Returns an instance of a magnetic interaction previously added to the simulation, using the corresponding interaction name as a string """ for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def skyrmion_number(self): pass def spin_at(self, i, j, k): """ Returns the x,y,z components of a spin in the [i, j, k] position of the mesh, where i,j,k are integer indexes. The index ordering is specified in the mesh class. """ i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.data_saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.data_saver.update_entity_order() def spin_length(self): """ Returns an array with the length of every spin in the mesh. The order is given by the mesh.coordinates order """ self.spin.shape = (-1, 3) length = np.sqrt(np.sum(self.spin ** 2, axis=1)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_average(self): """ Compute the average values of the 3 components of the magnetisation vector field """ self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): """ Compute the total energy of the magnetic system """ energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def get_field_array(self, interaction): """ Returns the field array corresponding to the interaction given: e.g. compute_interaction_field('Demag') returns a numpy array containing the Demag field. """ field = self.get_interaction(interaction) # Copy here to avoid destroying the field accidentally # e.g. through reshaping f = field.field.copy() return f
class LLG(object): def __init__(self, mesh, name='unnamed', integrator='sundials'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 if integrator == "sundials": self.integrator = SundialsIntegrator(self.spin, self.sundials_rhs) elif integrator == "euler" or integrator == "rk4": self.integrator = StepIntegrator(self.spin, self.step_rhs, integrator) else: raise NotImplemented("integrator must be sundials, euler or rk4") self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.integrator.rhs_evals, 'header': 'rhs_evals'} self.saver.entities['real_time'] = { 'unit': '<s>', 'get': lambda _: time.time(), # seconds since epoch 'header': 'real_time'} self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.set_default_options() def set_default_options(self, gamma=2.21e5, Ms=8.0e5, alpha=0.1): self.default_c = 1e11 self._alpha[:] = alpha self._Ms[:] = Ms self.gamma = gamma self.do_procession = True def reset_integrator(self, t=0): self.integrator.reset(self.spin, t) self.t = t # also reinitialise the simulation time and step self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10, max_ord=None, reset=True): if max_ord is not None: self.integrator.set_tols(rtol=rtol, atol=atol, max_ord=max_ord) else: # not all integrators have max_ord (only VODE does) # and we don't want to encode a default value here either self.integrator.set_tols(rtol=rtol, atol=atol) if reset: self.reset_integrator(self.t) def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._Ms[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) self.integrator.set_initial_value(self.spin, self.t) def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_Ms(self): return self._Ms def set_Ms(self, value): self._Ms[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._Ms[i] > 0.0: self._Ms_inv = 1.0 / self._Ms[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 self.Ms_const = np.max(self._Ms) Ms = property(get_Ms, set_Ms) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, Ms=self._Ms) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return self.spin_last[:] = self.spin[:] flag = self.integrator.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = self.integrator.y[:] self.t = t self.step += 1 self.compute_effective_field(t) # update fields before saving data self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def step_rhs(self, t, y): self.t = t self.compute_effective_field(t) clib.compute_llg_rhs(self.dm_dt, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c) return self.dm_dt def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skymrion_number( self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.saver.update_entity_order() def save_vtk(self): self.vtk.save_vtk(self.spin.reshape(-1, 3), self.Ms, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) def stat(self): return self.integrator.stat() def spin_length(self): self.spin.shape = (3, -1) length = np.sqrt(np.sum(self.spin**2, axis=0)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((3, -1)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=0))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): ONE_DEGREE_PER_NS = 17453292.52 for i in range(0, max_steps + 1): cvode_dt = self.integrator.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print 'step=%d, time=%g, max_dmdt=%g ode_step=%g' % (self.step, self.t, dmdt / ONE_DEGREE_PER_NS, cvode_dt) if dmdt < stopping_dmdt * ONE_DEGREE_PER_NS: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk()
class LLG(object): def __init__(self, mesh, name='unnamed', integrator='sundials'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 if integrator == "sundials": self.integrator = SundialsIntegrator(self.spin, self.sundials_rhs) elif integrator == "euler" or integrator == "rk4": self.integrator = StepIntegrator(self.spin, self.step_rhs, integrator) else: raise NotImplemented("integrator must be sundials, euler or rk4") self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.integrator.rhs_evals, 'header': 'rhs_evals'} self.saver.entities['real_time'] = { 'unit': '<s>', 'get': lambda _: time.time(), # seconds since epoch 'header': 'real_time'} self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.set_default_options() def set_default_options(self, gamma=2.21e5, Ms=8.0e5, alpha=0.1): self.default_c = 1e11 self._alpha[:] = alpha self._Ms[:] = Ms self.gamma = gamma self.do_precession = True def reset_integrator(self, t=0): self.integrator.reset(self.spin, t) self.t = t # also reinitialise the simulation time and step self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10, max_ord=None, reset=True): if max_ord is not None: self.integrator.set_tols(rtol=rtol, atol=atol, max_ord=max_ord) else: # not all integrators have max_ord (only VODE does) # and we don't want to encode a default value here either self.integrator.set_tols(rtol=rtol, atol=atol) if reset: self.reset_integrator(self.t) def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._Ms[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) self.integrator.set_initial_value(self.spin, self.t) def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_Ms(self): return self._Ms def set_Ms(self, value): self._Ms[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._Ms[i] > 0.0: self._Ms_inv = 1.0 / self._Ms[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 self.Ms_const = np.max(self._Ms) Ms = property(get_Ms, set_Ms) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, Ms=self._Ms) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return self.spin_last[:] = self.spin[:] flag = self.integrator.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = self.integrator.y[:] self.t = t self.step += 1 self.compute_effective_field(t) # update fields before saving data self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def step_rhs(self, t, y): self.t = t self.compute_effective_field(t) clib.compute_llg_rhs(self.dm_dt, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) return self.dm_dt def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skymrion_number( self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.saver.update_entity_order() def save_vtk(self): self.vtk.save_vtk(self.spin.reshape(-1, 3), self.Ms, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) def stat(self): return self.integrator.stat() def spin_length(self): self.spin.shape = (3, -1) length = np.sqrt(np.sum(self.spin**2, axis=0)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((3, -1)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=0))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): ONE_DEGREE_PER_NS = 17453292.52 for i in range(0, max_steps + 1): cvode_dt = self.integrator.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print 'step=%d, time=%g, max_dmdt=%g ode_step=%g' % (self.step, self.t, dmdt / ONE_DEGREE_PER_NS, cvode_dt) if dmdt < stopping_dmdt * ONE_DEGREE_PER_NS: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk()
class MonteCarlo(object): def __init__(self, mesh, name='unnamed'): self.mesh = mesh self.name = name self.n = mesh.n self.n_nonzero = self.n self.ngbs = mesh.neighbours self._mu_s = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self.random_spin = np.zeros(3 * self.n, dtype=np.float) self._H = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.create_tablewriter() self.vtk = SaveVTK(self.mesh, name=name) self.hexagnoal_mesh = False if mesh.mesh_type == 'hexagonal': self.hexagnoal_mesh = True #FIX ME !!!! self.nngbs = np.copy(mesh.neighbours) else: self.nngbs = mesh.next_neighbours self.step = 0 self.skx_num = 0 self.mc = clib.monte_carlo() self.set_options() def set_options(self, J=50.0, J1=0, D=0, D1=0, Kc=0, H=None, seed=100, T=10.0, S=1): """ J, D and Kc in units of k_B H in units of Tesla. S is the spin length """ self.mc.set_seed(seed) self.J = J self.J1 = J1 self.D = D self.D1 = D1 self.T = T self.Kc = Kc self.mu_s = 1.0 if H is not None: self._H[:] = helper.init_vector(H, self.mesh) self._H[:] = self._H[:]*const.mu_s_1*S/const.k_B #We have set k_B = 1 def create_tablewriter(self): entities = { 'step': {'unit': '<>', 'get': lambda sim: sim.step, 'header': 'step'}, 'm': {'unit': '<>', 'get': lambda sim: sim.compute_average(), 'header': ('m_x', 'm_y', 'm_z')}, 'skx_num':{'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} } self.saver = DataSaver(self, self.name + '.txt', entities=entities) self.saver.update_entity_order() def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._mu_s[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) def get_mu_s(self): return self._mu_s def set_mu_s(self, value): self._mu_s[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._mu_s[i] > 0.0: nonzero += 1 self.n_nonzero = nonzero mu_s = property(get_mu_s, set_mu_s) def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (-1,) return average def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skyrmion_number( self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) self.skx_num = number return number def save_vtk(self): """ Save a VTK file with the magnetisation vector field and magnetic moments as cell data. Magnetic moments are saved in units of Bohr magnetons NOTE: It is recommended to use a *cell to point data* filter in Paraview or Mayavi to plot the vector field """ self.vtk.save_vtk(self.spin.reshape(-1, 3), self._mu_s, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def run(self, steps=1000, save_m_steps=100, save_vtk_steps=100, save_data_steps=1): if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk() for step in range(1, steps + 1): self.step = step self.mc.run_step(self.spin, self.random_spin, self.ngbs, self.nngbs, self.J, self.J1, self.D, self.D1, self._H, self.Kc, self.n, self.T, self.hexagnoal_mesh) if save_data_steps is not None: if step % save_data_steps == 0: self.saver.save() print("step=%d, skyrmion number=%0.9g"%(self.step, self.skx_num)) if save_vtk_steps is not None: if step % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if step % save_m_steps == 0: self.save_m()
class LLG(object): def __init__(self, mesh, name='unnamed'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total' } self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error' } self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num' } self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.cvode_stat_output(sim), 'header': 'rhs_evals' } self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) self.set_default_options() self.set_tols() def set_default_options(self, gamma=2.21e5, Ms=8.0e5, alpha=0.1): self.default_c = 1e11 self._alpha[:] = alpha self._Ms[:] = Ms self.gamma = gamma self.do_procession = True def reset_integrator(self, t=0): self.vode.reset(self.spin, t) # Also reinitialise the simulation time and step self.t = t self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10): if self.integrator_tolerances_set is True: self.reset_integrator(self.t) self.vode.set_options(rtol, atol) self.integrator_tolerances_set = True def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._Ms[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1, ) self.vode.set_initial_value(self.spin, self.t) def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_Ms(self): return self._Ms def set_Ms(self, value): self._Ms[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._Ms[i] > 0.0: self._Ms_inv = 1.0 / self._Ms[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 self.Ms_const = np.max(self._Ms) Ms = property(get_Ms, set_Ms) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, Ms=self._Ms) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name } if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name). average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn) } self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return ode = self.vode self.spin_last[:] = self.spin[:] flag = ode.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = ode.y[:] self.t = t self.step += 1 # update field before saving data self.compute_effective_field(t) self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skymrion_number(self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z') } self.saver.update_entity_order() def save_vtk(self): self.vtk.save_vtk(self.spin.reshape(-1, 3), self.Ms, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) def stat(self): return self.vode.stat() def spin_length(self): self.spin.shape = (3, -1) length = np.sqrt(np.sum(self.spin**2, axis=0)) self.spin.shape = (-1, ) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((3, -1)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=0))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): ONE_DEGREE_PER_NS = 17453292.52 for i in range(0, max_steps + 1): cvode_dt = self.vode.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print 'step=%d, time=%g, max_dmdt=%g ode_step=%g' % ( self.step, self.t, dmdt / ONE_DEGREE_PER_NS, cvode_dt) if dmdt < stopping_dmdt * ONE_DEGREE_PER_NS: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk() def cvode_stat_output(self, sim): """ This function tries to get the values from the CVODE statistics. For a 'sim' simulation object, this is done starting from calling sim.vode.stats() According to the CVODE version, this call can generate a string: CvodeSolver(nsteps = 18, nfevals = 32, njevals = 14. ) where: nsteps --> number of steps taken by CVODE nfevals --> number of calls to the user's f function (I guess this is what we need) njevals --> the cumulative number of calls to the Jacobian function So, for example, we can regex search any number preceded by "nfevals = " to get the number of evaluations of the RHS and convert the result to an integer OR it can give a tuple with 3 values, which must be in the same order than before For now, we are only interested in the RHS evaluations, so we return a single value """ cvode_stat = sim.vode.stat() if isinstance(cvode_stat, str): out = int( re.search(r'(?<=nfevals\s=\s)[0-9]*', cvode_stat).group(0)) elif isinstance(cvode_stat, tuple): out = cvode_stat[1] else: raise NotImplementedError('Cannot retrieve the values' 'from CVODE stats') return out
def __init__(self, mesh, name='unnamed'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.cvode_stat_output(sim), 'header': 'rhs_evals'} self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) self.set_default_options() self.set_tols()
class LLG(object): def __init__(self, mesh, name='unnamed'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.cvode_stat_output(sim), 'header': 'rhs_evals'} self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) self.set_default_options() self.set_tols() def set_default_options(self, gamma=2.21e5, Ms=8.0e5, alpha=0.1): self.default_c = 1e11 self._alpha[:] = alpha self._Ms[:] = Ms self.gamma = gamma self.do_procession = True def reset_integrator(self, t=0): self.vode.reset(self.spin, t) # Also reinitialise the simulation time and step self.t = t self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10): if self.integrator_tolerances_set is True: self.reset_integrator(self.t) self.vode.set_options(rtol, atol) self.integrator_tolerances_set = True def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._Ms[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) self.vode.set_initial_value(self.spin, self.t) def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_Ms(self): return self._Ms def set_Ms(self, value): self._Ms[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._Ms[i] > 0.0: self._Ms_inv = 1.0 / self._Ms[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._Ms)): if self._Ms[i] == 0.0: self._pins[i] = 1 self.Ms_const = np.max(self._Ms) Ms = property(get_Ms, set_Ms) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, Ms=self._Ms) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return ode = self.vode self.spin_last[:] = self.spin[:] flag = ode.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = ode.y[:] self.t = t self.step += 1 # update field before saving data self.compute_effective_field(t) self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_procession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skymrion_number( self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.saver.update_entity_order() def save_vtk(self): self.vtk.save_vtk(self.spin.reshape(-1, 3), self.Ms, step=self.step) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) def stat(self): return self.vode.stat() def spin_length(self): self.spin.shape = (3, -1) length = np.sqrt(np.sum(self.spin**2, axis=0)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((3, -1)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=0))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): ONE_DEGREE_PER_NS = 17453292.52 for i in range(0, max_steps + 1): cvode_dt = self.vode.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print 'step=%d, time=%g, max_dmdt=%g ode_step=%g' % (self.step, self.t, dmdt / ONE_DEGREE_PER_NS, cvode_dt) if dmdt < stopping_dmdt * ONE_DEGREE_PER_NS: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk() def cvode_stat_output(self, sim): """ This function tries to get the values from the CVODE statistics. For a 'sim' simulation object, this is done starting from calling sim.vode.stats() According to the CVODE version, this call can generate a string: CvodeSolver(nsteps = 18, nfevals = 32, njevals = 14. ) where: nsteps --> number of steps taken by CVODE nfevals --> number of calls to the user's f function (I guess this is what we need) njevals --> the cumulative number of calls to the Jacobian function So, for example, we can regex search any number preceded by "nfevals = " to get the number of evaluations of the RHS and convert the result to an integer OR it can give a tuple with 3 values, which must be in the same order than before For now, we are only interested in the RHS evaluations, so we return a single value """ cvode_stat = sim.vode.stat() if isinstance(cvode_stat, str): out = int(re.search(r'(?<=nfevals\s=\s)[0-9]*', cvode_stat).group(0) ) elif isinstance(cvode_stat, tuple): out = cvode_stat[1] else: raise NotImplementedError('Cannot retrieve the values' 'from CVODE stats') return out
class SimBase(object): """ A class with common methods and definitions for both micromagnetic and atomistic simulations """ def __init__(self, mesh, name): self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._magnetisation = np.zeros(self.n, dtype=np.float) # Inverse magnetisation self._magnetisation_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] # This is for old C files codes using the xperiodic variables try: self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity except ValueError: self.xperiodic, self.yperiodic = mesh.periodicity # To save the simulation data: ---------------------------------------- self.data_saver = DataSaver(self, name + '.txt') self.data_saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: self.compute_energy(), 'header': 'E_total'} self.data_saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: self.compute_spin_error(), 'header': 'm_error'} self.data_saver.update_entity_order() # --------------------------------------------------------------------- def set_m(self, m0=(1, 0, 0), normalise=True): """ Set the magnetisation/spin three dimensional vector field profile. ARGUMENTS: m0 :: * To set every spin with the same direction, set this value as a 3 elements tuple or list. * For a spatially dependent vector field, you can specify a function that returns a 3 element list depending on the spatial coordinates. For example, a magnetisation field that depends on the x position: def m_profile(r): for r[0] > 2: return (0, 0, 1) else: return (0, 0, -1) * You can also manually specify an array with (3 * n) elements with the spins directions in the following order: [mx_0 my_0 mz_0 mx_1 my_1 ... mx_n, my_n, mz_n] where n is the number of mesh nodes and the order of the magnetisation vectors follow the same order than the mesh coordinates array. * Alternatively, if you previously saved the magnetisation field array to a numpy file, you can load it using numpy.load(my_array) """ self.spin[:] = helper.init_vector(m0, self.mesh, 3, normalise) # TODO: carefully checking and requires to call set_mu first # Set the magnetisation/spin direction to (0, 0, 0) for sites # with no material, i.e. M_s = 0 or mu_s = 0 # TODO: Check for atomistic and micromagnetic cases self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._magnetisation[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) # Set the initial state for the Sundials integrator using the # spins array # Minimiser methods do not have integrator try: self.driver.integrator.set_initial_value(self.spin, self.driver.t) except AttributeError: pass def get_pins(self): """ Returns the array with pinned spins in the sample: sites with 0 are unpinned and sites with 1 are pinned. The order of the array follows the same order than the mesh coordinates """ return self._pins def set_pins(self, pin): """ An scalar field with values 1 or 0 to specify mesh/lattice sites with pinned or unpinned magnetic moments, respectively ARGUMENTS: pin :: * You can specify a function that returns 1 or 0 depending on the spatial coordinates. For example, to pin the spins in a range in the x direction: def pin_profile(r): for r[0] > 2 and r[0] < 4: return 1 else: return 0 * You can also manually specify an array with n elements (1 or 0) with the pinned/unpinned values in the same order than the mesh coordinates array. * Alternatively, if you previously saved the pin field array to a numpy file, you can load it using numpy.load(my_array) """ self._pins[:] = helper.init_scalar(pin, self.mesh) # Sites with no material, i.e. Mu_s or mu_s equal to zero, # will be pinned for i in range(len(self._magnetisation)): if self._magnetisation[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def add(self, interaction, save_field=False): """ Add an interaction from one of the Energy subclasses. By default, the average energy of the added interaction is saved to the data file when relaxing the system OPTIONAL ARGUMENTS: save_field :: Set True to save the average values of this interaction field when relaxing the system """ # magnetisation is Ms for the micromagnetic Sim class, and it is # mu_s for the atomistic Sim class interaction.setup(self.mesh, self.spin, self._magnetisation, self._magnetisation_inv ) # TODO: FIX --> ?? # When adding an interaction that was previously added, using # the same name, append a '_2' to the new interaction name (?) for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) # Specify a name for the energy of the interaction, which will # appear in a file with saved values # When saving the energy values, we call the compute_energy() method # from the (micromagnetic/atomistic) Energy class (overhead?) energy_name = 'E_{0}'.format(interaction.name) self.data_saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} # Save the average values of the interaction vector field components if save_field: fn = '{0}'.format(interaction.name) self.data_saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.data_saver.update_entity_order() def get_interaction(self, name): """ Returns an instance of a magnetic interaction previously added to the simulation, using the corresponding interaction name as a string """ for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def remove(self, name): """ Removes an interaction from a simulation. This is useful because it reduces the run-time if the interaction calculation time is substantial. """ interaction = None # First we remove it from the list of interactions print(self.interactions) for i, intn in enumerate(self.interactions): print(intn.name) if intn.name == name: interaction = self.interactions.pop(i) break if not interaction: raise ValueError("Could not find the " "interaction with name {}".format(name)) # Next, we need to change the data saver entities. # We don't want to remove the relevant interaction # completely because if this is done, the table # would be incorrect. What we need to do is therefore # replace the lambda functions with dummy ones which # just return zeros; for example.if no Zeeman intn then # the Zeeman energy and field are zero anyway. self.data_saver.entities['E_{}'.format(name)]['get'] = lambda sim: 0 # We don't save field by default, so need to check if # save field is selected. Easiest just to use a try/except # block here; not a performance critical function. try: self.data_saver.entities[name]['get'] = \ lambda sim: np.array([0.0, 0.0, 0.0]) except: pass def skyrmion_number(self): pass def spin_at(self, i, j, k): """ Returns the x,y,z components of a spin in the [i, j, k] position of the mesh, where i,j,k are integer indexes. The index ordering is specified in the mesh class. """ i1 = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[i1], self.spin[i1 + 1], self.spin[i1 + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.data_saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.data_saver.update_entity_order() def spin_length(self): """ Returns an array with the length of every spin in the mesh. The order is given by the mesh.coordinates order """ self.spin.shape = (-1, 3) length = np.sqrt(np.sum(self.spin ** 2, axis=1)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_average(self): """ Compute the average values of the 3 components of the magnetisation vector field """ self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (3 * self.n) return average def compute_energy(self): """ Compute the total energy of the magnetic system """ energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def get_field_array(self, interaction): """ Returns the field array corresponding to the interaction given: e.g. compute_interaction_field('Demag') returns a numpy array containing the Demag field. """ field = self.get_interaction(interaction) # Copy here to avoid destroying the field accidentally # e.g. through reshaping f = field.field.copy() return f
class LLG(object): def __init__(self, mesh, name='unnamed', use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver( self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols() # When initialising the integrator in the self.vode call, the CVOde # class calls the set_initial_value function (with flag_m=0), which # initialises a new integrator and allocates memory in this process. # Now, when we set the magnetisation, we will use the same memory # setting this flag_m to 1, so instead of calling CVodeInit we call # CVodeReInit. If don't, memory is allocated in every call of set_m self.flag_m = 1 def set_default_options(self, gamma=1, mu_s=1, alpha=0.1): self.default_c = -1 self._alpha[:] = alpha self._mu_s[:] = mu_s self.gamma = gamma self.do_precession = True def reset_integrator(self, t=0): self.vode.reset(self.spin, t) self.t = t # also reinitialise the simulation time and step self.step = 0 def set_tols(self, rtol=1e-8, atol=1e-10): self.vode.set_options(rtol, atol) def set_options(self, rtol=1e-8, atol=1e-10): self.set_tols(rtol, atol) def set_m(self, m0=(1, 0, 0), normalise=True): self.spin[:] = helper.init_vector(m0, self.mesh, normalise) # TODO: carefully checking and requires to call set_mu first self.spin.shape = (-1, 3) for i in range(self.spin.shape[0]): if self._mu_s[i] == 0: self.spin[i, :] = 0 self.spin.shape = (-1,) self.vode.set_initial_value(self.spin, self.t) if not self.flag_m: self.flag_m = 1 def get_pins(self): return self._pins def set_pins(self, pin): self._pins[:] = helper.init_scalar(pin, self.mesh) for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 pins = property(get_pins, set_pins) def get_alpha(self): return self._alpha def set_alpha(self, value): self._alpha[:] = helper.init_scalar(value, self.mesh) alpha = property(get_alpha, set_alpha) def get_mu_s(self): return self._mu_s def set_mu_s(self, value): self._mu_s[:] = helper.init_scalar(value, self.mesh) nonzero = 0 for i in range(self.n): if self._mu_s[i] > 0.0: self._mu_s_inv[i] = 1.0 / self._mu_s[i] nonzero += 1 self.n_nonzero = nonzero for i in range(len(self._mu_s)): if self._mu_s[i] == 0.0: self._pins[i] = 1 mu_s = property(get_mu_s, set_mu_s) def add(self, interaction, save_field=False): interaction.setup(self.mesh, self.spin, self._mu_s) # TODO: FIX for i in self.interactions: if i.name == interaction.name: interaction.name = i.name + '_2' self.interactions.append(interaction) energy_name = 'E_{0}'.format(interaction.name) self.saver.entities[energy_name] = { 'unit': '<J>', 'get': lambda sim: sim.get_interaction(interaction.name).compute_energy(), 'header': energy_name} if save_field: fn = '{0}'.format(interaction.name) self.saver.entities[fn] = { 'unit': '<>', 'get': lambda sim: sim.get_interaction(interaction.name).average_field(), 'header': ('%s_x' % fn, '%s_y' % fn, '%s_z' % fn)} self.saver.update_entity_order() def get_interaction(self, name): for interaction in self.interactions: if interaction.name == name: return interaction else: raise ValueError("Failed to find the interaction with name '{0}', " "available interactions: {1}.".format( name, [x.name for x in self.interactions])) def run_until(self, t): if t <= self.t: if t == self.t and self.t == 0.0: self.compute_effective_field(t) self.saver.save() return ode = self.vode self.spin_last[:] = self.spin[:] flag = ode.run_until(t) if flag < 0: raise Exception("Run cython run_until failed!!!") self.spin[:] = ode.y[:] self.t = t self.step += 1 # update field before saving data self.compute_effective_field(t) self.saver.save() def compute_effective_field(self, t): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: self.field += obj.compute_field(t) def compute_effective_field_jac(self, t, spin): #self.spin[:] = y[:] self.field[:] = 0 for obj in self.interactions: if obj.jac is True: self.field += obj.compute_field(t, spin=spin) def sundials_rhs(self, t, y, ydot): self.t = t # already synchronized when call this funciton # self.spin[:]=y[:] self.compute_effective_field(t) clib.compute_llg_rhs(ydot, self.spin, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) #ydot[:] = self.dm_dt[:] return 0 def sundials_jtn(self, mp, Jmp, t, m, fy): # we can not copy mp to self.spin since m and self.spin is one object. #self.spin[:] = mp[:] print('NO jac...........') self.compute_effective_field_jac(t, mp) clib.compute_llg_jtimes(Jmp, m, fy, mp, self.field, self.alpha, self._pins, self.gamma, self.n, self.do_precession, self.default_c) return 0 def compute_average(self): self.spin.shape = (-1, 3) average = np.sum(self.spin, axis=0) / self.n_nonzero self.spin.shape = (-1,) return average def compute_energy(self): energy = 0 for obj in self.interactions: energy += obj.compute_energy() return energy def skyrmion_number(self): nx = self.mesh.nx ny = self.mesh.ny nz = self.mesh.nz number = clib.compute_skyrmion_number( self.spin, self._skx_number, nx, ny, nz, self.mesh.neighbours) return number def spin_at(self, i, j, k): """ Returns the spin components of the spin at i-th position in the z direction, j-th position in the y direction and k-th position in x direction """ nxyz = self.mesh.n index = 3 * self.mesh.index(i, j, k) # print self.spin.shape,nxy,nx,i1,i2,i3 return np.array([self.spin[index], self.spin[index + 1], self.spin[index + 2]]) def add_monitor_at(self, i, j, k, name='p'): """ Save site spin with index (i,j,k) to txt file. """ self.saver.entities[name] = { 'unit': '<>', 'get': lambda sim: sim.spin_at(i, j, k), 'header': (name + '_x', name + '_y', name + '_z')} self.saver.update_entity_order() def save_vtk(self): """ Save a VTK file with the magnetisation vector field and magnetic moments as cell data. Magnetic moments are saved in units of Bohr magnetons NOTE: It is recommended to use a *cell to point data* filter in Paraview or Mayavi to plot the vector field """ self.vtk.save_vtk(self.spin.reshape(-1, 3), self._mu_s / const.mu_B, step=self.step ) def save_m(self): if not os.path.exists('%s_npys' % self.name): os.makedirs('%s_npys' % self.name) name = '%s_npys/m_%g.npy' % (self.name, self.step) np.save(name, self.spin) def save_skx(self, vtk=False): if not os.path.exists('%s_skx_npys' % self.name): os.makedirs('%s_skx_npys' % self.name) name = '%s_skx_npys/m_%g.npy' % (self.name, self.step) np.save(name, self._skx_number) if vtk is True: self.vtk.save_vtk_scalar(self._skx_number, self.step) def stat(self): return self.vode.stat() def spin_length(self): self.spin.shape = (-1, 3) length = np.sqrt(np.sum(self.spin**2, axis=1)) self.spin.shape = (-1,) return length def compute_spin_error(self): length = self.spin_length() - 1.0 length[self._pins > 0] = 0 return np.max(abs(length)) def compute_dmdt(self, dt): m0 = self.spin_last m1 = self.spin dm = (m1 - m0).reshape((-1, 3)) max_dm = np.max(np.sqrt(np.sum(dm**2, axis=1))) max_dmdt = max_dm / dt return max_dmdt def relax(self, dt=1e-11, stopping_dmdt=0.01, max_steps=1000, save_m_steps=100, save_vtk_steps=100): for i in range(0, max_steps + 1): cvode_dt = self.vode.get_current_step() increment_dt = dt if cvode_dt > dt: increment_dt = cvode_dt self.run_until(self.t + increment_dt) if save_vtk_steps is not None: if i % save_vtk_steps == 0: self.save_vtk() if save_m_steps is not None: if i % save_m_steps == 0: self.save_m() dmdt = self.compute_dmdt(increment_dt) print('step=%d, time=%0.3g, max_dmdt=%0.3g ode_step=%0.3g' % (self.step,self.t, dmdt,cvode_dt)) if dmdt < stopping_dmdt: break if save_m_steps is not None: self.save_m() if save_vtk_steps is not None: self.save_vtk()
def __init__(self, mesh, name='unnamed', integrator='sundials'): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._Ms = np.zeros(self.n, dtype=np.float) self._Ms_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.integrator_tolerances_set = False self.step = 0 if integrator == "sundials": self.integrator = SundialsIntegrator(self.spin, self.sundials_rhs) elif integrator == "euler" or integrator == "rk4": self.integrator = StepIntegrator(self.spin, self.step_rhs, integrator) else: raise NotImplemented("integrator must be sundials, euler or rk4") self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.entities['rhs_evals'] = { 'unit': '<>', 'get': lambda sim: self.integrator.rhs_evals, 'header': 'rhs_evals'} self.saver.entities['real_time'] = { 'unit': '<s>', 'get': lambda _: time.time(), # seconds since epoch 'header': 'real_time'} self.saver.update_entity_order() # This is for old C files codes using the xperiodic variables self.xperiodic, self.yperiodic, self.zperiodic = mesh.periodicity self.vtk = SaveVTK(self.mesh, name=name) self.set_default_options()
def __init__(self, mesh, name='unnamed', use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total'} self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error'} self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num'} self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver( self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols() # When initialising the integrator in the self.vode call, the CVOde # class calls the set_initial_value function (with flag_m=0), which # initialises a new integrator and allocates memory in this process. # Now, when we set the magnetisation, we will use the same memory # setting this flag_m to 1, so instead of calling CVodeInit we call # CVodeReInit. If don't, memory is allocated in every call of set_m self.flag_m = 1
def __init__(self, mesh, name='unnamed', use_jac=False): """Simulation object. *Arguments* name : the Simulation name (used for writing data files, for examples) """ self.t = 0 self.name = name self.mesh = mesh self.n = mesh.n self.n_nonzero = mesh.n self.unit_length = mesh.unit_length self._alpha = np.zeros(self.n, dtype=np.float) self._mu_s = np.zeros(self.n, dtype=np.float) self._mu_s_inv = np.zeros(self.n, dtype=np.float) self.spin = np.ones(3 * self.n, dtype=np.float) self.spin_last = np.ones(3 * self.n, dtype=np.float) self._pins = np.zeros(self.n, dtype=np.int32) self.field = np.zeros(3 * self.n, dtype=np.float) self.dm_dt = np.zeros(3 * self.n, dtype=np.float) self._skx_number = np.zeros(self.n, dtype=np.float) self.interactions = [] self.pin_fun = None self.step = 0 self.saver = DataSaver(self, name + '.txt') self.saver.entities['E_total'] = { 'unit': '<J>', 'get': lambda sim: sim.compute_energy(), 'header': 'E_total' } self.saver.entities['m_error'] = { 'unit': '<>', 'get': lambda sim: sim.compute_spin_error(), 'header': 'm_error' } self.saver.entities['skx_num'] = { 'unit': '<>', 'get': lambda sim: sim.skyrmion_number(), 'header': 'skx_num' } self.saver.update_entity_order() # This is only for old C files using the xperiodic variable self.xperiodic, self.yperiodic = mesh.periodicity[0], mesh.periodicity[ 1] self.vtk = SaveVTK(self.mesh, name=name) if use_jac is not True: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs) else: self.vode = cvode.CvodeSolver(self.spin, self.sundials_rhs, self.sundials_jtn) self.set_default_options() self.set_tols()