llg3d 1.4.0__py3-none-any.whl → 2.0.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 DELETED
@@ -1,447 +0,0 @@
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()
@@ -1,13 +0,0 @@
1
- llg3d/__init__.py,sha256=l3zMgZ2LZUQze9yacGblHZQ2au7pIlM501H7Bc3HXj8,21
2
- llg3d/llg3d.py,sha256=RD_xkQir-kZsjAkx2YzwNAIWl5KZE-5iuTNKKkp8WLs,21388
3
- llg3d/llg3d_seq.py,sha256=lwcgYjxUETKVtHl60X6NtxAEwBWEqAyO3Uqvg7zF8Xo,12147
4
- llg3d/post/__init__.py,sha256=PUQUkQURiZrnZmfGWJK5YCqk5Wv1iqo_IzczftqgJVQ,38
5
- llg3d/post/process.py,sha256=yIM4LuhqSckfXCBTToPSnRS7qSswJ8tk3DNzJ-wQerU,3422
6
- llg3d/post/temperature.py,sha256=mPUFRW0qISTMYbM3BSiN2fOeS3oXye0bLIwenel4xPU,2290
7
- llg3d-1.4.0.dist-info/AUTHORS,sha256=vhJ88HikYvOrGiB_l1xH2X6hyn9ZJafx6mpoMYNhh1I,297
8
- llg3d-1.4.0.dist-info/LICENSE,sha256=aFxTGAyyve8nM9T2jWTarJzQhdSSC3MbbN1heNAev9c,1062
9
- llg3d-1.4.0.dist-info/METADATA,sha256=VkEPakPl9xuUuYwy1yYNs4uMGxjpcFhoETi3e031YiE,2296
10
- llg3d-1.4.0.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
11
- llg3d-1.4.0.dist-info/entry_points.txt,sha256=Mg-RCoAx4RLA13I_O664e6nrWu2rQVNgvMNkQPCW6LU,84
12
- llg3d-1.4.0.dist-info/top_level.txt,sha256=cBZ0roaXt3CAXqYojuO84lGPCtWuLlXxLGLYRKmHZy0,6
13
- llg3d-1.4.0.dist-info/RECORD,,
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- llg3d = llg3d.llg3d:main
3
- llg3d.post = llg3d.post.temperature:main