Skip to content

Commit

Permalink
better resistance computation
Browse files Browse the repository at this point in the history
  • Loading branch information
LemurPwned committed Dec 27, 2024
1 parent cb2b93a commit 621f391
Show file tree
Hide file tree
Showing 6 changed files with 482 additions and 56 deletions.
26 changes: 25 additions & 1 deletion cmtj/models/general_sb.py
Original file line number Diff line number Diff line change
Expand Up @@ -861,8 +861,32 @@ def _compute_numerical_inverse(self, A_matrix):
A_inv_np = np.linalg.inv(A_np)
return sym.Matrix(A_inv_np)

@lru_cache(maxsize=1000) # noqa: B019
def _compute_A_and_V_matrices(self, n, Vdc_ex_variable, H, frequency):
A_matrix = sym.zeros(2 * n, 2 * n)
V_matrix = sym.zeros(2 * n, 1)
U = self.create_energy(H=H, volumetric=False)
omega = sym.Symbol(r"\omega") if frequency is None else 2 * sym.pi * frequency
for i, layer in enumerate(self.layers):
rhs = layer.rhs_spherical_llg(U / layer.thickness, osc=True)
alpha_factor = 1 + layer.alpha**2
V_matrix[2 * i] = sym.diff(rhs[0] * alpha_factor, Vdc_ex_variable)
V_matrix[2 * i + 1] = sym.diff(rhs[1] * alpha_factor, Vdc_ex_variable)
theta, phi = layer.get_coord_sym()
fn_theta = (omega * sym.I * theta - rhs[0]) * alpha_factor
fn_phi = (omega * sym.I * phi - rhs[1]) * alpha_factor
# the functions are only valid for that row i (theta) and i + 1 (phi)
# so we only need to compute the derivatives for the other layers
# for the other layers, the derivatives are zero
for j, layer_j in enumerate(self.layers):
theta_, phi_ = layer_j.get_coord_sym()
A_matrix[2 * i, 2 * j] = sym.diff(fn_theta, theta_)
A_matrix[2 * i, 2 * j + 1] = sym.diff(fn_theta, phi_)
A_matrix[2 * i + 1, 2 * j] = sym.diff(fn_phi, theta_)
A_matrix[2 * i + 1, 2 * j + 1] = sym.diff(fn_phi, phi_)
return A_matrix, V_matrix

@lru_cache(maxsize=1000) # noqa: B019
def _compute_A_and_V_matrices_old(self, n, Vdc_ex_variable, H, frequency):
A_matrix = sym.zeros(2 * n, 2 * n)
V_matrix = sym.zeros(2 * n, 1)
U = self.create_energy(H=H, volumetric=False)
Expand Down
38 changes: 37 additions & 1 deletion cmtj/utils/parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from multiprocess import Pool
from tqdm import tqdm

__all__ = ["distribute"]
from ..models.general_sb import LayerDynamic

__all__ = ["distribute", "parallel_vsd_sb_model"]


def distribute(
Expand Down Expand Up @@ -47,3 +49,37 @@ def func_wrapper(iterable):
iterable, output = result
indx = indexes[iterables.index(iterable)]
yield indx, output


def parallel_vsd_sb_model(
simulation_fn: Callable,
frequencies: list[float],
Hvecs: list[list[float]],
layers: list[LayerDynamic],
J1: list[float] = None,
J2: list[float] = None,
iDMI: list[float] = None,
n_cores: int = None,
):
"""
Parallelise the VSD SB model.
:param simulation_fn: function to be distributed.
This function must take a tuple of arguments, where the first argument is the
frequency, then Hvectors, the list of layers and finally the list of J1 and J2 values.
:param frequencies: list of frequencies
:param Hvecs: list of Hvectors in cartesian coordinates
:param layers: list of layers
:param J1: list of J1 values
:param J2: list of J2 values
:param n_cores: number of cores to use.
:returns: list of simulation_fn outputs for each frequency
"""
if J1 is None:
J1 = [0] * (len(layers) - 1)
if J2 is None:
J2 = [0] * (len(layers) - 1)
if iDMI is None:
iDMI = [0] * (len(layers) - 1)
args = [(f, Hvecs, *layers, J1, J2, iDMI) for f in frequencies]
with Pool(processes=n_cores) as pool:
return list(tqdm(pool.imap(simulation_fn, args), total=len(frequencies)))
184 changes: 143 additions & 41 deletions cmtj/utils/resistance.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from functools import lru_cache
from typing import Union

import numpy as np
Expand All @@ -8,9 +9,7 @@
EPS = np.finfo("float64").resolution


def compute_sd(
dynamic_r: np.ndarray, dynamic_i: np.ndarray, integration_step: float
) -> np.ndarray:
def compute_sd(dynamic_r: np.ndarray, dynamic_i: np.ndarray, integration_step: float) -> np.ndarray:
"""Computes the SD voltage.
:param dynamic_r: magnetoresistance from log
:param dynamic_i: excitation current
Expand Down Expand Up @@ -51,11 +50,7 @@ def compute_resistance(
for i in range(number_of_layers):
w_l = w[i] / l[i]
SxAll[i] = Rx0[i] + (AMR[i] * m[i, 0] ** 2 + SMR[i] * m[i, 1] ** 2)
SyAll[i] = (
Ry0[i]
+ 0.5 * AHE[i] * m[i, 2]
+ (w_l) * (SMR[i] - AMR[i]) * m[i, 0] * m[i, 1]
)
SyAll[i] = Ry0[i] + 0.5 * AHE[i] * m[i, 2] + (w_l) * (SMR[i] - AMR[i]) * m[i, 0] * m[i, 1]
return SxAll, SyAll


Expand All @@ -77,10 +72,7 @@ def calculate_magnetoresistance(Rp: float, Rap: float, m: np.ndarray):
if not isinstance(m, np.ndarray):
m = np.asarray(m)
if m.shape[0] != 2:
raise ValueError(
"The magnetoresistance can only be computed for 2 layers"
f". Current shape {m.shape}"
)
raise ValueError("The magnetoresistance can only be computed for 2 layers" f". Current shape {m.shape}")
return Rp + 0.5 * (Rap - Rp) * np.sum(m[0] * m[1], axis=0)


Expand Down Expand Up @@ -179,6 +171,46 @@ def angular_calculate_resistance_gmr(
return compute_gmr(Rp, Rap, m1, m2)


@lru_cache(maxsize=5)
def Rxx_symbolic(id: int, AMR: float, SMR: float):
"""Compute the Rxx resistance for a given layer.
:param id: layer id
:param AMR: anisotropic magnetoresistance
:param SMR: spin Hall magnetoresistance
"""
theta1 = sym.Symbol(r"\theta_" + str(id))
phi1 = sym.Symbol(r"\phi_" + str(id))
m = sym.Matrix(
[
sym.sin(theta1) * sym.cos(phi1),
sym.sin(theta1) * sym.sin(phi1),
sym.cos(theta1),
]
)
return AMR * m[0] ** 2 + SMR * m[1] ** 2, theta1, phi1, m


@lru_cache(maxsize=5)
def Rxy_symbolic(id: int, AMR: float, SMR: float, AHE: float, w_l: float):
"""Compute the Rxy resistance for a given layer.
:param id: layer id
:param AMR: anisotropic magnetoresistance
:param SMR: spin Hall magnetoresistance
:param AHE: anomalous Hall effect
:param w_l: width to length ratio
"""
theta1 = sym.Symbol(r"\theta_" + str(id))
phi1 = sym.Symbol(r"\phi_" + str(id))
m = sym.Matrix(
[
sym.sin(theta1) * sym.cos(phi1),
sym.sin(theta1) * sym.sin(phi1),
sym.cos(theta1),
]
)
return (0.5 * AHE * m[-1]) + w_l * (SMR - AMR) * m[0] * m[1], theta1, phi1, m


def calculate_linearised_resistance(
GMR: float,
AMR: list[float],
Expand All @@ -189,32 +221,103 @@ def calculate_linearised_resistance(
:param GMR: GMR
:param AMR: AMR
:param SMR: SMR
:param stationary_angles: stationary angles [t1, p1, t2, p2]
:param linearised_angles: linearised angles [dt1, dp1, dt2, dp2]
"""
theta1 = sym.Symbol(r"\theta_1")
phi1 = sym.Symbol(r"\phi_1")
theta2 = sym.Symbol(r"\theta_2")
phi2 = sym.Symbol(r"\phi_2")
m1 = sym.Matrix(
[
sym.sin(theta1) * sym.cos(phi1),
sym.sin(theta1) * sym.sin(phi1),
sym.cos(theta1),
]

Rxx1, theta1, phi1, m1 = Rxx_symbolic(1, AMR[0], SMR[0])
Rxx2, theta2, phi2, m2 = Rxx_symbolic(2, AMR[1], SMR[1])
GMR_resistance = GMR * (1 - (m1.dot(m2))) / 2.0
return Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2


def Rxx_parallel_bilayer_expr():
"""Get the symbolic expressions for the parallel and linearised resistance of a bilayer system.
:returns: linearised and parallel resistance functions
Signals:
- GMR: GMR
- AMR1: AMR of layer 1
- SMR1: SMR of layer 1
- AMR2: AMR of layer 2
- SMR2: SMR of layer 2
- stationary angles: [t1, p1, t2, p2]
- linearised angles: [dt1, dp1, dt2, dp2]
Function signatures
- Rlin_func: linearised resistance function
f(GMR, AMR1, SMR1, AMR2, SMR2, [t1, p1, t2, p2], [dt1, dp1, dt2, dp2])
- R_func: series resistance function
f(GMR, AMR1, SMR1, AMR2, SMR2, [t1, p1, t2, p2])
"""
AMR_1 = sym.Symbol(r"\mathrm{AMR}_1")
SMR_1 = sym.Symbol(r"\mathrm{SMR}_1")
AMR_2 = sym.Symbol(r"\mathrm{AMR}_2")
SMR_2 = sym.Symbol(r"\mathrm{SMR}_2")
GMR_s = sym.Symbol(r"\mathrm{GMR}")
R_1, t1, p1, m1 = Rxx_symbolic(1, AMR_1, SMR_1)
R_2, t2, p2, m2 = Rxx_symbolic(2, AMR_2, SMR_2)
gmr_term = GMR_s * (1 - m1.dot(m2)) / 2

Rparallel = gmr_term + (R_1 * R_2) / (R_1 + R_2 + EPS)
linearised_terms = sym.symbols(r"\partial\theta_1, \partial\phi_1, \partial\theta_2, \partial\phi_2")
dRdtheta1 = sym.diff(Rparallel, t1) * linearised_terms[0]
dRdphi1 = sym.diff(Rparallel, p1) * linearised_terms[1]
dRdtheta2 = sym.diff(Rparallel, t2) * linearised_terms[2]
dRdphi2 = sym.diff(Rparallel, p2) * linearised_terms[3]

linearised_R = dRdtheta1 + dRdtheta2 + dRdphi1 + dRdphi2

Rlin_func = sym.lambdify(
[GMR_s, AMR_1, SMR_1, AMR_2, SMR_2, [t1, p1, t2, p2], linearised_terms],
linearised_R,
)
m2 = sym.Matrix(
[
sym.sin(theta2) * sym.cos(phi2),
-sym.sin(theta2) * sym.sin(phi2),
sym.cos(theta2),
]
R_func = sym.lambdify([GMR_s, AMR_1, SMR_1, AMR_2, SMR_2, [t1, p1, t2, p2]], Rparallel)

return Rlin_func, R_func


def Rxx_series_bilayer_expr():
"""Get the symbolic expressions for the series and linearised resistance of a bilayer system.
:returns: linearised and series resistance functions
Signals:
- GMR: GMR
- AMR1: AMR of layer 1
- SMR1: SMR of layer 1
- AMR2: AMR of layer 2
- SMR2: SMR of layer 2
- stationary angles: [t1, p1, t2, p2]
- linearised angles: [dt1, dp1, dt2, dp2]
Function signatures
- Rlin_func: linearised resistance function
f(GMR, AMR1, SMR1, AMR2, SMR2, [t1, p1, t2, p2], [dt1, dp1, dt2, dp2])
- R_func: series resistance function
f(GMR, AMR1, SMR1, AMR2, SMR2, [t1, p1, t2, p2])
"""
AMR_1 = sym.Symbol(r"\mathrm{AMR}_1")
SMR_1 = sym.Symbol(r"\mathrm{SMR}_1")
AMR_2 = sym.Symbol(r"\mathrm{AMR}_2")
SMR_2 = sym.Symbol(r"\mathrm{SMR}_2")
GMR_s = sym.Symbol(r"\mathrm{GMR}")
R_1, t1, p1, m1 = Rxx_symbolic(1, AMR_1, SMR_1)
R_2, t2, p2, m2 = Rxx_symbolic(2, AMR_2, SMR_2)
gmr_term = GMR_s * (1 - m1.dot(m2)) / 2

Rseries = gmr_term + R_1 + R_2
linearised_terms = sym.symbols(r"\partial\theta_1, \partial\phi_1, \partial\theta_2, \partial\phi_2")
dRdtheta1 = sym.diff(Rseries, t1) * linearised_terms[0]
dRdphi1 = sym.diff(Rseries, p1) * linearised_terms[1]
dRdtheta2 = sym.diff(Rseries, t2) * linearised_terms[2]
dRdphi2 = sym.diff(Rseries, p2) * linearised_terms[3]

linearised_R = dRdtheta1 + dRdtheta2 + dRdphi1 + dRdphi2

Rlin_func = sym.lambdify(
[GMR_s, AMR_1, SMR_1, AMR_2, SMR_2, [t1, p1, t2, p2], linearised_terms],
linearised_R,
)
GMR_resistance = GMR * (1 - (m1.dot(m2))) / 2.0
R_func = sym.lambdify([GMR_s, AMR_1, SMR_1, AMR_2, SMR_2, [t1, p1, t2, p2]], Rseries)

Rxx1 = AMR[0] * m1[0] ** 2 + SMR[0] * m1[1] ** 2
Rxx2 = AMR[1] * m2[0] ** 2 + SMR[1] * m2[1] ** 2
return Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2
return Rlin_func, R_func


def calculate_linearised_resistance_parallel(
Expand All @@ -236,19 +339,20 @@ def calculate_linearised_resistance_parallel(
t02, p02 = stationary_angles[2:]
dt1, dp1 = linearised_angles[:2]
dt2, dp2 = linearised_angles[2:]
Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2 = (
calculate_linearised_resistance(GMR, AMR, SMR)
)
Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2 = calculate_linearised_resistance(GMR, AMR, SMR)
Rparallel = GMR_resistance
if any(AMR) or any(SMR):
Rparallel += (Rxx1 * Rxx2) / (Rxx1 + Rxx2 + EPS)
elif GMR == 0:
return 0, 0
dRparallel = (
sym.diff(Rparallel, theta1) * dt1
+ sym.diff(Rparallel, phi1) * dp1
+ sym.diff(Rparallel, theta2) * dt2
+ sym.diff(Rparallel, phi2) * dp2
)

if isinstance(dRparallel, (list, np.ndarray)):
dRparallel = dRparallel[0]
dRparallel = dRparallel.subs(
{
theta1: t01,
Expand Down Expand Up @@ -287,9 +391,7 @@ def calculate_linearised_resistance_series(
t02, p02 = stationary_angles[2:]
dt1, dp1 = linearised_angles[:2]
dt2, dp2 = linearised_angles[2:]
Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2 = (
calculate_linearised_resistance(GMR, AMR, SMR)
)
Rxx1, Rxx2, GMR_resistance, theta1, phi1, theta2, phi2 = calculate_linearised_resistance(GMR, AMR, SMR)
Rseries = GMR_resistance + Rxx1 + Rxx2
dRseries = (
sym.diff(Rseries, theta1) * dt1
Expand Down
192 changes: 181 additions & 11 deletions docs/tutorials/SBModel.ipynb

Large diffs are not rendered by default.

Loading

0 comments on commit 621f391

Please sign in to comment.