llg3d 2.0.0__py3-none-any.whl → 3.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.
Files changed (48) hide show
  1. llg3d/__init__.py +3 -3
  2. llg3d/__main__.py +2 -2
  3. llg3d/benchmarks/__init__.py +1 -0
  4. llg3d/benchmarks/compare_commits.py +321 -0
  5. llg3d/benchmarks/efficiency.py +451 -0
  6. llg3d/benchmarks/utils.py +25 -0
  7. llg3d/element.py +118 -31
  8. llg3d/grid.py +51 -64
  9. llg3d/io.py +395 -0
  10. llg3d/main.py +36 -38
  11. llg3d/parameters.py +159 -49
  12. llg3d/post/__init__.py +1 -1
  13. llg3d/post/extract.py +105 -0
  14. llg3d/post/info.py +178 -0
  15. llg3d/post/m1_vs_T.py +90 -0
  16. llg3d/post/m1_vs_time.py +56 -0
  17. llg3d/post/process.py +82 -75
  18. llg3d/post/utils.py +38 -0
  19. llg3d/post/x_profiles.py +141 -0
  20. llg3d/py.typed +1 -0
  21. llg3d/solvers/__init__.py +153 -0
  22. llg3d/solvers/base.py +345 -0
  23. llg3d/solvers/experimental/__init__.py +9 -0
  24. llg3d/solvers/experimental/jax.py +361 -0
  25. llg3d/solvers/math_utils.py +41 -0
  26. llg3d/solvers/mpi.py +370 -0
  27. llg3d/solvers/numpy.py +126 -0
  28. llg3d/solvers/opencl.py +439 -0
  29. llg3d/solvers/profiling.py +38 -0
  30. {llg3d-2.0.0.dist-info → llg3d-3.0.0.dist-info}/METADATA +6 -3
  31. llg3d-3.0.0.dist-info/RECORD +36 -0
  32. {llg3d-2.0.0.dist-info → llg3d-3.0.0.dist-info}/WHEEL +1 -1
  33. llg3d-3.0.0.dist-info/entry_points.txt +9 -0
  34. llg3d/output.py +0 -108
  35. llg3d/post/plot_results.py +0 -65
  36. llg3d/post/temperature.py +0 -83
  37. llg3d/simulation.py +0 -104
  38. llg3d/solver/__init__.py +0 -45
  39. llg3d/solver/jax.py +0 -383
  40. llg3d/solver/mpi.py +0 -449
  41. llg3d/solver/numpy.py +0 -210
  42. llg3d/solver/opencl.py +0 -329
  43. llg3d/solver/solver.py +0 -93
  44. llg3d-2.0.0.dist-info/RECORD +0 -25
  45. llg3d-2.0.0.dist-info/entry_points.txt +0 -4
  46. {llg3d-2.0.0.dist-info → llg3d-3.0.0.dist-info}/licenses/AUTHORS +0 -0
  47. {llg3d-2.0.0.dist-info → llg3d-3.0.0.dist-info}/licenses/LICENSE +0 -0
  48. {llg3d-2.0.0.dist-info → llg3d-3.0.0.dist-info}/top_level.txt +0 -0
llg3d/solver/opencl.py DELETED
@@ -1,329 +0,0 @@
1
- """
2
- LLG3D Solver using OpenCL
3
- """
4
-
5
- import time
6
- from pathlib import Path
7
-
8
- import numpy as np
9
- import pyopencl as cl
10
- from pyopencl import clrandom
11
- from pyopencl import array as clarray
12
-
13
- from ..output import progress_bar, get_output_files, close_output_files
14
- from ..grid import Grid
15
- from ..element import Element, Cobalt
16
-
17
-
18
- def get_context_and_device(device_selection: str = "auto") -> tuple[cl.Context, cl.Device]:
19
- """
20
- Get the OpenCL context and device
21
-
22
- Args:
23
- device_selection:
24
-
25
- - ``"auto"``: Let OpenCL choose automatically
26
- - ``"cpu"``: Select CPU device
27
- - ``"gpu"``: Select first available GPU
28
- - ``"gpu:N"``: Select specific GPU by index (e.g., ``"gpu:0"``, ``"gpu:1"``)
29
-
30
- Returns:
31
- - The OpenCL context
32
- - The OpenCL device
33
- """
34
- if device_selection == "auto":
35
- context = cl.create_some_context(interactive=False)
36
- device = context.devices[0]
37
- return context, device
38
-
39
- # Get all platforms and devices
40
- platforms = cl.get_platforms()
41
- all_devices = []
42
-
43
- for platform in platforms:
44
- all_devices.extend(platform.get_devices())
45
-
46
- if not all_devices:
47
- raise RuntimeError("No OpenCL devices found")
48
-
49
- # Filter devices based on selection
50
- if device_selection == "cpu":
51
- cpu_devices = [d for d in all_devices if d.type & cl.device_type.CPU]
52
- if not cpu_devices:
53
- raise RuntimeError("No CPU devices found")
54
- selected_device = cpu_devices[0]
55
- elif device_selection == "gpu":
56
- gpu_devices = [d for d in all_devices if d.type & cl.device_type.GPU]
57
- if not gpu_devices:
58
- raise RuntimeError("No GPU devices found")
59
- selected_device = gpu_devices[0]
60
- elif device_selection.startswith("gpu:"):
61
- gpu_devices = [d for d in all_devices if d.type & cl.device_type.GPU]
62
- if not gpu_devices:
63
- raise RuntimeError("No GPU devices found")
64
-
65
- gpu_index = int(device_selection.split(":")[1])
66
- if gpu_index >= len(gpu_devices):
67
- raise RuntimeError(f"GPU index {gpu_index} not available. Found {len(gpu_devices)} GPU(s)")
68
- selected_device = gpu_devices[gpu_index]
69
- else:
70
- raise ValueError(f"Invalid device selection: {device_selection}")
71
-
72
- # Create context with selected device
73
- context = cl.Context([selected_device])
74
- print(f"Selected OpenCL device: {selected_device.name} ({selected_device.type})")
75
-
76
- return context, selected_device
77
-
78
-
79
- def get_precision(device: cl.Device, precision: str) -> np.dtype:
80
- """
81
- Get the numpy float type based on the precision
82
-
83
- Args:
84
- precision: Precision of the simulation (single or double)
85
-
86
- Returns:
87
- The numpy float type (float32 or float64)
88
-
89
- Raises:
90
- RuntimeError: If double precision is asked while the device does not support it
91
- """
92
- # Check that cl device supports double precision
93
- if precision == "double" and not device.double_fp_config:
94
- raise RuntimeError("The selected device does not support double precision.")
95
-
96
- return np.float64 if precision == "double" else np.float32
97
-
98
-
99
- class Program:
100
- """
101
- Class to manage the OpenCL kernels for the LLG3D simulation
102
- """
103
-
104
- def __init__(self, g: Grid, context: cl.Context, np_float: np.dtype):
105
- self.grid = g
106
- self.context = context
107
- self.np_float = np_float
108
- self.cl_program = self._get_built_program()
109
-
110
- def _get_built_program(self) -> cl.Program:
111
- """
112
- Return the OpenCL program built from the source code
113
-
114
- Returns:
115
- The OpenCL program object
116
- """
117
-
118
- opencl_code = (Path(__file__).parent / "llg3d.cl").read_text()
119
- build_options = "-D USE_DOUBLE_PRECISION" if self.np_float == np.float64 else ""
120
- build_options += (
121
- f" -D NX={self.grid.Jx} -D NY={self.grid.Jy} -D NZ={self.grid.Jz}"
122
- )
123
- return cl.Program(self.context, opencl_code).build(options=build_options)
124
-
125
- def get_kernel(self, kernel_name: str, arg_types: list = [None]) -> cl.Kernel:
126
- """
127
- Returns the specified kernel by name.
128
-
129
- Args:
130
- kernel_name: Name of the kernel to retrieve
131
- arg_types: List of argument types for the kernel
132
-
133
- Returns:
134
- The OpenCL kernel object
135
- """
136
- kernel: cl.Kernel = getattr(self.cl_program, kernel_name)
137
- kernel.set_arg_types(arg_types)
138
- return kernel
139
-
140
-
141
- def simulate(
142
- N: int,
143
- Jx: int,
144
- Jy: int,
145
- Jz: int,
146
- dx: float,
147
- T: float,
148
- H_ext: float,
149
- dt: float,
150
- start_averaging: int,
151
- n_mean: int,
152
- n_profile: int,
153
- element_class: Element,
154
- precision: str,
155
- seed: int,
156
- device: str = "auto",
157
- **_,
158
- ) -> tuple[float, str, float]:
159
- """
160
- Simulates the system over N iterations
161
-
162
- Args:
163
- N: Number of iterations
164
- Jx: Number of grid points in x direction
165
- Jy: Number of grid points in y direction
166
- Jz: Number of grid points in z direction
167
- dx: Grid spacing
168
- T: Temperature in Kelvin
169
- H_ext: External magnetic field strength
170
- dt: Time step for the simulation
171
- start_averaging: Number of iterations for averaging
172
- n_mean: Number of iterations for integral output
173
- n_profile: Number of iterations for profile output
174
- element_class: Element of the sample (default: Cobalt)
175
- precision: Precision of the simulation (single or double)
176
- seed: Random seed for temperature fluctuations
177
- device: Device to use ('cpu', 'gpu', 'gpu:0', 'gpu:1', etc., or 'auto')
178
-
179
- Returns:
180
- - The grid object
181
- - The time taken for the simulation
182
- - The output filename
183
- """
184
-
185
- context, opencl_device = get_context_and_device(device)
186
- np_float = get_precision(opencl_device, precision)
187
-
188
- g = Grid(Jx, Jy, Jz, dx)
189
- print(g)
190
-
191
- e = element_class(T, H_ext, g, dt)
192
- if not isinstance(e, Cobalt):
193
- raise NotImplementedError(f"Element is {type(e)} but only {Cobalt} is supported at the moment.")
194
- print(f"CFL = {e.get_CFL()}")
195
-
196
- # --- Initialization ---
197
-
198
- def theta_init(shape):
199
- """Initialization of theta"""
200
- return np.zeros(shape, dtype=np_float)
201
-
202
- def phi_init(t, shape):
203
- """Initialization of phi"""
204
- return np.zeros(shape, dtype=np_float) + e.gamma_0 * H_ext * t
205
-
206
- m_n = np.zeros((3,) + g.dims, dtype=np_float)
207
-
208
- theta = theta_init(g.dims)
209
- phi = phi_init(0, g.dims)
210
-
211
- m_n[0] = np.cos(theta)
212
- m_n[1] = np.sin(theta) * np.cos(phi)
213
- m_n[2] = np.sin(theta) * np.sin(phi)
214
-
215
- queue = cl.CommandQueue(context)
216
-
217
- program = Program(g, context, np_float)
218
- slope_kernel = program.get_kernel("slope", [None] * 3 + [np_float] * 8)
219
- update_1_kernel = program.get_kernel("update_1", [None] * 3 + [np_float])
220
- update_2_kernel = program.get_kernel("update_2", [None] * 4 + [np_float])
221
- normalize_kernel = program.get_kernel("normalize")
222
-
223
- # Create a CL array for m1 component in order to compute averages
224
- d_m1 = clarray.empty(queue, g.ntot, np_float)
225
- copy_m1_kernel = program.get_kernel("copy_m1", [None, None])
226
-
227
- mf = cl.mem_flags
228
- mem_size = m_n.nbytes
229
-
230
- d_m_n = cl.Buffer(context, mf.READ_WRITE | mf.COPY_HOST_PTR, hostbuf=m_n)
231
- d_R_alea = cl.array.Array(queue, (3,) + g.dims, np_float)
232
- d_m_np1 = cl.Buffer(context, mf.READ_WRITE, mem_size)
233
- d_s_pre = cl.Buffer(context, mf.READ_WRITE, mem_size)
234
- d_s_cor = cl.Buffer(context, mf.READ_WRITE, mem_size)
235
-
236
- rng = clrandom.PhiloxGenerator(context, seed=seed)
237
-
238
- f_mean, f_profiles, output_filenames = get_output_files(g, T, n_mean, n_profile)
239
-
240
- t = 0.0
241
- m1_average = 0.0
242
-
243
- start_time = time.perf_counter()
244
-
245
- for n in progress_bar(range(1, N + 1), "Iteration : ", 40):
246
- t += dt
247
-
248
- rng.fill_normal(d_R_alea)
249
- queue.finish() # ensure the array is filled
250
-
251
- # Prediction phase
252
-
253
- # calculate s_i_pre from m_i^n
254
- slope_kernel(
255
- queue,
256
- g.dims,
257
- None,
258
- d_m_n,
259
- d_R_alea.data,
260
- d_s_pre,
261
- g.dx,
262
- g.dy,
263
- g.dz,
264
- e.coeff_1,
265
- e.coeff_2,
266
- e.coeff_3,
267
- e.coeff_4,
268
- e.lambda_G,
269
- )
270
-
271
- # m_i^n+1 = m_i^n + dt * s_i_pre
272
- update_1_kernel(queue, (3 * g.ntot,), None, d_m_n, d_m_np1, d_s_pre, dt)
273
- queue.finish()
274
-
275
- # # Correction phase
276
-
277
- # calculate s_i_cor from m_i^n+1
278
- slope_kernel(
279
- queue,
280
- g.dims,
281
- None,
282
- d_m_np1,
283
- d_R_alea.data,
284
- d_s_cor,
285
- g.dx,
286
- g.dy,
287
- g.dz,
288
- e.coeff_1,
289
- e.coeff_2,
290
- e.coeff_3,
291
- e.coeff_4,
292
- e.lambda_G,
293
- )
294
- # m_i^n+1 = m_i^n + dt * (s_i_pre + s_i_cor) / 2
295
- # Update using the corrected values
296
- update_2_kernel(
297
- queue, (3 * g.ntot,), None, d_m_n, d_m_np1, d_s_pre, d_s_cor, dt
298
- )
299
- queue.finish()
300
-
301
- # Normalization
302
- normalize_kernel(queue, (g.ntot,), None, d_m_np1).wait()
303
-
304
- # Swap the buffers for the next iteration
305
- d_m_n, d_m_np1 = d_m_np1, d_m_n
306
-
307
- # Space average of m_1 using the midpoint method with OpenCL sum
308
- if n_mean != 0 and n % n_mean == 0:
309
- # Copy only the first component from d_m_n to d_m1 with weights applied
310
- # d_m_n contains [m1, m2, m3] interleaved, we want only m1
311
- copy_m1_kernel(queue, g.dims, None, d_m_n, d_m1.data)
312
-
313
- # Use PyOpenCL array sum to compute the weighted sum
314
- weighted_sum = clarray.sum(d_m1).get()
315
- m1_mean = weighted_sum / g.ncell
316
-
317
- if n >= start_averaging:
318
- m1_average += m1_mean * n_mean
319
-
320
- f_mean.write(f"{t:10.8e} {m1_mean:10.8e}\n")
321
-
322
- total_time = time.perf_counter() - start_time
323
-
324
- close_output_files(f_mean, f_profiles)
325
-
326
- if n > start_averaging:
327
- m1_average /= N - start_averaging
328
-
329
- return total_time, output_filenames, m1_average
llg3d/solver/solver.py DELETED
@@ -1,93 +0,0 @@
1
- """
2
- Common functions for the LLG3D solver
3
- """
4
-
5
- import numpy as np
6
-
7
- from ..grid import Grid
8
- from ..element import Element
9
-
10
-
11
- def cross_product(a: np.ndarray, b: np.ndarray) -> np.ndarray:
12
- r"""
13
- Compute cross product :math:`a \times b`.
14
-
15
- This implementation is faster than np.cross for large arrays.
16
-
17
- Args:
18
- a: First vector (shape (3, nx, ny, nz))
19
- b: Second vector (shape (3, nx, ny, nz))
20
-
21
- Returns:
22
- Cross product :math:`a \times b` (shape (3, nx, ny, nz))
23
- """
24
-
25
- return np.stack(
26
- [
27
- a[1] * b[2] - a[2] * b[1], # x-component
28
- a[2] * b[0] - a[0] * b[2], # y-component
29
- a[0] * b[1] - a[1] * b[0], # z-component
30
- ],
31
- axis=0,
32
- )
33
-
34
-
35
- def compute_H_anisotropy(e: Element, m: np.ndarray) -> np.ndarray:
36
- """
37
- Compute the anisotropy field
38
- Args:
39
- e: Element object
40
- m: Magnetization array (shape (3, nx, ny, nz))
41
- Returns:
42
- Anisotropy field array (shape (3, nx, ny, nz))
43
- """
44
-
45
- m1, m2, m3 = m
46
-
47
- m1m1 = m1 * m1
48
- m2m2 = m2 * m2
49
- m3m3 = m3 * m3
50
-
51
- if e.anisotropy == "uniaxial":
52
- aniso_1 = m1
53
- aniso_2 = np.zeros_like(m1)
54
- aniso_3 = np.zeros_like(m1)
55
-
56
- if e.anisotropy == "cubic":
57
- aniso_1 = -(1 - m1m1 + m2m2 * m3m3) * m1
58
- aniso_2 = -(1 - m2m2 + m1m1 * m3m3) * m2
59
- aniso_3 = -(1 - m3m3 + m1m1 * m2m2) * m3
60
-
61
- return e.coeff_2 * np.stack([aniso_1, aniso_2, aniso_3], axis=0)
62
-
63
-
64
- def space_average(g: Grid, m: np.ndarray, copy: bool = True) -> float:
65
- """
66
- Returns the spatial average of m with shape (g.dims)
67
- using the midpoint method
68
-
69
- Args:
70
- g: Grid object
71
- m: Array to be integrated
72
- copy: If True, copy m to avoid modifying its value
73
-
74
- Returns:
75
- float: Spatial average of m
76
- """
77
-
78
- # copy m to avoid modifying its value
79
- mm = m.copy() if copy else m
80
-
81
- # on the edges, we divide the contribution by 2
82
- # x
83
- mm[0, :, :] /= 2
84
- mm[-1, :, :] /= 2
85
- # y
86
- mm[:, 0, :] /= 2
87
- mm[:, -1, :] /= 2
88
- # z
89
- mm[:, :, 0] /= 2
90
- mm[:, :, -1] /= 2
91
-
92
- average = mm.sum() / g.ncell
93
- return average
@@ -1,25 +0,0 @@
1
- llg3d/__init__.py,sha256=7Bcs0le_-tmgNMoIH8KaKJRqHjrzfNLu6RMP6c8T-Jo,160
2
- llg3d/__main__.py,sha256=wZ2BSBj_ZgnfGNmYZkeDVrfMPpzLOEmjmSnXrjnwfzo,96
3
- llg3d/element.py,sha256=3umDR1CxRjMfaBKjDPp35RxG24cSagONwzWkY5doxuI,4290
4
- llg3d/grid.py,sha256=Vkj0K9lkYZLlmSk6-S3BOupKh6JtxrWTABiK06GZHsk,3844
5
- llg3d/main.py,sha256=A4d1_6NfPNfHptPoitE772zZT4akeF0zFVXM5Gu1Tjo,1829
6
- llg3d/output.py,sha256=llPZ7lQ1bKwfe-A_hBYEn0QWf0A2aFjHneJAv-djVJk,2921
7
- llg3d/parameters.py,sha256=Jg1sDPWLaUeDMFFJBdb3YmN7ljgsQ-8qDXMpXUUqjGI,2437
8
- llg3d/simulation.py,sha256=XtmB8vYaQbFsx9sQPXqGQs1m-iGTPqRpis1Y67W5hO0,3551
9
- llg3d/post/__init__.py,sha256=PUQUkQURiZrnZmfGWJK5YCqk5Wv1iqo_IzczftqgJVQ,38
10
- llg3d/post/plot_results.py,sha256=NEhIEpVouQO0kf5Gsq5CLsDtnQtBuC0hxQ8IWII04Fg,1507
11
- llg3d/post/process.py,sha256=yIM4LuhqSckfXCBTToPSnRS7qSswJ8tk3DNzJ-wQerU,3422
12
- llg3d/post/temperature.py,sha256=Zz7za7Af3zQAxuSVznpCf5Ive9da4dvOK-2U2-Vwhuc,2271
13
- llg3d/solver/__init__.py,sha256=cAXNyd8XZrdkfutNI0vgyMG4YueJiRL3Mqbf8nI7jvo,886
14
- llg3d/solver/jax.py,sha256=o-c8Vs6T6hAxn0eFchHwBjry8QOWr3MK-d2Rs7XpRRA,11965
15
- llg3d/solver/mpi.py,sha256=HINbUbVxLvqrM0yKMdg8sz_28eZ3TXOScaSNYvSgovg,12850
16
- llg3d/solver/numpy.py,sha256=vqQIMatv84obDcNjoCmKRtuRmOBxRAjwfxmKPH1h5YM,6035
17
- llg3d/solver/opencl.py,sha256=ZkZrjzsQryrYDqoJbGLq2E7uJHaxAeXJUEebOLaNlWw,10221
18
- llg3d/solver/solver.py,sha256=7m5iU1JtZfdRW6l20tnRElU4sPsmm-gqgNujyqe1BuE,2152
19
- llg3d-2.0.0.dist-info/licenses/AUTHORS,sha256=vhJ88HikYvOrGiB_l1xH2X6hyn9ZJafx6mpoMYNhh1I,297
20
- llg3d-2.0.0.dist-info/licenses/LICENSE,sha256=aFxTGAyyve8nM9T2jWTarJzQhdSSC3MbbN1heNAev9c,1062
21
- llg3d-2.0.0.dist-info/METADATA,sha256=4wnGvwrqKOLrZcsb5XIPKWBhWt1-diU3-f9LSUdbRP4,1952
22
- llg3d-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- llg3d-2.0.0.dist-info/entry_points.txt,sha256=kjuf09uDEuROnDeuHkVen3oAS4ThEPS084TpHmQAbJ8,133
24
- llg3d-2.0.0.dist-info/top_level.txt,sha256=cBZ0roaXt3CAXqYojuO84lGPCtWuLlXxLGLYRKmHZy0,6
25
- llg3d-2.0.0.dist-info/RECORD,,
@@ -1,4 +0,0 @@
1
- [console_scripts]
2
- llg3d = llg3d.main:main
3
- llg3d.plot_results = llg3d.post.plot_results:main
4
- llg3d.post = llg3d.post.temperature:main