55import streamlit as st
66from helpers import read_mh_data
77from 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
1010from cmtj import AxialDriver , Junction
1111from cmtj .utils import FieldScan
1212
1313_lock = RLock ()
1414
15+
1516with 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
7084with 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
194238def 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+
224308simulate_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+ )
0 commit comments