1
1
import os
2
2
import subprocess
3
- import tarfile
4
3
import argparse
5
4
import platform
6
- import hashlib
7
5
import shutil
8
- import urllib .request
9
6
from os .path import join , isfile
10
- from urllib .parse import urlparse
11
- from concurrent .futures import ThreadPoolExecutor
12
7
from multiprocessing import cpu_count
13
8
import pathlib
14
9
15
10
16
11
def build_solvers ():
17
- SUITESPARSE_VERSION = "7.8.3"
18
- SUITESPARSE_URL = f"https://github.com/DrTimothyAldenDavis/SuiteSparse/archive/v{ SUITESPARSE_VERSION } .tar.gz"
19
- SUITESPARSE_CHECKSUM = (
20
- "ce39b28d4038a09c14f21e02c664401be73c0cb96a9198418d6a98a7db73a259"
21
- )
22
- SUNDIALS_VERSION = "7.3.0"
23
- SUNDIALS_URL = f"https://github.com/LLNL/sundials/releases/download/v{ SUNDIALS_VERSION } /sundials-{ SUNDIALS_VERSION } .tar.gz"
24
- SUNDIALS_CHECKSUM = (
25
- "697b7b0dbc229f149e39b293d1ab03d321d61adb6733ffb78c0ddbffaf73d839"
26
- )
27
12
DEFAULT_INSTALL_DIR = str (pathlib .Path (__file__ ).parent .resolve () / ".idaklu" )
28
13
29
14
def safe_remove_dir (path ):
30
15
if os .path .exists (path ):
31
16
shutil .rmtree (path )
32
17
33
- def install_suitesparse (download_dir ):
18
+ def install_suitesparse ():
34
19
# The SuiteSparse KLU module has 4 dependencies:
35
20
# - suitesparseconfig
36
21
# - AMD
37
22
# - COLAMD
38
23
# - BTF
39
- suitesparse_dir = f"SuiteSparse-{ SUITESPARSE_VERSION } "
40
- suitesparse_src = os .path .join (download_dir , suitesparse_dir )
24
+ suitesparse_src = pathlib .Path ("SuiteSparse" )
41
25
print ("-" * 10 , "Building SuiteSparse_config" , "-" * 40 )
42
26
make_cmd = [
43
27
"make" ,
@@ -61,28 +45,26 @@ def install_suitesparse(download_dir):
61
45
# dylibs to find libomp.dylib when repairing the wheel
62
46
if os .environ .get ("CIBUILDWHEEL" ) == "1" :
63
47
env ["CMAKE_OPTIONS" ] = (
64
- f"-DCMAKE_INSTALL_PREFIX={ install_dir } -DCMAKE_INSTALL_RPATH={ install_dir } /lib"
48
+ f"-DCMAKE_INSTALL_PREFIX={ DEFAULT_INSTALL_DIR } -DCMAKE_INSTALL_RPATH={ DEFAULT_INSTALL_DIR } /lib"
65
49
)
66
50
else :
67
- env ["CMAKE_OPTIONS" ] = f"-DCMAKE_INSTALL_PREFIX={ install_dir } "
51
+ env ["CMAKE_OPTIONS" ] = (
52
+ f"-DCMAKE_INSTALL_PREFIX={ DEFAULT_INSTALL_DIR } "
53
+ )
68
54
else :
69
55
# For AMD, COLAMD, BTF and KLU; do not set a BUILD RPATH but use an
70
56
# INSTALL RPATH in order to ensure that the dynamic libraries are found
71
57
# at runtime just once. Otherwise, delocate complains about multiple
72
58
# references to the SuiteSparse_config dynamic library (auditwheel does not).
73
59
env ["CMAKE_OPTIONS" ] = (
74
- f"-DCMAKE_INSTALL_PREFIX={ install_dir } -DCMAKE_INSTALL_RPATH={ install_dir } /lib -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE -DCMAKE_BUILD_WITH_INSTALL_RPATH=FALSE"
60
+ f"-DCMAKE_INSTALL_PREFIX={ DEFAULT_INSTALL_DIR } -DCMAKE_INSTALL_RPATH={ DEFAULT_INSTALL_DIR } /lib -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE -DCMAKE_BUILD_WITH_INSTALL_RPATH=FALSE"
75
61
)
76
62
subprocess .run (make_cmd , cwd = build_dir , env = env , shell = True , check = True )
77
63
subprocess .run (install_cmd , cwd = build_dir , check = True )
78
64
79
- def install_sundials (download_dir , install_dir ):
80
- # Set install dir for SuiteSparse libs
81
- # Ex: if install_dir -> "/usr/local/" then
82
- # KLU_INCLUDE_DIR -> "/usr/local/include"
83
- # KLU_LIBRARY_DIR -> "/usr/local/lib"
84
- KLU_INCLUDE_DIR = os .path .join (install_dir , "include" , "suitesparse" )
85
- KLU_LIBRARY_DIR = os .path .join (install_dir , "lib" )
65
+ def install_sundials ():
66
+ KLU_INCLUDE_DIR = os .path .join (DEFAULT_INSTALL_DIR , "include" , "suitesparse" )
67
+ KLU_LIBRARY_DIR = os .path .join (DEFAULT_INSTALL_DIR , "lib" )
86
68
cmake_args = [
87
69
"-DENABLE_LAPACK=ON" ,
88
70
"-DSUNDIALS_INDEX_SIZE=32" ,
@@ -93,7 +75,7 @@ def install_sundials(download_dir, install_dir):
93
75
"-DENABLE_OPENMP=ON" ,
94
76
f"-DKLU_INCLUDE_DIR={ KLU_INCLUDE_DIR } " ,
95
77
f"-DKLU_LIBRARY_DIR={ KLU_LIBRARY_DIR } " ,
96
- "-DCMAKE_INSTALL_PREFIX=" + install_dir ,
78
+ "-DCMAKE_INSTALL_PREFIX=" + DEFAULT_INSTALL_DIR ,
97
79
# on macOS use fixed paths rather than rpath
98
80
"-DCMAKE_INSTALL_NAME_DIR=" + KLU_LIBRARY_DIR ,
99
81
]
@@ -134,24 +116,22 @@ def install_sundials(download_dir, install_dir):
134
116
"-DOpenMP_omp_LIBRARY=" + OpenMP_omp_LIBRARY ,
135
117
]
136
118
137
- # SUNDIALS are built within download_dir 'build_sundials' in the PyBaMM root
138
- # download_dir
139
- build_dir = os .path .abspath (os .path .join (download_dir , "build_sundials" ))
119
+ build_dir = pathlib .Path ("build_sundials" )
140
120
if not os .path .exists (build_dir ):
141
121
print ("\n -" * 10 , "Creating build dir" , "-" * 40 )
142
122
os .makedirs (build_dir )
143
123
144
- sundials_src = f "../sundials- { SUNDIALS_VERSION } "
124
+ sundials_src = "../sundials"
145
125
print ("-" * 10 , "Running CMake prepare" , "-" * 40 )
146
126
subprocess .run (["cmake" , sundials_src , * cmake_args ], cwd = build_dir , check = True )
147
127
148
128
print ("-" * 10 , "Building SUNDIALS" , "-" * 40 )
149
129
make_cmd = ["make" , f"-j{ cpu_count ()} " , "install" ]
150
130
subprocess .run (make_cmd , cwd = build_dir , check = True )
151
131
152
- def check_libraries_installed (install_dir ):
132
+ def check_libraries_installed ():
153
133
# Define the directories to check for SUNDIALS and SuiteSparse libraries
154
- lib_dirs = [install_dir ]
134
+ lib_dirs = [DEFAULT_INSTALL_DIR ]
155
135
156
136
sundials_files = [
157
137
"libsundials_idas" ,
@@ -217,121 +197,38 @@ def check_libraries_installed(install_dir):
217
197
218
198
return sundials_lib_found , suitesparse_lib_found
219
199
220
- def calculate_sha256 (file_path ):
221
- sha256_hash = hashlib .sha256 ()
222
- with open (file_path , "rb" ) as f :
223
- # Read and update hash in chunks of 4K
224
- for byte_block in iter (lambda : f .read (4096 ), b"" ):
225
- sha256_hash .update (byte_block )
226
- return sha256_hash .hexdigest ()
227
-
228
- def download_extract_library (url , expected_checksum , download_dir ):
229
- file_name = url .split ("/" )[- 1 ]
230
- file_path = os .path .join (download_dir , file_name )
231
-
232
- # Check if file already exists and validate checksum
233
- if os .path .exists (file_path ):
234
- print (f"Validating checksum for { file_name } ..." )
235
- actual_checksum = calculate_sha256 (file_path )
236
- print (f"Found { actual_checksum } against { expected_checksum } " )
237
- if actual_checksum == expected_checksum :
238
- print (f"Checksum valid. Skipping download for { file_name } ." )
239
- # Extract the archive as the checksum is valid
240
- with tarfile .open (file_path ) as tar :
241
- tar .extractall (download_dir )
242
- return
243
- else :
244
- print (f"Checksum invalid. Redownloading { file_name } ." )
245
-
246
- # Download and extract archive at url
247
- parsed_url = urlparse (url )
248
- if parsed_url .scheme not in ["http" , "https" ]:
249
- raise ValueError (
250
- f"Invalid URL scheme: { parsed_url .scheme } . Only HTTP and HTTPS are allowed."
251
- )
252
- with urllib .request .urlopen (url ) as response :
253
- os .makedirs (download_dir , exist_ok = True )
254
- with open (file_path , "wb" ) as out_file :
255
- out_file .write (response .read ())
256
- with tarfile .open (file_path ) as tar :
257
- tar .extractall (download_dir )
258
-
259
- def parallel_download (urls , download_dir ):
260
- # Use 2 processes for parallel downloading
261
- with ThreadPoolExecutor (max_workers = len (urls )) as executor :
262
- futures = [
263
- executor .submit (
264
- download_extract_library , url , expected_checksum , download_dir
265
- )
266
- for (url , expected_checksum ) in urls
267
- ]
268
- for future in futures :
269
- future .result ()
270
-
271
200
# First check requirements: make and cmake
272
201
check_build_tools ()
273
202
274
203
# Build in parallel wherever possible
275
204
os .environ ["CMAKE_BUILD_PARALLEL_LEVEL" ] = str (cpu_count ())
276
205
277
- # Create download directory in PyBaMM dir
278
- pybamm_dir = pathlib .Path (__file__ ).parent .resolve ()
279
- download_dir = str (pybamm_dir / "install_KLU_Sundials" )
280
- if not os .path .exists (download_dir ):
281
- os .makedirs (download_dir )
282
-
283
206
# Get installation location
284
207
parser = argparse .ArgumentParser (
285
- description = "Download, compile and install Sundials and SuiteSparse."
208
+ description = "Compile and install Sundials and SuiteSparse."
286
209
)
287
210
parser .add_argument (
288
211
"--force" ,
289
212
action = "store_true" ,
290
213
help = "Force installation even if libraries are already found. This will overwrite the pre-existing files." ,
291
214
)
292
- parser .add_argument ("--install-dir" , type = str , default = DEFAULT_INSTALL_DIR )
293
215
args = parser .parse_args ()
294
- install_dir = (
295
- args .install_dir
296
- if os .path .isabs (args .install_dir )
297
- else os .path .join (pybamm_dir , args .install_dir )
298
- )
299
216
300
217
if args .force :
301
218
print (
302
219
"The '--force' option is activated: installation will be forced, ignoring any existing libraries."
303
220
)
304
- safe_remove_dir (os .path .join (download_dir , "build_sundials" ))
305
- safe_remove_dir (
306
- os .path .join (download_dir , f"SuiteSparse-{ SUITESPARSE_VERSION } " )
307
- )
308
- safe_remove_dir (os .path .join (download_dir , f"sundials-{ SUNDIALS_VERSION } " ))
221
+ safe_remove_dir (pathlib .Path ("build_sundials" ))
309
222
sundials_found , suitesparse_found = False , False
310
223
else :
311
224
# Check whether the libraries are installed
312
- sundials_found , suitesparse_found = check_libraries_installed (install_dir )
225
+ sundials_found , suitesparse_found = check_libraries_installed ()
313
226
314
- # Determine which libraries to download based on whether they were found
315
- if not sundials_found and not suitesparse_found :
316
- # Both SUNDIALS and SuiteSparse are missing, download and install both
317
- parallel_download (
318
- [
319
- (SUITESPARSE_URL , SUITESPARSE_CHECKSUM ),
320
- (SUNDIALS_URL , SUNDIALS_CHECKSUM ),
321
- ],
322
- download_dir ,
323
- )
324
- install_suitesparse (download_dir )
325
- install_sundials (download_dir , install_dir )
326
- else :
327
- if not sundials_found :
328
- # Only SUNDIALS is missing, download and install it
329
- parallel_download ([(SUNDIALS_URL , SUNDIALS_CHECKSUM )], download_dir )
330
- install_sundials (download_dir , install_dir )
331
- if not suitesparse_found :
332
- # Only SuiteSparse is missing, download and install it
333
- parallel_download ([(SUITESPARSE_URL , SUITESPARSE_CHECKSUM )], download_dir )
334
- install_suitesparse (download_dir )
227
+ # Determine which libraries to install
228
+ if not suitesparse_found :
229
+ install_suitesparse ()
230
+ if not sundials_found :
231
+ install_sundials ()
335
232
336
233
337
234
def check_build_tools ():
0 commit comments