def test_differentiate_heff(): ns = [2, 2, 2] mesh = df.BoxMesh(0, 0, 0, 1, 1, 1, *ns) sim = Simulation(mesh, 1.2) sim.set_m([1, 0, 0]) sim.add(Demag()) sim.add(Exchange(2.3 * mu0)) compute_H = compute_H_func(sim) # Check that H_eff is linear without Zeeman np.random.seed(1) m1 = fnormalise(np.random.randn(*sim.m.shape)) m2 = fnormalise(np.random.randn(*sim.m.shape)) # TODO: need to use a non-iterative solver here to increase accuracy assert np.max( np.abs(compute_H(m1) + compute_H(m2) - compute_H(m1 + m2))) < 1e-6 # Add the zeeman field now sim.add(Zeeman([2.5, 3.5, 4.3])) # Check that both fd2 and fd4 give the same result assert np.max( np.abs( differentiate_fd4(compute_H, m1, m2) - differentiate_fd2(compute_H, m1, m2))) < 1e-10
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 use_slonczewski(self, J, P, d, p, Lambda=2, epsilonprime=0.0, with_time_update=None): """ Activates the computation of the Slonczewski spin-torque term in the LLG. *Arguments* J is the current density in A/m^2 as a number, dolfin function, dolfin expression or Python function. In the last case the current density is assumed to be spatially constant but can vary with time. Thus J=J(t) should be a function expecting a single variable t (the simulation time) and return a number. Note that a time-dependent current density can also be given as a dolfin Expression, but a python function should be much more efficient. P is the polarisation (between 0 and 1). It is defined as P = (x-y)/(x+y), where x and y are the fractions of spin up/down electrons). d is the thickness of the free layer in m. p is the direction of the polarisation as a triple (is automatically normalised to unit length). - Lambda: the Lambda parameter in the Slonczewski/Xiao spin-torque term - epsilonprime: the strength of the secondary spin transfer term - with_time_update: A function of the form J(t), which accepts a time step `t` as its only argument and returns the new current density. N.B.: For efficiency reasons, the return value is currently assumed to be a number, i.e. J is assumed to be spatially constant (and only varying with time). """ self.do_slonczewski = True self.fun_slonczewski_time_update = with_time_update self.Lambda = Lambda self.epsilonprime = epsilonprime if isinstance(J, df.Expression): J = df.interpolate(J, self.S1) if not isinstance(J, df.Function): func = df.Function(self.S1) func.assign(df.Constant(J)) J = func self.J = J.vector().array() assert P >= 0.0 and P <= 1.0 self.P = P self.d = d polarisation = df.Function(self.S3) polarisation.assign(df.Constant((p))) # we use fnormalise to ensure that p has unit length self.p = helpers.fnormalise( polarisation.vector().array()).reshape((3, -1))
def m_average(self): self._m.vector().set_local(helpers.fnormalise(self.M)) mx = df.assemble(df.dot(self._m, df.Constant([1, 0, 0])) * df.dx) my = df.assemble(df.dot(self._m, df.Constant([0, 1, 0])) * df.dx) mz = df.assemble(df.dot(self._m, df.Constant([0, 0, 1])) * df.dx) volume = df.assemble(df.Constant(1)*df.dx, mesh=self.mesh) return np.array([mx, my, mz])/volume
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 set_m(self, value, **kwargs): """ Set the magnetisation (scaled automatically). There are several ways to use this function. Either you provide a 3-tuple of numbers, which will get cast to a dolfin.Constant, or a dolfin.Constant directly. Then a 3-tuple of strings (with keyword arguments if needed) that will get cast to a dolfin.Expression, or directly a dolfin.Expression. You can provide a numpy.ndarray of nodal values of shape (3*n,), where n is the number of nodes. Finally, you can pass a function (any callable object will do) which accepts the coordinates of the mesh as a numpy.ndarray of shape (3, n) and returns the magnetisation like that as well. 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. """ if isinstance(value, tuple): if isinstance(value[0], str): # a tuple of strings is considered to be the ingredient # for a dolfin expression, whereas a tuple of numbers # would signify a constant val = df.Expression(value, degree=1, **kwargs) else: val = df.Constant(value) new_m = df.interpolate(val, self.S3) elif isinstance(value, (df.Constant, df.Expression)): new_m = df.interpolate(value, self.S3) elif isinstance(value, (list, np.ndarray)): new_m = df.Function(self.S3) new_m.vector()[:] = value elif hasattr(value, '__call__'): coords = np.array(zip(*self.S3.mesh().coordinates())) new_m = df.Function(self.S3) new_m.vector()[:] = value(coords).flatten() else: raise AttributeError new_m.vector()[:] = h.fnormalise(new_m.vector().array()) self._m.vector()[:] = new_m.vector()[:] tmp = df.assemble( self._Ms_cell * df.dot(df.TestFunction(self.S3), df.Constant([1, 1, 1])) * df.dx) self._Ms.vector().set_local(tmp / self.vol) self._M.vector().set_local(self.Ms * self.m) self.prepare_solver()
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. """ m0 = helpers.vector_valued_function(value, self.S3, normalise=False, **kwargs).vector().array()[self.v2d_xxx] if np.any(np.isnan(m0)): raise ValueError("Attempting to initialise m with NaN(s)") if normalise: m0 = helpers.fnormalise(m0) self._m_field.set_with_ordered_numpy_array_xxx(m0)
C = 1.3e-11 # J/m exchange constant Ms = 8.6e5 # A/m saturation magnetisation t = 0 # s H_app = (0, 0, 0) H_app = interpolate(Constant(H_app), V) pins = [] # Defaults overwrite from spinwave program Ms = 1e6 alpha = 0.02 m0_tuple = (("1", "5 * pow(cos(pi * (x[0] * pow(10, 9) - 11) / 6), 3) \ * pow(cos(pi * x[1] * pow(10, 9) / 6), 3)", "0")) M = interpolate(Expression(m0_tuple), V) M.vector()[:] = h.fnormalise(M.vector().array()) m_arr = M.vector().array() for i in xrange(nb_nodes): x, y, z = mesh.coordinates()[i] mx = 1 my = 0 mz = 0 if 8e-9 < x < 14e-9 and -3e-9 < y < 3e-9: pass else: m_arr[i] = mx m_arr[i + nb_nodes] = my m_arr[i + 2 * nb_nodes] = mz M.vector()[:] = m_arr
def m(self): mh = helpers.fnormalise(self.M) self._m.vector().set_local(mh) return mh