nmag-python-3 0.0.2__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 (147) hide show
  1. anisotropy/__init__.py +28 -0
  2. anisotropy/anisotropy.py +13 -0
  3. anisotropy/evaluation.py +70 -0
  4. anisotropy/model.py +200 -0
  5. anisotropy/predefined.py +202 -0
  6. anisotropy/py.typed +1 -0
  7. anisotropy/values.py +84 -0
  8. mag_material/__init__.py +3 -0
  9. mag_material/mag_material.py +231 -0
  10. mag_material/parameters.py +150 -0
  11. mag_material/py.typed +1 -0
  12. nmag/__init__.py +36 -0
  13. nmag/backends.py +493 -0
  14. nmag/checkpoint.py +327 -0
  15. nmag/config.py +174 -0
  16. nmag/demag/__init__.py +39 -0
  17. nmag/demag/bem_operator.py +149 -0
  18. nmag/demag/geometry.py +134 -0
  19. nmag/demag/lindholm.py +133 -0
  20. nmag/demag/lindholm_fast.py +463 -0
  21. nmag/demag/linear.py +489 -0
  22. nmag/dynamics/__init__.py +255 -0
  23. nmag/output.py +27 -0
  24. nmag/parallel.py +45 -0
  25. nmag/py.typed +1 -0
  26. nmag/resources.py +38 -0
  27. nmag/simulation/__init__.py +519 -0
  28. nmag/simulation/anisotropy/__init__.py +5 -0
  29. nmag/simulation/anisotropy/fields.py +56 -0
  30. nmag/simulation/anisotropy/materials.py +138 -0
  31. nmag/simulation/demag/__init__.py +1 -0
  32. nmag/simulation/demag/bem/__init__.py +11 -0
  33. nmag/simulation/demag/bem/diagnostics.py +73 -0
  34. nmag/simulation/demag/bem/dirichlet.py +85 -0
  35. nmag/simulation/demag/bem/hierarchical.py +74 -0
  36. nmag/simulation/demag/bem/operator.py +292 -0
  37. nmag/simulation/demag/fem/__init__.py +11 -0
  38. nmag/simulation/demag/fem/assembly.py +114 -0
  39. nmag/simulation/demag/fem/charges.py +38 -0
  40. nmag/simulation/demag/fem/geometry.py +261 -0
  41. nmag/simulation/demag/fields/__init__.py +11 -0
  42. nmag/simulation/demag/fields/auxiliary.py +186 -0
  43. nmag/simulation/demag/fields/probe.py +77 -0
  44. nmag/simulation/demag/fields/recovery.py +207 -0
  45. nmag/simulation/demag/solver.py +17 -0
  46. nmag/simulation/dynamics/__init__.py +92 -0
  47. nmag/simulation/dynamics/advance.py +199 -0
  48. nmag/simulation/dynamics/integrator.py +263 -0
  49. nmag/simulation/exchange/__init__.py +13 -0
  50. nmag/simulation/exchange/coefficients.py +185 -0
  51. nmag/simulation/exchange/fields.py +106 -0
  52. nmag/simulation/exchange/llg_rhs.py +218 -0
  53. nmag/simulation/fields/__init__.py +19 -0
  54. nmag/simulation/fields/arrays.py +111 -0
  55. nmag/simulation/fields/availability.py +188 -0
  56. nmag/simulation/fields/averages.py +293 -0
  57. nmag/simulation/fields/derived.py +164 -0
  58. nmag/simulation/fields/maxangle.py +163 -0
  59. nmag/simulation/fields/probes.py +106 -0
  60. nmag/simulation/implicit_dynamics.py +221 -0
  61. nmag/simulation/mesh/__init__.py +13 -0
  62. nmag/simulation/mesh/geometry.py +153 -0
  63. nmag/simulation/mesh/materials.py +299 -0
  64. nmag/simulation/mesh/probe.py +234 -0
  65. nmag/simulation/restart.py +103 -0
  66. nmag/simulation/support.py +224 -0
  67. nmag_python_3-0.0.2.dist-info/METADATA +157 -0
  68. nmag_python_3-0.0.2.dist-info/RECORD +147 -0
  69. nmag_python_3-0.0.2.dist-info/WHEEL +5 -0
  70. nmag_python_3-0.0.2.dist-info/licenses/LICENSE +339 -0
  71. nmag_python_3-0.0.2.dist-info/top_level.txt +8 -0
  72. nmesh/__init__.py +130 -0
  73. nmesh/backend.py +286 -0
  74. nmesh/geometry/__init__.py +52 -0
  75. nmesh/geometry/boolean_operations.py +157 -0
  76. nmesh/geometry/primitives.py +453 -0
  77. nmesh/geometry/transform.py +126 -0
  78. nmesh/io/__init__.py +50 -0
  79. nmesh/io/ascii.py +132 -0
  80. nmesh/io/legacy_nmesh_hdf5.py +318 -0
  81. nmesh/io/meshio_support.py +170 -0
  82. nmesh/mesh_generation.py +182 -0
  83. nmesh/mesh_io.py +227 -0
  84. nmesh/mesh_model.py +147 -0
  85. nmesh/mesh_utilities.py +79 -0
  86. nmesh/mesher/__init__.py +21 -0
  87. nmesh/mesher/driver.py +146 -0
  88. nmesh/mesher/meshing_defaults.py +252 -0
  89. nmesh/mesher/meshing_parameters.py +185 -0
  90. nmesh/mesher/parity.py +21 -0
  91. nmesh/mesher/parity_canonical.py +142 -0
  92. nmesh/mesher/parity_comparison.py +191 -0
  93. nmesh/mesher/parity_metrics.py +114 -0
  94. nmesh/mesher/periodic.py +97 -0
  95. nmesh/mesher/relaxation/__init__.py +14 -0
  96. nmesh/mesher/relaxation/_constants.py +20 -0
  97. nmesh/mesher/relaxation/_types.py +15 -0
  98. nmesh/mesher/relaxation/density.py +170 -0
  99. nmesh/mesher/relaxation/engine/__init__.py +18 -0
  100. nmesh/mesher/relaxation/engine/state.py +155 -0
  101. nmesh/mesher/relaxation/engine/steps.py +248 -0
  102. nmesh/mesher/relaxation/engine/topology.py +230 -0
  103. nmesh/mesher/relaxation/forces/__init__.py +96 -0
  104. nmesh/mesher/relaxation/forces/jit.py +102 -0
  105. nmesh/mesher/relaxation/forces/neighbors.py +186 -0
  106. nmesh/mesher/relaxation/forces/simplex.py +302 -0
  107. nmesh/mesher/relaxation/forces/summary.py +207 -0
  108. nmesh/mesher/relaxation/forces/types.py +92 -0
  109. nmesh/mesher/relaxation/geometry/__init__.py +6 -0
  110. nmesh/mesher/relaxation/geometry/builder.py +154 -0
  111. nmesh/mesher/relaxation/geometry/model.py +194 -0
  112. nmesh/mesher/relaxation/seeding/__init__.py +74 -0
  113. nmesh/mesher/relaxation/seeding/periodic.py +88 -0
  114. nmesh/mesher/relaxation/seeding/points.py +88 -0
  115. nmesh/mesher/relaxation/seeding/sampling.py +142 -0
  116. nmesh/mesher/relaxation/topology/__init__.py +297 -0
  117. nmesh/mesher/relaxation/topology/finalize.py +78 -0
  118. nmesh/mesher/relaxation/topology/recovery.py +310 -0
  119. nmesh/mesher/sectioned_config.py +70 -0
  120. nmesh/nmesh.py +99 -0
  121. nmesh/py.typed +1 -0
  122. nmesh/utils/__init__.py +33 -0
  123. nmesh/utils/array_list_utils.py +128 -0
  124. nmesh/utils/constants.py +22 -0
  125. nmesh/utils/timing_memory_utils.py +51 -0
  126. nmesh/utils/types.py +13 -0
  127. si/constants.py +49 -0
  128. si/physical.py +722 -0
  129. si/py.typed +1 -0
  130. simulation/__init__.py +1 -0
  131. simulation/clock.py +237 -0
  132. simulation/data_writer.py +273 -0
  133. simulation/data_writer_collection.py +267 -0
  134. simulation/hysteresis.py +74 -0
  135. simulation/hysteresis_runner.py +286 -0
  136. simulation/hysteresis_schedule.py +180 -0
  137. simulation/inference/__init__.py +3 -0
  138. simulation/inference/inference.py +95 -0
  139. simulation/py.typed +1 -0
  140. simulation/quantity.py +88 -0
  141. simulation/simulation_core.py +458 -0
  142. throttler/__init__.py +3 -0
  143. throttler/py.typed +1 -0
  144. throttler/throttler.py +55 -0
  145. when/__init__.py +3 -0
  146. when/py.typed +1 -0
  147. when/when.py +416 -0
@@ -0,0 +1,463 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from functools import wraps
5
+ from typing import Any, ParamSpec, TypeVar, cast
6
+
7
+ import numpy as np
8
+
9
+ _JIT_FUNCTIONS: list[Callable[..., Any]] = []
10
+ _jit_compiled = False
11
+ P = ParamSpec("P")
12
+ R = TypeVar("R")
13
+
14
+
15
+ def __getattr__(name: str) -> Any:
16
+ if name == "_JIT_COMPILED":
17
+ return _jit_compiled
18
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
19
+
20
+
21
+ def _compile_jit_functions() -> None:
22
+ global _jit_compiled
23
+ if _jit_compiled:
24
+ return
25
+
26
+ try:
27
+ from numba import njit
28
+ except ImportError: # pragma: no cover - numba is a project dependency.
29
+ compiled_functions = {function.__name__: function for function in _JIT_FUNCTIONS}
30
+ else:
31
+ compiled_functions = {
32
+ function.__name__: njit(cache=True)(function) for function in _JIT_FUNCTIONS
33
+ }
34
+
35
+ globals().update(compiled_functions)
36
+ _jit_compiled = True
37
+
38
+
39
+ def _jit(function: Callable[P, R]) -> Callable[P, R]:
40
+ _JIT_FUNCTIONS.append(function)
41
+
42
+ @wraps(function)
43
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
44
+ _compile_jit_functions()
45
+ compiled = cast(Callable[P, R], globals()[function.__name__])
46
+ return compiled(*args, **kwargs)
47
+
48
+ cast(Any, wrapper).py_func = function
49
+ return wrapper
50
+
51
+
52
+ @_jit
53
+ def _dot3(left: np.ndarray, right: np.ndarray) -> float:
54
+ return left[0] * right[0] + left[1] * right[1] + left[2] * right[2]
55
+
56
+
57
+ @_jit
58
+ def _cross3(left: np.ndarray, right: np.ndarray) -> np.ndarray:
59
+ result = np.empty(3, dtype=np.float64)
60
+ result[0] = left[1] * right[2] - left[2] * right[1]
61
+ result[1] = left[2] * right[0] - left[0] * right[2]
62
+ result[2] = left[0] * right[1] - left[1] * right[0]
63
+ return result
64
+
65
+
66
+ @_jit
67
+ def _subtract3(left: np.ndarray, right: np.ndarray) -> np.ndarray:
68
+ result = np.empty(3, dtype=np.float64)
69
+ result[0] = left[0] - right[0]
70
+ result[1] = left[1] - right[1]
71
+ result[2] = left[2] - right[2]
72
+ return result
73
+
74
+
75
+ @_jit
76
+ def _norm3(vector: np.ndarray) -> float:
77
+ return np.sqrt(_dot3(vector, vector))
78
+
79
+
80
+ @_jit
81
+ def _normalised3(vector: np.ndarray) -> np.ndarray:
82
+ result = np.zeros(3, dtype=np.float64)
83
+ length = _norm3(vector)
84
+ if length == 0.0:
85
+ return result
86
+ result[0] = vector[0] / length
87
+ result[1] = vector[1] / length
88
+ result[2] = vector[2] / length
89
+ return result
90
+
91
+
92
+ @_jit
93
+ def _triangle_space_angle_fast(
94
+ observer: np.ndarray,
95
+ p0: np.ndarray,
96
+ p1: np.ndarray,
97
+ p2: np.ndarray,
98
+ ) -> float:
99
+ r0 = _subtract3(p0, observer)
100
+ r1 = _subtract3(p1, observer)
101
+ r2 = _subtract3(p2, observer)
102
+ r0_len = _norm3(r0)
103
+ r1_len = _norm3(r1)
104
+ r2_len = _norm3(r2)
105
+ dot01 = _dot3(r0, r1)
106
+ dot12 = _dot3(r1, r2)
107
+ dot20 = _dot3(r2, r0)
108
+ numerator = r0_len * r1_len * r2_len + r0_len * dot12 + r1_len * dot20 + r2_len * dot01
109
+ denominator_squared = (
110
+ 2.0 * (r1_len * r2_len + dot12) * (r2_len * r0_len + dot20) * (r0_len * r1_len + dot01)
111
+ )
112
+ if denominator_squared <= 0.0:
113
+ return 0.0
114
+ quotient = numerator / np.sqrt(denominator_squared)
115
+ if quotient < -1.0:
116
+ quotient = -1.0
117
+ elif quotient > 1.0:
118
+ quotient = 1.0
119
+ return 2.0 * np.arccos(quotient)
120
+
121
+
122
+ @_jit
123
+ def _safe_log_ratio_fast(numerator: float, denominator: float) -> float:
124
+ if denominator <= 0.0:
125
+ return np.nan
126
+ return np.log(numerator / denominator)
127
+
128
+
129
+ @_jit
130
+ def _lindholm_triangle_contributions_fast(
131
+ observer: np.ndarray,
132
+ p0: np.ndarray,
133
+ p1: np.ndarray,
134
+ p2: np.ndarray,
135
+ outward_surface_normal: np.ndarray,
136
+ ) -> np.ndarray:
137
+ angle = _triangle_space_angle_fast(observer, p0, p1, p2)
138
+ r0 = _subtract3(p0, observer)
139
+ r1 = _subtract3(p1, observer)
140
+ r2 = _subtract3(p2, observer)
141
+ s0 = _subtract3(r2, r1)
142
+ s1 = _subtract3(r0, r2)
143
+ s2 = _subtract3(r1, r0)
144
+ xi0 = _normalised3(s0)
145
+ xi1 = _normalised3(s1)
146
+ xi2 = _normalised3(s2)
147
+ area_vector = _cross3(_subtract3(p1, p0), _subtract3(p2, p0))
148
+ area = 0.5 * _norm3(area_vector)
149
+ contributions = np.zeros(3, dtype=np.float64)
150
+ if area == 0.0:
151
+ return contributions
152
+
153
+ zeta_vector = _normalised3(area_vector)
154
+ r0_len = _norm3(r0)
155
+ r1_len = _norm3(r1)
156
+ r2_len = _norm3(r2)
157
+ s0_len = _norm3(s0)
158
+ s1_len = _norm3(s1)
159
+ s2_len = _norm3(s2)
160
+ c01 = _dot3(xi0, xi1)
161
+ c12 = _dot3(xi1, xi2)
162
+ c20 = _dot3(xi2, xi0)
163
+ zeta = _dot3(zeta_vector, r0)
164
+ eta0 = _cross3(zeta_vector, xi0)
165
+ eta1 = _cross3(zeta_vector, xi1)
166
+ eta2 = _cross3(zeta_vector, xi2)
167
+ log0 = _safe_log_ratio_fast(r1_len + r2_len + s0_len, r1_len + r2_len - s0_len)
168
+ log1 = _safe_log_ratio_fast(r2_len + r0_len + s1_len, r2_len + r0_len - s1_len)
169
+ log2 = _safe_log_ratio_fast(r0_len + r1_len + s2_len, r0_len + r1_len - s2_len)
170
+ sign_zeta = 0.0
171
+ if zeta < 0.0:
172
+ sign_zeta = -1.0
173
+ elif zeta > 0.0:
174
+ sign_zeta = 1.0
175
+ angle_pm = angle * sign_zeta
176
+ denominator_factor = 1.0 / (8.0 * np.pi * area)
177
+ gamma0_log = log0 + c01 * log1 + c20 * log2
178
+ gamma1_log = c01 * log0 + log1 + c12 * log2
179
+ gamma2_log = c20 * log0 + c12 * log1 + log2
180
+ values = np.empty(3, dtype=np.float64)
181
+ values[0] = s0_len * denominator_factor * (angle_pm * _dot3(eta0, r1) - zeta * gamma0_log)
182
+ values[1] = s1_len * denominator_factor * (angle_pm * _dot3(eta1, r2) - zeta * gamma1_log)
183
+ values[2] = s2_len * denominator_factor * (angle_pm * _dot3(eta2, r0) - zeta * gamma2_log)
184
+ if _dot3(zeta_vector, outward_surface_normal) < 0.0:
185
+ values[0] = -values[0]
186
+ values[1] = -values[1]
187
+ values[2] = -values[2]
188
+ for index in range(3):
189
+ if np.isfinite(values[index]):
190
+ contributions[index] = values[index]
191
+ return contributions
192
+
193
+
194
+ def _normalised_rows(vectors: np.ndarray) -> np.ndarray:
195
+ lengths = np.linalg.norm(vectors, axis=1)
196
+ result = np.zeros_like(vectors, dtype=float)
197
+ positive = lengths > 0.0
198
+ result[positive] = vectors[positive] / lengths[positive, np.newaxis]
199
+ return result
200
+
201
+
202
+ def _precompute_lindholm_face_geometry(
203
+ points: np.ndarray,
204
+ face_nodes: np.ndarray,
205
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
206
+ face_points = points[face_nodes]
207
+ if len(face_nodes) == 0:
208
+ return (
209
+ face_points,
210
+ np.empty((0, 3), dtype=float),
211
+ np.empty((0, 3, 3), dtype=float),
212
+ np.empty((0, 3), dtype=float),
213
+ np.empty((0, 3), dtype=float),
214
+ np.empty(0, dtype=float),
215
+ )
216
+
217
+ edge0 = face_points[:, 2] - face_points[:, 1]
218
+ edge1 = face_points[:, 0] - face_points[:, 2]
219
+ edge2 = face_points[:, 1] - face_points[:, 0]
220
+ edge_vectors = np.stack([edge0, edge1, edge2], axis=1)
221
+ edge_lengths = np.linalg.norm(edge_vectors, axis=2)
222
+ xis = np.zeros_like(edge_vectors, dtype=float)
223
+ for index in range(3):
224
+ xis[:, index] = _normalised_rows(edge_vectors[:, index])
225
+
226
+ area_vectors = np.cross(
227
+ face_points[:, 1] - face_points[:, 0],
228
+ face_points[:, 2] - face_points[:, 0],
229
+ )
230
+ double_areas = np.linalg.norm(area_vectors, axis=1)
231
+ zeta_vectors = np.zeros_like(area_vectors, dtype=float)
232
+ positive_area = double_areas > 0.0
233
+ zeta_vectors[positive_area] = (
234
+ area_vectors[positive_area] / double_areas[positive_area, np.newaxis]
235
+ )
236
+
237
+ eta_vectors = np.stack(
238
+ [
239
+ np.cross(zeta_vectors, xis[:, 0]),
240
+ np.cross(zeta_vectors, xis[:, 1]),
241
+ np.cross(zeta_vectors, xis[:, 2]),
242
+ ],
243
+ axis=1,
244
+ )
245
+ corner_cosines = np.stack(
246
+ [
247
+ np.einsum("ij,ij->i", xis[:, 0], xis[:, 1]),
248
+ np.einsum("ij,ij->i", xis[:, 1], xis[:, 2]),
249
+ np.einsum("ij,ij->i", xis[:, 2], xis[:, 0]),
250
+ ],
251
+ axis=1,
252
+ )
253
+ denominator_factors = np.zeros(len(face_nodes), dtype=float)
254
+ denominator_factors[positive_area] = 1.0 / (4.0 * np.pi * double_areas[positive_area])
255
+ return face_points, zeta_vectors, eta_vectors, edge_lengths, corner_cosines, denominator_factors
256
+
257
+
258
+ @_jit
259
+ def _lindholm_triangle_contributions_precomputed_fast(
260
+ observer: np.ndarray,
261
+ p0: np.ndarray,
262
+ p1: np.ndarray,
263
+ p2: np.ndarray,
264
+ zeta_vector: np.ndarray,
265
+ eta0: np.ndarray,
266
+ eta1: np.ndarray,
267
+ eta2: np.ndarray,
268
+ edge_lengths: np.ndarray,
269
+ corner_cosines: np.ndarray,
270
+ denominator_factor: float,
271
+ ) -> np.ndarray:
272
+ contributions = np.zeros(3, dtype=np.float64)
273
+ if denominator_factor == 0.0:
274
+ return contributions
275
+
276
+ r0 = _subtract3(p0, observer)
277
+ r1 = _subtract3(p1, observer)
278
+ r2 = _subtract3(p2, observer)
279
+ r0_len = _norm3(r0)
280
+ r1_len = _norm3(r1)
281
+ r2_len = _norm3(r2)
282
+ dot01 = _dot3(r0, r1)
283
+ dot12 = _dot3(r1, r2)
284
+ dot20 = _dot3(r2, r0)
285
+ numerator = r0_len * r1_len * r2_len + r0_len * dot12 + r1_len * dot20 + r2_len * dot01
286
+ denominator_squared = (
287
+ 2.0 * (r1_len * r2_len + dot12) * (r2_len * r0_len + dot20) * (r0_len * r1_len + dot01)
288
+ )
289
+ angle = 0.0
290
+ if denominator_squared > 0.0:
291
+ quotient = numerator / np.sqrt(denominator_squared)
292
+ if quotient < -1.0:
293
+ quotient = -1.0
294
+ elif quotient > 1.0:
295
+ quotient = 1.0
296
+ angle = 2.0 * np.arccos(quotient)
297
+
298
+ s0_len = edge_lengths[0]
299
+ s1_len = edge_lengths[1]
300
+ s2_len = edge_lengths[2]
301
+ c01 = corner_cosines[0]
302
+ c12 = corner_cosines[1]
303
+ c20 = corner_cosines[2]
304
+ zeta = _dot3(zeta_vector, r0)
305
+ log0 = _safe_log_ratio_fast(r1_len + r2_len + s0_len, r1_len + r2_len - s0_len)
306
+ log1 = _safe_log_ratio_fast(r2_len + r0_len + s1_len, r2_len + r0_len - s1_len)
307
+ log2 = _safe_log_ratio_fast(r0_len + r1_len + s2_len, r0_len + r1_len - s2_len)
308
+ sign_zeta = 0.0
309
+ if zeta < 0.0:
310
+ sign_zeta = -1.0
311
+ elif zeta > 0.0:
312
+ sign_zeta = 1.0
313
+ angle_pm = angle * sign_zeta
314
+ gamma0_log = log0 + c01 * log1 + c20 * log2
315
+ gamma1_log = c01 * log0 + log1 + c12 * log2
316
+ gamma2_log = c20 * log0 + c12 * log1 + log2
317
+
318
+ value0 = s0_len * denominator_factor * (angle_pm * _dot3(eta0, r1) - zeta * gamma0_log)
319
+ value1 = s1_len * denominator_factor * (angle_pm * _dot3(eta1, r2) - zeta * gamma1_log)
320
+ value2 = s2_len * denominator_factor * (angle_pm * _dot3(eta2, r0) - zeta * gamma2_log)
321
+ if np.isfinite(value0):
322
+ contributions[0] = value0
323
+ if np.isfinite(value1):
324
+ contributions[1] = value1
325
+ if np.isfinite(value2):
326
+ contributions[2] = value2
327
+ return contributions
328
+
329
+
330
+ @_jit
331
+ def _boundary_node_solid_angles_fast(points: np.ndarray, simplices: np.ndarray) -> np.ndarray:
332
+ angles = np.zeros(len(points), dtype=np.float64)
333
+ for simplex_index in range(len(simplices)):
334
+ simplex = simplices[simplex_index]
335
+ point0 = int(simplex[0])
336
+ point1 = int(simplex[1])
337
+ point2 = int(simplex[2])
338
+ point3 = int(simplex[3])
339
+ angles[point0] += abs(
340
+ _triangle_space_angle_fast(
341
+ points[point0],
342
+ points[point1],
343
+ points[point2],
344
+ points[point3],
345
+ )
346
+ )
347
+ angles[point1] += abs(
348
+ _triangle_space_angle_fast(
349
+ points[point1],
350
+ points[point0],
351
+ points[point2],
352
+ points[point3],
353
+ )
354
+ )
355
+ angles[point2] += abs(
356
+ _triangle_space_angle_fast(
357
+ points[point2],
358
+ points[point0],
359
+ points[point1],
360
+ points[point3],
361
+ )
362
+ )
363
+ angles[point3] += abs(
364
+ _triangle_space_angle_fast(
365
+ points[point3],
366
+ points[point0],
367
+ points[point1],
368
+ points[point2],
369
+ )
370
+ )
371
+ return angles
372
+
373
+
374
+ @_jit
375
+ def _build_lindholm_bem_matrix_fast(
376
+ points: np.ndarray,
377
+ simplices: np.ndarray,
378
+ boundary_faces: np.ndarray,
379
+ face_points: np.ndarray,
380
+ zeta_vectors: np.ndarray,
381
+ eta_vectors: np.ndarray,
382
+ edge_lengths: np.ndarray,
383
+ corner_cosines: np.ndarray,
384
+ denominator_factors: np.ndarray,
385
+ boundary_nodes: np.ndarray,
386
+ local_index_by_point: np.ndarray,
387
+ ) -> np.ndarray:
388
+ bem = np.zeros((len(boundary_nodes), len(boundary_nodes)), dtype=np.float64)
389
+ solid_angles = _boundary_node_solid_angles_fast(points, simplices)
390
+
391
+ for row in range(len(boundary_nodes)):
392
+ observer_index = int(boundary_nodes[row])
393
+ observer = points[observer_index]
394
+ for face_index in range(len(boundary_faces)):
395
+ face = boundary_faces[face_index]
396
+ point0 = int(face[0])
397
+ point1 = int(face[1])
398
+ point2 = int(face[2])
399
+ if denominator_factors[face_index] == 0.0:
400
+ continue
401
+ contributions = _lindholm_triangle_contributions_precomputed_fast(
402
+ observer,
403
+ face_points[face_index, 0],
404
+ face_points[face_index, 1],
405
+ face_points[face_index, 2],
406
+ zeta_vectors[face_index],
407
+ eta_vectors[face_index, 0],
408
+ eta_vectors[face_index, 1],
409
+ eta_vectors[face_index, 2],
410
+ edge_lengths[face_index],
411
+ corner_cosines[face_index],
412
+ denominator_factors[face_index],
413
+ )
414
+ bem[row, int(local_index_by_point[point0])] += contributions[0]
415
+ bem[row, int(local_index_by_point[point1])] += contributions[1]
416
+ bem[row, int(local_index_by_point[point2])] += contributions[2]
417
+ bem[row, row] += solid_angles[observer_index] / (4.0 * np.pi) - 1.0
418
+ return bem
419
+
420
+
421
+ @_jit
422
+ def _apply_lindholm_bem_matrix_free_fast(
423
+ points: np.ndarray,
424
+ boundary_faces: np.ndarray,
425
+ face_points: np.ndarray,
426
+ zeta_vectors: np.ndarray,
427
+ eta_vectors: np.ndarray,
428
+ edge_lengths: np.ndarray,
429
+ corner_cosines: np.ndarray,
430
+ denominator_factors: np.ndarray,
431
+ boundary_nodes: np.ndarray,
432
+ local_index_by_point: np.ndarray,
433
+ solid_angles: np.ndarray,
434
+ values: np.ndarray,
435
+ ) -> np.ndarray:
436
+ result = np.zeros(len(boundary_nodes), dtype=np.float64)
437
+ for row in range(len(boundary_nodes)):
438
+ observer_index = int(boundary_nodes[row])
439
+ observer = points[observer_index]
440
+ value = 0.0
441
+ for face_index in range(len(boundary_faces)):
442
+ if denominator_factors[face_index] == 0.0:
443
+ continue
444
+ face = boundary_faces[face_index]
445
+ contributions = _lindholm_triangle_contributions_precomputed_fast(
446
+ observer,
447
+ face_points[face_index, 0],
448
+ face_points[face_index, 1],
449
+ face_points[face_index, 2],
450
+ zeta_vectors[face_index],
451
+ eta_vectors[face_index, 0],
452
+ eta_vectors[face_index, 1],
453
+ eta_vectors[face_index, 2],
454
+ edge_lengths[face_index],
455
+ corner_cosines[face_index],
456
+ denominator_factors[face_index],
457
+ )
458
+ value += contributions[0] * values[int(local_index_by_point[int(face[0])])]
459
+ value += contributions[1] * values[int(local_index_by_point[int(face[1])])]
460
+ value += contributions[2] * values[int(local_index_by_point[int(face[2])])]
461
+ value += (solid_angles[observer_index] / (4.0 * np.pi) - 1.0) * values[row]
462
+ result[row] = value
463
+ return result