vmex 0.2.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 (61) hide show
  1. vmec_jax/__init__.py +45 -0
  2. vmex/__init__.py +131 -0
  3. vmex/__main__.py +14 -0
  4. vmex/_compat.py +310 -0
  5. vmex/core/__init__.py +29 -0
  6. vmex/core/bootstrap.py +985 -0
  7. vmex/core/boozer.py +155 -0
  8. vmex/core/boozer_tables.py +183 -0
  9. vmex/core/cli.py +835 -0
  10. vmex/core/device.py +171 -0
  11. vmex/core/errors.py +119 -0
  12. vmex/core/fields.py +703 -0
  13. vmex/core/forces.py +913 -0
  14. vmex/core/fourier.py +371 -0
  15. vmex/core/freeboundary.py +1194 -0
  16. vmex/core/freeboundary_diff.py +727 -0
  17. vmex/core/geometry.py +386 -0
  18. vmex/core/implicit.py +1291 -0
  19. vmex/core/input.py +649 -0
  20. vmex/core/mgrid.py +456 -0
  21. vmex/core/multigrid.py +299 -0
  22. vmex/core/nyquist.py +1013 -0
  23. vmex/core/omnigenity.py +589 -0
  24. vmex/core/optimize.py +1906 -0
  25. vmex/core/parallel.py +154 -0
  26. vmex/core/plotting.py +693 -0
  27. vmex/core/postprocess.py +617 -0
  28. vmex/core/preconditioner.py +697 -0
  29. vmex/core/preconditioner_2d.py +179 -0
  30. vmex/core/printing.py +145 -0
  31. vmex/core/profiles.py +634 -0
  32. vmex/core/residuals.py +598 -0
  33. vmex/core/setup.py +999 -0
  34. vmex/core/solver.py +1520 -0
  35. vmex/core/stability.py +432 -0
  36. vmex/core/statephysics.py +458 -0
  37. vmex/core/step.py +168 -0
  38. vmex/core/transforms.py +837 -0
  39. vmex/core/turbulence.py +571 -0
  40. vmex/core/vacuum.py +869 -0
  41. vmex/core/wout.py +865 -0
  42. vmex/doctor.py +204 -0
  43. vmex/mirror/__init__.py +84 -0
  44. vmex/mirror/analytic.py +368 -0
  45. vmex/mirror/basis.py +600 -0
  46. vmex/mirror/exterior.py +954 -0
  47. vmex/mirror/forces.py +885 -0
  48. vmex/mirror/free_boundary.py +934 -0
  49. vmex/mirror/geometry.py +596 -0
  50. vmex/mirror/implicit.py +536 -0
  51. vmex/mirror/model.py +242 -0
  52. vmex/mirror/output.py +1015 -0
  53. vmex/mirror/solver.py +639 -0
  54. vmex/mirror/splines.py +1177 -0
  55. vmex/resources/input.nfp4_QH_warm_start +47 -0
  56. vmex-0.2.0.dist-info/METADATA +615 -0
  57. vmex-0.2.0.dist-info/RECORD +61 -0
  58. vmex-0.2.0.dist-info/WHEEL +5 -0
  59. vmex-0.2.0.dist-info/entry_points.txt +3 -0
  60. vmex-0.2.0.dist-info/licenses/LICENSE +21 -0
  61. vmex-0.2.0.dist-info/top_level.txt +2 -0
vmec_jax/__init__.py ADDED
@@ -0,0 +1,45 @@
1
+ """Compatibility shim: ``vmec_jax`` was renamed to :mod:`vmex`.
2
+
3
+ Importing ``vmec_jax`` re-exports everything from :mod:`vmex` and emits a
4
+ ``DeprecationWarning``. Submodule access (``vmec_jax.core.solver`` etc.) is
5
+ forwarded by aliasing the already-imported ``vmex`` submodules into
6
+ ``sys.modules`` under the old names. This shim is a one-release courtesy;
7
+ update imports to ``vmex``.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import importlib
12
+ import sys
13
+ import warnings
14
+
15
+ warnings.warn(
16
+ "the 'vmec_jax' package has been renamed to 'vmex'; import 'vmex' instead "
17
+ "(this compatibility shim will be removed in a future release)",
18
+ DeprecationWarning,
19
+ stacklevel=2,
20
+ )
21
+
22
+ import vmex as _vmex
23
+
24
+ # Re-export the public API.
25
+ from vmex import * # noqa: F401,F403
26
+ __version__ = _vmex.__version__
27
+ if hasattr(_vmex, "__all__"):
28
+ __all__ = list(_vmex.__all__)
29
+
30
+ # Forward submodule access: alias every imported vmex submodule under the old
31
+ # top-level name so `import vmec_jax.core.X` and `from vmec_jax.core import X`
32
+ # resolve to the identical vmex module object.
33
+ for _name, _mod in list(sys.modules.items()):
34
+ if _name == "vmex" or _name.startswith("vmex."):
35
+ sys.modules["vmec_jax" + _name[len("vmex"):]] = _mod
36
+
37
+
38
+ def __getattr__(name: str):
39
+ """Lazily import and forward any vmex submodule not yet loaded."""
40
+ try:
41
+ mod = importlib.import_module(f"vmex.{name}")
42
+ except ModuleNotFoundError as exc: # pragma: no cover - mirror vmex
43
+ raise AttributeError(name) from exc
44
+ sys.modules[f"vmec_jax.{name}"] = mod
45
+ return mod
vmex/__init__.py ADDED
@@ -0,0 +1,131 @@
1
+ """vmex: a JAX implementation of VMEC2000 for fixed and free-boundary equilibria.
2
+
3
+ Public API (lazily imported; ``import vmex as vj``):
4
+
5
+ - :class:`~vmex.core.input.VmecInput` — INDATA / VMEC++-JSON input pytree
6
+ - :func:`~vmex.core.solver.solve` — single-grid fixed-boundary solve
7
+ - :func:`~vmex.core.multigrid.solve_multigrid` — NS_ARRAY ladder (runvmec.f)
8
+ - :func:`~vmex.core.freeboundary.solve_free_boundary` — NESTOR free boundary
9
+ - :func:`~vmex.core.wout.read_wout` / :func:`~vmex.core.wout.write_wout`
10
+ / :func:`~vmex.core.wout.wout_from_state` / :class:`~vmex.core.wout.WoutData`
11
+ - :func:`~vmex.core.plotting.plot_wout` / :func:`~vmex.core.plotting.plot_boozmn`
12
+ - :func:`~vmex.core.boozer.run_booz_xform` — Boozer transform (booz_xform_jax)
13
+ - :func:`~vmex.core.mgrid.read_mgrid` / :func:`~vmex.core.mgrid.write_mgrid`
14
+ / :class:`~vmex.core.mgrid.MgridField` (external field is an mgrid or any
15
+ ``xyz->B`` callable; coils live in ESSOS, ``essos.coils.Coils``)
16
+ - ``vmex.optimize`` — objectives + least-squares driver (module)
17
+ - ``vmex.implicit`` — implicit differentiation of the equilibrium (module)
18
+ - ``vmex.parallel`` — concurrent ensembles of independent solves (module)
19
+ - ``vmex.errors`` — typed zero-crash exceptions (also exported directly)
20
+
21
+ The ``vmec`` console entry point lives in :mod:`vmex.core.cli`.
22
+ """
23
+
24
+ from importlib import import_module as _import_module
25
+ from importlib.metadata import PackageNotFoundError as _PackageNotFoundError
26
+ from importlib.metadata import version as _package_version
27
+ import os as _os
28
+ from pathlib import Path as _Path
29
+
30
+ from ._compat import _default_compilation_cache_dir as _default_jax_cache_dir
31
+
32
+
33
+ def _source_tree_version() -> str | None:
34
+ pyproject = _Path(__file__).resolve().parents[1] / "pyproject.toml"
35
+ if not pyproject.exists():
36
+ return None
37
+ in_project = False
38
+ for raw_line in pyproject.read_text(encoding="utf-8").splitlines():
39
+ line = raw_line.strip()
40
+ if line == "[project]":
41
+ in_project = True
42
+ continue
43
+ if in_project and line.startswith("["):
44
+ return None
45
+ if in_project and line.startswith("version"):
46
+ return line.split("=", 1)[1].strip().strip('"')
47
+ return None
48
+
49
+
50
+ try:
51
+ __version__ = _source_tree_version() or _package_version("vmex")
52
+ except _PackageNotFoundError: # pragma: no cover - source tree without installed metadata.
53
+ __version__ = "0+unknown"
54
+
55
+ # Suppress noisy C++ warnings from XLA/PjRt backend (e.g. repeated
56
+ # "Assume version compatibility. PjRt-IFRT does not track XLA executable
57
+ # versions." on persistent-cache hits). Must be set before *any* ``import
58
+ # jax`` in the process. Uses setdefault so the user can still override via the
59
+ # environment.
60
+ _os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "2")
61
+ _os.environ.setdefault("ABSL_MIN_LOG_LEVEL", "2")
62
+ _os.environ.setdefault("GLOG_minloglevel", "2")
63
+
64
+ # Enable the JAX persistent XLA compilation cache in a machine-scoped
65
+ # directory when requested by the backend/env policy in _compat. Accelerator
66
+ # runs use the cache by default; CPU runs are opt-in to avoid XLA:CPU AOT
67
+ # feature-mismatch warnings on shared or changing runtime environments.
68
+ # ``core.solver._harden_compilation_cache`` re-applies this policy on every
69
+ # solve path in case this module never ran (namespace-package shadowing).
70
+ import jax as _jax
71
+
72
+ _jax_cache_dir = _default_jax_cache_dir()
73
+ if _jax_cache_dir is not None:
74
+ _os.makedirs(_jax_cache_dir, exist_ok=True)
75
+ _jax.config.update("jax_enable_compilation_cache", True)
76
+ _jax.config.update("jax_compilation_cache_dir", _jax_cache_dir)
77
+
78
+ # Lazy public exports: name -> (module, attribute). ``attribute=None``
79
+ # exports the module itself.
80
+ _LAZY_ATTRS: dict[str, tuple[str, str | None]] = {
81
+ # input
82
+ "VmecInput": (".core.input", "VmecInput"),
83
+ # solvers
84
+ "solve": (".core.solver", "solve"),
85
+ "solve_multigrid": (".core.multigrid", "solve_multigrid"),
86
+ "solve_free_boundary": (".core.freeboundary", "solve_free_boundary"),
87
+ # wout IO
88
+ "WoutData": (".core.wout", "WoutData"),
89
+ "read_wout": (".core.wout", "read_wout"),
90
+ "write_wout": (".core.wout", "write_wout"),
91
+ "wout_from_state": (".core.wout", "wout_from_state"),
92
+ # plotting + Boozer
93
+ "plot_wout": (".core.plotting", "plot_wout"),
94
+ "plot_boozmn": (".core.plotting", "plot_boozmn"),
95
+ "run_booz_xform": (".core.boozer", "run_booz_xform"),
96
+ # external fields
97
+ "MgridData": (".core.mgrid", "MgridData"),
98
+ "MgridField": (".core.mgrid", "MgridField"),
99
+ "read_mgrid": (".core.mgrid", "read_mgrid"),
100
+ "write_mgrid": (".core.mgrid", "write_mgrid"),
101
+ # errors
102
+ "VmecError": (".core.errors", "VmecError"),
103
+ "VmecInputError": (".core.errors", "VmecInputError"),
104
+ "VmecJacobianError": (".core.errors", "VmecJacobianError"),
105
+ "VmecConvergenceError": (".core.errors", "VmecConvergenceError"),
106
+ "MgridNotFoundError": (".core.errors", "MgridNotFoundError"),
107
+ # modules
108
+ "core": (".core", None),
109
+ "errors": (".core.errors", None),
110
+ "optimize": (".core.optimize", None),
111
+ "implicit": (".core.implicit", None),
112
+ "parallel": (".core.parallel", None),
113
+ "doctor": (".doctor", None),
114
+ }
115
+
116
+ __all__ = ["__version__", *sorted(_LAZY_ATTRS)]
117
+
118
+
119
+ def __getattr__(name: str):
120
+ entry = _LAZY_ATTRS.get(name)
121
+ if entry is None:
122
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
123
+ module_name, attribute = entry
124
+ module = _import_module(module_name, __name__)
125
+ value = module if attribute is None else getattr(module, attribute)
126
+ globals()[name] = value
127
+ return value
128
+
129
+
130
+ def __dir__():
131
+ return sorted(set(globals()) | set(_LAZY_ATTRS))
vmex/__main__.py ADDED
@@ -0,0 +1,14 @@
1
+ """Module entry point for `python -m vmex`."""
2
+
3
+ import os as _os
4
+
5
+ # Suppress noisy C++ warnings from XLA/PjRt before any JAX import.
6
+ _os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "2")
7
+ _os.environ.setdefault("ABSL_MIN_LOG_LEVEL", "2")
8
+ _os.environ.setdefault("GLOG_minloglevel", "2")
9
+
10
+ from .core.cli import main
11
+
12
+
13
+ if __name__ == "__main__": # pragma: no cover
14
+ raise SystemExit(main())
vmex/_compat.py ADDED
@@ -0,0 +1,310 @@
1
+ """JAX environment defaults + persistent compilation-cache policy.
2
+
3
+ Historically this module was a full JAX/NumPy backend shim (``has_jax`` /
4
+ ``asarray`` / ``einsum`` / a no-op ``jit`` and a thread-local numpy mode).
5
+ The core became JAX-only long ago and nothing imported that machinery any
6
+ more, so it was deleted (Item I.8a). What remains — and is
7
+ actually used — is:
8
+
9
+ - :func:`_configure_jax_environment` (run at import, i.e. before
10
+ ``vmex/__init__`` does ``import jax``): environment defaults that must
11
+ be set before JAX/XLA initializes — float64 (``JAX_ENABLE_X64``, VMEC
12
+ parity), synchronous CPU dispatch, quiet XLA/PjRt C++ logging, GPU
13
+ demand allocation, the machine-scoped persistent compilation-cache
14
+ directory, and the XLA:CPU fast-compile flags;
15
+ - the compilation-cache policy helpers
16
+ :func:`_default_compilation_cache_dir` / :func:`_cache_machine_fingerprint`
17
+ / :func:`_configure_compilation_cache`, consumed by ``vmex/__init__``
18
+ and re-applied by ``core.solver._harden_compilation_cache`` on every solve
19
+ path (namespace-package shadowing guard).
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from typing import Any
25
+ import hashlib
26
+ from importlib import metadata as importlib_metadata
27
+ import sys
28
+
29
+ import os
30
+ import platform
31
+
32
+
33
+ def _env(name: str, default: str = "") -> str:
34
+ """Read ``VMEX_<name>``, falling back to the legacy ``VMEC_JAX_<name>``.
35
+
36
+ The package was renamed vmec_jax -> vmex; environment variables a user may
37
+ have set in their shell profile (the ``*_COMPILATION_CACHE*`` knobs in
38
+ particular) keep working under their old names for one release.
39
+ """
40
+ val = os.environ.get(f"VMEX_{name}")
41
+ if val is not None:
42
+ return val
43
+ return os.environ.get(f"VMEC_JAX_{name}", default)
44
+
45
+
46
+ def _cache_machine_fingerprint() -> str:
47
+ """Return a short cache key for host-specific XLA CPU executables.
48
+
49
+ XLA CPU persistent-cache entries are native executables. On shared home
50
+ directories, reusing an entry compiled on another CPU can trigger XLA AOT
51
+ loader errors or even illegal-instruction failures. The fingerprint keeps
52
+ vmex's default cache portable by separating entries by OS, machine, and
53
+ CPU-feature/model signature. Users who deliberately want a shared cache can
54
+ still set ``VMEX_COMPILATION_CACHE_DIR`` or ``JAX_COMPILATION_CACHE_DIR``.
55
+ """
56
+
57
+ parts = [
58
+ platform.system(),
59
+ platform.machine(),
60
+ platform.processor(),
61
+ f"python={sys.version_info.major}.{sys.version_info.minor}",
62
+ ]
63
+ for package in ("jax", "jaxlib"):
64
+ try:
65
+ parts.append(f"{package}={importlib_metadata.version(package)}")
66
+ except Exception:
67
+ pass
68
+ try:
69
+ if os.path.exists("/proc/cpuinfo"):
70
+ wanted = ("model name", "cpu family", "model", "stepping", "flags", "Features")
71
+ seen: set[str] = set()
72
+ with open("/proc/cpuinfo", encoding="utf-8", errors="ignore") as fh:
73
+ for line in fh:
74
+ if ":" not in line:
75
+ continue
76
+ key, value = (part.strip() for part in line.split(":", 1))
77
+ if key in wanted and key not in seen:
78
+ parts.append(f"{key}={value}")
79
+ seen.add(key)
80
+ except Exception:
81
+ pass
82
+ # macOS has no /proc/cpuinfo — capture the CPU brand + microarchitecture via
83
+ # sysctl so Intel/Apple-Silicon (and different chip generations) never share
84
+ # an XLA:CPU AOT cache entry.
85
+ if platform.system() == "Darwin":
86
+ try:
87
+ import subprocess
88
+ for key in ("machdep.cpu.brand_string", "hw.optional.arm.FEAT_SME",
89
+ "hw.cpufamily"):
90
+ out = subprocess.run(["sysctl", "-n", key], capture_output=True,
91
+ text=True, timeout=2)
92
+ if out.returncode == 0 and out.stdout.strip():
93
+ parts.append(f"{key}={out.stdout.strip()}")
94
+ except Exception:
95
+ pass
96
+ if not any(str(part).strip() for part in parts[:3]):
97
+ try:
98
+ parts.append(platform.node())
99
+ except Exception:
100
+ pass
101
+ digest = hashlib.sha256("|".join(parts).encode("utf-8", errors="ignore")).hexdigest()[:16]
102
+ system = platform.system().lower() or "unknown"
103
+ machine = platform.machine().lower() or "unknown"
104
+ return f"{system}-{machine}-{digest}"
105
+
106
+
107
+ def _default_compilation_cache_dir() -> str | None:
108
+ """Return the configured JAX compilation-cache directory.
109
+
110
+ The persistent cache is enabled **by default on every backend** (CPU too)
111
+ so that repeated cold-process CLI/API runs reuse compiled kernels instead
112
+ of recompiling — a solovev CLI run drops 4.3 s -> 1.2 s on the second
113
+ invocation (R26c). The XLA:CPU host-feature-mismatch hazard (AOT
114
+ executables tied to a specific instruction set, dangerous on shared home
115
+ filesystems) is handled by :func:`_cache_machine_fingerprint`, whose
116
+ per-machine suffix hashes the CPU model + feature flags (AVX2/AVX512/...),
117
+ so heterogeneous machines never share a cache entry. Opt out with
118
+ ``VMEX_COMPILATION_CACHE=disabled`` (or ``VMEX_COMPILATION_CACHE_DIR=
119
+ disabled``); point it elsewhere with ``JAX_COMPILATION_CACHE_DIR=/path``.
120
+ """
121
+ # Already set by the user — respect it.
122
+ if "JAX_COMPILATION_CACHE_DIR" in os.environ:
123
+ val = os.environ["JAX_COMPILATION_CACHE_DIR"].strip()
124
+ if val.lower() in ("", "disabled", "0", "false", "no"):
125
+ return None
126
+ return val
127
+
128
+ # User can opt out via VMEX_COMPILATION_CACHE_DIR=disabled
129
+ vmec_val = _env("COMPILATION_CACHE_DIR").strip()
130
+ if vmec_val.lower() in ("disabled", "0", "false", "no"):
131
+ return None
132
+ if vmec_val:
133
+ return vmec_val
134
+
135
+ cache_flag = _env("COMPILATION_CACHE").strip().lower()
136
+ if cache_flag in ("disabled", "0", "false", "no", "off"):
137
+ return None
138
+
139
+ # Default cache location: ~/.cache/vmex/jax_cache/<machine-fingerprint>
140
+ # The host-specific suffix (CPU model + feature flags) prevents unsafe
141
+ # XLA:CPU AOT reuse on shared home filesystems where different machines see
142
+ # the same ~/.cache directory.
143
+ try:
144
+ import pathlib
145
+ return str(
146
+ pathlib.Path.home()
147
+ / ".cache"
148
+ / "vmex"
149
+ / "jax_cache"
150
+ / _cache_machine_fingerprint()
151
+ )
152
+ except Exception:
153
+ return None
154
+
155
+
156
+ def _configure_compilation_cache(jax_module: Any, cache_dir: str | None) -> None:
157
+ """Apply vmex's persistent-cache defaults to an imported JAX module."""
158
+ if cache_dir is None:
159
+ return
160
+ try:
161
+ jax_module.config.update("jax_enable_compilation_cache", True)
162
+ except Exception:
163
+ pass
164
+ try:
165
+ jax_module.config.update("jax_compilation_cache_dir", cache_dir)
166
+ except Exception:
167
+ pass
168
+ try:
169
+ min_compile = _env("CACHE_MIN_COMPILE_TIME_SECS", "0")
170
+ jax_module.config.update("jax_persistent_cache_min_compile_time_secs", float(min_compile))
171
+ except Exception:
172
+ pass
173
+ try:
174
+ min_entry = _env("CACHE_MIN_ENTRY_SIZE_BYTES", "-1")
175
+ jax_module.config.update("jax_persistent_cache_min_entry_size_bytes", int(min_entry))
176
+ except Exception:
177
+ pass
178
+ try:
179
+ xla_caches = _env("PERSISTENT_CACHE_XLA_CACHES").strip()
180
+ if not xla_caches:
181
+ platform_name = os.environ.get("JAX_PLATFORM_NAME", "").strip().lower()
182
+ platforms = os.environ.get("JAX_PLATFORMS", "").strip().lower()
183
+ visible_cuda = os.environ.get("CUDA_VISIBLE_DEVICES", "").strip().lower()
184
+ gpu_requested = (
185
+ platform_name in ("gpu", "cuda")
186
+ or any(part.strip() in ("gpu", "cuda") for part in platforms.split(","))
187
+ or visible_cuda not in ("", "-1", "none", "no")
188
+ )
189
+ xla_caches = "xla_gpu_per_fusion_autotune_cache_dir" if gpu_requested else "none"
190
+ if xla_caches.lower() not in ("", "none", "0", "false", "no", "off"):
191
+ jax_module.config.update("jax_persistent_cache_enable_xla_caches", xla_caches)
192
+ except Exception:
193
+ pass
194
+ try:
195
+ max_size = _env("COMPILATION_CACHE_MAX_SIZE")
196
+ if max_size:
197
+ jax_module.config.update("jax_compilation_cache_max_size", int(max_size))
198
+ except Exception:
199
+ pass
200
+ try:
201
+ explain = _env("EXPLAIN_CACHE_MISSES")
202
+ if explain.strip().lower() not in ("", "0", "false", "no"):
203
+ jax_module.config.update("jax_explain_cache_misses", True)
204
+ except Exception:
205
+ pass
206
+
207
+
208
+ def _configure_jax_environment() -> None:
209
+ """Set JAX/XLA environment defaults, then import + configure JAX.
210
+
211
+ Runs once at ``vmex._compat`` import time — before
212
+ ``vmex/__init__`` (or anything else in the package) imports JAX — so
213
+ the env-var defaults reliably reach XLA backend initialization. Every
214
+ default uses ``setdefault``: an explicit user environment always wins.
215
+ """
216
+ try:
217
+ # Enable x64 by default for VMEC parity unless the user opted out.
218
+ os.environ.setdefault("JAX_ENABLE_X64", "1")
219
+ # VMEC/JAX optimization callbacks immediately materialize most results
220
+ # on the host (SciPy residuals/Jacobians, history, wout writing). On
221
+ # CPU, asynchronous dispatch can leave completed XLA/PjRt work and
222
+ # executable state queued across many exact-Jacobian callbacks in one
223
+ # long-lived process. Default CPU dispatch to synchronous execution so
224
+ # memory is reclaimed at callback boundaries; users can still override
225
+ # this before import with JAX_CPU_ENABLE_ASYNC_DISPATCH=true.
226
+ os.environ.setdefault("JAX_CPU_ENABLE_ASYNC_DISPATCH", "false")
227
+ # Suppress noisy C++ warnings from XLA/PjRt backend (e.g.
228
+ # repeated "Assume version compatibility. PjRt-IFRT does not
229
+ # track XLA executable versions." on persistent-cache hits).
230
+ # These are harmless informational messages emitted by the XLA
231
+ # runtime logging stack. Level 0=INFO, 1=WARNING, 2=ERROR — we
232
+ # default to ERROR-only so that genuine errors still surface.
233
+ os.environ.setdefault("TF_CPP_MIN_LOG_LEVEL", "2")
234
+ os.environ.setdefault("ABSL_MIN_LOG_LEVEL", "2")
235
+ os.environ.setdefault("GLOG_minloglevel", "2")
236
+ # JAX's default GPU allocator preallocates most device memory. That
237
+ # hurts vmex's exact-optimizer workload in practice: it prevents
238
+ # concurrent profiling/worker processes from starting and can make the
239
+ # accepted-point replay path much slower. Default to demand allocation
240
+ # unless the user already set JAX's allocator env var or explicitly
241
+ # asks vmex to keep JAX's preallocation default.
242
+ _vmec_gpu_prealloc = _env("GPU_PREALLOCATE").strip().lower()
243
+ if (
244
+ "XLA_PYTHON_CLIENT_PREALLOCATE" not in os.environ
245
+ and _vmec_gpu_prealloc not in ("1", "true", "yes", "on")
246
+ ):
247
+ os.environ["XLA_PYTHON_CLIENT_PREALLOCATE"] = "false"
248
+
249
+ # Enable the JAX disk compilation cache in a machine-scoped directory.
250
+ # This avoids unsafe XLA:CPU AOT reuse across hosts while preserving
251
+ # repeated cold-process speedups on the same machine.
252
+ _cache_dir = _default_compilation_cache_dir()
253
+ if _cache_dir is not None:
254
+ os.environ.setdefault("JAX_COMPILATION_CACHE_DIR", _cache_dir)
255
+
256
+ # XLA:CPU compile-time flags. The differentiable/optimization pipeline
257
+ # is COMPILE-dominated (the fused adjoint VJP + GMRES graph is ~21 s of a
258
+ # ~24 s cold ``value_and_grad``); XLA's default backend optimization
259
+ # level (3) spends most of that in expensive LLVM passes. Level 1 plus
260
+ # disabling the expensive passes typically cuts compile wall-time
261
+ # ~1.3-2x, at the cost of slightly slower *warm* kernels -- a good trade
262
+ # for this compile-bound workload. Applied on CPU only (LLVM codegen),
263
+ # never with fast-math (that would break float64 parity/determinism),
264
+ # skipped if the user set XLA_FLAGS, and opt-out via
265
+ # VMEX_FAST_COMPILE=0 (e.g. a very long single-process opt loop that
266
+ # amortizes compile over many warm calls prefers level 3).
267
+ _fast_compile = _env("FAST_COMPILE", "1").strip().lower()
268
+ _accel_req = os.environ.get("JAX_PLATFORM_NAME", "").strip().lower()
269
+ _accel_reqs = os.environ.get("JAX_PLATFORMS", "").strip().lower()
270
+ _cuda_vis = os.environ.get("CUDA_VISIBLE_DEVICES", "").strip()
271
+ _on_accel = (
272
+ any(a in f"{_accel_req} {_accel_reqs}" for a in ("cuda", "gpu", "tpu", "rocm"))
273
+ or (_cuda_vis not in ("", "-1"))
274
+ )
275
+ if (
276
+ _fast_compile not in ("0", "false", "no", "off")
277
+ and "XLA_FLAGS" not in os.environ
278
+ and not _on_accel
279
+ ):
280
+ os.environ["XLA_FLAGS"] = (
281
+ "--xla_backend_optimization_level=1 "
282
+ "--xla_llvm_disable_expensive_passes=true"
283
+ )
284
+
285
+ import jax
286
+
287
+ try:
288
+ jax.config.update("jax_enable_x64", os.environ.get("JAX_ENABLE_X64", "0") == "1")
289
+ except Exception:
290
+ pass
291
+ try:
292
+ _cpu_async = os.environ.get("JAX_CPU_ENABLE_ASYNC_DISPATCH", "true")
293
+ jax.config.update(
294
+ "jax_cpu_enable_async_dispatch",
295
+ _cpu_async.strip().lower() not in ("0", "false", "no", "off"),
296
+ )
297
+ except Exception:
298
+ pass
299
+
300
+ # Wire up the compilation cache via jax.config too; the env-var path
301
+ # alone does not cover all JAX/JAXLIB versions and cache thresholds.
302
+ _configure_compilation_cache(jax, _cache_dir)
303
+ except Exception:
304
+ # Never block a vmex import over environment tuning (e.g. docs
305
+ # builds with a mocked JAX): core.solver enforces the hard
306
+ # requirements (x64, cache hardening) on every solve path anyway.
307
+ pass
308
+
309
+
310
+ _configure_jax_environment()
vmex/core/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ """Clean-room core of vmex (§5) — will replace the legacy modules.
2
+
3
+ Module map (each header docstring names its VMEC2000 counterpart):
4
+
5
+ - ``errors`` typed zero-crash exceptions + werror table
6
+ - ``printing`` VMEC2000-format console output (byte-exact)
7
+ - ``input`` VmecInput: INDATA + VMEC++-JSON parsing, round-trip writers
8
+ - ``profiles`` pressure/iota/current parameterizations (pure jnp)
9
+ - ``fourier`` Resolution, ModeTable, trig tables (fixaray.f)
10
+ - ``transforms`` totzsps/totzspa/tomnsps/tomnspa as batched matmuls
11
+ - ``geometry`` real-space R/Z/lambda, half-mesh jacobian (jacobian.f)
12
+ - ``fields`` metrics, B components, energies, tcon (bcovar.f)
13
+ - ``forces`` MHD force kernels + spectral condensation (forces.f, alias.f)
14
+ - ``residuals`` m=1 constraint, fsqr/fsqz/fsql, preconditioned lane (residue.f90)
15
+ - ``preconditioner`` 1D radial tridiagonal preconditioner (precondn.f, scalfor.f)
16
+ - ``step`` Richardson stepping + restart control (evolve.f, restart.f)
17
+ - ``setup`` radial profiles + initial guess (profil1d/3d.f, readin.f)
18
+ - ``solver`` single-grid fixed-boundary solve loop (funct3d.f, eqsolve.f)
19
+ - ``statephysics`` shared state-physics primitives (_field_chain, half-mesh iota/sampling)
20
+ - ``implicit`` implicit differentiation of the equilibrium (custom VJP + adjoint GMRES)
21
+ - ``stability`` differentiable ideal-MHD stability (infinite-n ballooning; COBRA port)
22
+ - ``freeboundary_diff`` differentiable free-boundary residual via virtual casing (R15.3/R19)
23
+ - ``device`` CPU/GPU placement policy (measured: benchmarks/gpu_baseline.json)
24
+
25
+ Every module is validated by A/B equivalence tests against the legacy
26
+ parity-proven implementation in ``tests/``; the solve loop is
27
+ validated end-to-end against VMEC2000 golden runs
28
+ (``tests/test_solver_end_to_end.py``).
29
+ """