pyvcell-odesolver 0.9.4__py3-none-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.
- pyvcell_odesolver/__init__.py +5 -0
- pyvcell_odesolver/_internal/__init__.py +0 -0
- pyvcell_odesolver/_internal/check_arch.py +114 -0
- pyvcell_odesolver/_internal/native_calls.py +53 -0
- pyvcell_odesolver/_internal/native_utils.py +56 -0
- pyvcell_odesolver/lib/IDAWin.dll +0 -0
- pyvcell_odesolver/lib/README.md +1 -0
- pyvcell_odesolver/sundials.py +13 -0
- pyvcell_odesolver-0.9.4.dist-info/METADATA +92 -0
- pyvcell_odesolver-0.9.4.dist-info/RECORD +11 -0
- pyvcell_odesolver-0.9.4.dist-info/WHEEL +5 -0
|
File without changes
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import platform
|
|
2
|
+
import struct
|
|
3
|
+
import os
|
|
4
|
+
import platform
|
|
5
|
+
|
|
6
|
+
def get_library_archs(lib_path: str) -> tuple[str, list[str]]:
|
|
7
|
+
"""Determine architecture based on file type and header."""
|
|
8
|
+
_, ext = os.path.splitext(lib_path)
|
|
9
|
+
|
|
10
|
+
# ELF (Linux .so files)
|
|
11
|
+
if ext == ".so" or ".so." in str(lib_path):
|
|
12
|
+
with open(lib_path, 'rb') as f:
|
|
13
|
+
f.seek(5)
|
|
14
|
+
endian = '<' if f.read(1)[0] == 1 else '>'
|
|
15
|
+
f.seek(18)
|
|
16
|
+
machine = struct.unpack(endian + 'H', f.read(2))[0]
|
|
17
|
+
|
|
18
|
+
arch_map = {
|
|
19
|
+
0x03: 'i386',
|
|
20
|
+
0x3E: 'x86_64',
|
|
21
|
+
0xB7: 'aarch64',
|
|
22
|
+
0x28: 'armv7',
|
|
23
|
+
}
|
|
24
|
+
return "Linux", [normalize_arch(arch_map.get(machine, f'unknown (0x{machine:x})'))]
|
|
25
|
+
|
|
26
|
+
# Mach-O (macOS .dylib files)
|
|
27
|
+
elif ext == '.dylib':
|
|
28
|
+
with open(lib_path, 'rb') as f:
|
|
29
|
+
# Check for fat binary magic
|
|
30
|
+
magic = f.read(4)
|
|
31
|
+
if magic == b'\xca\xfe\xba\xbe' or magic == b'\xbf\xba\xfe\xca':
|
|
32
|
+
# Fat binary - list all architectures
|
|
33
|
+
f.seek(4)
|
|
34
|
+
num_archs = struct.unpack('>I', f.read(4))[0]
|
|
35
|
+
archs = set()
|
|
36
|
+
for i in range(num_archs):
|
|
37
|
+
cpu_type = struct.unpack('>I', f.read(4))[0]
|
|
38
|
+
f.read(4) # Skip cpu_subtype
|
|
39
|
+
f.read(4) # Skip offset
|
|
40
|
+
f.read(4) # Skip size
|
|
41
|
+
f.read(4) # Skip align
|
|
42
|
+
|
|
43
|
+
arch_map = {
|
|
44
|
+
0x00000007: 'i386',
|
|
45
|
+
0x01000007: 'x86_64',
|
|
46
|
+
0x0100000C: 'aarch64',
|
|
47
|
+
}
|
|
48
|
+
archs.add(normalize_arch(arch_map.get(cpu_type, f'unknown (0x{cpu_type:x})')))
|
|
49
|
+
return "Darwin", list(archs)
|
|
50
|
+
else:
|
|
51
|
+
# Single-arch Mach-O - determine endianness
|
|
52
|
+
f.seek(4)
|
|
53
|
+
cpu_type_bytes = f.read(4)
|
|
54
|
+
# Try little-endian first (most common)
|
|
55
|
+
cpu_type = struct.unpack('<I', cpu_type_bytes)[0]
|
|
56
|
+
|
|
57
|
+
arch_map = {
|
|
58
|
+
0x00000007: 'i386',
|
|
59
|
+
0x01000007: 'x86_64',
|
|
60
|
+
0x0100000C: 'aarch64',
|
|
61
|
+
}
|
|
62
|
+
return "Darwin", [ normalize_arch(arch_map.get(cpu_type, f'unknown (0x{cpu_type:x})')) ]
|
|
63
|
+
|
|
64
|
+
# PE (Windows .dll files)
|
|
65
|
+
elif ext == '.dll':
|
|
66
|
+
with open(lib_path, 'rb') as f:
|
|
67
|
+
f.seek(0x3c)
|
|
68
|
+
pe_offset = struct.unpack('<I', f.read(4))[0]
|
|
69
|
+
f.seek(pe_offset + 4)
|
|
70
|
+
machine = struct.unpack('<H', f.read(2))[0]
|
|
71
|
+
|
|
72
|
+
arch_map = {
|
|
73
|
+
0x014C: 'i386',
|
|
74
|
+
0x8664: 'x86_64',
|
|
75
|
+
0xAA64: 'aarch64',
|
|
76
|
+
}
|
|
77
|
+
return "Windows", [ normalize_arch(arch_map.get(machine, f'unknown (0x{machine:x})')) ]
|
|
78
|
+
|
|
79
|
+
return 'unknown', [ ]
|
|
80
|
+
|
|
81
|
+
def get_all_valid_libraries_from_dir(lib_dir: str):
|
|
82
|
+
dir_files = [str(full_file) for f in os.listdir(lib_dir) if os.path.isfile(full_file := os.path.join(lib_dir, f))]
|
|
83
|
+
return filter_all_valid_libraries(dir_files)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def filter_all_valid_libraries(lib_path_list: list[str]) -> list[str]:
|
|
87
|
+
if not lib_path_list:
|
|
88
|
+
raise ValueError("A list must be provided, `None` is not allowed.")
|
|
89
|
+
|
|
90
|
+
system_name = platform.system()
|
|
91
|
+
system_arch = normalize_arch(platform.machine())
|
|
92
|
+
valid_libraries = []
|
|
93
|
+
for lib_path in lib_path_list:
|
|
94
|
+
required_system_name, shared_library_archs = get_library_archs(lib_path)
|
|
95
|
+
if system_arch in shared_library_archs and required_system_name in system_name:
|
|
96
|
+
valid_libraries.append(lib_path)
|
|
97
|
+
return valid_libraries
|
|
98
|
+
|
|
99
|
+
def normalize_arch(machine_str=None):
|
|
100
|
+
if machine_str is None:
|
|
101
|
+
machine_str = platform.machine().lower()
|
|
102
|
+
|
|
103
|
+
# Normalize common variants
|
|
104
|
+
if machine_str in ('x86_64', 'amd64', 'x64'):
|
|
105
|
+
return 'x86_64'
|
|
106
|
+
elif machine_str in ('aarch64', 'arm64'):
|
|
107
|
+
return 'aarch64'
|
|
108
|
+
elif machine_str in ('i386', 'i486', 'i586', 'i686', 'x86'):
|
|
109
|
+
return 'i386'
|
|
110
|
+
elif machine_str.startswith('arm'):
|
|
111
|
+
return 'arm'
|
|
112
|
+
elif machine_str.lower() != machine_str:
|
|
113
|
+
return normalize_arch(machine_str.lower())
|
|
114
|
+
raise OSError(f"Unknown machine type detected: `{machine_str}`")
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import logging
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
from pyvcell_odesolver._internal.native_utils import VCellNativeLibraryLoader
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ReturnValue(BaseModel):
|
|
11
|
+
success: bool
|
|
12
|
+
message: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MutableString:
|
|
16
|
+
def __init__(self, value: str):
|
|
17
|
+
self.value: str = value
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ODENativeCalls:
|
|
21
|
+
def __init__(self) -> None:
|
|
22
|
+
self.loader = VCellNativeLibraryLoader()
|
|
23
|
+
self.lib: ctypes.CDLL = self.loader.lib
|
|
24
|
+
|
|
25
|
+
def call_version(self) -> ReturnValue:
|
|
26
|
+
try:
|
|
27
|
+
version_str: str = self.lib.version_ctypes()
|
|
28
|
+
if version_str is None:
|
|
29
|
+
error_msg = "Failed to collect and covert version information"
|
|
30
|
+
logging.error(error_msg)
|
|
31
|
+
return ReturnValue(success=False, message=error_msg)
|
|
32
|
+
return ReturnValue(success=True, message=version_str.decode("utf-8"))
|
|
33
|
+
except Exception as e:
|
|
34
|
+
logging.exception("Error in vcml_to_finite_volume_input()", exc_info=e)
|
|
35
|
+
raise
|
|
36
|
+
|
|
37
|
+
def call_solve(
|
|
38
|
+
self, input_file_path: str, output_file_path: str, task_id: int
|
|
39
|
+
) -> ReturnValue:
|
|
40
|
+
try:
|
|
41
|
+
solver_return_code: int = self.lib.solve_ctypes(
|
|
42
|
+
ctypes.c_char_p(input_file_path.encode("utf-8")),
|
|
43
|
+
ctypes.c_char_p(output_file_path.encode("utf-8")),
|
|
44
|
+
ctypes.c_int(task_id),
|
|
45
|
+
)
|
|
46
|
+
if solver_return_code == 0:
|
|
47
|
+
return ReturnValue(success=True, message="Solver returned successfully.")
|
|
48
|
+
error_msg = "Solver failed with exit code {}".format(solver_return_code)
|
|
49
|
+
logging.error(error_msg)
|
|
50
|
+
return ReturnValue(success=False, message=error_msg)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
logging.exception("Error in calling SundialsSolverInterface::solve_ctypes(...)", exc_info=e)
|
|
53
|
+
raise e
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import ctypes
|
|
2
|
+
import platform
|
|
3
|
+
from _ctypes import byref
|
|
4
|
+
from importlib.resources import files
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
import pyvcell_odesolver
|
|
9
|
+
from pyvcell_odesolver._internal import check_arch
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class VCellNativeLibraryLoader:
|
|
13
|
+
lib: ctypes.CDLL
|
|
14
|
+
|
|
15
|
+
def __init__(self) -> None:
|
|
16
|
+
self.lib = self._load_library()
|
|
17
|
+
self._define_entry_points()
|
|
18
|
+
|
|
19
|
+
def _load_library(self) -> ctypes.CDLL:
|
|
20
|
+
system = platform.system()
|
|
21
|
+
lib_ext = {"Linux": ".so", "Darwin": ".dylib", "Windows": ".dll"}.get(system)
|
|
22
|
+
version_identifier = "VCell ODE solver (CVODE/IDA) v"
|
|
23
|
+
|
|
24
|
+
if lib_ext is None:
|
|
25
|
+
raise OSError(f"Unsupported operating system: {system}")
|
|
26
|
+
|
|
27
|
+
libs_dir = files(pyvcell_odesolver).joinpath("lib")
|
|
28
|
+
if not libs_dir.is_dir():
|
|
29
|
+
raise OSError(f"Could not find the shared library directory {libs_dir}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
valid_libraries = check_arch.get_all_valid_libraries_from_dir(str(libs_dir))
|
|
33
|
+
for file in valid_libraries:
|
|
34
|
+
file_str = str(file)
|
|
35
|
+
try:
|
|
36
|
+
lib = ctypes.CDLL(name=file_str)
|
|
37
|
+
lib.version_ctypes.restype = ctypes.c_char_p # signals ctypes to return as char-array
|
|
38
|
+
version_str: str = lib.version_ctypes().decode("utf-8")
|
|
39
|
+
if version_identifier not in version_str:
|
|
40
|
+
err_str = f"`{file_str}` isn't desired lib (`{version_identifier}` not in `{version_str}`)"
|
|
41
|
+
print(err_str, file=sys.stderr)
|
|
42
|
+
except AttributeError as e:
|
|
43
|
+
print(f"library `{file_str}` could not be loaded: {e}", file=sys.stderr)
|
|
44
|
+
continue # If we didn't get a version, we didn't get the correct lib
|
|
45
|
+
return lib
|
|
46
|
+
|
|
47
|
+
# Didn't find what we needed
|
|
48
|
+
files_checked = "\n - ".join(valid_libraries if len(valid_libraries) > 0 else os.listdir(str(libs_dir)))
|
|
49
|
+
raise OSError(f"Could not find the shared library; in `{str(libs_dir)}` checked: \n - {files_checked}")
|
|
50
|
+
|
|
51
|
+
def _define_entry_points(self) -> None:
|
|
52
|
+
self.lib.version_ctypes.restype = ctypes.c_char_p # technically, we did this above, but just in case
|
|
53
|
+
self.lib.version_ctypes.argtypes = []
|
|
54
|
+
|
|
55
|
+
self.lib.solve_ctypes.restype = ctypes.c_int
|
|
56
|
+
self.lib.solve_ctypes.argtypes = [ctypes.c_char_p, ctypes.c_char_p, ctypes.c_int]
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
After building the IDAWin library, a copy of it should be placed here for packaging into a working python wheel.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from pyvcell_odesolver._internal.native_calls import ODENativeCalls
|
|
2
|
+
|
|
3
|
+
def version() -> str:
|
|
4
|
+
obj = ODENativeCalls()
|
|
5
|
+
return obj.call_version()
|
|
6
|
+
|
|
7
|
+
def solve(input_file_path: str, output_file_path: str, task_id: int = -1) -> int:
|
|
8
|
+
obj = ODENativeCalls()
|
|
9
|
+
# In python, True == 1; we want 0 == true (a.k.a.: process returned exit code 0)
|
|
10
|
+
return int(not obj.call_solve(input_file_path, output_file_path, task_id).success)
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
print(version())
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pyvcell_odesolver
|
|
3
|
+
Version: 0.9.4
|
|
4
|
+
Summary: Virtual Cell CVODE / IDA based ODE solver
|
|
5
|
+
Author: Jim Schaff, Logan Drescher
|
|
6
|
+
Author-email: Jim Schaff <schaff@uchc.edu>, Logan Drescher <drescher@uchc.edu>
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: pydantic>=2.13.4
|
|
16
|
+
Requires-Dist: build ; extra == 'build'
|
|
17
|
+
Requires-Dist: wheel ; extra == 'build'
|
|
18
|
+
Requires-Dist: pytest ; extra == 'test'
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Provides-Extra: build
|
|
21
|
+
Provides-Extra: test
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
<p align="center" width="100%">
|
|
25
|
+
<a href="https://vcell.org">
|
|
26
|
+
<img width="10%" src="https://github.com/biosimulations/biosimulations/blob/dev/docs/src/assets/images/about/partners/vcell.svg">
|
|
27
|
+
</a>
|
|
28
|
+
</p>
|
|
29
|
+
|
|
30
|
+
## The Virtual Cell Project
|
|
31
|
+
The Virtual Cell is a modeling and simulation framework for computational biology. For details see http://vcell.org and http://github.com/virtualcell.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
# vcell-ode
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
Virtual Cell ODE [virtualcell/vcell-ode](https://github.com/virtualcell/vcell-ode) is a collection of numerical
|
|
38
|
+
simulation libraries and protocols used to process ODEs in the Virtual Cell framework [virtualcell/vcell](https://github.com/virtualcell/vcell)).
|
|
39
|
+
|
|
40
|
+
## Building VCell ODE
|
|
41
|
+
There are two ways to build VCell ODE, but both start out the same way
|
|
42
|
+
### Step 1: Acquire the source-code
|
|
43
|
+
The source code can be found online at the GitHub repository.
|
|
44
|
+
`git clone https://github.com/virtualcell/vcell.git`
|
|
45
|
+
|
|
46
|
+
### Step 2: Install Dependencies
|
|
47
|
+
You will need to acquire a unix-style C and C++ compiler suite in order to build VCell ODE. Traditionally, this project
|
|
48
|
+
uses CLang + Mold, but other compiler suites may work. Additionally, you'll need to install the other dependencies for
|
|
49
|
+
VCell ODE, which can be done in one of two ways:
|
|
50
|
+
|
|
51
|
+
#### Method 1: Conan Toolchain (Recommended)
|
|
52
|
+
The VCell ODE project uses the C++ dependency management system called ***conan*** to handle dependency management when
|
|
53
|
+
building VCell ODE. We provide a number of conan-profiles that will provide conan the information need to automatically
|
|
54
|
+
build the desired toolchain for cmake.
|
|
55
|
+
|
|
56
|
+
#### Method 2: Manual Dependency Installation
|
|
57
|
+
If you'd rather manually install all the dependencies build-tools to create VCell CLI, you'll need the following:
|
|
58
|
+
##### Dependencies
|
|
59
|
+
* If you want live messaging while the solver runs: `libcurl` (add `-DOPTION_TARGET_MESSAGING` to cmake call below)
|
|
60
|
+
##### Build Tools
|
|
61
|
+
* `cmake` to perform the build configuration
|
|
62
|
+
* `ninja` (or equivalent) to perform the actual build process
|
|
63
|
+
|
|
64
|
+
### Step 3: Invoke the Build
|
|
65
|
+
In a shell with conan and/or the other build tools in path, navigate to the project's root directory and
|
|
66
|
+
run the following commands (tested in bash on unix and powershell on windows)
|
|
67
|
+
|
|
68
|
+
Note that if using `conan`, you'll need to define a profile. use `conan profile detect --force` to generate one automatically,
|
|
69
|
+
or use one of the provided ones in `<project_root>/conan-profiles` [link](https://docs.conan.io/2/reference/config_files/profiles.html)
|
|
70
|
+
to further reading on the official `conan` website.
|
|
71
|
+
#### Powershell
|
|
72
|
+
```pwsh
|
|
73
|
+
mdkir build # Must do if not using conan
|
|
74
|
+
conan install . --output-folder build --build=missing # If building using conan's help
|
|
75
|
+
cd build
|
|
76
|
+
./conanbuild.ps1 # If building using conan's help
|
|
77
|
+
cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release
|
|
78
|
+
cmake --build . --config Release
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
mdkir build # must do if not building conan
|
|
83
|
+
conan install . --output-folder build --build=missing # If building using conan's help
|
|
84
|
+
cd build
|
|
85
|
+
./conanbuild.sh # If building using conan's help
|
|
86
|
+
cmake -B . -S .. -G "Ninja" -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release
|
|
87
|
+
cmake --build . --config Release
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Step 4: Manual user/system install
|
|
91
|
+
Currently, VCell ODE does not have an automated installation script. You will either need to move the resulting executables
|
|
92
|
+
in `<project_root>/build/bin` to an appropriate folder in path, or put said folder into path for your computer.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pyvcell_odesolver/__init__.py,sha256=tCVNXPq-mNEw3_Mgl2jY2plhJ47alNww0Ey9K7AbXbo,147
|
|
2
|
+
pyvcell_odesolver/_internal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pyvcell_odesolver/_internal/check_arch.py,sha256=SldYkqmGc9RqXidlzO9z_MKbgPCPJmLlClTHNdJgfpA,4352
|
|
4
|
+
pyvcell_odesolver/_internal/native_calls.py,sha256=pBrkg9ceWtHpjUiVHK7KK_FoZhw2SXIKEiszUziMwHE,1935
|
|
5
|
+
pyvcell_odesolver/_internal/native_utils.py,sha256=l78LJXZ4PPOoBGd2GuNrgt2uDonHdeXcz2SzQYmcYYo,2381
|
|
6
|
+
pyvcell_odesolver/lib/IDAWin.dll,sha256=uRmIDqFZj2pr-XeNk0_j0lDio9o2b8ClLRGJwC8uBuw,1033728
|
|
7
|
+
pyvcell_odesolver/lib/README.md,sha256=O3e3gN08qZWr85izOaKG1-kb8GMuDLUxDSA-ozMMWow,112
|
|
8
|
+
pyvcell_odesolver/sundials.py,sha256=Cxz85SKDxHxOfrToCViCMHOBIgFEPieVmOy5B7chLsU,490
|
|
9
|
+
pyvcell_odesolver-0.9.4.dist-info/WHEEL,sha256=1EjGHjH5dz_UZIF9e_7PpgY8tR3oVUD4yQEimBKjzys,88
|
|
10
|
+
pyvcell_odesolver-0.9.4.dist-info/METADATA,sha256=xvSfIP_SIAcd2bZYNpunQwHpKoJqrjHv3JuRnAPfaX4,4566
|
|
11
|
+
pyvcell_odesolver-0.9.4.dist-info/RECORD,,
|