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,310 @@
1
+ """Surface-recovery helpers for the relaxation meshing pipeline."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from itertools import combinations
7
+ from typing import cast
8
+
9
+ import numpy as np
10
+
11
+ from .._constants import (
12
+ BOUNDARY_FUZZ,
13
+ DENSITY_EPSILON,
14
+ STATE_BOUNDARY,
15
+ STATE_FIXED,
16
+ STATE_MOBILE,
17
+ STATE_SIMPLE,
18
+ )
19
+ from .._types import FloatArray
20
+ from ..geometry import FemGeometry
21
+ from . import _simplex_measures
22
+
23
+
24
+ def mirror_surface_recovery_points(
25
+ points: FloatArray,
26
+ states: np.ndarray,
27
+ geometry: FemGeometry,
28
+ a0: float,
29
+ simplices: np.ndarray,
30
+ ) -> FloatArray:
31
+ """Return legacy mirror/prevention points for poor boundary simplices."""
32
+
33
+ dim = geometry.dim
34
+ if dim not in (2, 3) or len(simplices) == 0:
35
+ return np.empty((0, dim), dtype=float)
36
+
37
+ simplex_measures = _simplex_measures(points, simplices, dim)
38
+ seen_surfaces: set[tuple[tuple[float, ...], ...]] = set()
39
+ additions: list[FloatArray] = []
40
+
41
+ for simplex, simplex_volume in zip(simplices, simplex_measures, strict=True):
42
+ simplex_points = np.asarray(points[simplex], dtype=float)
43
+ boundary_points, interior_points = _split_boundary_and_interior_points(
44
+ simplex,
45
+ simplex_points,
46
+ states,
47
+ )
48
+ if dim == 2:
49
+ additions.extend(
50
+ _mirror_points_2d(
51
+ boundary_points,
52
+ interior_points,
53
+ simplex_points,
54
+ float(simplex_volume),
55
+ geometry,
56
+ a0,
57
+ seen_surfaces,
58
+ )
59
+ )
60
+ else:
61
+ additions.extend(
62
+ _prevention_points_3d(
63
+ boundary_points,
64
+ simplex_points,
65
+ float(simplex_volume),
66
+ geometry,
67
+ a0,
68
+ seen_surfaces,
69
+ )
70
+ )
71
+
72
+ if not additions:
73
+ return np.empty((0, dim), dtype=float)
74
+ return _dedupe_recovery_points(np.asarray(additions, dtype=float))
75
+
76
+
77
+ def _split_boundary_and_interior_points(
78
+ simplex: np.ndarray,
79
+ simplex_points: FloatArray,
80
+ states: np.ndarray,
81
+ ) -> tuple[list[FloatArray], list[FloatArray]]:
82
+ """Split simplex vertices into boundary-like and mobile interior vertices."""
83
+
84
+ boundary_points: list[FloatArray] = []
85
+ interior_points: list[FloatArray] = []
86
+ for local_index, point_index in enumerate(simplex.tolist()):
87
+ state = int(states[int(point_index)])
88
+ point = simplex_points[local_index]
89
+ if state in (STATE_BOUNDARY, STATE_FIXED, STATE_SIMPLE):
90
+ boundary_points.append(point)
91
+ elif state == STATE_MOBILE:
92
+ interior_points.append(point)
93
+ return boundary_points, interior_points
94
+
95
+
96
+ def _mirror_points_2d(
97
+ boundary_points: list[FloatArray],
98
+ interior_points: list[FloatArray],
99
+ simplex_points: FloatArray,
100
+ simplex_volume: float,
101
+ geometry: FemGeometry,
102
+ a0: float,
103
+ seen_surfaces: set[tuple[tuple[float, ...], ...]],
104
+ ) -> list[FloatArray]:
105
+ """Return mirrored points for the legacy 2D boundary-simplex cases."""
106
+
107
+ additions: list[FloatArray] = []
108
+ if len(boundary_points) == 3:
109
+ shifted = list(boundary_points)
110
+ for _ in range(3):
111
+ vertex_point = shifted[0]
112
+ surface_points = np.asarray(shifted[1:], dtype=float)
113
+ if _register_surface(surface_points, seen_surfaces):
114
+ shifted = shifted[1:] + shifted[:1]
115
+ continue
116
+ additions.extend(
117
+ _node_action_2d(
118
+ surface_points, vertex_point, simplex_points, simplex_volume, geometry, a0
119
+ )
120
+ )
121
+ shifted = shifted[1:] + shifted[:1]
122
+ return additions
123
+
124
+ if len(boundary_points) == 2 and len(interior_points) == 1:
125
+ surface_points = np.asarray(boundary_points, dtype=float)
126
+ if not _register_surface(surface_points, seen_surfaces):
127
+ additions.extend(
128
+ _node_action_2d(
129
+ surface_points,
130
+ interior_points[0],
131
+ simplex_points,
132
+ simplex_volume,
133
+ geometry,
134
+ a0,
135
+ )
136
+ )
137
+ return additions
138
+
139
+
140
+ def _node_action_2d(
141
+ surface_points: FloatArray,
142
+ vertex_point: FloatArray,
143
+ simplex_points: FloatArray,
144
+ simplex_volume: float,
145
+ geometry: FemGeometry,
146
+ a0: float,
147
+ ) -> list[FloatArray]:
148
+ """Mirror one 2D point when the surface angle and volume tests request it."""
149
+
150
+ angle = _solid_angle_2d(surface_points, vertex_point)
151
+ if (
152
+ angle > 0.55 * math.pi
153
+ and _volume_ratio(simplex_points, simplex_volume, geometry, a0) > 3.0e-2
154
+ ):
155
+ return [_mirror_point(surface_points, vertex_point, 2)]
156
+ return []
157
+
158
+
159
+ def _prevention_points_3d(
160
+ boundary_points: list[FloatArray],
161
+ simplex_points: FloatArray,
162
+ simplex_volume: float,
163
+ geometry: FemGeometry,
164
+ a0: float,
165
+ seen_surfaces: set[tuple[tuple[float, ...], ...]],
166
+ ) -> list[FloatArray]:
167
+ """Return midpoint prevention points for legacy 3D boundary edges."""
168
+
169
+ if len(boundary_points) < 2:
170
+ return []
171
+
172
+ additions: list[FloatArray] = []
173
+ for left, right in combinations(boundary_points, 2):
174
+ surface_points = np.asarray([left, right], dtype=float)
175
+ already_seen = _register_surface(surface_points, seen_surfaces)
176
+ if already_seen:
177
+ continue
178
+ middle_point = 0.5 * (surface_points[0] + surface_points[1])
179
+ middle_density = geometry.density_at(middle_point)
180
+ middle_rod_length = a0 / (middle_density ** (1.0 / max(geometry.dim, 1)))
181
+ distance = float(np.linalg.norm(surface_points[0] - surface_points[1]))
182
+ if (
183
+ distance > 1.2 * middle_rod_length
184
+ and _volume_ratio(simplex_points, simplex_volume, geometry, a0) > 3.0e-2
185
+ and _is_outer_region_constrained(geometry, middle_point)
186
+ ):
187
+ additions.append(middle_point)
188
+ return additions
189
+
190
+
191
+ def _surface_key(surface_points: FloatArray) -> tuple[tuple[float, ...], ...]:
192
+ """Return a stable set-like key for a boundary surface."""
193
+
194
+ return tuple(
195
+ sorted(
196
+ tuple(
197
+ cast(
198
+ list[float],
199
+ np.asarray(np.round(point, decimals=10), dtype=float).tolist(),
200
+ )
201
+ )
202
+ for point in surface_points
203
+ )
204
+ )
205
+
206
+
207
+ def _register_surface(
208
+ surface_points: FloatArray,
209
+ seen_surfaces: set[tuple[tuple[float, ...], ...]],
210
+ ) -> bool:
211
+ """Register a surface and return whether it had already been processed."""
212
+
213
+ key = _surface_key(surface_points)
214
+ if key in seen_surfaces:
215
+ return True
216
+ seen_surfaces.add(key)
217
+ return False
218
+
219
+
220
+ def _solid_angle_2d(surface_points: FloatArray, vertex_point: FloatArray) -> float:
221
+ """Return the angle subtended by two surface points from a vertex."""
222
+
223
+ first = surface_points[0] - vertex_point
224
+ second = surface_points[1] - vertex_point
225
+ denom = math.sqrt(float(np.sum(first * first))) * math.sqrt(float(np.sum(second * second)))
226
+ if denom <= DENSITY_EPSILON:
227
+ return 0.0
228
+ cosine = min(1.0, max(-1.0, float(np.sum(first * second)) / denom))
229
+ return float(math.acos(cosine))
230
+
231
+
232
+ def _mirror_point(surface_points: FloatArray, point: FloatArray, dim: int) -> FloatArray:
233
+ """Mirror a point across a line/plane defined by boundary points."""
234
+
235
+ if len(surface_points) == dim:
236
+ origin = surface_points[0]
237
+ directions = surface_points[1:] - origin
238
+ _, _, vh = np.linalg.svd(directions, full_matrices=True)
239
+ normal = np.asarray(vh[-1], dtype=float)
240
+ normal_norm_sq = float(np.dot(normal, normal))
241
+ if normal_norm_sq <= DENSITY_EPSILON:
242
+ return np.asarray(point, dtype=float)
243
+ factor = float(np.dot(point - origin, normal)) / normal_norm_sq
244
+ projection = point - factor * normal
245
+ return np.asarray(2.0 * projection - point, dtype=float)
246
+
247
+ if len(surface_points) == dim - 1 and dim == 3:
248
+ start = surface_points[0]
249
+ segment = surface_points[1] - start
250
+ denom = float(np.dot(segment, segment))
251
+ if denom <= DENSITY_EPSILON:
252
+ return np.asarray(point, dtype=float)
253
+ fraction = float(np.dot(point - start, segment)) / denom
254
+ projection = start + fraction * segment
255
+ return np.asarray(2.0 * projection - point, dtype=float)
256
+
257
+ return np.asarray(point, dtype=float)
258
+
259
+
260
+ def _volume_ratio(
261
+ simplex_points: FloatArray,
262
+ simplex_volume: float,
263
+ geometry: FemGeometry,
264
+ a0: float,
265
+ ) -> float:
266
+ """Return the legacy real-to-ideal simplex volume ratio."""
267
+
268
+ if simplex_volume <= DENSITY_EPSILON:
269
+ return 0.0
270
+ center = np.mean(simplex_points, axis=0)
271
+ density_here = geometry.density_at(center)
272
+ rod_scaled = a0 / (density_here ** (1.0 / max(geometry.dim, 1)))
273
+ ideal_volume = _regular_simplex_volume(rod_scaled, geometry.dim)
274
+ return abs(simplex_volume / max(ideal_volume, DENSITY_EPSILON))
275
+
276
+
277
+ def _regular_simplex_volume(edge_length: float, dim: int) -> float:
278
+ """Return the volume of a regular simplex with the supplied edge length."""
279
+
280
+ numerator = edge_length**dim * math.sqrt(dim + 1.0)
281
+ denominator = math.factorial(dim) * math.sqrt(2.0**dim)
282
+ return numerator / denominator
283
+
284
+
285
+ def _is_outer_region_constrained(geometry: FemGeometry, point: FloatArray) -> bool:
286
+ """Return whether a point satisfies the legacy outer-region boundary check."""
287
+
288
+ if not geometry.points_in_bbox(np.asarray(point, dtype=float)[np.newaxis, :])[0]:
289
+ return False
290
+ for body in geometry.region_bodies:
291
+ if body is None:
292
+ continue
293
+ if float(body.evaluate(point)) >= BOUNDARY_FUZZ:
294
+ return False
295
+ return True
296
+
297
+
298
+ def _dedupe_recovery_points(points: FloatArray) -> FloatArray:
299
+ """Deduplicate generated recovery points while preserving order."""
300
+
301
+ keep_indices: list[int] = []
302
+ seen: set[tuple[float, ...]] = set()
303
+ for index, point in enumerate(points):
304
+ rounded = np.asarray(np.round(point, decimals=10), dtype=float)
305
+ key = tuple(cast(list[float], rounded.tolist()))
306
+ if key in seen:
307
+ continue
308
+ seen.add(key)
309
+ keep_indices.append(index)
310
+ return points[np.asarray(keep_indices, dtype=int)]
@@ -0,0 +1,70 @@
1
+ """Case-preserving, sectioned INI data used by meshing parameters."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import configparser
6
+ from collections.abc import ItemsView
7
+ from io import StringIO
8
+ from os import PathLike
9
+ from typing import Any
10
+
11
+
12
+ class _CasePreservingConfigParser(configparser.ConfigParser):
13
+ def optionxform(self, optionstr: str) -> str:
14
+ return optionstr
15
+
16
+
17
+ class SectionedConfig:
18
+ """Small mutable INI container with typed scalar coercion."""
19
+
20
+ def __init__(self) -> None:
21
+ self._data: dict[str, dict[str, Any]] = {}
22
+
23
+ @staticmethod
24
+ def _coerce_value(value: str) -> Any:
25
+ text = value.strip()
26
+ if not text:
27
+ return text
28
+ if text.lower() in {"true", "false"}:
29
+ return text.lower() == "true"
30
+ try:
31
+ return int(text)
32
+ except ValueError:
33
+ pass
34
+ try:
35
+ return float(text)
36
+ except ValueError:
37
+ return text
38
+
39
+ def _load_from_parser(self, parser: configparser.ConfigParser) -> None:
40
+ for section in parser.sections():
41
+ self.add_section(section)
42
+ for name, value in parser.items(section):
43
+ self.set(section, name, self._coerce_value(value))
44
+
45
+ def from_file(self, file_path: str | PathLike[str]) -> None:
46
+ parser = _CasePreservingConfigParser(delimiters=("=", ":"), interpolation=None)
47
+ with open(file_path, encoding="utf-8") as stream:
48
+ parser.read_file(stream)
49
+ self._load_from_parser(parser)
50
+
51
+ def from_string(self, string: str) -> None:
52
+ parser = _CasePreservingConfigParser(delimiters=("=", ":"), interpolation=None)
53
+ parser.read_file(StringIO(string))
54
+ self._load_from_parser(parser)
55
+
56
+ def add_section(self, section: str) -> None:
57
+ self._data.setdefault(section, {})
58
+
59
+ def get(self, section: str, name: str, raw: bool = False) -> Any:
60
+ del raw
61
+ return self._data.get(section, {}).get(name)
62
+
63
+ def set(self, section: str, name: str, value: Any) -> None:
64
+ self._data.setdefault(section, {})[name] = value
65
+
66
+ def items(self, section: str) -> ItemsView[str, Any]:
67
+ return self._data.get(section, {}).items()
68
+
69
+ def to_string(self) -> str:
70
+ return str(self._data)
nmesh/nmesh.py ADDED
@@ -0,0 +1,99 @@
1
+ """Public nmesh compatibility façade with lazy geometry and driver exports."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from .mesh_generation import Mesh, get_default_meshing_parameters
8
+ from .mesh_io import (
9
+ MeshFromFile,
10
+ hdf5_mesh_get_permutation,
11
+ load,
12
+ mesh_from_points_and_simplices,
13
+ save,
14
+ write_mesh,
15
+ )
16
+ from .mesh_model import MeshBase, Point, Simplex
17
+ from .mesh_utilities import (
18
+ generate_1d_mesh,
19
+ generate_1d_mesh_components,
20
+ memory_report,
21
+ outer_corners,
22
+ to_lists,
23
+ tolists,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ pass
28
+
29
+ _GEOMETRY_EXPORTS = {
30
+ "AffineTransform",
31
+ "Body",
32
+ "Box",
33
+ "Conic",
34
+ "Ellipsoid",
35
+ "Helix",
36
+ "MeshObject",
37
+ "bc_box",
38
+ "bc_ellipsoid",
39
+ "bc_frustum",
40
+ "bc_helix",
41
+ "difference",
42
+ "intersect",
43
+ "inverse_axis_rotation",
44
+ "inverse_plane_rotation",
45
+ "inverse_scale",
46
+ "inverse_shift",
47
+ "union",
48
+ }
49
+ _MESHER_EXPORTS = {
50
+ "Callback",
51
+ "EngineFunc",
52
+ "MeshEngineCommand",
53
+ "MeshEngineStatus",
54
+ "MeshingParameters",
55
+ "do_every_n_steps_driver",
56
+ "make_mg_gendriver",
57
+ }
58
+ __all__ = [
59
+ "Mesh",
60
+ "MeshBase",
61
+ "MeshFromFile",
62
+ "Point",
63
+ "Simplex",
64
+ "generate_1d_mesh",
65
+ "generate_1d_mesh_components",
66
+ "get_default_meshing_parameters",
67
+ "hdf5_mesh_get_permutation",
68
+ "load",
69
+ "memory_report",
70
+ "mesh_from_points_and_simplices",
71
+ "outer_corners",
72
+ "save",
73
+ "to_lists",
74
+ "tolists",
75
+ "write_mesh",
76
+ *_GEOMETRY_EXPORTS,
77
+ *_MESHER_EXPORTS,
78
+ ]
79
+
80
+
81
+ def __getattr__(name: str) -> Any:
82
+ if name in _GEOMETRY_EXPORTS:
83
+ from . import geometry
84
+
85
+ value = getattr(geometry, name)
86
+ globals()[name] = value
87
+ return value
88
+ if name == "MeshingParameters":
89
+ from .mesher.meshing_parameters import MeshingParameters
90
+
91
+ globals()[name] = MeshingParameters
92
+ return MeshingParameters
93
+ if name in _MESHER_EXPORTS:
94
+ from .mesher import driver
95
+
96
+ value = getattr(driver, name)
97
+ globals()[name] = value
98
+ return value
99
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
nmesh/py.typed ADDED
@@ -0,0 +1 @@
1
+ # Marker for PEP 561 inline type information.
@@ -0,0 +1,33 @@
1
+ from .array_list_utils import (
2
+ array_filter,
3
+ array_one_shorter,
4
+ array_position,
5
+ array_position_if,
6
+ cross_product_3d,
7
+ det_and_inv,
8
+ determinant,
9
+ inverse,
10
+ )
11
+ from .constants import BOUNDARY_FUZZ, MIN_ABS_SCALE_FACTOR, MIN_DIVISION_MAGNITUDE
12
+ from .timing_memory_utils import memstats, time_passed, time_vmem_rss
13
+ from .types import BoolArray, FloatArray, IntArray
14
+
15
+ __all__ = [
16
+ "BOUNDARY_FUZZ",
17
+ "BoolArray",
18
+ "FloatArray",
19
+ "IntArray",
20
+ "MIN_ABS_SCALE_FACTOR",
21
+ "MIN_DIVISION_MAGNITUDE",
22
+ "array_filter",
23
+ "array_one_shorter",
24
+ "array_position",
25
+ "array_position_if",
26
+ "cross_product_3d",
27
+ "det_and_inv",
28
+ "determinant",
29
+ "inverse",
30
+ "memstats",
31
+ "time_passed",
32
+ "time_vmem_rss",
33
+ ]
@@ -0,0 +1,128 @@
1
+ from collections.abc import Callable, Sequence
2
+ from typing import Any, TypeVar
3
+
4
+ import numpy as np
5
+ from numpy.typing import NDArray
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ def array_filter(p: Callable[[T], bool], arr: Sequence[T]) -> NDArray[Any]:
11
+ """Filter array elements using a predicate function.
12
+
13
+ Args:
14
+ p: Predicate function returning True for elements to keep
15
+ arr: Input array or sequence
16
+
17
+ Returns:
18
+ NumPy array containing only elements where p(x) is True
19
+ """
20
+ arr_np = np.asanyarray(arr)
21
+ mask = np.array([p(x) for x in arr_np], dtype=bool)
22
+ return arr_np[mask]
23
+
24
+
25
+ def array_position(x: T, arr: Sequence[T], start: int = 0) -> int:
26
+ """Find first position of element x in array starting from index start.
27
+
28
+ Args:
29
+ x: Element to find
30
+ arr: Array to search
31
+ start: Starting index (default 0)
32
+
33
+ Returns:
34
+ Index of first occurrence, or -1 if not found
35
+ """
36
+ arr_np = np.asanyarray(arr)
37
+ sub_arr = arr_np[start:]
38
+ indices = np.where(sub_arr == x)[0]
39
+ if len(indices) > 0:
40
+ return int(indices[0] + start)
41
+ return -1
42
+
43
+
44
+ def array_position_if(p: Callable[[T], bool], arr: Sequence[T], start: int = 0) -> int:
45
+ """Find first position where predicate p is True, starting from index start.
46
+
47
+ Args:
48
+ p: Predicate function
49
+ arr: Array to search
50
+ start: Starting index (default 0)
51
+
52
+ Returns:
53
+ Index of first match, or -1 if not found
54
+ """
55
+ arr_np = np.asanyarray(arr)
56
+ sub_arr = arr_np[start:]
57
+ mask = np.array([p(x) for x in sub_arr], dtype=bool)
58
+ indices = np.where(mask)[0]
59
+ if len(indices) > 0:
60
+ return int(indices[0] + start)
61
+ return -1
62
+
63
+
64
+ def array_one_shorter(arr: Sequence[T], pos: int) -> NDArray[Any]:
65
+ """Remove element at position pos from array.
66
+
67
+ Args:
68
+ arr: Input array
69
+ pos: Index of element to remove
70
+
71
+ Returns:
72
+ New array with element removed
73
+ """
74
+ return np.delete(np.asanyarray(arr), pos)
75
+
76
+
77
+ def determinant(mx: Sequence[Sequence[float]]) -> float:
78
+ """Compute determinant of a matrix.
79
+
80
+ Args:
81
+ mx: Square matrix
82
+
83
+ Returns:
84
+ Determinant as float
85
+ """
86
+ return float(np.linalg.det(np.asanyarray(mx)))
87
+
88
+
89
+ def inverse(mx: Sequence[Sequence[float]]) -> NDArray[np.float64]:
90
+ """Compute inverse of a matrix.
91
+
92
+ Args:
93
+ mx: Square invertible matrix
94
+
95
+ Returns:
96
+ Inverse matrix as NumPy array
97
+ """
98
+ return np.linalg.inv(np.asanyarray(mx))
99
+
100
+
101
+ def det_and_inv(
102
+ mx: Sequence[Sequence[float]],
103
+ ) -> tuple[float, NDArray[np.float64]]:
104
+ """Compute determinant and inverse of a matrix simultaneously.
105
+
106
+ Args:
107
+ mx: Square invertible matrix
108
+
109
+ Returns:
110
+ Tuple of (determinant, inverse_matrix)
111
+ """
112
+ arr = np.asanyarray(mx)
113
+ det = np.linalg.det(arr)
114
+ inv = np.linalg.inv(arr)
115
+ return float(det), inv
116
+
117
+
118
+ def cross_product_3d(v1: Sequence[float], v2: Sequence[float]) -> NDArray[np.float64]:
119
+ """Compute 3D cross product of two vectors.
120
+
121
+ Args:
122
+ v1: First 3D vector
123
+ v2: Second 3D vector
124
+
125
+ Returns:
126
+ Cross product v1 × v2 as NumPy array
127
+ """
128
+ return np.cross(np.asanyarray(v1), np.asanyarray(v2))
@@ -0,0 +1,22 @@
1
+ """
2
+ Common numerical constants used throughout the nmesh package.
3
+
4
+ These constants provide consistent numerical tolerance values across different
5
+ modules for operations like scale validation, division safety checks, and
6
+ floating-point comparisons.
7
+ """
8
+
9
+ __all__ = [
10
+ "BOUNDARY_FUZZ",
11
+ "MIN_ABS_SCALE_FACTOR",
12
+ "MIN_DIVISION_MAGNITUDE",
13
+ ]
14
+
15
+ # Default tolerance used when testing whether points lie on or near boundaries.
16
+ BOUNDARY_FUZZ = 1e-6
17
+
18
+ # Smallest allowed absolute scale factor for affine transformations.
19
+ MIN_ABS_SCALE_FACTOR = 1e-12
20
+
21
+ # Smallest divisor magnitude accepted before force-law clamping kicks in.
22
+ MIN_DIVISION_MAGNITUDE = 1e-15