Skip to content

Commit

Permalink
Merge pull request #13 from LemurPwned/thermal
Browse files Browse the repository at this point in the history
Thermal merge queue PR
  • Loading branch information
LemurPwned authored Nov 11, 2022
2 parents b3ea062 + ecc9916 commit c95bac2
Show file tree
Hide file tree
Showing 30 changed files with 2,241 additions and 462 deletions.
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Changelog

## [Unreleased yet]

- Adding SB model as an alternative mode of calculation.

## 1.2.0

- Oersted field computation helper class in [cmtj/models/oersted.py](cmtj/models/oersted.py). Basic functionality is there, but needs to be futher tested and documented. Next release potentially will move the computation to C++ for speed.
- Added Heun (2nd order) solver and made it default for thermal computation. This is a more stable solver than the Euler solver, but is slower. The Euler solver is still available as an option.
- Stack class now supports arbitrary layer ids to be coupled.
- Extended the plotting capabilities of the Stack class. Now supports plotting of the magnetic field and the current density.
- Added alternative STT formulation which in some cases may be useful.
- Fixed some minor bugs in the thermal solver.
- Fixed some minor bugs in the Stack class.
- Updating tutorials on the docs page.
- Bunch of extra documentation and examples.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
[![Downloads](https://img.shields.io/pypi/dm/cmtj.svg)]()

## Short description

A name may be misleading -- the MTJ (Magnetic Tunnel Junctions) are not the only structures that may be simulated.
The library allows for macromagnetic simulation of various multilayer spintronic structures. The package uses C++ implementation of (s)LLGS (stochastic Landau-Lifschitz-Gilbert-Slonczewski) equation with various field contributions included for instance: anisotropy, interlayer exchange coupling, demagnetisation, dipole fields etc.
The library allows for macromagnetic simulation of various multilayer spintronic structures. The package uses C++ implementation of (s)LLGS (stochastic Landau-Lifschitz-Gilbert-Slonczewski) equation with various field contributions included for instance: anisotropy, interlayer exchange coupling, demagnetisation, dipole fields etc.
It is also possible to connect devices in parallel or in series to have electrically coupled arrays.

## Quickstart
Expand Down Expand Up @@ -72,11 +73,13 @@ There's a GUI version available! If you wish to conduct a subset of simulations,
## Citing

We would appreciate citing either of the listed work if you decide to use the project:
1) [**Numerical model of Harmonic Hall voltage detection for spintronic devices**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.106.024403)

1. [**Numerical model of Harmonic Hall voltage detection for spintronic devices**](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.106.024403)

```bibtex
@article{PhysRevB.106.024403,
title = {Numerical model of harmonic Hall voltage detection for spintronic devices},
author = {Zi\ifmmode \mbox{\k{e}}\else \k{e}\fi{}tek, S\l{}awomir and Mojsiejuk, Jakub and Grochot, Krzysztof and \L{}azarski, Stanis\l{}aw and Skowro\ifmmode \acute{n}\else \'{n}\fi{}ski, Witold and Stobiecki, Tomasz},
author = {Ziętek, Sławomir and Mojsiejuk, Jakub and Grochot, Krzysztof and Łazarski, Stanisław and Skowroński, Witold and Stobiecki, Tomasz},
journal = {Phys. Rev. B},
volume = {106},
issue = {2},
Expand All @@ -89,7 +92,9 @@ We would appreciate citing either of the listed work if you decide to use the pr
url = {https://link.aps.org/doi/10.1103/PhysRevB.106.024403}
}
```
2) [**A comprehensive simulation package for analysis of multilayer spintronic devices**](https://arxiv.org/abs/2207.11606)

2. [**A comprehensive simulation package for analysis of multilayer spintronic devices**](https://arxiv.org/abs/2207.11606)

```bibtex
@article{mojsiejuk_comprehensive_2022,
title = {A comprehensive simulation package for analysis of multilayer spintronic devices},
Expand All @@ -106,7 +111,11 @@ We would appreciate citing either of the listed work if you decide to use the pr
}
```

## Development
# Development

## Acknowledgements

Many thanks to professor Jack Sankey for his help with the development of thermal contributions, with inspiration from the [macrospinmob project](https://github.com/Spinmob/macrospinmob).

## Contributions

Expand Down Expand Up @@ -147,7 +156,10 @@ There are couple of stages to building the documentation
This stage is done by running:
`python3 docs/docgen.py `
The deployment of the documentation is done via:

```bash
mkdocs gh-deploy
```
```bash
mkdocs gh-deploy
```
But first, worth a check:
```bash
mkdocs serve
```
1 change: 1 addition & 0 deletions cmtj/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ class Reference:

DormandPrice: SolverMode
EulerHeun: SolverMode
Heun: SolverMode
RK4: SolverMode
bottom: Reference
fixed: Reference
Expand Down
223 changes: 223 additions & 0 deletions cmtj/models/oersted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import math
from dataclasses import dataclass
from typing import Literal

import matplotlib.pyplot as plt
import numpy as np
from numba import njit, prange
from tqdm import tqdm

two_pi = math.pi * 2


@njit
def distance(p1, p2):
return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)


@njit
def ampere_law(I, p1, p2):
r = distance(p1, p2)
return I / (two_pi * r)


@dataclass
class Block:
ix: int
iy: int
iz: int
dx: float
dy: float
dz: float
Hlocal: float = 0
I: float = 0

def __post_init__(self):
self.x = (self.ix + 1) * self.dx / 2. # compute center point
self.y = (self.iy + 1) * self.dy / 2. # compute center point
self.z = (self.iz + 1) * self.dz / 2. # compute center point
self.area = self.dx * self.dy
self.dl = self.dz
return self.dz

def distance_sqr_from(self, other_block: 'Block'):
return (self.x - other_block.x)**2 + (self.y - other_block.y)**2 + (
self.z - other_block.z)**2

def set_I(self, I):
self.I = I

def set_j(self, j):
self.I = j * self.area

def add_H(self, H):
self.Hlocal += H

def biot_savart(self, other_block: 'Block'):
r = distance((self.x, self.y), (other_block.x, other_block.y))
if r < 1e-15:
return 0.
H = other_block.I * other_block.area * self.dl / r**2
return H / (4 * math.pi)

def ampere_law(self, other_block: 'Block'):
return ampere_law(other_block.I, (self.x, self.y),
(other_block.x, other_block.y))

def __eq__(self, __o: 'Block') -> bool:
if (self.ix == __o.ix) and (self.iy == __o.iy) and (self.iz == __o.iz):
return True
return False


class Structure:

def __init__(self,
maxX,
maxY,
maxZ,
dx,
dy,
dz,
method: Literal['ampere', 'biot-savart'] = 'ampere') -> None:
self.maxX = maxX
self.maxY = maxY
self.maxZ = maxZ
self.dx = dx
self.dy = dy
self.dz = dz
self.Xsize = math.ceil(maxX / dx)
self.Ysize = math.ceil(maxY / dy)
self.Zsize = max(math.ceil(maxZ / dz), 1)
print(f"Creating {self.Xsize}x{self.Ysize}x{self.Zsize} blocks")
if (method == 'ampere') and (self.Zsize > 1):
raise ValueError("Wasting compute with z dim non-zero!")

self.blocks = self.init_blocks()
self.borders = []
self.labels = []

def set_region_I_idx(self, I, min_y_indx, max_y_indx=-1):
if max_y_indx == -1:
max_y_indx = self.Ysize
I_mag = I / (self.Xsize *
(min(max_y_indx + 1, self.Ysize) - min_y_indx))
# print(f"Setting I={I*1e3:.2f}mA in region {min_y_indx}:{max_y_indx}")
# print(f"Unit I={I_mag*1e6:.2f}uA")
for yindx in prange(min_y_indx, min(max_y_indx, self.Ysize)):
for x in range(self.Xsize):
for zindx in range(self.Zsize):
self.blocks[x, yindx, zindx].set_I(I_mag)

def set_region_I(self, I, min_y, max_y=-1, label=None):
# convert to index
self.borders.append(min_y)
self.labels.append(label)
min_y_indx = math.ceil(min_y / self.dy)
min_y_indx, max_y_indx = self.__ycoords2indx(min_y, max_y)
self.set_region_I_idx(I, min_y_indx, max_y_indx)

def init_blocks(self):
null_blocks = np.empty((self.Xsize, self.Ysize, self.Zsize),
dtype=Block)
for ix in prange(self.Xsize):
for iy in range(self.Ysize):
for iz in range(self.Zsize):
null_blocks[ix, iy, iz] = Block(ix, iy, iz, self.dx,
self.dy, self.dz)
return null_blocks

def reset(self):
self.blocks = self.init_blocks()

def compute_blocks(self):
all_blocks = self.blocks.flatten()
for i in tqdm(prange(len(all_blocks)), mininterval=0.5):
for j in range(i + 1, len(all_blocks)):
block = all_blocks[i]
other_block = all_blocks[j]
# use symmetry to reduce complexity
Hval1 = block.ampere_law(other_block)
Hval2 = other_block.ampere_law(block)
block.Hlocal += Hval1
other_block.Hlocal += Hval2

def __ycoords2indx(self, min_coord_y, max_coord_y):
min_y_indx = math.ceil(min_coord_y / self.dy)
if max_coord_y == -1:
max_y_indx = self.maxY
else:
max_y_indx = math.ceil(max_coord_y / self.dy)
return min_y_indx, max_y_indx

def get_region_contributions_idx(self, min_y_indx, max_y_indx=-1):
if max_y_indx == -1:
max_y_indx = self.Ysize
H = 0
for yindx in prange(min_y_indx, min(max_y_indx, self.Ysize)):
for x in range(self.Xsize):
for zindx in range(self.Zsize):
H += self.blocks[x, yindx, zindx].Hlocal
return H

def compute_region_contribution(self, source_min_y, source_max_y,
target_min_y, target_max_y):
source_min_y_indx, source_max_y_indx = self.__ycoords2indx(
source_min_y, source_max_y)
target_min_y_indx, target_max_y_indx = self.__ycoords2indx(
target_min_y, target_max_y)

return self.compute_region_contribution_idx(source_min_y_indx,
source_max_y_indx,
target_min_y_indx,
target_max_y_indx)

def compute_region_contribution_idx(self, source_min_y_indx,
source_max_y_indx, target_min_y_indx,
target_max_y_indx):
print(
f"Computing H from {source_min_y_indx}:{source_max_y_indx} to {target_min_y_indx}:{target_max_y_indx}"
)
total_H = 0
block_src = self.blocks[:, source_min_y_indx:
source_max_y_indx, :].flatten()
block_targ = self.blocks[:, target_min_y_indx:
target_max_y_indx, :].flatten()
print(f"Source blocks: {block_src.shape}")

print(f"Target blocks: {block_targ.shape}")
for source_block in block_src:
for target_block in block_targ:
if source_block == target_block:
continue
H = target_block.ampere_law(source_block)
total_H += H
return total_H

def show_field(self, log=False):
field = np.zeros((self.Xsize, self.Ysize))
for block in self.blocks.flatten():
field[block.ix, block.iy] = block.Hlocal
with plt.style.context(['nature', 'science']):
fig, ax = plt.subplots(dpi=300)

if log:
img = ax.pcolormesh(np.log(field).T, cmap='viridis')
else:
img = ax.pcolormesh(field.T, cmap='viridis')
# add colorbar
ax.set_xticklabels([
f"{x*1e9:.2f}"
for x in np.linspace(0, self.Xsize * self.dx, self.Xsize)
])
ax.set_yticklabels([
f"{y*1e9:.2f}"
for y in np.linspace(0, self.Ysize * self.dy, self.Ysize)
])
ax.set_xlabel("x (nm)")
ax.set_ylabel("y (nm)")
# add colorbar
for unq_border, label in zip(self.borders, self.labels):
ax.axhline(unq_border / self.dy, color='crimson')
fig.colorbar(img, ax=ax)
return field
Loading

0 comments on commit c95bac2

Please sign in to comment.