Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cmtj updates #77

Merged
merged 5 commits into from
Aug 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "Python 3",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
"customizations": {
"codespaces": {
"openFiles": [
"README.md",
"view/streamlit_app.py"
]
},
"vscode": {
"settings": {},
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance"
]
}
},
"updateContentCommand": "[ -f packages.txt ] && sudo apt update && sudo apt upgrade -y && sudo xargs apt install -y <packages.txt; [ -f requirements.txt ] && pip3 install --user -r requirements.txt; pip3 install --user streamlit; echo '✅ Packages installed and Requirements met'",
"postAttachCommand": {
"server": "streamlit run view/streamlit_app.py --server.enableCORS false --server.enableXsrfProtection false"
},
"portsAttributes": {
"8501": {
"label": "Application",
"onAutoForward": "openPreview"
}
},
"forwardPorts": [
8501
]
}
3 changes: 2 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ __pycache__/
.ipynb_checkpoints/
.vscode/
.idea/
assets/
articles/
examples/
docs/
!assets/icon-larger.png
!view/assets/icon-larger.png
Binary file added assets/icon-larger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 3 additions & 4 deletions cmtj/utils/linear.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,9 @@ def amplitude_scan(
Hz = ct * Hspan
if back:
forward = np.vstack((Hx, Hy, Hz)).T
back = forward[::-1]
return np.concatenate((Hspan, Hspan[::-1]),
axis=0), np.concatenate((forward, back),
axis=0)
return np.concatenate((Hspan[:-1], Hspan[::-1]),
axis=0), np.concatenate(
(forward[:-1], forward[::-1]), axis=0)
return Hspan, np.vstack((Hx, Hy, Hz)).T

@staticmethod
Expand Down
3 changes: 2 additions & 1 deletion docker/Dockerfile.app
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ EXPOSE 8501

WORKDIR /app
COPY view/ /app/
COPY assets/ /app/
RUN ls
RUN python3 -m pip install streamlit && \
git clone https://github.com/LemurPwned/cmtj.git && \
cd cmtj && \
python3 -m pip install .[utils]

CMD streamlit run streamlit_app.py
CMD streamlit run nav.py
Binary file added view/assets/icon-larger.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
231 changes: 231 additions & 0 deletions view/domain.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
from threading import RLock
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider modularizing the domain.py file

The domain.py file is quite long and handles multiple responsibilities. Consider splitting it into smaller, more focused components to improve maintainability and readability.


import matplotlib.pyplot as plt
import numpy as np
import streamlit as st
from helpers import read_mh_data
from simulation_fns import create_single_domain, get_axis_angles
from utils import GENERIC_BOUNDS, GENERIC_UNITS

from cmtj import AxialDriver, Junction
from cmtj.utils import FieldScan

_lock = RLock()

with st.container():
st.write("# Domain fitting")

st.write(
"Fit M(H) to multidomain model. "
"Upload your file with experimental data: with columns H, mx, my, mz.\n"
"First column is always H in A/m, the rest are the components of the magnetisation in range (-1, 1)."
"If you upload just two columns, the script will assume that the data is in the form H, mx."
)

progress_bar = st.progress(0)


def simulate_domains():
htheta, hphi = get_axis_angles(st.session_state.H_axis)
Hscan, Hvecs = FieldScan.amplitude_scan(
st.session_state.Hmin * 1e3,
st.session_state.Hmax * 1e3,
st.session_state.Hsteps,
htheta,
hphi,
back=st.session_state.back_to_back_scan,
)

domains = []
for i in range(st.session_state.N):
dom = create_single_domain(i)
domains.append(dom)
j = Junction(layers=domains)
maxlen = len(Hvecs)
# plot data
weights = [st.session_state[f"area{i}"] * 1e-18 for i in range(st.session_state.N)]
wsum = sum(weights)
weights = [w / wsum for w in weights]
Mmixed = np.zeros((len(Hscan), 3))
for i, H in enumerate(Hvecs):
j.clearLog()
j.setLayerExternalFieldDriver("all", AxialDriver(*H))
j.runSimulation(
st.session_state.sim_time * 1e-9,
st.session_state.int_step,
st.session_state.int_step,
)
log = j.getLog()
for l in range(st.session_state.N):
mx = np.mean(log[f"domain_{l}_mx"][-st.session_state.last_N :])
my = np.mean(log[f"domain_{l}_my"][-st.session_state.last_N :])
mz = np.mean(log[f"domain_{l}_mz"][-st.session_state.last_N :])
Mmixed[i] += np.array([mx, my, mz]) * weights[l]
# for each layer take last N values
progress_bar.progress(int((i / maxlen) * 100))
st.session_state["Mmixed"] = Mmixed
st.session_state["Hscan"] = Hscan


with st.sidebar:
experimental_file = st.file_uploader(
"Upload your experimental data here",
help="Upload your data here. "
"Must be `\t` separated values and have H and m headers. "
"Express H in A/m and m in units",
type=["txt", "dat", "tsv"],
accept_multiple_files=False,
key="upload",
)
with st.expander("Simulation parameters", expanded=True):
st.selectbox(
"H axis", options=["x", "y", "z", "xy", "xz", "yz"], key="H_axis", index=0
)
st.number_input(
"Hmin (kA/m)", min_value=-1000.0, max_value=1000.0, value=-400.0, key="Hmin"
)
st.number_input(
"Hmax (kA/m)", min_value=0.0, max_value=1000.0, value=400.0, key="Hmax"
)
st.number_input(
"H steps", min_value=1, max_value=1000, value=50, key="Hsteps", format="%d"
)
# boolean if to back to back scan
st.checkbox("Back to back scan", key="back_to_back_scan", value=True)
st.number_input(
"int_step",
min_value=1e-14,
max_value=1e-12,
value=1e-12,
key="int_step",
format="%.1e",
)
st.number_input(
"sim_time (ns)",
min_value=1,
max_value=500,
value=16,
key="sim_time",
format="%d",
)
st.number_input(
"last N values to consider",
min_value=1,
max_value=1000,
value=100,
key="last_N",
)

shared_params = st.expander("Shared parameters", expanded=True)
with shared_params:
st.slider(
f"Ms ({GENERIC_UNITS['Ms']})",
min_value=GENERIC_BOUNDS["Ms"][0],
max_value=GENERIC_BOUNDS["Ms"][1],
value=1.8,
step=0.01,
key="Ms_shared",
)
st.number_input(
"thickness (nm)",
min_value=1.0,
max_value=10.0,
value=1.0,
key="thickness_shared",
)
st.number_input(
"alpha",
min_value=1e-3,
max_value=0.1,
value=1e-3,
key="alpha_shared",
format="%.3f",
)
domain_params = st.expander("Domain parameters", expanded=True)
with domain_params:
N = st.number_input(
"Number of domains", min_value=1, max_value=10, value=1, key="N"
)
for i in range(N):
st.markdown(f"#### Domain {i+1}")
unit_k = GENERIC_UNITS["K"]
st.number_input(
f"K ({i+1}) " r" ($\mathrm{" f"{unit_k}" "}$)",
min_value=GENERIC_BOUNDS["K"][0],
max_value=GENERIC_BOUNDS["K"][1],
value=1.2e3,
step=10.0,
key=f"K{i}",
)

st.number_input(
f"area ({i+1}) " r"($\mathrm{nm^2}$)",
min_value=1.0,
max_value=500.0,
value=10.0,
key=f"area{i}",
)
st.radio(
f"anisotropy axis ({i+1})",
options=["x", "y", "z"],
key=f"anisotropy_axis{i}",
index=2,
)
st.markdown("-----\n")


def render(Hscan, Mmixed):
if len(Hscan) <= 0 or len(Mmixed) <= 0:
return
with _lock:
with plt.style.context(["science", "nature"]):
w, h = plt.figaspect(1 / 3)
fig, ax = plt.subplots(
1, 3, dpi=400, figsize=(w, h), sharex=True, sharey=True
)
if experimental_file is not None:
fields, mh = read_mh_data()
render_from_exp(ax, fields=fields, mh=mh)
render_from_sim(ax, Hscan, Mmixed)

st.pyplot(fig)


def render_from_exp(ax, fields, mh):
if len(fields) <= 0 or len(mh) <= 0:
st.toast("No data to render")

m = np.asarray(mh)
for i, l in zip(range(m.shape[1]), "xyz"):
ax[i].plot(
fields,
m[:, i],
label=f"$m_{l}$",
color="black",
marker="x",
alpha=0.5,
linestyle="--",
)


def render_from_sim(ax, Hscan, Mmixed):
ax[0].plot(Hscan / 1e3, Mmixed[:, 0], label="$m_x$", color="crimson")
ax[1].plot(Hscan / 1e3, Mmixed[:, 1], label="$m_y$", color="forestgreen")
ax[2].plot(Hscan / 1e3, Mmixed[:, 2], label="$m_z$", color="royalblue")

ax[0].set_title(r"$m_x$")
ax[1].set_title(r"$m_y$")
ax[2].set_title(r"$m_z$")
ax[0].set_xlabel("H (kA/m)")
ax[1].set_xlabel("H (kA/m)")
ax[2].set_xlabel("H (kA/m)")


simulate_btn = st.button(
"Simulate",
key="simulate",
on_click=simulate_domains,
type="primary",
)

render(st.session_state.get("Hscan", []), st.session_state.get("Mmixed", []))
14 changes: 14 additions & 0 deletions view/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@
from cmtj.utils import Filters


def read_mh_data():
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider generalizing read_mh_data function

The read_mh_data() function is specific to a particular file format. Consider generalizing this function to handle various input formats, making it more flexible and reusable.

def read_data(file_format='mh'):
    filedata = st.session_state.upload.read().decode("utf-8")
    lines = filedata.split("\n")

    if file_format == 'mh':
        return parse_mh_data(lines)
    elif file_format == 'other_format':
        return parse_other_format(lines)
    else:
        raise ValueError(f"Unsupported file format: {file_format}")

filedata = st.session_state.upload.read().decode("utf-8")
lines = filedata.split("\n")
fields, mag = [], []
for line in lines[1:]:
if line.startswith("#"):
continue
vals = line.split()
if len(vals):
fields.append(float(vals[0]))
mag.append([float(val) for val in vals[1:]])
return fields, mag


def read_data():
filedata = st.session_state.upload.read().decode("utf-8")
lines = filedata.split("\n")
Expand Down
27 changes: 27 additions & 0 deletions view/nav.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import streamlit as st
from PIL import Image

app_title = "CMTJ UI"
im = Image.open("assets/icon-larger.png")
st.logo(im)
domain_page = st.Page(
"domain.py",
title="Domain fitting",
url_path="domains",
icon=":material/bubble_chart:",
)
spectrum_page = st.Page(
"streamlit_app.py",
title="Spectrum fitting",
url_path="spectrum",
icon=":material/bar_chart:",
)

pg = st.navigation([domain_page, spectrum_page])
st.set_page_config(
page_title=app_title,
page_icon="🦈",
layout="wide",
)
pg.run()
# > streamlit run ./view/streamlit_app.py --server.runOnSave true
Loading
Loading