llg3d 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
llg3d/llg3d_seq.py ADDED
@@ -0,0 +1,447 @@
1
+ """
2
+ Solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D
3
+ (sequential version for history)
4
+ """
5
+ import argparse
6
+ import sys
7
+ import time
8
+ from dataclasses import dataclass
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+
13
+ # Create a random number generator by setting the seed
14
+ rng = np.random.default_rng(0)
15
+
16
+ # Initialize a sequence of random seeds
17
+ # See: https://numpy.org/doc/stable/reference/random/parallel.html#seedsequence-spawning
18
+ ss = np.random.SeedSequence(12345)
19
+
20
+ # Deploy size x SeedSequence to be passed to child processes
21
+ child_seeds = ss.spawn(1)
22
+ rng = np.random.default_rng(child_seeds[0])
23
+
24
+
25
+ # Parameters: default value and description
26
+ parameters = {
27
+ "N": (500, "Number of temporal iterations"),
28
+ "dt": (1.0e-14, "Time step"),
29
+ "Jx": (300, "Number of points in x"),
30
+ "Jy": (21, "Number of points in y"),
31
+ "Jz": (21, "Number of points in z"),
32
+ "Lx": (2.99e-7, "Length in x"),
33
+ "Ly": (1.0e-8, "Length in y"),
34
+ "Lz": (1.0e-8, "Length in z"),
35
+ "T": (1100, "Temperature"),
36
+ "H_ext": (0.0, "External field"),
37
+ "n_average": (2000, "Starting index of temporal averaging"),
38
+ }
39
+
40
+
41
+ def progress_bar(it, prefix="", size=60, out=sys.stdout):
42
+ """
43
+ Displays a progress bar
44
+ (Source: https://stackoverflow.com/a/34482761/16593179)
45
+ """
46
+
47
+ count = len(it)
48
+
49
+ def show(j):
50
+ x = int(size * j / count)
51
+ print(
52
+ f"{prefix}[{u'█'*x}{('.'*(size-x))}] {j}/{count}",
53
+ end="\r",
54
+ file=out,
55
+ flush=True,
56
+ )
57
+
58
+ show(0)
59
+ for i, item in enumerate(it):
60
+ yield item
61
+ # To avoid slowing down the computation, we do not display at every iteration
62
+ if i % 5 == 0:
63
+ show(i + 1)
64
+ show(i + 1)
65
+ print("\n", flush=True, file=out)
66
+
67
+
68
+ @dataclass
69
+ class Grid:
70
+ """Stores grid data"""
71
+
72
+ # Parameters refer to the entire grid
73
+ Jx: int
74
+ Jy: int
75
+ Jz: int
76
+ Lx: float
77
+ Ly: float
78
+ Lz: float
79
+
80
+ def __post_init__(self) -> None:
81
+ """Calculates grid characteristics"""
82
+ self.dx = self.Lx / (self.Jx - 1)
83
+ self.dy = self.Ly / (self.Jy - 1)
84
+ self.dz = self.Lz / (self.Jz - 1)
85
+ # Shape of the local array for the process
86
+ self.dims = self.Jx, self.Jy, self.Jz
87
+ # Volume of a grid cell
88
+ self.dV = self.dx * self.dy * self.dz
89
+ # Total volume
90
+ self.V = self.Lx * self.Ly * self.Lz
91
+ # Total number of points
92
+ self.ntot = self.Jx * self.Jy * self.Jz
93
+ self.ncell = (self.Jx - 1) * (self.Jy - 1) * (self.Jz - 1)
94
+
95
+ def __repr__(self):
96
+ s = "\t" + "\t\t".join(("x", "y", "z")) + "\n"
97
+ s += f"J =\t{self.Jx}\t\t{self.Jy}\t\t{self.Jz}\n"
98
+ s += f"L =\t{self.Lx}\t\t{self.Ly}\t\t{self.Lz}\n"
99
+ s += f"d =\t{self.dx:.08e}\t{self.dy:.08e}\t{self.dz:.08e}\n\n"
100
+ s += f"dV = {self.dV:.08e}\n"
101
+ s += f"V = {self.V:.08e}\n"
102
+ s += f"ntot = {self.ntot:d}\n"
103
+
104
+ return s
105
+
106
+ def get_filename(self, T: float) -> str:
107
+ """Returns the output file name for a given temperature"""
108
+ suffix = f"T{int(T)}_{self.Jx}x{self.Jy}x{self.Jz}"
109
+ return f"m1_integral_space_{suffix}.txt"
110
+
111
+
112
+ class Element:
113
+ """Abstract class for an element"""
114
+
115
+ A = 0.0
116
+ K = 0.0
117
+ gamma = 0.0
118
+ mu_0 = 0.0
119
+ k_B = 0.0
120
+ lambda_G = 0.0
121
+ M_s = 0.0
122
+ a_eff = 0.0
123
+
124
+ def __init__(self, T: float, H_ext, g: Grid, dt: float) -> None:
125
+ self.g = g
126
+ self.dt = dt
127
+ self.gamma_0 = self.gamma * self.mu_0
128
+
129
+ # --- Characteristic scales ---
130
+ self.coeff_1 = self.gamma_0 * 2.0 * self.A / (self.mu_0 * self.M_s)
131
+ self.coeff_2 = self.gamma_0 * 2.0 * self.K / (self.mu_0 * self.M_s)
132
+ self.coeff_3 = self.gamma_0 * H_ext
133
+
134
+ # corresponds to the temperature actually put into the random field
135
+ T_simu = T * self.g.dx / self.a_eff
136
+ # calculation of the random field related to temperature
137
+ # (we only take the volume over one mesh)
138
+ h_alea = np.sqrt(
139
+ 2
140
+ * self.lambda_G
141
+ * self.k_B
142
+ / (self.gamma_0 * self.mu_0 * self.M_s * self.g.dV)
143
+ )
144
+ H_alea = h_alea * np.sqrt(T_simu) * np.sqrt(1.0 / self.dt)
145
+ self.coeff_4 = H_alea * self.gamma_0
146
+
147
+ def get_CFL(self) -> float:
148
+ """Returns the value of the CFL"""
149
+ return self.dt * self.coeff_1 / self.g.dx**2
150
+
151
+
152
+ class Cobalt(Element):
153
+ A = 30.0e-12
154
+ K = 520.0e3
155
+ gamma = 1.76e11
156
+ mu_0 = 1.26e-6
157
+ k_B = 1.38e-23
158
+ # #mu_B=9.27e-24
159
+ lambda_G = 0.5
160
+ M_s = 1400.0e3
161
+ a_eff = 0.25e-9
162
+
163
+
164
+ class Iron(Element):
165
+ A = 21.0e-12
166
+ K = 48.0e3
167
+ gamma = 1.76e11
168
+ mu_0 = 1.26e-6
169
+ gamma_0 = gamma * mu_0 # 2.34e+5
170
+ k_B = 1.38e-23
171
+ # mu_B=9.27e-24
172
+ lambda_G = 0.5
173
+ M_s = 1700.0e3
174
+ a_eff = 0.286e-9
175
+
176
+
177
+ def calculate_laplacian(e: Element, g: Grid, m):
178
+ """Returns the laplacian of m (* coeff_1) in 3D"""
179
+
180
+ # Extract slices for Neumann boundary conditions
181
+ m_start_x = m[1:2, :, :]
182
+ m_end_x = m[-2:-1, :, :]
183
+
184
+ m_start_y = m[:, 1:2, :]
185
+ m_end_y = m[:, -2:-1, :]
186
+
187
+ m_start_z = m[:, :, 1:2]
188
+ m_end_z = m[:, :, -2:-1]
189
+
190
+ laplacian = (
191
+ (
192
+ np.concatenate((m[1:, :, :], m_end_x), axis=0)
193
+ + np.concatenate((m_start_x, m[:-1, :, :]), axis=0)
194
+ )
195
+ / g.dx ** 2
196
+ + (
197
+ np.concatenate((m[:, 1:, :], m_end_y), axis=1)
198
+ + np.concatenate((m_start_y, m[:, :-1, :]), axis=1)
199
+ )
200
+ / g.dy ** 2
201
+ + (
202
+ np.concatenate((m[:, :, 1:], m_end_z), axis=2)
203
+ + np.concatenate((m_start_z, m[:, :, :-1]), axis=2)
204
+ )
205
+ / g.dz ** 2
206
+ - 2 * (1 / g.dx ** 2 + 1 / g.dy ** 2 + 1 / g.dz ** 2) * m
207
+ )
208
+
209
+ return e.coeff_1 * laplacian
210
+
211
+
212
+ def calculate_si(
213
+ e: Element, m1, m2, m3, laplacian_m1, laplacian_m2, laplacian_m3, R_alea
214
+ ):
215
+ """Returns the s_i = a_i + b_i"""
216
+
217
+ # Precalculate terms that appear multiple times
218
+
219
+ R_1 = laplacian_m1 + e.coeff_2 * m1 + e.coeff_3 + e.coeff_4 * R_alea[0]
220
+ R_2 = laplacian_m2 + e.coeff_4 * R_alea[1]
221
+ R_3 = laplacian_m3 + e.coeff_4 * R_alea[2]
222
+
223
+ l_G_m1m2 = e.lambda_G * m1 * m2
224
+ l_G_m1m3 = e.lambda_G * m1 * m3
225
+ l_G_m2m3 = e.lambda_G * m2 * m3
226
+
227
+ m1m1 = m1 * m1
228
+ m2m2 = m2 * m2
229
+ m3m3 = m3 * m3
230
+
231
+ s1 = (
232
+ (-m2 - l_G_m1m3) * R_3
233
+ + +(m3 - l_G_m1m2) * R_2
234
+ + +e.lambda_G * (m2m2 + m3m3) * R_1
235
+ )
236
+
237
+ s2 = (
238
+ (-m3 - l_G_m1m2) * R_1
239
+ + (m1 - l_G_m2m3) * R_3
240
+ + e.lambda_G * (m1m1 + m3m3) * R_2
241
+ )
242
+
243
+ s3 = (
244
+ (-m1 - l_G_m2m3) * R_2
245
+ + (m2 - l_G_m1m3) * R_1
246
+ + e.lambda_G * (m1m1 + m2m2) * R_3
247
+ )
248
+
249
+ return s1, s2, s3
250
+
251
+
252
+ def integral(g, m: np.ndarray) -> float:
253
+ """
254
+ Returns the spatial average of m with shape (g.dims)
255
+ using the midpoint method
256
+ """
257
+
258
+ # copy m to avoid modifying its value
259
+ mm = m.copy()
260
+
261
+ # on the edges, we divide the contribution by 2
262
+ # x
263
+ mm[0, :, :] /= 2
264
+ mm[-1, :, :] /= 2
265
+ # y
266
+ mm[:, 0, :] /= 2
267
+ mm[:, -1, :] /= 2
268
+ # z
269
+ mm[:, :, 0] /= 2
270
+ mm[:, :, -1] /= 2
271
+
272
+ return mm.sum() / g.ncell
273
+
274
+
275
+
276
+ def simulate(N, Jx, Jy, Jz, Lx, Ly, Lz, T, H_ext, dt, n_average, element):
277
+ """Simulates the system over N iterations"""
278
+
279
+ g = Grid(Jx=Jx, Jy=Jy, Jz=Jz, Lx=Lx, Ly=Ly, Lz=Lz)
280
+ print(g)
281
+
282
+ dims = g.dims
283
+
284
+ e = element(T, H_ext, g, dt)
285
+ print(f"CFL = {e.get_CFL()}")
286
+
287
+ # --- Initialization ---
288
+
289
+ def theta_init(shape):
290
+ """Initialization of theta"""
291
+ return np.zeros(shape)
292
+
293
+ def phi_init(t, shape):
294
+ """Initialization of phi"""
295
+ return np.zeros(shape) + e.gamma_0 * H_ext * t
296
+
297
+ m1 = np.zeros((2,) + dims)
298
+ m2 = np.zeros_like(m1)
299
+ m3 = np.zeros_like(m1)
300
+
301
+ theta = theta_init(dims)
302
+ phi = phi_init(0, dims)
303
+
304
+ m1[0] = np.cos(theta)
305
+ m2[0] = np.sin(theta) * np.cos(phi)
306
+ m3[0] = np.sin(theta) * np.sin(phi)
307
+
308
+ # Output file
309
+ f = open(g.get_filename(T), "w")
310
+
311
+ t = 0.0
312
+ m1_mean = 0.0
313
+
314
+ start_time = time.perf_counter()
315
+
316
+ for n in progress_bar(range(1, N + 1), "Iteration : ", 40):
317
+ t += dt
318
+
319
+ # Adding randomness: temperature effect
320
+ R_alea = rng.standard_normal((3,) + dims)
321
+
322
+ # Prediction phase
323
+
324
+ laplacian_m1 = calculate_laplacian(e, g, m1[0])
325
+ laplacian_m2 = calculate_laplacian(e, g, m2[0])
326
+ laplacian_m3 = calculate_laplacian(e, g, m3[0])
327
+
328
+ s1_pre, s2_pre, s3_pre = calculate_si(
329
+ e, m1[0], m2[0], m3[0], laplacian_m1, laplacian_m2, laplacian_m3, R_alea
330
+ )
331
+
332
+ # Update
333
+ m1[1] = m1[0] + dt * s1_pre
334
+ m2[1] = m2[0] + dt * s2_pre
335
+ m3[1] = m3[0] + dt * s3_pre
336
+
337
+ # Correction phase
338
+
339
+ laplacian_m1 = calculate_laplacian(e, g, m1[1])
340
+ laplacian_m2 = calculate_laplacian(e, g, m2[1])
341
+ laplacian_m3 = calculate_laplacian(e, g, m3[1])
342
+
343
+ s1_cor, s2_cor, s3_cor = calculate_si(
344
+ e, m1[1], m2[1], m3[1], laplacian_m1, laplacian_m2, laplacian_m3, R_alea
345
+ )
346
+
347
+ # Update
348
+ m1[1] = m1[0] + dt * 0.5 * (s1_pre + s1_cor)
349
+ m2[1] = m2[0] + dt * 0.5 * (s2_pre + s2_cor)
350
+ m3[1] = m3[0] + dt * 0.5 * (s3_pre + s3_cor)
351
+
352
+ # We renormalize to check the constraint of being on the sphere
353
+ norm = np.sqrt(m1[1] ** 2 + m2[1] ** 2 + m3[1] ** 2)
354
+ m1[1] /= norm
355
+ m2[1] /= norm
356
+ m3[1] /= norm
357
+
358
+ m1[0] = m1[1]
359
+ m2[0] = m2[1]
360
+ m3[0] = m3[1]
361
+
362
+ # Midpoint method
363
+ m1_integral = integral(g, m1[0])
364
+ if n >= n_average:
365
+ m1_mean += m1_integral
366
+
367
+ f.write(f"{t:10.8e} {m1_integral:10.8e}\n")
368
+
369
+ m1_mean /= N - n_average
370
+
371
+ print(f"Output in {g.get_filename(T)}")
372
+ f.close()
373
+
374
+ print(f"{t = :e} T_f = {N * dt}")
375
+ if m1_mean != 0.0:
376
+ print(f"{m1_mean = :e}")
377
+
378
+ return g, (time.perf_counter() - start_time)
379
+
380
+
381
+ def check_solution(g: Grid, T: float) -> bool:
382
+ """
383
+ Verifies that the solution is identical to m1_integral_space_T1100_ref.txt,
384
+ obtained with the reference code using np.random.default_rng(0)
385
+ """
386
+ filename = g.get_filename(T)
387
+ ref_filename = Path(filename).stem + "_ref.txt"
388
+ try:
389
+ with open(filename) as f:
390
+ with open(ref_filename) as f_ref:
391
+ for line, line_ref in zip(f, f_ref):
392
+ assert line == line_ref
393
+ print("Check OK")
394
+ return True
395
+ except AssertionError:
396
+ # The solution is not identical: we calculate the L2 norm of the error
397
+ data = np.loadtxt(filename)
398
+ ref = np.loadtxt(ref_filename)
399
+ nmax = min(data.shape[0], ref.shape[0])
400
+ error = np.linalg.norm(data[:nmax, 1] - ref[:nmax, 1], ord=2)
401
+ norm = np.linalg.norm(ref[:nmax, 1], ord=2)
402
+ print(f"Relative error (L2 norm): {error/norm = :08e}")
403
+ return False
404
+
405
+
406
+ def parse_args(args) -> argparse.Namespace:
407
+ """Argument parser for llg3d_seq"""
408
+ parser = argparse.ArgumentParser(
409
+ description=__doc__, formatter_class=argparse.ArgumentDefaultsHelpFormatter
410
+ )
411
+ parser.add_argument(
412
+ "-c", "--check", action="store_true", help="Check against the reference"
413
+ )
414
+ parser.add_argument(
415
+ "-element", type=str, default="Cobalt", help="Element of the sample"
416
+ )
417
+ # Add arguments from the parameters dictionary
418
+ for name, data in parameters.items():
419
+ value, description = data
420
+ parser.add_argument(
421
+ f"-{name}", type=type(value), help=description, default=value
422
+ )
423
+
424
+ return parser.parse_args(args)
425
+
426
+
427
+ def main(args_main=None):
428
+ """Evaluates the command line and starts the simulation"""
429
+ args = parse_args(args_main)
430
+
431
+ check = args.check
432
+ del args.check
433
+ N = args.N
434
+
435
+ # Convert the element object from the string
436
+ if "element" in args:
437
+ vars(args)["element"] = globals()[args.element]
438
+ grid, total_time = simulate(**vars(args))
439
+
440
+ print(f"{N = } iterations")
441
+ print(f"total_time [s] = {total_time:.03f}")
442
+ print(f"temps/ite [s/ite] = {total_time / N:.03e}")
443
+ if check:
444
+ check_solution(grid, args.T)
445
+
446
+ if __name__ == "__main__":
447
+ main()
llg3d/post/__init__.py ADDED
File without changes
llg3d/post/process.py ADDED
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Post-processes a set of runs grouped into a `run.json` file or
4
+ into a set of SLURM job arrays:
5
+
6
+ 1. Extracts result data,
7
+ 2. Plots the computed average magnetization against temperature,
8
+ 3. Interpolates the computed points using cubic splines,
9
+ 4. Determines the Curie temperature as the value corresponding to the minimal (negative) slope of the interpolated curve.
10
+ """
11
+
12
+ import json
13
+ from pathlib import Path
14
+
15
+ import numpy as np
16
+ from scipy.interpolate import interp1d
17
+
18
+
19
+ class MagData:
20
+ """
21
+ Class to handle magnetization data and interpolation according to temperature
22
+ """
23
+
24
+ n_interp = 200
25
+
26
+ def __init__(self, job_dir: Path = None, run_file: Path = Path("run.json")) -> None:
27
+
28
+ if job_dir:
29
+ self.parentpath = job_dir
30
+ data, self.run = self.process_slurm_jobs()
31
+ elif run_file:
32
+ self.parentpath = run_file.parent
33
+ data, self.run = self.process_json(run_file)
34
+
35
+ self.temperature = data[:, 0]
36
+ self.m1_mean = data[:, 1]
37
+ self.interp = interp1d(self.temperature, self.m1_mean, kind="cubic")
38
+ self.T = np.linspace(
39
+ self.temperature.min(), self.temperature.max(), self.n_interp
40
+ )
41
+
42
+ def process_slurm_jobs(self) -> tuple[np.array, dict]:
43
+ """
44
+ Iterates through calculation directories to assemble data.
45
+
46
+ Args:
47
+ parentdir (str): path to the directory containing the runs
48
+
49
+ Returns:
50
+ tuple: (data, run) where data is a numpy array (T, <m>) and run
51
+ is a descriptive dictionary of the run
52
+ """
53
+ json_filename = "run.json"
54
+
55
+ # List of run directories
56
+ jobdirs = [f for f in self.parentpath.iterdir() if f.is_dir()]
57
+ if len(jobdirs) == 0:
58
+ exit(f"No job directories found in {self.parentpath}")
59
+ data = []
60
+ # Iterating through run directories
61
+ for jobdir in jobdirs:
62
+ try:
63
+ # Reading the JSON file
64
+ with open(jobdir / json_filename) as f:
65
+ run = json.load(f)
66
+ # Adding temperature and averaging value to the data list
67
+ data.extend(
68
+ [[float(T), res["m1_mean"]] for T, res in run["results"].items()]
69
+ )
70
+ except FileNotFoundError:
71
+ print(f"Warning: {json_filename} file not found " f"in {jobdir.as_posix()}")
72
+
73
+ data.sort() # Sorting by increasing temperatures
74
+
75
+ return np.array(data), run
76
+
77
+
78
+ def process_json(json_filepath: Path) -> tuple[np.array, dict]:
79
+ """
80
+ Reads the run.json file and extracts result data.
81
+
82
+ Args:
83
+ json_filepath: path to the run.json file
84
+
85
+ Returns:
86
+ tuple: (data, run) where data is a numpy array (T, <m>) and run
87
+ is a descriptive dictionary of the run
88
+ """
89
+ with open(json_filepath) as f:
90
+ run = json.load(f)
91
+
92
+ data = [[int(T), res["m1_mean"]] for T, res in run["results"].items()]
93
+
94
+ data.sort() # Sorting by increasing temperatures
95
+
96
+ return np.array(data), run
97
+
98
+ @property
99
+ def T_Curie(self) -> float:
100
+ """
101
+ Return the Curie temperature defined as the temperature at
102
+ which the magnetization is below 0.1
103
+ """
104
+ i_max = np.where(0.1 - self.interp(self.T) > 0)[0].min()
105
+ return self.T[i_max]
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Plot the magnetization vs temperature and determine the Curie temperature.
4
+ """
5
+
6
+ import argparse
7
+ from pathlib import Path
8
+
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+
12
+ from .process import MagData
13
+
14
+
15
+ def plot_m_vs_T(m: MagData, show: bool):
16
+ """
17
+ Plots the data (T, <m>), interpolates the values,
18
+ calculates the Curie temperature.
19
+ Exports to PNG.
20
+
21
+ Args:
22
+ data: numpy array (T, <m>)
23
+ parentdir: path to the directory containing the runs
24
+ run: descriptive dictionary of the run
25
+ show: display the graph in a graphical window
26
+ """
27
+
28
+ print(f"T_Curie = {m.T_Curie:.0f} K")
29
+
30
+ fig, ax = plt.subplots()
31
+ fig.suptitle("Average magnetization vs Temperature")
32
+ params = m.run["params"]
33
+ ax.set_title(
34
+ params["element"]
35
+ + rf", ${params['Jx']}\times{params['Jy']}\times{params['Jz']}$"
36
+ rf" ($dx = ${params['dx']})",
37
+ fontdict={"size": 10},
38
+ )
39
+ ax.plot(m.temperature, m.m1_mean, "o", label="computed")
40
+ ax.plot(m.T, m.interp(m.T), label="interpolated (cubic)")
41
+ ax.annotate(
42
+ "$T_{{Curie}} = {:.0f} K$".format(m.T_Curie),
43
+ xy=(m.T_Curie, m.interp(m.T_Curie)),
44
+ xytext=(m.T_Curie + 20, m.interp(m.T_Curie) + 0.01),
45
+ )
46
+ ax.axvline(x=m.T_Curie, color="k")
47
+ ax.set_xlabel("Temperature [K]")
48
+ ax.set_ylabel("Magnetization")
49
+ ax.legend()
50
+
51
+ if show:
52
+ plt.show()
53
+
54
+ image_filename = m.parentpath / "m1_mean.png"
55
+ fig.savefig(image_filename)
56
+ print(f"Image saved in {image_filename}")
57
+
58
+
59
+ def main():
60
+ """
61
+ Parses the command line to execute processing functions
62
+ """
63
+ parser = argparse.ArgumentParser(description=__doc__)
64
+ parser.add_argument("--job_dir", type=Path, help="Slurm main job directory")
65
+ parser.add_argument(
66
+ "--run_file", type=Path, default="run.json", help="Path to the run.json file"
67
+ )
68
+ parser.add_argument(
69
+ "-s",
70
+ "--show",
71
+ action="store_true",
72
+ default=False,
73
+ help="Display the graph in a graphical window",
74
+ )
75
+ args = parser.parse_args()
76
+ if args.job_dir:
77
+ m = MagData(job_dir=args.job_dir)
78
+ else:
79
+ m = MagData(run_file=args.run_file)
80
+ plot_m_vs_T(m, args.show)
81
+
82
+
83
+ if __name__ == "__main__":
84
+ main()
@@ -0,0 +1,3 @@
1
+ This code was started by Clémentine Courtès <clementine.courtes@math.unistra.fr> as a jupyter notebook
2
+ in 2022. It was then converted to a python package, optimized and parallelized with MPI
3
+ by Matthieu Boileau <matthieu.boileau@math.unistra.fr>.
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 IRMA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.1
2
+ Name: llg3d
3
+ Version: 1.1.0
4
+ Summary: Solveur pour l'équation de Landau-Lifshitz-Gilbert stochastique en 3D
5
+ Author-email: IRMA <matthieu.boileau@math.unistra.fr>
6
+ Project-URL: Homepage, https://gitlab.math.unistra.fr/llg3d/llg3d
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ License-File: AUTHORS
14
+ Requires-Dist: numpy
15
+ Requires-Dist: mpi4py
16
+ Requires-Dist: matplotlib
17
+ Requires-Dist: scipy
18
+ Provides-Extra: doc
19
+ Requires-Dist: Sphinx >=7.2.2 ; extra == 'doc'
20
+ Requires-Dist: myst-parser ; extra == 'doc'
21
+ Requires-Dist: furo ; extra == 'doc'
22
+ Requires-Dist: nbsphinx ; extra == 'doc'
23
+ Requires-Dist: sphinx-copybutton ; extra == 'doc'
24
+ Requires-Dist: sphinx-autobuild ; extra == 'doc'
25
+ Requires-Dist: sphinx-prompt ; extra == 'doc'
26
+ Requires-Dist: sphinx-last-updated-by-git ; extra == 'doc'
27
+ Requires-Dist: sphinxcontrib-programoutput ; extra == 'doc'
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest ; extra == 'test'
30
+ Requires-Dist: pytest-cov ; extra == 'test'
31
+ Requires-Dist: pytest-mpi ; extra == 'test'
32
+
33
+ # LLG3D: A solver for the stochastic Landau-Lifshitz-Gilbert equation in 3D
34
+
35
+ [![pipeline status](https://gitlab.math.unistra.fr/llg3d/llg3d/badges/main/pipeline.svg)](https://gitlab.math.unistra.fr/llg3d/llg3d/-/commits/main)
36
+ [![coverage report](https://gitlab.math.unistra.fr/llg3d/llg3d/badges/main/coverage.svg)](https://llg3d.pages.math.unistra.fr/llg3d/coverage)
37
+ [![Latest Release](https://gitlab.math.unistra.fr/llg3d/llg3d/-/badges/release.svg)](https://gitlab.math.unistra.fr/llg3d/llg3d/-/releases)
38
+ [![Doc](https://img.shields.io/badge/doc-sphinx-blue)](https://llg3d.pages.math.unistra.fr/llg3d/)
39
+
40
+ LLG3D is written in Python and utilizes the MPI library for parallelizing computations.
41
+
42
+ See the [documentation](https://llg3d.pages.math.unistra.fr/llg3d/).