def setup(): """ Create a cuboid mesh representing a magnetic material and two dolfin.Functions defined on this mesh: m -- unit magnetisation (linearly varying across the sample) Ms_func -- constant function representing the saturation magnetisation Ms *Returns* A triple (m_space, m, Ms_func), where m_space is the VectorFunctionSpace (of type "continuous Lagrange") on which the magnetisation m is defined and m, Ms_funct are as above. """ m_space = df.VectorFunctionSpace(mesh, "CG", 1) m = Field(m_space, value=df.Expression(("1e-9", "x[0]/10", "0"), degree=1)) m.set_with_numpy_array_debug(fnormalise(m.get_numpy_array_debug())) Ms_space = df.FunctionSpace(mesh, "DG", 0) Ms_func = df.interpolate(df.Constant(Ms), Ms_space) return m_space, m, Ms_func
def compute_scalar_potential_native_gcr(mesh, m_expr=df.Constant([1, 0, 0]), Ms=1.0): gcrdemag = Demag("GCR") V = df.VectorFunctionSpace(mesh, "Lagrange", 1) m = Field(V, value=m_expr) m.set_with_numpy_array_debug(helpers.fnormalise(m.get_numpy_array_debug())) gcrdemag.setup(m, Ms, unit_length=1) phi1 = gcrdemag.compute_potential() normalise_phi(phi1, mesh) return phi1
def compute_scalar_potential_llg(mesh, m_expr=df.Constant([1, 0, 0]), Ms=1.): S3 = df.VectorFunctionSpace(mesh, "Lagrange", 1, dim=3) m = Field(S3, value=m_expr) m.set_with_numpy_array_debug(helpers.fnormalise(m.get_numpy_array_debug())) demag = Demag() demag.setup(m, Field(df.FunctionSpace(mesh, 'DG', 0), Ms), unit_length=1) phi = demag.compute_potential() normalise_phi(phi, mesh) return phi
def exchange(mesh, unit_length): S3 = df.VectorFunctionSpace(mesh, "Lagrange", 1) m = Field(S3, value=df.Expression(("x[1]*u", "0", "sqrt(1-pow(x[1]*u, 2))"), u=unit_length, degree=1)) exch = Exchange(A) exch.setup(m, Field(df.FunctionSpace(mesh, 'DG', 0), Ms), unit_length=unit_length) H = exch.compute_field() E = exch.compute_energy() return m.get_numpy_array_debug(), H, E
def three_dimensional_problem(): x_max = 10e-9 y_max = 1e-9 z_max = 1e-9 mesh = df.BoxMesh(df.Point(0, 0, 0), df.Point(x_max, y_max, z_max), 40, 2, 2) V = df.VectorFunctionSpace(mesh, 'Lagrange', 1) Ms = 8.6e5 m0_x = "pow(sin(0.2*x[0]*1e9), 2)" m0_y = "0" m0_z = "pow(cos(0.2*x[0]*1e9), 2)" m = Field(V, value=vector_valued_function((m0_x, m0_y, m0_z), V, normalise=True)) C = 1.3e-11 u_exch = Exchange(C) u_exch.setup(m, Field(df.FunctionSpace(mesh, 'DG', 0), Ms)) finmag_exch = u_exch.compute_field() magpar_result = os.path.join(MODULE_DIR, 'magpar_result', 'test_exch') nodes, magpar_exch = magpar.get_field(magpar_result, 'exch') ## Uncomment the line below to invoke magpar to compute the results, ## rather than using our previously saved results. # nodes, magpar_exch = magpar.compute_exch_magpar(m, A=C, Ms=Ms) print magpar_exch # Because magpar have changed the order of the nodes!!! tmp = df.Function(V) tmp_c = mesh.coordinates() mesh.coordinates()[:] = tmp_c * 1e9 finmag_exch, magpar_exch, \ diff, rel_diff = magpar.compare_field( mesh.coordinates(), finmag_exch, nodes, magpar_exch) return dict(m0=m.get_numpy_array_debug(), mesh=mesh, exch=finmag_exch, magpar_exch=magpar_exch, diff=diff, rel_diff=rel_diff)
class SLLG(object): def __init__(self, S1, S3, method='RK2b', checking_length=False, unit_length=1): self.S1 = S1 self.S3 = S3 self.mesh = S1.mesh() self._t = 0 self.time_scale = 1e-9 self._m_field = Field(self.S3, name='m') self.nxyz = self._m_field.f.vector().size() / 3 self._T = np.zeros(self.nxyz) self._alpha = np.zeros(self.nxyz) self.m = np.zeros(3 * self.nxyz) self.field = np.zeros(3 * self.nxyz) self.grad_m = np.zeros(3 * self.nxyz) self.dm_dt = np.zeros(3 * self.nxyz) # Note: nxyz for Ms length is more suitable? self._Ms = np.zeros(3 * self.nxyz) self.pin_fun = None self.method = method self.checking_length = checking_length self.unit_length = unit_length self.DG = df.FunctionSpace(self.mesh, "DG", 0) self._Ms_dg = Field(self.DG) self.effective_field = EffectiveField(self._m_field, self.Ms, self.unit_length) self.zhangli_stt = False self.set_default_values() def set_default_values(self): self.Ms = Field(df.FunctionSpace(self.mesh, 'DG', 0), 8.6e5) # A/m saturation magnetisation self._pins = np.array([], dtype="int") self.volumes = df.assemble( df.dot(df.TestFunction(self.S3), df.Constant([1, 1, 1])) * df.dx).array() self.Volume = mesh_volume(self.mesh) self.real_volumes = self.volumes * self.unit_length**3 self.m_pred = np.zeros(self.m.shape) self.integrator = native_llb.StochasticSLLGIntegrator( self.m, self.m_pred, self._Ms, self._T, self.real_volumes, self._alpha, self.stochastic_update_field, self.method) self.alpha = 0.1 self._gamma = consts.gamma self._seed = np.random.random_integers(4294967295) self.dt = 1e-13 self.T = 0 @property def cur_t(self): return self._t * self.time_scale @cur_t.setter def cur_t(self, value): self._t = value / self.time_scale @property def dt(self): return self._dt * self.time_scale @property def gamma(self): return self._gamma @property def seed(self): return self._seed @seed.setter def seed(self, value): self._seed = value self.setup_parameters() @gamma.setter def gamma(self, value): self._gamma = value self.setup_parameters() @dt.setter def dt(self, value): self._dt = value / self.time_scale self.setup_parameters() def setup_parameters(self): # print 'seed:', self.seed self.integrator.set_parameters(self.dt, self.gamma, self.seed, self.checking_length) log.info("seed=%d." % self.seed) log.info("dt=%g." % self.dt) log.info("gamma=%g." % self.gamma) #log.info("checking_length: "+str(self.checking_length)) def set_m(self, value, normalise=True): m_tmp = helpers.vector_valued_function( value, self.S3, normalise=normalise).vector().array() self._m_field.set_with_numpy_array_debug(m_tmp) self.m[:] = self._m_field.get_numpy_array_debug() def advance_time(self, t): tp = t / self.time_scale if tp <= self._t: return try: while tp - self._t > 1e-12: if self.zhangli_stt: self.integrator.run_step(self.field, self.grad_m) else: self.integrator.run_step(self.field) self._m_field.set_with_numpy_array_debug(self.m) self._t += self._dt except Exception, error: log.info(error) raise Exception(error) if abs(tp - self._t) < 1e-12: self._t = tp
class UniaxialAnisotropy(EnergyBase): """ Compute the uniaxial anisotropy field. .. math:: E_{\\text{anis}} = \\int_\\Omega K_1 - (1 - a \\cdot m)^2 dx *Arguments* K1 The anisotropy constant K2 The anisotropy constant (default=0) axis The easy axis. Should be a unit vector. Ms The saturation magnetisation. method The method used to compute the anisotropy field. For alternatives and explanation, see EnergyBase class. *Example of Usage* .. code-block:: python import dolfin as df from finmag import UniaxialAnisotropy L = 1e-8; nL = 5; mesh = df.BoxMesh(df.Point(0, L, 0), df.Point(L, 0, L), nL, nL, nL) S3 = df.VectorFunctionSpace(mesh, 'Lagrange', 1) K = 520e3 # For Co (J/m3) a = df.Constant((0, 0, 1)) # Easy axis in z-direction m = df.project(df.Constant((1, 0, 0)), V) # Initial magnetisation Ms = 1e6 anisotropy = UniaxialAnisotropy(K, a) anisotropy.setup(S3, m) # Print energy print anisotropy.compute_energy() # Assign anisotropy field H_ani = anisotropy.compute_field() """ def __init__(self, K1, axis, K2=0, method="box-matrix-petsc", name='Anisotropy', assemble=True): """ Define a uniaxial anisotropy with (first) anisotropy constant `K1` (in J/m^3) and easy axis `axis`. K1 and axis can be passed as df.Constant or df.Function, although automatic convertion will be attempted from float for K1 and a sequence type for axis. It is possible to specify spatially varying anisotropy by using df.Functions. """ self.K1_value = K1 self.K2_value = K2 self.axis_value = axis self.name = name self.assemble = assemble super(UniaxialAnisotropy, self).__init__(method, in_jacobian=True) if K2 != 0: self.assemble = False @timer.method def setup(self, m, Ms, unit_length=1): """ Function to be called after the energy object has been constructed. *Arguments* m magnetisation field (usually normalised) Ms Saturation magnetisation field unit_length real length of 1 unit in the mesh """ assert isinstance(m, Field) assert isinstance(Ms, Field) cg_scalar_functionspace = df.FunctionSpace(m.mesh(), 'CG', 1) self.K1 = Field(cg_scalar_functionspace, self.K1_value, name='K1') self.K2 = Field(cg_scalar_functionspace, self.K2_value, name='K2') cg_vector_functionspace = df.VectorFunctionSpace(m.mesh(), 'CG', 1, 3) self.axis = Field(cg_vector_functionspace, self.axis_value, name='axis') # Anisotropy energy # HF's version inline with nmag, breaks comparison with analytical # solution in the energy density test for anisotropy, as this uses # the Scholz-Magpar method. Should anyway be an easy fix when we # decide on method. # FIXME: we should use DG0 space here? E_integrand = self.K1.f * \ (df.Constant(1) - (df.dot(self.axis.f, m.f)) ** 2) if self.K2_value != 0: E_integrand -= self.K2.f * df.dot(self.axis.f, m.f)**4 del (self.K1_value) del (self.K2_value) del (self.axis_value) super(UniaxialAnisotropy, self).setup(E_integrand, m, Ms, unit_length) if not self.assemble: self.H = self.m.get_numpy_array_debug() self.Ms = self.Ms.get_numpy_array_debug() self.u = self.axis.get_numpy_array_debug() self.K1_arr = self.K1.get_numpy_array_debug() self.K2_arr = self.K2.get_numpy_array_debug() self.volumes = df.assemble( df.TestFunction(cg_scalar_functionspace) * df.dx) self.compute_field = self.__compute_field_directly def __compute_field_directly(self): m = self.m.get_numpy_array_debug() m.shape = (3, -1) self.H.shape = (3, -1) self.u.shape = (3, -1) native_llg.compute_anisotropy_field(m, self.Ms, self.H, self.u, self.K1_arr, self.K2_arr) m.shape = (-1, ) self.H.shape = (-1, ) self.u.shape = (-1, ) return self.H
class LLG_STT(object): """ Solves the Landau-Lifshitz-Gilbert equation with the nonlocal spin transfer torque. """ def __init__(self, S1, S3, unit_length=1, average=False): self.S1 = S1 self.S3 = S3 self.unit_length = unit_length self.mesh = S1.mesh() self._m_field = Field(self.S3, name='m') self._delta_m = df.Function(self.S3) self.nxyz = len(self.m) self._alpha = np.zeros(self.nxyz / 3) self.delta_m = np.zeros(self.nxyz) self.H_eff = np.zeros(self.nxyz) self.dy_m = np.zeros(2 * self.nxyz) # magnetisation and delta_m self.dm_dt = np.zeros(2 * self.nxyz) # magnetisation and delta_m self.set_default_values() self.effective_field = EffectiveField(self._m_field, self.Ms, self.unit_length) self._t = 0 def set_default_values(self): self.set_alpha(0.5) self.gamma = consts.gamma self.c = 1e11 # 1/s numerical scaling correction \ # 0.1e12 1/s is the value used by default in nmag 0.2 self.Ms = 8.6e5 # A/m saturation magnetisation self.vol = df.assemble( df.dot(df.TestFunction(self.S3), df.Constant([1, 1, 1])) * df.dx).array() self.real_vol = self.vol * self.unit_length**3 self.pins = [] self._pre_rhs_callables = [] self._post_rhs_callables = [] self.interactions = [] def set_parameters(self, J_profile=(1e10, 0, 0), P=0.5, D=2.5e-4, lambda_sf=5e-9, lambda_J=1e-9, speedup=1): self._J = helpers.vector_valued_function(J_profile, self.S3) self.J = self._J.vector().array() self.compute_gradient_matrix() self.H_gradm = df.PETScVector() self.P = P self.D = D / speedup self.lambda_sf = lambda_sf self.lambda_J = lambda_J self.tau_sf = lambda_sf**2 / D * speedup self.tau_sd = lambda_J**2 / D * speedup self.compute_laplace_matrix() self.H_laplace = df.PETScVector() self.nodal_volume_S3 = nodal_volume(self.S3) def set_pins(self, nodes): """ Hold the magnetisation constant for certain nodes in the mesh. Pass the indices of the pinned sites as *nodes*. Any type of sequence is fine, as long as the indices are between 0 (inclusive) and the highest index. This means you CANNOT use python style indexing with negative offsets counting backwards. """ if len(nodes) > 0: nb_nodes_mesh = len(self._m_field.get_numpy_array_debug()) / 3 if min(nodes) >= 0 and max(nodes) < nb_nodes_mesh: self._pins = np.array(nodes, dtype="int") else: log.error( "Indices of pinned nodes should be in [0, {}), were [{}, {}]." .format(nb_nodes_mesh, min(nodes), max(nodes))) else: self._pins = np.array([], dtype="int") def pins(self): return self._pins pins = property(pins, set_pins) @property def Ms(self): return self._Ms_dg @Ms.setter def Ms(self, value): self._Ms_dg = Field(df.FunctionSpace(self.mesh, 'DG', 0), value) self._Ms_dg.name = 'Ms' self.volumes = df.assemble(df.TestFunction(self.S1) * df.dx) Ms = df.assemble(self._Ms_dg.f * df.TestFunction(self.S1) * df.dx).array() / self.volumes self._Ms = Ms.copy() self.Ms_av = np.average(self._Ms_dg.vector().array()) @property def M(self): """The magnetisation, with length Ms.""" # FIXME:error here m = self.m.view().reshape((3, -1)) Ms = self.Ms.vector().array() if isinstance(self.Ms, df.Function) else self.Ms M = Ms * m return M.ravel() @property def M_average(self): """The average magnetisation, computed with m_average().""" volume_Ms = df.assemble(self._Ms_dg * df.dx) volume = df.assemble(self._Ms_dg * df.dx) return self.m_average * volume_Ms / volume @property def m(self): """The unit magnetisation.""" return self._m_field.get_numpy_array_debug() @m.setter def m(self, value): # Not enforcing unit length here, as that is better done # once at the initialisation of m. self._m_field.set_with_numpy_array_debug(value) self.dy_m.shape = (2, -1) self.dy_m[0][:] = value self.dy_m.shape = (-1, ) @property def sundials_m(self): """The unit magnetisation.""" return self.dy_m @sundials_m.setter def sundials_m(self, value): # used to copy back from sundials cvode self.dy_m[:] = value[:] self.dy_m.shape = (2, -1) self._m_field.set_with_numpy_array_debug(self.dy_m[0][:]) self.dy_m.shape = (-1, ) def m_average_fun(self, dx=df.dx): """ Compute and return the average polarisation according to the formula :math:`\\langle m \\rangle = \\frac{1}{V} \int m \: \mathrm{d}V` """ # mx = df.assemble(self._Ms_dg*df.dot(self._m, df.Constant([1, 0, 0])) * dx) # my = df.assemble(self._Ms_dg*df.dot(self._m, df.Constant([0, 1, 0])) * dx) # mz = df.assemble(self._Ms_dg*df.dot(self._m, df.Constant([0, 0, 1])) * dx) # volume = df.assemble(self._Ms_dg*dx) # # return np.array([mx, my, mz]) / volume return self._m_field.average(dx=dx) m_average = property(m_average_fun) def set_m(self, value, normalise=True, **kwargs): """ Set the magnetisation (if `normalise` is True, it is automatically normalised to unit length). `value` can have any of the forms accepted by the function 'finmag.util.helpers.vector_valued_function' (see its docstring for details). You can call this method anytime during the simulation. However, when providing a numpy array during time integration, the use of the attribute m instead of this method is advised for performance reasons and because the attribute m doesn't normalise the vector. """ self.m = helpers.vector_valued_function(value, self.S3, normalise=normalise, **kwargs).vector().array() def set_alpha(self, value): """ Set the damping constant :math:`\\alpha`. The parameter `value` can have any of the types accepted by the function :py:func:`finmag.util.helpers.scalar_valued_function` (see its docstring for details). """ self._alpha[:] = helpers.scalar_valued_function( value, self.S1).vector().array()[:] def compute_gradient_matrix(self): """ compute (J nabla) m , we hope we can use a matrix M such that M*m = (J nabla)m. """ tau = df.TrialFunction(self.S3) sigma = df.TestFunction(self.S3) dim = self.S3.mesh().topology().dim() ty = tz = 0 tx = self._J[0] * df.dot(df.grad(tau)[:, 0], sigma) if dim >= 2: ty = self._J[1] * df.dot(df.grad(tau)[:, 1], sigma) if dim >= 3: tz = self._J[2] * df.dot(df.grad(tau)[:, 2], sigma) self.gradM = df.assemble(1 / self.unit_length * (tx + ty + tz) * df.dx) def compute_gradient_field(self): self.gradM.mult(self._m_field.f.vector(), self.H_gradm) return self.H_gradm.array() / self.nodal_volume_S3 def compute_laplace_matrix(self): u3 = df.TrialFunction(self.S3) v3 = df.TestFunction(self.S3) self.laplace_M = df.assemble(self.D / self.unit_length**2 * df.inner(df.grad(u3), df.grad(v3)) * df.dx) def compute_laplace_field(self): self.laplace_M.mult(self._delta_m.vector(), self.H_laplace) return -1.0 * self.H_laplace.array() / self.nodal_volume_S3 def sundials_rhs(self, t, y, ydot): self.t = t y.shape = (2, -1) self._m_field.set_with_numpy_array_debug(y[0]) self._delta_m.vector().set_local(y[1]) y.shape = (-1, ) self.effective_field.update(t) H_eff = self.effective_field.H_eff # alias (for readability) H_eff.shape = (3, -1) timer.start("sundials_rhs", self.__class__.__name__) # Use the same characteristic time as defined by c H_gradm = self.compute_gradient_field() H_gradm.shape = (3, -1) H_laplace = self.compute_laplace_field() H_laplace.shape = (3, -1) self.dm_dt.shape = (6, -1) m = self.m m.shape = (3, -1) char_time = 0.1 / self.c delta_m = self._delta_m.vector().array() delta_m.shape = (3, -1) native_llg.calc_llg_nonlocal_stt_dmdt(m, delta_m, H_eff, H_laplace, H_gradm, self.dm_dt, self.pins, self.gamma, self._alpha, char_time, self.P, self.tau_sd, self.tau_sf, self._Ms) timer.stop("sundials_rhs", self.__class__.__name__) self.dm_dt.shape = (-1, ) ydot[:] = self.dm_dt[:] H_gradm.shape = (-1, ) H_eff.shape = (-1, ) m.shape = (-1, ) delta_m.shape = (-1, ) return 0
class Zeeman(object): def __init__(self, H, name='Zeeman', **kwargs): """ Specify an external field (in A/m). H can have any of the forms accepted by the function 'finmag.util.helpers.vector_valued_function' (see its docstring for details). """ self.H_value = H self.name = name self.kwargs = kwargs self.in_jacobian = False def setup(self, m, Ms, unit_length=1): """ Function to be called after the energy object has been constructed. *Arguments* m magnetisation field (usually normalised) Ms Saturation magnetisation (scalar, or scalar dolfin function) unit_length real length of 1 unit in the mesh """ self.m = m self.Ms = Ms self.unit_length = unit_length dofmap = self.m.functionspace.dofmap() self.S1 = df.FunctionSpace( m.mesh(), "Lagrange", 1, constrained_domain=dofmap.constrained_domain) # self.dim = S3.mesh().topology().dim() # self.nodal_volume_S1 = nodal_volume(self.S1, self.unit_length) self.set_value(self.H_value, **self.kwargs) def set_value(self, value, **kwargs): """ Set the value of the field (in A/m). `value` can have any of the forms accepted by the function 'finmag.util.helpers.vector_valued_function' (see its docstring for details). """ self.value = value dofmap = self.m.functionspace.dofmap() dg_vector_functionspace = df.VectorFunctionSpace( self.m.mesh(), 'CG', 1, 3, constrained_domain=dofmap.constrained_domain) self.H = Field(dg_vector_functionspace, value, name='H_ext') self.E = -mu0 * self.Ms.f * df.dot(self.m.f, self.H.f) # Energy density. def average_field(self): """ Compute the average applied field. """ return helpers.average_field(self.compute_field()) def compute_field(self): return self.H.get_numpy_array_debug() def compute_energy(self, dx=df.dx): dim = self.m.mesh_dim() E = df.assemble(self.E * dx) * self.unit_length**dim return E def energy_density(self): """ Return energy density (as a finmag.Field object). """ # Previous version #return df.project(df.dot(self.m.f, self.H.f) * self.Ms.f * -mu0, self.S1).vector().array() # # New version using the Field class. Note that this is # currently ca. 6-7 times *slower* than the version above. # However, it is slightly more accurate (no rounding errors # due to projection), and we should be able to tune # performance in the Field class. return self.m.dot(self.H) * self.Ms * -mu0 def energy_density_function(self): if not hasattr(self, "E_density_function"): self.E_density_function = self.energy_density().f return self.E_density_function