metatomic-torch 0.1.0__tar.gz

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 (30) hide show
  1. metatomic_torch-0.1.0/AUTHORS +2 -0
  2. metatomic_torch-0.1.0/CMakeLists.txt +88 -0
  3. metatomic_torch-0.1.0/LICENSE +28 -0
  4. metatomic_torch-0.1.0/MANIFEST.in +10 -0
  5. metatomic_torch-0.1.0/PKG-INFO +39 -0
  6. metatomic_torch-0.1.0/README.rst +4 -0
  7. metatomic_torch-0.1.0/build-backend/backend.py +33 -0
  8. metatomic_torch-0.1.0/git_version_info +2 -0
  9. metatomic_torch-0.1.0/metatomic/__init__.py +0 -0
  10. metatomic_torch-0.1.0/metatomic/torch/__init__.py +49 -0
  11. metatomic_torch-0.1.0/metatomic/torch/_c_lib.py +134 -0
  12. metatomic_torch-0.1.0/metatomic/torch/_extensions.py +202 -0
  13. metatomic_torch-0.1.0/metatomic/torch/ase_calculator.py +774 -0
  14. metatomic_torch-0.1.0/metatomic/torch/documentation.py +523 -0
  15. metatomic_torch-0.1.0/metatomic/torch/io.py +164 -0
  16. metatomic_torch-0.1.0/metatomic/torch/model.py +794 -0
  17. metatomic_torch-0.1.0/metatomic/torch/outputs.py +337 -0
  18. metatomic_torch-0.1.0/metatomic/torch/systems_to_torch.py +125 -0
  19. metatomic_torch-0.1.0/metatomic/torch/utils.py +52 -0
  20. metatomic_torch-0.1.0/metatomic/torch/version.py +4 -0
  21. metatomic_torch-0.1.0/metatomic-torch-cxx-0.1.0.tar.gz +0 -0
  22. metatomic_torch-0.1.0/metatomic_torch.egg-info/PKG-INFO +39 -0
  23. metatomic_torch-0.1.0/metatomic_torch.egg-info/SOURCES.txt +28 -0
  24. metatomic_torch-0.1.0/metatomic_torch.egg-info/dependency_links.txt +1 -0
  25. metatomic_torch-0.1.0/metatomic_torch.egg-info/not-zip-safe +1 -0
  26. metatomic_torch-0.1.0/metatomic_torch.egg-info/requires.txt +4 -0
  27. metatomic_torch-0.1.0/metatomic_torch.egg-info/top_level.txt +2 -0
  28. metatomic_torch-0.1.0/pyproject.toml +56 -0
  29. metatomic_torch-0.1.0/setup.cfg +4 -0
  30. metatomic_torch-0.1.0/setup.py +344 -0
@@ -0,0 +1,2 @@
1
+ Guillaume Fraux
2
+ Filippo Bigi
@@ -0,0 +1,88 @@
1
+ # This file allow the python module in metatomic-torch to either use an
2
+ # externally-provided version of the shared metatomic_torch library; or to build
3
+ # the code from source and bundle the shared library inside the wheel.
4
+ #
5
+ # The first case is used when distributing the code in conda (since we have a
6
+ # separate libmetatomic_torch package), the second one is used everywhere else
7
+ # (for local development builds and for the PyPI distribution).
8
+
9
+ cmake_minimum_required(VERSION 3.16)
10
+ project(metatomic-torch-python NONE)
11
+
12
+ option(METATOMIC_TORCH_PYTHON_USE_EXTERNAL_LIB "Force the usage of an external version of metatomic-torch" OFF)
13
+ set(METATOMIC_TORCH_SOURCE_DIR "" CACHE PATH "Path to the sources of metatomic-torch")
14
+
15
+ file(REMOVE ${CMAKE_INSTALL_PREFIX}/_external.py)
16
+
17
+ set(REQUIRED_METATOMIC_TORCH_VERSION "0.1.0")
18
+ if(${METATOMIC_TORCH_PYTHON_USE_EXTERNAL_LIB})
19
+ # when building a source checkout, update version to include git information
20
+ # this will not apply when building a sdist
21
+ if (EXISTS ${CMAKE_SOURCE_DIR}/../../metatomic-torch/cmake/dev-versions.cmake)
22
+ include(${CMAKE_SOURCE_DIR}/../../metatomic-torch/cmake/dev-versions.cmake)
23
+ create_development_version(
24
+ "${REQUIRED_METATOMIC_TORCH_VERSION}"
25
+ REQUIRED_METATOMIC_TORCH_VERSION
26
+ "metatomic-torch-v"
27
+ )
28
+ endif()
29
+
30
+ # strip any -dev/-rc suffix on the version since find_package does not support it
31
+ string(
32
+ REGEX REPLACE "([0-9]*)\\.([0-9]*)\\.([0-9]*).*" "\\1.\\2.\\3"
33
+ REQUIRED_METATOMIC_TORCH_VERSION
34
+ ${REQUIRED_METATOMIC_TORCH_VERSION}
35
+ )
36
+ find_package(metatomic_torch ${REQUIRED_METATOMIC_TORCH_VERSION} REQUIRED)
37
+
38
+ get_target_property(METATOMIC_TORCH_LOCATION metatomic_torch LOCATION)
39
+ message(STATUS "Using external metatomic-torch v${metatomic_torch_VERSION} at ${METATOMIC_TORCH_LOCATION}")
40
+
41
+ # Get the prefix to use as cmake_prefix_path when trying to load this
42
+ # version of the library again
43
+ get_filename_component(METATOMIC_TORCH_PREFIX "${METATOMIC_TORCH_LOCATION}" DIRECTORY)
44
+ get_filename_component(METATOMIC_TORCH_PREFIX "${METATOMIC_TORCH_PREFIX}" DIRECTORY)
45
+
46
+ file(WRITE ${CMAKE_INSTALL_PREFIX}/_external.py
47
+ "EXTERNAL_METATOMIC_TORCH_PATH = \"${METATOMIC_TORCH_LOCATION}\"\n\n"
48
+ )
49
+ file(APPEND ${CMAKE_INSTALL_PREFIX}/_external.py
50
+ "EXTERNAL_METATOMIC_TORCH_PREFIX = \"${METATOMIC_TORCH_PREFIX}\"\n"
51
+ )
52
+
53
+ install(CODE "message(STATUS \"nothing to install\")")
54
+ else()
55
+ if ("${METATOMIC_TORCH_SOURCE_DIR}" STREQUAL "")
56
+ message(FATAL_ERROR
57
+ "Missing METATOMIC_TORCH_SOURCE_DIR, please specify where to \
58
+ find the source code for metatomic-torch"
59
+ )
60
+ endif()
61
+
62
+ message(STATUS "Using internal metatomic-torch from ${METATOMIC_TORCH_SOURCE_DIR}")
63
+
64
+ add_subdirectory("${METATOMIC_TORCH_SOURCE_DIR}" metatomic-torch)
65
+
66
+
67
+ if (LINUX OR APPLE)
68
+ if (LINUX)
69
+ set(rpath_origin "$ORIGIN")
70
+ elseif(APPLE)
71
+ set(rpath_origin "@loader_path")
72
+ endif()
73
+ find_package(Torch)
74
+
75
+ set(metatomic_install_rpath "${CMAKE_INSTALL_RPATH}")
76
+ # when loading the libraries from a Python installation:
77
+ # - $ORIGIN/../../../../torch/lib is where libtorch.so will be
78
+ # - $ORIGIN/../../../../metatensor/lib is where libmetatensor.so will be
79
+ # - $ORIGIN/../../../../metatensor/torch/torch-x.y/lib is where libmetatensor_torch.so will be
80
+ set(metatomic_install_rpath "${metatomic_install_rpath};${rpath_origin}/../../../../torch/lib")
81
+ set(metatomic_install_rpath "${metatomic_install_rpath};${rpath_origin}/../../../../metatensor/lib")
82
+ set(metatomic_install_rpath "${metatomic_install_rpath};${rpath_origin}/../../../../metatensor/torch/torch-${Torch_VERSION_MAJOR}.${Torch_VERSION_MINOR}/lib")
83
+
84
+ set_target_properties(
85
+ metatomic_torch PROPERTIES INSTALL_RPATH "${metatomic_install_rpath}"
86
+ )
87
+ endif()
88
+ endif()
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, metatomic developers
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,10 @@
1
+ include pyproject.toml
2
+ include CMakeLists.txt
3
+ include AUTHORS
4
+ include LICENSE
5
+
6
+ include git_version_info
7
+
8
+ include metatomic-torch-*.tar.gz
9
+
10
+ recursive-include build-backend *.py
@@ -0,0 +1,39 @@
1
+ Metadata-Version: 2.4
2
+ Name: metatomic-torch
3
+ Version: 0.1.0
4
+ Summary: TODO
5
+ Author: Guillaume Fraux, Filippo Bigi
6
+ License-Expression: BSD-3-Clause
7
+ Project-URL: homepage, https://docs.metatensor.org/metatomic/
8
+ Project-URL: documentation, https://docs.metatensor.org/metatomic/
9
+ Project-URL: repository, https://github.com/metatensor/metatomic
10
+ Project-URL: changelog, https://docs.metatensor.org/metatomic/latest/torch/CHANGELOG.html
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: Operating System :: POSIX
14
+ Classifier: Operating System :: MacOS :: MacOS X
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
20
+ Classifier: Topic :: Scientific/Engineering :: Chemistry
21
+ Classifier: Topic :: Scientific/Engineering :: Physics
22
+ Classifier: Topic :: Software Development :: Libraries
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/x-rst
26
+ License-File: LICENSE
27
+ License-File: AUTHORS
28
+ Requires-Dist: torch>=2.1
29
+ Requires-Dist: vesin
30
+ Requires-Dist: metatensor-torch<0.8.0,>=0.7.0
31
+ Requires-Dist: metatensor-operations<0.4.0,>=0.3.0
32
+ Dynamic: author
33
+ Dynamic: license-file
34
+ Dynamic: requires-dist
35
+
36
+ metatensor-torch
37
+ ================
38
+
39
+ This package contains the TorchScript bindings to the core API of metatensor.
@@ -0,0 +1,4 @@
1
+ metatensor-torch
2
+ ================
3
+
4
+ This package contains the TorchScript bindings to the core API of metatensor.
@@ -0,0 +1,33 @@
1
+ # This is a custom Python build backend wrapping setuptool's to only depend on
2
+ # torch/metatensor-torch when building the wheel and not the sdist
3
+ import os
4
+
5
+ from setuptools import build_meta
6
+
7
+
8
+ ROOT = os.path.realpath(os.path.dirname(__file__))
9
+
10
+ FORCED_TORCH_VERSION = os.environ.get("METATOMIC_TORCH_BUILD_WITH_TORCH_VERSION")
11
+ if FORCED_TORCH_VERSION is not None:
12
+ TORCH_DEP = f"torch =={FORCED_TORCH_VERSION}"
13
+ else:
14
+ TORCH_DEP = "torch >=2.1"
15
+
16
+ # ==================================================================================== #
17
+ # Build backend functions definition #
18
+ # ==================================================================================== #
19
+
20
+ # Use the default version of these
21
+ prepare_metadata_for_build_wheel = build_meta.prepare_metadata_for_build_wheel
22
+ get_requires_for_build_sdist = build_meta.get_requires_for_build_sdist
23
+ build_wheel = build_meta.build_wheel
24
+ build_sdist = build_meta.build_sdist
25
+
26
+
27
+ # Special dependencies to build the wheels
28
+ def get_requires_for_build_wheel(config_settings=None):
29
+ defaults = build_meta.get_requires_for_build_wheel(config_settings)
30
+ return defaults + [
31
+ TORCH_DEP,
32
+ "metatensor-torch >=0.7.0,<0.8.0",
33
+ ]
@@ -0,0 +1,2 @@
1
+ 0
2
+ git.47daea3
File without changes
@@ -0,0 +1,49 @@
1
+ import os
2
+
3
+ import torch
4
+
5
+ from ._c_lib import _load_library
6
+ from .version import __version__ # noqa: F401
7
+
8
+
9
+ if os.environ.get("METATOMIC_IMPORT_FOR_SPHINX", "0") != "0":
10
+ from .documentation import (
11
+ ModelCapabilities,
12
+ ModelEvaluationOptions,
13
+ ModelMetadata,
14
+ ModelOutput,
15
+ NeighborListOptions,
16
+ System,
17
+ check_atomistic_model,
18
+ load_model_extensions,
19
+ read_model_metadata,
20
+ register_autograd_neighbors,
21
+ unit_conversion_factor,
22
+ )
23
+
24
+ else:
25
+ _load_library()
26
+
27
+ System = torch.classes.metatomic.System
28
+ NeighborListOptions = torch.classes.metatomic.NeighborListOptions
29
+
30
+ ModelOutput = torch.classes.metatomic.ModelOutput
31
+ ModelEvaluationOptions = torch.classes.metatomic.ModelEvaluationOptions
32
+ ModelCapabilities = torch.classes.metatomic.ModelCapabilities
33
+ ModelMetadata = torch.classes.metatomic.ModelMetadata
34
+
35
+ read_model_metadata = torch.ops.metatomic.read_model_metadata
36
+ load_model_extensions = torch.ops.metatomic.load_model_extensions
37
+ check_atomistic_model = torch.ops.metatomic.check_atomistic_model
38
+
39
+ register_autograd_neighbors = torch.ops.metatomic.register_autograd_neighbors
40
+ unit_conversion_factor = torch.ops.metatomic.unit_conversion_factor
41
+
42
+ from .io import load_system, save # noqa: F401
43
+ from .model import ( # noqa: F401
44
+ AtomisticModel,
45
+ ModelInterface,
46
+ is_atomistic_model,
47
+ load_atomistic_model, # noqa: F401
48
+ )
49
+ from .systems_to_torch import systems_to_torch # noqa: F401
@@ -0,0 +1,134 @@
1
+ import glob
2
+ import importlib
3
+ import os
4
+ import sys
5
+
6
+ import metatensor.torch
7
+ import torch
8
+
9
+ from .utils import parse_version, version_compatible
10
+ from .version import __version__
11
+
12
+
13
+ _HERE = os.path.realpath(os.path.dirname(__file__))
14
+
15
+
16
+ def _lib_path():
17
+ torch_version = parse_version(torch.__version__)
18
+ install_prefix = os.path.join(
19
+ _HERE, f"torch-{torch_version.major}.{torch_version.minor}"
20
+ )
21
+
22
+ if os.path.exists(install_prefix):
23
+ # check if we are using an externally-provided version of the shared library
24
+ external_path = os.path.join(install_prefix, "_external.py")
25
+ if os.path.exists(external_path):
26
+ spec = importlib.util.spec_from_file_location("_external", external_path)
27
+ module = importlib.util.module_from_spec(spec)
28
+ spec.loader.exec_module(module)
29
+ return module.EXTERNAL_METATOMIC_TORCH_PATH
30
+
31
+ if sys.platform.startswith("darwin"):
32
+ path = os.path.join(install_prefix, "lib", "libmetatomic_torch.dylib")
33
+ windows = False
34
+ elif sys.platform.startswith("linux"):
35
+ path = os.path.join(install_prefix, "lib", "libmetatomic_torch.so")
36
+ windows = False
37
+ elif sys.platform.startswith("win"):
38
+ path = os.path.join(install_prefix, "bin", "metatomic_torch.dll")
39
+ windows = True
40
+ else:
41
+ raise ImportError("Unknown platform. Please edit this file")
42
+
43
+ if os.path.isfile(path):
44
+ if windows:
45
+ _check_dll(path)
46
+ return path
47
+ else:
48
+ raise ImportError(
49
+ "Could not find metatomic_torch shared library at " + path
50
+ )
51
+
52
+ # gather which torch version(s) the current install was built
53
+ # with to create the error message
54
+ existing_versions = []
55
+ for prefix in glob.glob(os.path.join(_HERE, "torch-*")):
56
+ existing_versions.append(os.path.basename(prefix)[6:])
57
+
58
+ if len(existing_versions) == 1:
59
+ raise ImportError(
60
+ f"Trying to load metatomic-torch with torch v{torch.__version__}, "
61
+ f"but it was compiled against torch v{existing_versions[0]}, which "
62
+ "is not ABI compatible"
63
+ )
64
+ else:
65
+ all_versions = ", ".join(map(lambda version: f"v{version}", existing_versions))
66
+ raise ImportError(
67
+ f"Trying to load metatomic-torch with torch v{torch.__version__}, "
68
+ f"we found builds for torch {all_versions}; which are not ABI compatible.\n"
69
+ "You can try to re-install from source with "
70
+ "`pip install metatomic-torch --no-binary=metatomic-torch`"
71
+ )
72
+
73
+
74
+ def _check_dll(path):
75
+ """
76
+ Check if the DLL pointer size matches Python (32-bit or 64-bit)
77
+ """
78
+ import platform
79
+ import struct
80
+
81
+ IMAGE_FILE_MACHINE_I386 = 332
82
+ IMAGE_FILE_MACHINE_AMD64 = 34404
83
+
84
+ machine = None
85
+ with open(path, "rb") as fd:
86
+ header = fd.read(2).decode(encoding="utf-8", errors="strict")
87
+ if header != "MZ":
88
+ raise ImportError(path + " is not a DLL")
89
+ else:
90
+ fd.seek(60)
91
+ header = fd.read(4)
92
+ header_offset = struct.unpack("<L", header)[0]
93
+ fd.seek(header_offset + 4)
94
+ header = fd.read(2)
95
+ machine = struct.unpack("<H", header)[0]
96
+
97
+ arch = platform.architecture()[0]
98
+ if arch == "32bit":
99
+ if machine != IMAGE_FILE_MACHINE_I386:
100
+ raise ImportError("Python is 32-bit, but this DLL is not")
101
+ elif arch == "64bit":
102
+ if machine != IMAGE_FILE_MACHINE_AMD64:
103
+ raise ImportError("Python is 64-bit, but this DLL is not")
104
+ else:
105
+ raise ImportError("Could not determine pointer size of Python")
106
+
107
+
108
+ def _load_library():
109
+ # Load metatensor_torch shared library in the process first, to ensure
110
+ # the metatomic_torch shared library can find it
111
+ metatensor.torch._c_lib._load_library()
112
+
113
+ # load the C++ operators and custom classes
114
+ try:
115
+ torch.ops.load_library(_lib_path())
116
+ except Exception as e:
117
+ if "undefined symbol" in str(e):
118
+ file_name = os.path.basename(_lib_path())
119
+ raise ImportError(
120
+ f"{file_name} is not compatible with the current PyTorch "
121
+ "installation.\nThis can happen if PyTorch comes from one source "
122
+ "(pip, conda, custom), but metatomic-torch comes from a different "
123
+ "one.\nIn this case, you can try to compile metatomic-torch yourself "
124
+ "with `pip install --no-binary=metatomic-torch metatomic-torch`"
125
+ ) from e
126
+ else:
127
+ raise e
128
+
129
+ lib_version = torch.ops.metatomic.version()
130
+ if not version_compatible(lib_version, __version__):
131
+ raise ImportError(
132
+ f"Trying to load the Python package metatomic-torch v{__version__} "
133
+ f"with the incompatible metatomic-torch C++ library v{lib_version}"
134
+ )
@@ -0,0 +1,202 @@
1
+ import glob
2
+ import hashlib
3
+ import os
4
+ import shutil
5
+ import site
6
+ import sys
7
+ import warnings
8
+
9
+ import metatensor.torch
10
+ import torch
11
+
12
+ from . import _c_lib
13
+
14
+
15
+ METATOMIC_TORCH_LIB_PATH = _c_lib._lib_path()
16
+ METATENSOR_TORCH_LIB_PATH = metatensor.torch._c_lib._lib_path()
17
+
18
+
19
+ def _rascaline_lib_path():
20
+ # This is kept for backward compatibility, but rascaline is now named featomic.
21
+ # This code should be removed by the middle of 2025.
22
+ import rascaline
23
+
24
+ return [rascaline._c_lib._lib_path()]
25
+
26
+
27
+ def _featomic_deps_path():
28
+ import featomic
29
+
30
+ deps_path = [featomic._c_lib._lib_path()]
31
+
32
+ libgomp_path = _find_openmp_dep("featomic_torch.libs")
33
+ if libgomp_path is not None:
34
+ deps_path.insert(0, libgomp_path)
35
+
36
+ return deps_path
37
+
38
+
39
+ def _sphericart_deps_path():
40
+ import sphericart.torch
41
+
42
+ deps_path = []
43
+
44
+ libgomp_path = _find_openmp_dep("sphericart_torch.libs")
45
+ if libgomp_path is not None:
46
+ deps_path.append(libgomp_path)
47
+
48
+ if sys.platform.startswith("linux"):
49
+ # sphericart uses a separate library to get the CUDA stream corresponding to a
50
+ # tensor, see https://github.com/lab-cosmo/sphericart/pull/164
51
+ sphericart_torch_path = sphericart.torch._lib_path()
52
+ lib_dir = os.path.dirname(sphericart_torch_path)
53
+
54
+ cuda_stream_lib = os.path.join(lib_dir, "libsphericart_torch_cuda_stream.so")
55
+ if os.path.exists(cuda_stream_lib):
56
+ deps_path.append(cuda_stream_lib)
57
+
58
+ return deps_path
59
+
60
+
61
+ def _find_openmp_dep(search_dir):
62
+ """
63
+ When building code that uses OpenMP on linux, we typically dynamically link to
64
+ libgomp. `cibuildwheel` then copies ``libgomp.so`` to
65
+ ``<wheel_name>.libs/libgomp-<hash>.so``, so we need to find and add this shared
66
+ library to the extensions dependencies.
67
+ """
68
+ if sys.platform.startswith("linux"):
69
+ libs_list = []
70
+
71
+ for prefix in site.getsitepackages():
72
+ libs_dir = os.path.join(prefix, search_dir)
73
+ if os.path.exists(libs_dir):
74
+ libs_list = glob.glob(os.path.join(libs_dir, "libgomp-*.so*"))
75
+ if len(libs_list) != 0:
76
+ # found it!
77
+ break
78
+
79
+ if len(libs_list) == 0:
80
+ warnings.warn(
81
+ f"No libgomp shared library found in '{search_dir}'. "
82
+ "This may cause issues when loading and running the model.",
83
+ stacklevel=2,
84
+ )
85
+ elif len(libs_list) > 1:
86
+ raise RuntimeError(
87
+ f"Multiple libgomp shared libraries found in '{search_dir}': "
88
+ f"{libs_list}. Try to re-install in a fresh environment."
89
+ )
90
+ else: # len(libs_list) == 1
91
+ return libs_list[0]
92
+
93
+
94
+ # Manual definition of which TorchScript extensions have their own dependencies. The
95
+ # dependencies should be returned in the order they need to be loaded.
96
+ EXTENSIONS_WITH_DEPENDENCIES = {
97
+ "rascaline_torch": _rascaline_lib_path,
98
+ "featomic_torch": _featomic_deps_path,
99
+ "sphericart_torch": _sphericart_deps_path,
100
+ }
101
+
102
+
103
+ def _collect_extensions(extensions_path):
104
+ """
105
+ Record the list of loaded TorchScript extensions (and their dependencies), to check
106
+ that they are also loaded when executing the model.
107
+ """
108
+ if extensions_path is not None:
109
+ if os.path.exists(extensions_path):
110
+ shutil.rmtree(extensions_path)
111
+ os.makedirs(extensions_path)
112
+ # TODO: the extensions are currently collected in a separate directory,
113
+ # should we store the files directly inside the model file? This would makes
114
+ # the model platform-specific but much more convenient (since the end user
115
+ # does not have to move a model around)
116
+
117
+ extensions = []
118
+ extensions_deps = []
119
+ for library in torch.ops.loaded_libraries:
120
+ assert os.path.exists(library)
121
+
122
+ # these should be provided by the simulation engine
123
+ if os.path.samefile(library, METATENSOR_TORCH_LIB_PATH):
124
+ continue
125
+ if os.path.samefile(library, METATOMIC_TORCH_LIB_PATH):
126
+ continue
127
+
128
+ path = _copy_extension(library, extensions_path)
129
+ info = _extension_info(library)
130
+ info["path"] = path
131
+ extensions.append(info)
132
+
133
+ for extra in EXTENSIONS_WITH_DEPENDENCIES.get(info["name"], lambda: [])():
134
+ path = _copy_extension(extra, extensions_path)
135
+ info = _extension_info(extra)
136
+ info["path"] = path
137
+ extensions_deps.append(info)
138
+
139
+ return extensions, extensions_deps
140
+
141
+
142
+ def _copy_extension(full_path, extensions_dir):
143
+ full_path = os.path.realpath(full_path)
144
+
145
+ prefixes = site.getsitepackages()
146
+ if site.ENABLE_USER_SITE:
147
+ prefixes.append(site.getusersitepackages())
148
+
149
+ # this also takes care of extensions installed directly in the same prefix as
150
+ # Python, for example when installing a standalone libmetatensor_torch with conda.
151
+ prefixes.append(sys.prefix)
152
+
153
+ path = full_path
154
+ for prefix in prefixes:
155
+ prefix = os.path.realpath(prefix)
156
+ assert os.path.isabs(prefix)
157
+
158
+ # Remove any local prefix
159
+ if path.startswith(prefix):
160
+ path = os.path.relpath(path, prefix)
161
+ break
162
+
163
+ if extensions_dir is not None:
164
+ collect_path = os.path.realpath(os.path.join(extensions_dir, path))
165
+ if collect_path == path:
166
+ raise RuntimeError(
167
+ f"extensions directory '{extensions_dir}' would overwrite files, "
168
+ "you should set it to a local path instead"
169
+ )
170
+
171
+ if os.path.exists(collect_path):
172
+ raise RuntimeError(
173
+ f"more than one extension would be collected at {collect_path}"
174
+ )
175
+
176
+ os.makedirs(os.path.dirname(collect_path), exist_ok=True)
177
+ shutil.copyfile(full_path, collect_path)
178
+
179
+ return path
180
+
181
+
182
+ def _extension_info(path):
183
+ # get the name of the library, excluding any shared object prefix/suffix
184
+ name = os.path.basename(path)
185
+ if name.startswith("lib"):
186
+ name = name[3:]
187
+
188
+ if name.endswith(".so"):
189
+ name = name[:-3]
190
+
191
+ if name.endswith(".dll"):
192
+ name = name[:-4]
193
+
194
+ if name.endswith(".dylib"):
195
+ name = name[:-6]
196
+
197
+ # Collect the hash of the extension shared library. We don't currently use
198
+ # this, but it would allow for binary-level reproducibility later.
199
+ with open(path, "rb") as fd:
200
+ sha256 = hashlib.sha256(fd.read()).hexdigest()
201
+
202
+ return {"name": name, "sha256": sha256}