fastatomstruct 2.0.0__cp39-abi3-win_amd64.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 (63) hide show
  1. fastatomstruct/__init__.py +143 -0
  2. fastatomstruct/__init__.pyi +133 -0
  3. fastatomstruct/_pjrt/__init__.py +185 -0
  4. fastatomstruct/_quantity.py +2441 -0
  5. fastatomstruct/active_learning/__init__.py +76 -0
  6. fastatomstruct/active_learning/ase_bridge.py +110 -0
  7. fastatomstruct/active_learning/checkpoint.py +214 -0
  8. fastatomstruct/active_learning/committee.py +123 -0
  9. fastatomstruct/active_learning/config.py +602 -0
  10. fastatomstruct/active_learning/data.py +272 -0
  11. fastatomstruct/active_learning/dataset.py +206 -0
  12. fastatomstruct/active_learning/devices.py +123 -0
  13. fastatomstruct/active_learning/dft.py +151 -0
  14. fastatomstruct/active_learning/dft_queue.py +766 -0
  15. fastatomstruct/active_learning/events.py +43 -0
  16. fastatomstruct/active_learning/logging_utils.py +56 -0
  17. fastatomstruct/active_learning/md_components.py +313 -0
  18. fastatomstruct/active_learning/runners/__init__.py +0 -0
  19. fastatomstruct/active_learning/runners/_base.py +509 -0
  20. fastatomstruct/active_learning/runners/md.py +3695 -0
  21. fastatomstruct/active_learning/runners/pool.py +191 -0
  22. fastatomstruct/active_learning/runners/relaxation.py +248 -0
  23. fastatomstruct/active_learning/runners/rss.py +123 -0
  24. fastatomstruct/active_learning/trainer.py +1071 -0
  25. fastatomstruct/analysis.pyi +4918 -0
  26. fastatomstruct/bonding.pyi +428 -0
  27. fastatomstruct/build.pyi +377 -0
  28. fastatomstruct/calc.pyi +856 -0
  29. fastatomstruct/curve_editor/__init__.py +24 -0
  30. fastatomstruct/curve_editor/editor.py +1004 -0
  31. fastatomstruct/curve_editor/export.py +91 -0
  32. fastatomstruct/curve_editor/io_json.py +125 -0
  33. fastatomstruct/curve_editor/presets.py +92 -0
  34. fastatomstruct/curve_editor/units.py +31 -0
  35. fastatomstruct/elph.pyi +961 -0
  36. fastatomstruct/fastatomstruct.pyd +4 -0
  37. fastatomstruct/fastatomstruct.pyi +10370 -0
  38. fastatomstruct/io.pyi +803 -0
  39. fastatomstruct/kubo_greenwood_transport.pyi +212 -0
  40. fastatomstruct/mace_export.py +614 -0
  41. fastatomstruct/mace_foundation.py +38 -0
  42. fastatomstruct/mace_jax_backend.py +522 -0
  43. fastatomstruct/mace_rust_backend.py +168 -0
  44. fastatomstruct/mace_rust_train.py +118 -0
  45. fastatomstruct/md.pyi +1734 -0
  46. fastatomstruct/nald.pyi +126 -0
  47. fastatomstruct/optical_spectrum.pyi +391 -0
  48. fastatomstruct/optimizers.pyi +136 -0
  49. fastatomstruct/phonon.pyi +346 -0
  50. fastatomstruct/plotting.py +989 -0
  51. fastatomstruct/py.typed +0 -0
  52. fastatomstruct/queue.pyi +374 -0
  53. fastatomstruct/transport.py +611 -0
  54. fastatomstruct/ui.py +105 -0
  55. fastatomstruct/units.pyi +2315 -0
  56. fastatomstruct/viewer.pyi +368 -0
  57. fastatomstruct-2.0.0.dist-info/METADATA +211 -0
  58. fastatomstruct-2.0.0.dist-info/RECORD +63 -0
  59. fastatomstruct-2.0.0.dist-info/WHEEL +4 -0
  60. fastatomstruct-2.0.0.dist-info/licenses/ADDITIONAL-PERMISSIONS.md +26 -0
  61. fastatomstruct-2.0.0.dist-info/licenses/LICENSE +674 -0
  62. fastatomstruct-2.0.0.dist-info/licenses/THIRD-PARTY-NOTICES.md +83 -0
  63. fastatomstruct-2.0.0.dist-info/sboms/fastatomstruct-python.cyclonedx.json +19768 -0
@@ -0,0 +1,143 @@
1
+ from __future__ import annotations
2
+
3
+ from .fastatomstruct import *
4
+
5
+ # ── units: Quantity type + unit registry, built from the Rust unit table ────
6
+ # ``fs.units`` becomes the unit namespace: ``fs.units.fs`` is the unit, so
7
+ # ``2 * fs.units.fs`` is a ``Quantity``; the ASE-style float constants move
8
+ # to ``fs.units.ase``, the registry is ``fs.units.u``. Everything is
9
+ # built from ``fs.units.units_table()``, which mirrors
10
+ # ``fastatomstruct_core::units::quantities::UNITS`` — the single source of
11
+ # truth for symbols, factors and dimensions.
12
+ from ._quantity import attach_to_units_module as _attach_units # noqa: E402
13
+
14
+ _attach_units(globals().get("units"))
15
+
16
+ # ── Convenience re-exports of the most common entry points. ────────────────
17
+ # The canonical location of each name is still the submodule; the aliases
18
+ # below are only there so quick scripts and notebooks stay compact. New
19
+ # symbols should not be added here lightly — submodules are the default.
20
+ #
21
+ # Every alias is guarded so a wheel that was built without the relevant
22
+ # optional Cargo feature (``mace``, ``cp2k``, ``io``, ``md``, …) still
23
+ # imports cleanly — the missing name simply doesn't appear at the top level.
24
+
25
+
26
+ def _alias(submodule_name: str, *names: str) -> None:
27
+ """Bind ``fastatomstruct.<name>`` for every ``name`` in *names*, picking
28
+ each one from ``fastatomstruct.<submodule_name>``. Anything missing
29
+ (submodule or attribute) is silently skipped so the import never blows
30
+ up because of an optional Cargo feature."""
31
+ submodule = globals().get(submodule_name)
32
+ if submodule is None:
33
+ return
34
+ for name in names:
35
+ attr = getattr(submodule, name, None)
36
+ if attr is not None:
37
+ globals()[name] = attr
38
+
39
+
40
+ def _get_exported_names() -> list[str]:
41
+ """Return a list of public names exported at the top level."""
42
+ return [n for n in globals() if not n.startswith("_")]
43
+
44
+
45
+ def _is_available(name: str) -> bool:
46
+ """Check whether *name* is exported at the top level."""
47
+ return name in globals()
48
+
49
+
50
+ # Verbs.
51
+ _alias("viewer", "view", "view_many", "view_interactive", "view_image")
52
+ _alias("io", "open", "read", "write")
53
+ _alias("md", "run_molecular_dynamics")
54
+ _alias("optimizers", "run_optimization")
55
+
56
+ # Calculators.
57
+ _alias(
58
+ "calc",
59
+ "LennardJones",
60
+ "EmtCalculator",
61
+ "ACECalculator",
62
+ "MACECalculator",
63
+ "CP2KCalculator",
64
+ )
65
+
66
+ # MD building blocks — integrator + standard thermostats + standard barostats.
67
+ _alias(
68
+ "md",
69
+ "VelocityVerlet",
70
+ "LangevinIntegrator",
71
+ "NoThermostat",
72
+ "BerendsenThermostat",
73
+ "BussiThermostat",
74
+ "NoseHooverThermostat",
75
+ "AndersenThermostat",
76
+ "NoBarostat",
77
+ "BussiBarostat",
78
+ "SemiIsotropicBussiBarostat",
79
+ )
80
+
81
+ # Optimizer building blocks.
82
+ _alias(
83
+ "optimizers",
84
+ "FireOptimizer",
85
+ "LbfgsbOptimizer",
86
+ "BfgsOptimizer",
87
+ "NagOptimizer",
88
+ "ConvergenceCriteria",
89
+ "CellConstraint",
90
+ )
91
+
92
+ # Phonon — only the three most frequently typed entry points stay at top
93
+ # level. Everything else lives at ``fs.phonon.*``.
94
+ _alias("phonon", "calculate_force_constants", "BandPath", "ForceConstants")
95
+
96
+ # NEGF electron-transport workflows (CP2K + SMEAGOL via cp2k_rs).
97
+ # Pure Python; imported as a submodule so `fs.transport` works. cp2k_rs is
98
+ # only required when a workflow actually runs, not at import time.
99
+ from . import transport # noqa: F401
100
+
101
+ # Plotting helpers (matplotlib-backed)
102
+ # These are pure-Python utilities shipped with the package.
103
+ # Every plotting helper is re-exported at the top level, because that is how
104
+ # they are used everywhere: docs, examples and tests write `fs.plot_dos(...)`,
105
+ # never `fs.plotting.plot_dos(...)`. Only three of the ten used to be listed
106
+ # here, so `fs.plot_cohp` — which docs/examples/cohp_coop.rst and
107
+ # docs/functions/bonding/index.rst both call — raised AttributeError.
108
+ from .plotting import (
109
+ plot_absorption_reflectivity,
110
+ plot_band_structure,
111
+ plot_band_structure_and_dos,
112
+ plot_cohp,
113
+ plot_dos,
114
+ plot_epsilon_complex,
115
+ plot_icohp_bars,
116
+ plot_n_kappa,
117
+ plot_optical_spectrum,
118
+ plot_sigma_complex,
119
+ )
120
+
121
+ try:
122
+ from .active_learning import run_active_learning_md
123
+ except ImportError:
124
+ import logging as _logging
125
+
126
+ _logging.getLogger(__name__).warning(
127
+ "active_learning requires numpy, ase, and mace-torch; "
128
+ "run_active_learning_md will not be available. "
129
+ "Install with: pip install fastatomstruct[mace-torch]",
130
+ )
131
+
132
+ # Interactive curve editor for temperature/pressure profiles (matplotlib).
133
+ # Pure-Python; attaches itself to the ``md`` submodule if available.
134
+ try:
135
+ from .curve_editor import curve_editor as _curve_editor_func
136
+
137
+ _md_mod = globals().get("md")
138
+ if _md_mod is not None:
139
+ setattr(_md_mod, "curve_editor", _curve_editor_func)
140
+ # The top-level name ``curve_editor`` is always the subpackage (as the stub says); without
141
+ # the ``md`` feature use ``from fastatomstruct.curve_editor import curve_editor``.
142
+ except ImportError:
143
+ pass # numpy or submodule not available
@@ -0,0 +1,133 @@
1
+ # This file is generated by gen_stub.py -- do not edit by hand.
2
+
3
+ """
4
+ This type stub file was generated by pyright.
5
+ """
6
+
7
+ from .fastatomstruct import *
8
+ from .fastatomstruct import (
9
+ AoMatrixCsrResult as AoMatrixCsrResult,
10
+ AoMatrixResult as AoMatrixResult,
11
+ Atom as Atom,
12
+ Atoms as Atoms,
13
+ AtomsIterator as AtomsIterator,
14
+ BandStructureResult as BandStructureResult,
15
+ BuildOptions as BuildOptions,
16
+ CutoffMode as CutoffMode,
17
+ CylindricalAxis as CylindricalAxis,
18
+ DomainDecomposition as DomainDecomposition,
19
+ ElectronDensityResult as ElectronDensityResult,
20
+ Element as Element,
21
+ EnergyComponents as EnergyComponents,
22
+ FilterAnd as FilterAnd,
23
+ FilterElement as FilterElement,
24
+ FilterIndices as FilterIndices,
25
+ FilterNot as FilterNot,
26
+ FilterOr as FilterOr,
27
+ FilterPosition as FilterPosition,
28
+ FilterTag as FilterTag,
29
+ FixAtoms as FixAtoms,
30
+ GenericParticle as GenericParticle,
31
+ GridInfo as GridInfo,
32
+ HomoLumoResult as HomoLumoResult,
33
+ LatticeParameters as LatticeParameters,
34
+ LatticeType as LatticeType,
35
+ MoCoefficientsResult as MoCoefficientsResult,
36
+ NeighbourMode as NeighbourMode,
37
+ NeighbourSelectionCriteria as NeighbourSelectionCriteria,
38
+ QuantumATKDataType as QuantumATKDataType,
39
+ ReadIndex as ReadIndex,
40
+ ScfInfo as ScfInfo,
41
+ TimeAxis as TimeAxis,
42
+ Trajectory as Trajectory,
43
+ TrajectoryChunks as TrajectoryChunks,
44
+ TrajectoryIterator as TrajectoryIterator,
45
+ VDOSMethod as VDOSMethod,
46
+ VacfMethod as VacfMethod,
47
+ analysis as analysis,
48
+ bonding as bonding,
49
+ build as build,
50
+ calc as calc,
51
+ convert_ase as convert_ase,
52
+ disable_telemetry as disable_telemetry,
53
+ elph as elph,
54
+ enable_telemetry as enable_telemetry,
55
+ fetch_cp2k_worker as fetch_cp2k_worker,
56
+ filter_indices as filter_indices,
57
+ finalize_mpi as finalize_mpi,
58
+ get_mpi_status as get_mpi_status,
59
+ get_num_threads as get_num_threads,
60
+ init_mpi as init_mpi,
61
+ io as io,
62
+ is_log_enabled as is_log_enabled,
63
+ kubo_greenwood_transport as kubo_greenwood_transport,
64
+ md as md,
65
+ nald as nald,
66
+ optical_spectrum as optical_spectrum,
67
+ optimizers as optimizers,
68
+ phonon as phonon,
69
+ queue as queue,
70
+ read_obs_log as read_obs_log,
71
+ run_active_learning_md as run_active_learning_md,
72
+ run_dd_md as run_dd_md,
73
+ set_log_enabled as set_log_enabled,
74
+ set_num_threads as set_num_threads,
75
+ suppress_telemetry_notice as suppress_telemetry_notice,
76
+ units as units,
77
+ viewer as viewer,
78
+ )
79
+ from .viewer import (
80
+ view as view,
81
+ view_image as view_image,
82
+ view_interactive as view_interactive,
83
+ view_many as view_many,
84
+ )
85
+ from .io import open as open, read as read, write as write
86
+ from .md import (
87
+ AndersenThermostat as AndersenThermostat,
88
+ BerendsenThermostat as BerendsenThermostat,
89
+ BussiBarostat as BussiBarostat,
90
+ BussiThermostat as BussiThermostat,
91
+ LangevinIntegrator as LangevinIntegrator,
92
+ NoBarostat as NoBarostat,
93
+ NoThermostat as NoThermostat,
94
+ NoseHooverThermostat as NoseHooverThermostat,
95
+ SemiIsotropicBussiBarostat as SemiIsotropicBussiBarostat,
96
+ VelocityVerlet as VelocityVerlet,
97
+ run_molecular_dynamics as run_molecular_dynamics,
98
+ )
99
+ from .optimizers import (
100
+ BfgsOptimizer as BfgsOptimizer,
101
+ CellConstraint as CellConstraint,
102
+ ConvergenceCriteria as ConvergenceCriteria,
103
+ FireOptimizer as FireOptimizer,
104
+ LbfgsbOptimizer as LbfgsbOptimizer,
105
+ NagOptimizer as NagOptimizer,
106
+ run_optimization as run_optimization,
107
+ )
108
+ from .calc import (
109
+ ACECalculator as ACECalculator,
110
+ CP2KCalculator as CP2KCalculator,
111
+ EmtCalculator as EmtCalculator,
112
+ LennardJones as LennardJones,
113
+ MACECalculator as MACECalculator,
114
+ )
115
+ from .phonon import (
116
+ BandPath as BandPath,
117
+ ForceConstants as ForceConstants,
118
+ calculate_force_constants as calculate_force_constants,
119
+ )
120
+ from .plotting import (
121
+ plot_absorption_reflectivity as plot_absorption_reflectivity,
122
+ plot_band_structure as plot_band_structure,
123
+ plot_band_structure_and_dos as plot_band_structure_and_dos,
124
+ plot_cohp as plot_cohp,
125
+ plot_dos as plot_dos,
126
+ plot_epsilon_complex as plot_epsilon_complex,
127
+ plot_icohp_bars as plot_icohp_bars,
128
+ plot_n_kappa as plot_n_kappa,
129
+ plot_optical_spectrum as plot_optical_spectrum,
130
+ plot_sigma_complex as plot_sigma_complex,
131
+ )
132
+ from . import curve_editor, transport
133
+ from .active_learning import run_active_learning_md
@@ -0,0 +1,185 @@
1
+ """XLA runtime libraries (PJRT plugins) for the Rust MACE backend.
2
+
3
+ The libraries are large and optional:
4
+
5
+ * CPU: the companion package ``fastatomstruct-xla-cpu``
6
+ (``pip install "fastatomstruct[xla]"``)
7
+ * CUDA: Google's CUDA 13 plugin ``jax-cuda13-pjrt`` (the XLA build of JAX)
8
+ and NVIDIA's CUDA 13 pip packages (``pip install "fastatomstruct[cuda13]"``),
9
+ in the versions verified against the MACE reference tests
10
+
11
+ :func:`plugin_path` finds whichever is installed, :func:`cueq_libraries` the
12
+ optional cuEquivariance kernels. For development,
13
+ ``scripts/fetch_pjrt.py`` can instead place the pinned builds in this
14
+ directory (wheels built afterwards ship them). Nothing here imports jax.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import ctypes
20
+ import importlib
21
+ import os
22
+ from importlib import metadata
23
+ from pathlib import Path
24
+
25
+ _HERE = Path(__file__).resolve().parent
26
+ _NAMES = {"cpu": "libpjrt_cpu.so", "cuda": "libpjrt_cuda.so"}
27
+ _ENV = {"cpu": "MACE_PJRT_PLUGIN", "cuda": "MACE_PJRT_CUDA_PLUGIN"}
28
+ _CPU_COMPANION = "fastatomstruct_xla_cpu"
29
+
30
+ #: The distribution that ships the CUDA plugin, and the plugin inside it.
31
+ CUDA_PLUGIN_DISTRIBUTION = "jax-cuda13-pjrt"
32
+ _CUDA_PLUGIN_FILE = "jax_plugins/xla_cuda13/xla_cuda_plugin.so"
33
+ # CUDA libraries the plugin opens by soname, by NVIDIA package, in loading
34
+ # order (the list of jax_plugins.xla_cuda13). The plugin's RPATH finds them
35
+ # in a standard site-packages layout as well; loading them first also covers
36
+ # the plugin and NVIDIA's packages in different site directories.
37
+ _CUDA_PRELOAD = (
38
+ ("cu13", ("libcudart.so.13",)),
39
+ ("cu13", ("libnvrtc.so.13",)),
40
+ ("cu13", ("libcublas.so.13", "libcublasLt.so.13")),
41
+ ("nccl", ("libnccl.so.2",)),
42
+ ("cu13", ("libcupti.so.13",)),
43
+ ("cu13", ("libcusparse.so.12",)),
44
+ ("cu13", ("libcusolver.so.12",)),
45
+ ("cu13", ("libcufft.so.12",)),
46
+ ("nvshmem", ("libnvshmem_host.so.3",)),
47
+ ("cudnn", ("libcudnn.so.9",)),
48
+ )
49
+ _CUDA_REQUIRED = ("libcudart.so.13", "libcublas.so.13")
50
+ _cuda_loaded: list[ctypes.CDLL] = []
51
+
52
+
53
+ def _cpu_companion() -> str | None:
54
+ try:
55
+ module = importlib.import_module(_CPU_COMPANION)
56
+ except ImportError:
57
+ return None
58
+ return module.library_path()
59
+
60
+
61
+ def _cuda_plugin() -> str | None:
62
+ """The plugin of the installed ``jax-cuda13-pjrt``, with NVIDIA's
63
+ libraries loaded first; ``None`` if it is not installed."""
64
+ try:
65
+ dist = metadata.distribution(CUDA_PLUGIN_DISTRIBUTION)
66
+ except metadata.PackageNotFoundError:
67
+ return None
68
+ path = Path(str(dist.locate_file(_CUDA_PLUGIN_FILE))).resolve()
69
+ if not path.is_file():
70
+ raise FileNotFoundError(
71
+ f"{path} is missing from {CUDA_PLUGIN_DISTRIBUTION}; reinstall "
72
+ '"fastatomstruct[cuda13]"'
73
+ )
74
+ _preload_cuda()
75
+ return str(path)
76
+
77
+
78
+ def _nvidia_root() -> Path | None:
79
+ try:
80
+ dist = metadata.distribution("nvidia-cuda-runtime")
81
+ except metadata.PackageNotFoundError:
82
+ return None
83
+ return Path(str(dist.locate_file("nvidia"))).resolve()
84
+
85
+
86
+ def _preload_cuda() -> None:
87
+ """Load the CUDA libraries from NVIDIA's pip packages into the process,
88
+ as ``jax_plugins.xla_cuda13`` does before it registers the plugin.
89
+
90
+ Libraries a package does not ship are skipped (the plugin opens optional
91
+ ones itself or does without); the CUDA runtime and cuBLAS must be there.
92
+ XLA finds ``nvvm/libdevice`` and ``ptxas`` in ``nvidia/cu13`` on its own.
93
+ """
94
+ if _cuda_loaded:
95
+ return
96
+ root = _nvidia_root()
97
+ found = set()
98
+ for package, names in _CUDA_PRELOAD:
99
+ for name in names:
100
+ path = None if root is None else root / package / "lib" / name
101
+ if path is None or not path.is_file():
102
+ continue
103
+ try:
104
+ _cuda_loaded.append(
105
+ ctypes.CDLL(str(path), mode=os.RTLD_NOW | os.RTLD_GLOBAL)
106
+ )
107
+ except OSError as exc:
108
+ raise OSError(f"could not load {path}: {exc}") from None
109
+ found.add(name)
110
+ missing = [name for name in _CUDA_REQUIRED if name not in found]
111
+ if missing:
112
+ _cuda_loaded.clear()
113
+ raise FileNotFoundError(
114
+ f"{missing} not found in NVIDIA's pip packages ({root}); "
115
+ 'reinstall "fastatomstruct[cuda13]"'
116
+ )
117
+
118
+
119
+ def plugin_path(device: str = "cpu") -> str | None:
120
+ """Return the PJRT plugin for ``device`` (``'cpu'`` or ``'cuda'``).
121
+
122
+ Order: the device's environment variable, the installed runtime
123
+ (``fastatomstruct-xla-cpu`` / ``jax-cuda13-pjrt``), then a library placed
124
+ in this package. ``None`` if none exists.
125
+ """
126
+ if device not in _NAMES:
127
+ raise ValueError(f"unknown device {device!r} (expected 'cpu' or 'cuda')")
128
+ env = os.environ.get(_ENV[device])
129
+ if env:
130
+ return env
131
+ installed = _cpu_companion() if device == "cpu" else _cuda_plugin()
132
+ if installed is not None:
133
+ return installed
134
+ bundled = _HERE / _NAMES[device]
135
+ return str(bundled) if bundled.is_file() else None
136
+
137
+
138
+ def library_dirs(device: str = "cpu") -> list[str]:
139
+ """Directories with the shared libraries the plugin for ``device`` needs.
140
+
141
+ In this process :func:`plugin_path` loads them itself. A process that is
142
+ not Python (the domain decomposition workers) gets the directories on
143
+ ``LD_LIBRARY_PATH``; the CUDA plugin also finds them through its RPATH.
144
+ """
145
+ if device not in _NAMES:
146
+ raise ValueError(f"unknown device {device!r} (expected 'cpu' or 'cuda')")
147
+ if device == "cpu":
148
+ return []
149
+ root = _nvidia_root()
150
+ if root is None:
151
+ return []
152
+ return [str(p) for p in sorted(root.glob("*/lib")) if p.is_dir()]
153
+
154
+
155
+ def cueq_libraries(device: str = "cuda") -> list[str]:
156
+ """The kernel library of NVIDIA cuEquivariance and what it depends on, in
157
+ loading order, from the installed packages ``cuequivariance-ops-jax`` and
158
+ ``cuequivariance-ops`` (proprietary; fastatomstruct does not ship them)."""
159
+ import importlib.util
160
+
161
+ if device != "cuda":
162
+ raise ValueError(
163
+ "enable_cueq needs device='cuda' (the kernels are CUDA kernels)"
164
+ )
165
+ paths = []
166
+ for package, library in (
167
+ ("cuequivariance_ops", "libcue_ops.so"),
168
+ ("cuequivariance_ops_jax", "libcue_ops_jax.so"),
169
+ ):
170
+ # (located without importing: the jax package would import jax)
171
+ spec = importlib.util.find_spec(package)
172
+ locations = list(getattr(spec, "submodule_search_locations", None) or [])
173
+ found = [
174
+ os.path.join(location, "lib", library)
175
+ for location in locations
176
+ if os.path.isfile(os.path.join(location, "lib", library))
177
+ ]
178
+ if not found:
179
+ raise ValueError(
180
+ "enable_cueq needs NVIDIA's cuEquivariance kernels: "
181
+ "pip install cuequivariance-ops-jax-cu13 "
182
+ f"({library} of the package {package} was not found)"
183
+ )
184
+ paths.append(found[0])
185
+ return paths