Skip to content

Commit 58f58a2

Browse files
authored
Merge pull request #81 from LemurPwned/streamlit/ui
Streamlit/UI
2 parents 7801e06 + a3b0324 commit 58f58a2

File tree

5 files changed

+248
-112
lines changed

5 files changed

+248
-112
lines changed

README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
[![pages-build-deployment](https://github.com/LemurPwned/cmtj/actions/workflows/pages/pages-build-deployment/badge.svg?branch=gh-pages)](https://github.com/LemurPwned/cmtj/actions/workflows/pages/pages-build-deployment)
77
[![Version](https://img.shields.io/pypi/v/cmtj)](https://pypi.org/project/cmtj/)
88
[![License](https://img.shields.io/pypi/l/cmtj.svg)](https://github.com/LemurPwned/cmtj/blob/master/LICENSE)
9-
[![Streamlit](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](http://cmtj-simulations.streamlit.app/)
9+
[![Streamlit](https://static.streamlit.io/badges/streamlit_badge_black_white.svg)](https://cmtj-app.streamlit.app/spectrum)
1010
![Downloads](https://img.shields.io/pypi/dm/cmtj.svg)
1111

1212
## Table of contents
@@ -36,7 +36,14 @@ It is also possible to connect devices in parallel or in series to have electric
3636

3737
## Web GUI
3838

39-
Check out the [streamlit hosted demo here](http://cmtj-simulations.streamlit.app/). You can simulate PIMM spectra and Spin-Diode spectra there. Let us know if you have any issues with the demo.
39+
Check out the [streamlit hosted demo here](https://cmtj-app.streamlit.app/spectrum).
40+
You can simulate:
41+
42+
* PIMM spectra and Spin-Diode spectra
43+
* Try some optimization fitting
44+
* Fit multi-domain or multi-level M(H) or R(H) loops in [Domain mode](https://cmtj-app.streamlit.app)
45+
46+
Let us know if you have any issues with the demo.
4047

4148
## Quickstart
4249

view/domain.py

Lines changed: 135 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,33 @@
55
import streamlit as st
66
from helpers import read_mh_data
77
from simulation_fns import create_single_domain, get_axis_angles
8-
from utils import GENERIC_BOUNDS, GENERIC_UNITS
8+
from utils import GENERIC_BOUNDS, GENERIC_UNITS, get_init_kval
99

1010
from cmtj import AxialDriver, Junction
1111
from cmtj.utils import FieldScan
1212

1313
_lock = RLock()
1414

15+
1516
with st.container():
1617
st.write("# Domain fitting")
17-
18-
st.write(
19-
"Fit M(H) to multidomain model. "
20-
"Upload your file with experimental data: with columns H, mx, my, mz.\n"
21-
"First column is always H in A/m, the rest are the components of the magnetisation in range (-1, 1)."
22-
"If you upload just two columns, the script will assume that the data is in the form H, mx."
23-
)
18+
with st.expander("Explanation"):
19+
st.write(
20+
"#### Fit M(H) to multidomain model. \n"
21+
"##### Experimental data\n"
22+
"Upload your file with experimental data: with columns H, mx, my, mz.\n"
23+
"First column is always H in A/m, the rest are the components of the magnetisation in range (-1, 1)."
24+
"If you upload just two columns, the script will assume that the data is in the form H, mx.\n"
25+
"##### Simulation\n"
26+
"Add new domains in the sidebar panel. Area of each domain is a weight. The resulting $m_\mathrm{mixed}(h)$ plot is produces with\n\n"
27+
r"$m_\mathrm{mixed} = (1/{\sum_i a_i})\sum_i a_i m_i$"
28+
"\n\n where $a_i$ is the area of $i$th domain and $m_i$ is $m(h)$ for that domain.\n"
29+
"##### Coordinate system\n"
30+
r"$\theta$ is the polar angle and $\phi$ is the azimuthal angle."
31+
r" $\theta=90^\circ$ denotes fully in-plane magnetisation, "
32+
r"$\theta=0^\circ$ denotes out-of-plane magnetisation (positive z-direction)"
33+
r"$\phi=0^\circ$ denotes magnetisation pointing in the positive x direction."
34+
)
2435

2536
progress_bar = st.progress(0)
2637

@@ -46,7 +57,8 @@ def simulate_domains():
4657
weights = [st.session_state[f"area{i}"] * 1e-18 for i in range(st.session_state.N)]
4758
wsum = sum(weights)
4859
weights = [w / wsum for w in weights]
49-
Mmixed = np.zeros((len(Hscan), 3))
60+
Mmixed = np.zeros((len(Hscan), 3), dtype=np.float16)
61+
Mdomains = np.zeros((st.session_state.N, len(Hscan), 3), np.float16)
5062
for i, H in enumerate(Hvecs):
5163
j.clearLog()
5264
j.setLayerExternalFieldDriver("all", AxialDriver(*H))
@@ -60,11 +72,13 @@ def simulate_domains():
6072
mx = np.mean(log[f"domain_{l}_mx"][-st.session_state.last_N :])
6173
my = np.mean(log[f"domain_{l}_my"][-st.session_state.last_N :])
6274
mz = np.mean(log[f"domain_{l}_mz"][-st.session_state.last_N :])
63-
Mmixed[i] += np.array([mx, my, mz]) * weights[l]
75+
Mdomains[l, i] = np.array([mx, my, mz])
76+
Mmixed[i] += Mdomains[l, i] * weights[l]
6477
# for each layer take last N values
6578
progress_bar.progress(int((i / maxlen) * 100))
6679
st.session_state["Mmixed"] = Mmixed
6780
st.session_state["Hscan"] = Hscan
81+
st.session_state["Mdomains"] = Mdomains
6882

6983

7084
with st.sidebar:
@@ -77,6 +91,52 @@ def simulate_domains():
7791
accept_multiple_files=False,
7892
key="upload",
7993
)
94+
st.checkbox("Show domains in polar angles", key="domain_use_angle", value=False)
95+
domain_params = st.expander("Domain parameters", expanded=True)
96+
with domain_params:
97+
N = st.number_input(
98+
"Number of domains", min_value=1, max_value=10, value=1, key="N"
99+
)
100+
for i in range(N):
101+
st.markdown(f"#### Domain {i+1}")
102+
unit_k = GENERIC_UNITS["K"]
103+
st.number_input(
104+
f"K ({i+1}) " r" ($\mathrm{" f"{unit_k}" "}$)",
105+
min_value=GENERIC_BOUNDS["K"][0],
106+
max_value=GENERIC_BOUNDS["K"][1],
107+
value=1.2e3,
108+
step=10.0,
109+
key=f"K{i}",
110+
help="Uniaxial anisotropy constant of the domain. Does not include shape anisotropy.",
111+
)
112+
113+
st.number_input(
114+
f"area ({i+1}) " r"($\mathrm{nm^2}$)",
115+
min_value=1.0,
116+
max_value=500.0,
117+
value=10.0,
118+
key=f"area{i}",
119+
help="Area of the domain. In fact, this is a weight of the domain.",
120+
)
121+
122+
st.number_input(
123+
r"$\theta_\mathrm{K}$ (deg.)",
124+
min_value=0.0,
125+
max_value=180.0,
126+
value=get_init_kval(i),
127+
key=f"theta_K{i}",
128+
help="Polar angle of the anisotropy axis",
129+
)
130+
st.number_input(
131+
r"$\phi_\mathrm{K}$ (deg.)",
132+
min_value=0.0,
133+
max_value=180.0,
134+
value=get_init_kval(i),
135+
key=f"phi_K{i}",
136+
help="Azimuthal angle of the anisotropy axis",
137+
)
138+
st.markdown("-----\n")
139+
80140
with st.expander("Simulation parameters", expanded=True):
81141
st.selectbox(
82142
"H axis", options=["x", "y", "z", "xy", "xz", "yz"], key="H_axis", index=0
@@ -125,6 +185,7 @@ def simulate_domains():
125185
value=1.8,
126186
step=0.01,
127187
key="Ms_shared",
188+
help="Saturation magnetisation of the system. Affects the shape anisotropy",
128189
)
129190
st.number_input(
130191
"thickness (nm)",
@@ -141,40 +202,9 @@ def simulate_domains():
141202
key="alpha_shared",
142203
format="%.3f",
143204
)
144-
domain_params = st.expander("Domain parameters", expanded=True)
145-
with domain_params:
146-
N = st.number_input(
147-
"Number of domains", min_value=1, max_value=10, value=1, key="N"
148-
)
149-
for i in range(N):
150-
st.markdown(f"#### Domain {i+1}")
151-
unit_k = GENERIC_UNITS["K"]
152-
st.number_input(
153-
f"K ({i+1}) " r" ($\mathrm{" f"{unit_k}" "}$)",
154-
min_value=GENERIC_BOUNDS["K"][0],
155-
max_value=GENERIC_BOUNDS["K"][1],
156-
value=1.2e3,
157-
step=10.0,
158-
key=f"K{i}",
159-
)
160205

161-
st.number_input(
162-
f"area ({i+1}) " r"($\mathrm{nm^2}$)",
163-
min_value=1.0,
164-
max_value=500.0,
165-
value=10.0,
166-
key=f"area{i}",
167-
)
168-
st.radio(
169-
f"anisotropy axis ({i+1})",
170-
options=["x", "y", "z"],
171-
key=f"anisotropy_axis{i}",
172-
index=2,
173-
)
174-
st.markdown("-----\n")
175206

176-
177-
def render(Hscan, Mmixed):
207+
def render(Hscan, Mmixed, Mdomains=None):
178208
if len(Hscan) <= 0 or len(Mmixed) <= 0:
179209
return
180210
with _lock:
@@ -187,21 +217,35 @@ def render(Hscan, Mmixed):
187217
fields, mh = read_mh_data()
188218
render_from_exp(ax, fields=fields, mh=mh)
189219
render_from_sim(ax, Hscan, Mmixed)
190-
220+
fig.suptitle("Mixed Domain model")
221+
st.pyplot(fig)
222+
n = 3
223+
if st.session_state["domain_use_angle"]:
224+
n = 2
225+
w, h = plt.figaspect(st.session_state.N / n)
226+
fig, ax = plt.subplots(
227+
st.session_state.N, n, dpi=400, figsize=(w, h), sharex=True, sharey=True
228+
)
229+
if st.session_state.N <= 1:
230+
ax = np.array([ax])
231+
try:
232+
render_individual_domains(fig, ax, Hscan, Mdomains)
233+
except IndexError:
234+
pass
191235
st.pyplot(fig)
192236

193237

194238
def render_from_exp(ax, fields, mh):
195239
if len(fields) <= 0 or len(mh) <= 0:
196240
st.toast("No data to render")
197-
241+
fields = np.asarray(fields)
198242
m = np.asarray(mh)
199243
for i, l in zip(range(m.shape[1]), "xyz"):
200244
ax[i].plot(
201-
fields,
245+
fields / 1e3, # A/m --> kA/m
202246
m[:, i],
203247
label=f"$m_{l}$",
204-
color="black",
248+
color="yellow",
205249
marker="x",
206250
alpha=0.5,
207251
linestyle="--",
@@ -221,11 +265,55 @@ def render_from_sim(ax, Hscan, Mmixed):
221265
ax[2].set_xlabel("H (kA/m)")
222266

223267

268+
def render_individual_domains(fig, ax, Hscan, M: list[list[float]]):
269+
n = 3
270+
if st.session_state["domain_use_angle"]:
271+
n = 2
272+
ax[0, 0].set_title(r"$\theta$")
273+
ax[0, 1].set_title(r"$\phi$")
274+
for i, domain_m in enumerate(M):
275+
# convert to polar
276+
theta_m, phi_m, _ = FieldScan.vector2angle(
277+
domain_m[:, 0], domain_m[:, 1], domain_m[:, 2]
278+
)
279+
ax[i, 0].plot(
280+
Hscan / 1e3, theta_m, label=rf"$\theta_{i+1}$", color="crimson"
281+
)
282+
ax[i, 1].plot(
283+
Hscan / 1e3, phi_m, label=rf"$\phi_{i+1}$", color="forestgreen"
284+
)
285+
ax[i, 0].set_ylabel(rf"$\theta_{i+1}$ (deg.)")
286+
ax[i, 1].set_ylabel(rf"$\phi_{i+1}$ (deg.)")
287+
else:
288+
n = 3
289+
ax[0, 0].set_title(r"$m_\mathrm{x}$")
290+
ax[0, 1].set_title(r"$m_\mathrm{y}$")
291+
ax[0, 2].set_title(r"$m_\mathrm{z}$")
292+
for i, domain_m in enumerate(M):
293+
ax[i, 0].plot(
294+
Hscan / 1e3, domain_m[:, 0], label=rf"$m_{i+1}$", color="crimson"
295+
)
296+
ax[i, 1].plot(
297+
Hscan / 1e3, domain_m[:, 1], label=rf"$m_{i+1}$", color="forestgreen"
298+
)
299+
ax[i, 2].plot(
300+
Hscan / 1e3, domain_m[:, 2], label=rf"$m_{i+1}$", color="royalblue"
301+
)
302+
ax[i, 0].set_ylabel(rf"$m_{i+1}$")
303+
for j in range(n):
304+
ax[-1, j].set_xlabel("H (kA/m)")
305+
fig.suptitle("Individual Domains") # fig.legend()
306+
307+
224308
simulate_btn = st.button(
225309
"Simulate",
226310
key="simulate",
227311
on_click=simulate_domains,
228312
type="primary",
229313
)
230314

231-
render(st.session_state.get("Hscan", []), st.session_state.get("Mmixed", []))
315+
render(
316+
st.session_state.get("Hscan", []),
317+
st.session_state.get("Mmixed", []),
318+
Mdomains=st.session_state.get("Mdomains", []),
319+
)

view/simulation_fns.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@
1414

1515
def create_single_domain(id_: str) -> Layer:
1616
demag = [CVector(0, 0, 0), CVector(0, 0, 0), CVector(0, 0, 1)]
17-
Kdir1 = get_axis_cvector(st.session_state[f"anisotropy_axis{id_}"])
17+
# Kdir1 = get_axis_cvector(st.session_state[f"anisotropy_axis{id_}"])
18+
Kdir = FieldScan.angle2vector(
19+
theta=st.session_state[f"theta_K{id_}"], phi=st.session_state[f"phi_K{id_}"]
20+
)
1821
layer = Layer(
1922
id=f"domain_{id_}",
20-
mag=Kdir1,
21-
anis=Kdir1,
23+
mag=Kdir,
24+
anis=Kdir,
2225
Ms=st.session_state["Ms_shared"],
2326
thickness=st.session_state["thickness_shared"] * 1e-9,
2427
cellSurface=1e-16,
@@ -34,11 +37,13 @@ def create_single_domain(id_: str) -> Layer:
3437
def create_single_layer(id_: str) -> tuple:
3538
"""Do not forget to rescale the units!"""
3639
demag = [CVector(0, 0, 0), CVector(0, 0, 0), CVector(0, 0, 1)]
37-
Kdir1 = get_axis_cvector(st.session_state[f"anisotropy_axis{id_}"])
40+
Kdir = FieldScan.angle2vector(
41+
theta=st.session_state[f"theta_K{id_}"], phi=st.session_state[f"phi_K{id_}"]
42+
)
3843
layer = Layer(
3944
id=f"layer_{id_}",
40-
mag=Kdir1,
41-
anis=Kdir1,
45+
mag=Kdir,
46+
anis=Kdir,
4247
Ms=st.session_state[f"Ms{id_}"],
4348
thickness=st.session_state[f"thickness{id_}"] * 1e-9,
4449
cellSurface=1e-16,

0 commit comments

Comments
 (0)