diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..3de9349 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore index 7ae23c2..1554161 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,7 @@ cfg_constructor/out **/data **/bin **/.venv -**/logdat \ No newline at end of file +**/logdat + +# built dgl lib +**/dgl diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index a1b9745..223e5fe 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Install dependencies Enter Poetry Shell `poetry shell` -## Running the CFG Creator +## Running the CFG Constructor Tool `(src/cfg_constructor)` To generate Control Flow Graphs (CFGs) from binary files, use the `cfg_creator.py` script. This script analyzes binary files and creates CFGs, which can be visualized or saved in different formats. @@ -19,20 +19,18 @@ To generate Control Flow Graphs (CFGs) from binary files, use the `cfg_creator.p Run the script from the root directory using the following command: ```sh -python cfg_constructor/cfg_creator.py --data_dir --vis_mode --job_id +python src/cfg_constructor/cfg_creator.py --data_dir --vis_mode --job_id ``` Args: - `--data_dir`: Path to the directory containing the binary files in the parent directory. (str, default='data') - `--vis_mode`: Visualization mode. 0 = visualize in window, 1 = save as HTML docs, 2 = save graphs w/o visualizing as edgelists and `csv` for node values. (int, default=2) -- `--job_id`: int for job id for use for logging + avoiding reprocessing already processed data based on job_id, vis_mode, and data dir (int, default=0) +- `--job-id`: int for job id for use for logging + avoiding reprocessing already processed data based on job_id, vis_mode, and data dir (int, default=0) Output will be stored dependent on the vis_mode: - vis_mode=0: no saved output, graphs will be displayed in GUI - vis_mode=1: HTML files in `cfg_constructor/out/out_html` -- vis_mode=2: CSV files in `cfg_constructor/out/out_edgelists` - -For vis_mode = {1, 2}, clear the directory before rerunning our tool as it will empty the directory and overwrite it with files from the currently running job +- vis_mode=2: CSV files in `cfg_constructor/out/out_adjacency_matrices` Example usage from my machine: ``` @@ -42,6 +40,7 @@ Example usage from my machine: This goes to the root directory of the repository and runs the constructor from a `data` dir (also in the root directory), visualizes each with mode `2` (saving adjacency lists to specified dir above), and assigns job_id `0` for logging (i.e. program crashes, can easily resume) +If a logging file with the existing job already exists, the script will load that and silently skip any files marked as processed by that log file. ## Methodology Using Static Analysis (deconstruction of binaries without execution) to extract Control Flow Graphs from a binary. diff --git a/cfg_constructor/utils/log_utils.py b/cfg_constructor/utils/log_utils.py deleted file mode 100644 index 5df4c43..0000000 --- a/cfg_constructor/utils/log_utils.py +++ /dev/null @@ -1,27 +0,0 @@ -import logging -import os -import json - -def setup_logging(job_id): - log_dir = 'cfg_constructor/logs' - os.makedirs(log_dir, exist_ok=True) - log_file = os.path.join(log_dir, f'visualization_log_{job_id}.json') - - if not os.path.exists(log_file): - with open(log_file, 'w') as f: - json.dump({}, f) - - return log_file - -def log_visualization(log_file, fname, vis_mode): - with open(log_file, 'r+') as f: - log_data = json.load(f) - log_data[fname] = vis_mode - f.seek(0) - json.dump(log_data, f, indent=2) - f.truncate() - -def is_already_visualized(log_file, fname): - with open(log_file, 'r') as f: - log_data = json.load(f) - return fname in log_data \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 7304519..8bff253 100644 --- a/poetry.lock +++ b/poetry.lock @@ -167,55 +167,77 @@ files = [ [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] +[[package]] +name = "filelock" +version = "3.16.1" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, + {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] +typing = ["typing-extensions (>=4.12.2)"] + [[package]] name = "fonttools" -version = "4.53.1" +version = "4.54.1" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.53.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0679a30b59d74b6242909945429dbddb08496935b82f91ea9bf6ad240ec23397"}, - {file = "fonttools-4.53.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8bf06b94694251861ba7fdeea15c8ec0967f84c3d4143ae9daf42bbc7717fe3"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b96cd370a61f4d083c9c0053bf634279b094308d52fdc2dd9a22d8372fdd590d"}, - {file = "fonttools-4.53.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1c7c5aa18dd3b17995898b4a9b5929d69ef6ae2af5b96d585ff4005033d82f0"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e013aae589c1c12505da64a7d8d023e584987e51e62006e1bb30d72f26522c41"}, - {file = "fonttools-4.53.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:9efd176f874cb6402e607e4cc9b4a9cd584d82fc34a4b0c811970b32ba62501f"}, - {file = "fonttools-4.53.1-cp310-cp310-win32.whl", hash = "sha256:c8696544c964500aa9439efb6761947393b70b17ef4e82d73277413f291260a4"}, - {file = "fonttools-4.53.1-cp310-cp310-win_amd64.whl", hash = "sha256:8959a59de5af6d2bec27489e98ef25a397cfa1774b375d5787509c06659b3671"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da33440b1413bad53a8674393c5d29ce64d8c1a15ef8a77c642ffd900d07bfe1"}, - {file = "fonttools-4.53.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ff7e5e9bad94e3a70c5cd2fa27f20b9bb9385e10cddab567b85ce5d306ea923"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6e7170d675d12eac12ad1a981d90f118c06cf680b42a2d74c6c931e54b50719"}, - {file = "fonttools-4.53.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bee32ea8765e859670c4447b0817514ca79054463b6b79784b08a8df3a4d78e3"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6e08f572625a1ee682115223eabebc4c6a2035a6917eac6f60350aba297ccadb"}, - {file = "fonttools-4.53.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b21952c092ffd827504de7e66b62aba26fdb5f9d1e435c52477e6486e9d128b2"}, - {file = "fonttools-4.53.1-cp311-cp311-win32.whl", hash = "sha256:9dfdae43b7996af46ff9da520998a32b105c7f098aeea06b2226b30e74fbba88"}, - {file = "fonttools-4.53.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4d0096cb1ac7a77b3b41cd78c9b6bc4a400550e21dc7a92f2b5ab53ed74eb02"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d92d3c2a1b39631a6131c2fa25b5406855f97969b068e7e08413325bc0afba58"}, - {file = "fonttools-4.53.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3b3c8ebafbee8d9002bd8f1195d09ed2bd9ff134ddec37ee8f6a6375e6a4f0e8"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32f029c095ad66c425b0ee85553d0dc326d45d7059dbc227330fc29b43e8ba60"}, - {file = "fonttools-4.53.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f5e6c3510b79ea27bb1ebfcc67048cde9ec67afa87c7dd7efa5c700491ac7f"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f677ce218976496a587ab17140da141557beb91d2a5c1a14212c994093f2eae2"}, - {file = "fonttools-4.53.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9e6ceba2a01b448e36754983d376064730690401da1dd104ddb543519470a15f"}, - {file = "fonttools-4.53.1-cp312-cp312-win32.whl", hash = "sha256:791b31ebbc05197d7aa096bbc7bd76d591f05905d2fd908bf103af4488e60670"}, - {file = "fonttools-4.53.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ed170b5e17da0264b9f6fae86073be3db15fa1bd74061c8331022bca6d09bab"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c818c058404eb2bba05e728d38049438afd649e3c409796723dfc17cd3f08749"}, - {file = "fonttools-4.53.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:651390c3b26b0c7d1f4407cad281ee7a5a85a31a110cbac5269de72a51551ba2"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e54f1bba2f655924c1138bbc7fa91abd61f45c68bd65ab5ed985942712864bbb"}, - {file = "fonttools-4.53.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9cd19cf4fe0595ebdd1d4915882b9440c3a6d30b008f3cc7587c1da7b95be5f"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2af40ae9cdcb204fc1d8f26b190aa16534fcd4f0df756268df674a270eab575d"}, - {file = "fonttools-4.53.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:35250099b0cfb32d799fb5d6c651220a642fe2e3c7d2560490e6f1d3f9ae9169"}, - {file = "fonttools-4.53.1-cp38-cp38-win32.whl", hash = "sha256:f08df60fbd8d289152079a65da4e66a447efc1d5d5a4d3f299cdd39e3b2e4a7d"}, - {file = "fonttools-4.53.1-cp38-cp38-win_amd64.whl", hash = "sha256:7b6b35e52ddc8fb0db562133894e6ef5b4e54e1283dff606fda3eed938c36fc8"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75a157d8d26c06e64ace9df037ee93a4938a4606a38cb7ffaf6635e60e253b7a"}, - {file = "fonttools-4.53.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4824c198f714ab5559c5be10fd1adf876712aa7989882a4ec887bf1ef3e00e31"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:becc5d7cb89c7b7afa8321b6bb3dbee0eec2b57855c90b3e9bf5fb816671fa7c"}, - {file = "fonttools-4.53.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84ec3fb43befb54be490147b4a922b5314e16372a643004f182babee9f9c3407"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:73379d3ffdeecb376640cd8ed03e9d2d0e568c9d1a4e9b16504a834ebadc2dfb"}, - {file = "fonttools-4.53.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:02569e9a810f9d11f4ae82c391ebc6fb5730d95a0657d24d754ed7763fb2d122"}, - {file = "fonttools-4.53.1-cp39-cp39-win32.whl", hash = "sha256:aae7bd54187e8bf7fd69f8ab87b2885253d3575163ad4d669a262fe97f0136cb"}, - {file = "fonttools-4.53.1-cp39-cp39-win_amd64.whl", hash = "sha256:e5b708073ea3d684235648786f5f6153a48dc8762cdfe5563c57e80787c29fbb"}, - {file = "fonttools-4.53.1-py3-none-any.whl", hash = "sha256:f1f8758a2ad110bd6432203a344269f445a2907dc24ef6bccfd0ac4e14e0d71d"}, - {file = "fonttools-4.53.1.tar.gz", hash = "sha256:e128778a8e9bc11159ce5447f76766cefbd876f44bd79aff030287254e4752c4"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ed7ee041ff7b34cc62f07545e55e1468808691dddfd315d51dd82a6b37ddef2"}, + {file = "fonttools-4.54.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:41bb0b250c8132b2fcac148e2e9198e62ff06f3cc472065dff839327945c5882"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7965af9b67dd546e52afcf2e38641b5be956d68c425bef2158e95af11d229f10"}, + {file = "fonttools-4.54.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278913a168f90d53378c20c23b80f4e599dca62fbffae4cc620c8eed476b723e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0e88e3018ac809b9662615072dcd6b84dca4c2d991c6d66e1970a112503bba7e"}, + {file = "fonttools-4.54.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4aa4817f0031206e637d1e685251ac61be64d1adef111060df84fdcbc6ab6c44"}, + {file = "fonttools-4.54.1-cp310-cp310-win32.whl", hash = "sha256:7e3b7d44e18c085fd8c16dcc6f1ad6c61b71ff463636fcb13df7b1b818bd0c02"}, + {file = "fonttools-4.54.1-cp310-cp310-win_amd64.whl", hash = "sha256:dd9cc95b8d6e27d01e1e1f1fae8559ef3c02c76317da650a19047f249acd519d"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5419771b64248484299fa77689d4f3aeed643ea6630b2ea750eeab219588ba20"}, + {file = "fonttools-4.54.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:301540e89cf4ce89d462eb23a89464fef50915255ece765d10eee8b2bf9d75b2"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ae5091547e74e7efecc3cbf8e75200bc92daaeb88e5433c5e3e95ea8ce5aa7"}, + {file = "fonttools-4.54.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82834962b3d7c5ca98cb56001c33cf20eb110ecf442725dc5fdf36d16ed1ab07"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d26732ae002cc3d2ecab04897bb02ae3f11f06dd7575d1df46acd2f7c012a8d8"}, + {file = "fonttools-4.54.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:58974b4987b2a71ee08ade1e7f47f410c367cdfc5a94fabd599c88165f56213a"}, + {file = "fonttools-4.54.1-cp311-cp311-win32.whl", hash = "sha256:ab774fa225238986218a463f3fe151e04d8c25d7de09df7f0f5fce27b1243dbc"}, + {file = "fonttools-4.54.1-cp311-cp311-win_amd64.whl", hash = "sha256:07e005dc454eee1cc60105d6a29593459a06321c21897f769a281ff2d08939f6"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:54471032f7cb5fca694b5f1a0aaeba4af6e10ae989df408e0216f7fd6cdc405d"}, + {file = "fonttools-4.54.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fa92cb248e573daab8d032919623cc309c005086d743afb014c836636166f08"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a911591200114969befa7f2cb74ac148bce5a91df5645443371aba6d222e263"}, + {file = "fonttools-4.54.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93d458c8a6a354dc8b48fc78d66d2a8a90b941f7fec30e94c7ad9982b1fa6bab"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5eb2474a7c5be8a5331146758debb2669bf5635c021aee00fd7c353558fc659d"}, + {file = "fonttools-4.54.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c9c563351ddc230725c4bdf7d9e1e92cbe6ae8553942bd1fb2b2ff0884e8b714"}, + {file = "fonttools-4.54.1-cp312-cp312-win32.whl", hash = "sha256:fdb062893fd6d47b527d39346e0c5578b7957dcea6d6a3b6794569370013d9ac"}, + {file = "fonttools-4.54.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4564cf40cebcb53f3dc825e85910bf54835e8a8b6880d59e5159f0f325e637e"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6e37561751b017cf5c40fce0d90fd9e8274716de327ec4ffb0df957160be3bff"}, + {file = "fonttools-4.54.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:357cacb988a18aace66e5e55fe1247f2ee706e01debc4b1a20d77400354cddeb"}, + {file = "fonttools-4.54.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e953cc0bddc2beaf3a3c3b5dd9ab7554677da72dfaf46951e193c9653e515a"}, + {file = "fonttools-4.54.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:58d29b9a294573d8319f16f2f79e42428ba9b6480442fa1836e4eb89c4d9d61c"}, + {file = "fonttools-4.54.1-cp313-cp313-win32.whl", hash = "sha256:9ef1b167e22709b46bf8168368b7b5d3efeaaa746c6d39661c1b4405b6352e58"}, + {file = "fonttools-4.54.1-cp313-cp313-win_amd64.whl", hash = "sha256:262705b1663f18c04250bd1242b0515d3bbae177bee7752be67c979b7d47f43d"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:ed2f80ca07025551636c555dec2b755dd005e2ea8fbeb99fc5cdff319b70b23b"}, + {file = "fonttools-4.54.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9dc080e5a1c3b2656caff2ac2633d009b3a9ff7b5e93d0452f40cd76d3da3b3c"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d152d1be65652fc65e695e5619e0aa0982295a95a9b29b52b85775243c06556"}, + {file = "fonttools-4.54.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8583e563df41fdecef31b793b4dd3af8a9caa03397be648945ad32717a92885b"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:0d1d353ef198c422515a3e974a1e8d5b304cd54a4c2eebcae708e37cd9eeffb1"}, + {file = "fonttools-4.54.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fda582236fee135d4daeca056c8c88ec5f6f6d88a004a79b84a02547c8f57386"}, + {file = "fonttools-4.54.1-cp38-cp38-win32.whl", hash = "sha256:e7d82b9e56716ed32574ee106cabca80992e6bbdcf25a88d97d21f73a0aae664"}, + {file = "fonttools-4.54.1-cp38-cp38-win_amd64.whl", hash = "sha256:ada215fd079e23e060157aab12eba0d66704316547f334eee9ff26f8c0d7b8ab"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f5b8a096e649768c2f4233f947cf9737f8dbf8728b90e2771e2497c6e3d21d13"}, + {file = "fonttools-4.54.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4e10d2e0a12e18f4e2dd031e1bf7c3d7017be5c8dbe524d07706179f355c5dac"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c32d7d4b0958600eac75eaf524b7b7cb68d3a8c196635252b7a2c30d80e986"}, + {file = "fonttools-4.54.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c39287f5c8f4a0c5a55daf9eaf9ccd223ea59eed3f6d467133cc727d7b943a55"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a7a310c6e0471602fe3bf8efaf193d396ea561486aeaa7adc1f132e02d30c4b9"}, + {file = "fonttools-4.54.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:d3b659d1029946f4ff9b6183984578041b520ce0f8fb7078bb37ec7445806b33"}, + {file = "fonttools-4.54.1-cp39-cp39-win32.whl", hash = "sha256:e96bc94c8cda58f577277d4a71f51c8e2129b8b36fd05adece6320dd3d57de8a"}, + {file = "fonttools-4.54.1-cp39-cp39-win_amd64.whl", hash = "sha256:e8a4b261c1ef91e7188a30571be6ad98d1c6d9fa2427244c545e2fa0a2494dd7"}, + {file = "fonttools-4.54.1-py3-none-any.whl", hash = "sha256:37cddd62d83dc4f72f7c3f3c2bcf2697e89a30efb152079896544a93907733bd"}, + {file = "fonttools-4.54.1.tar.gz", hash = "sha256:957f669d4922f92c171ba01bef7f29410668db09f6c02111e22b2bce446f3285"}, ] [package.extras] @@ -232,6 +254,45 @@ ufo = ["fs (>=2.2.0,<3)"] unicode = ["unicodedata2 (>=15.1.0)"] woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] +[[package]] +name = "fsspec" +version = "2024.9.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +files = [ + {file = "fsspec-2024.9.0-py3-none-any.whl", hash = "sha256:a0947d552d8a6efa72cc2c730b12c41d043509156966cca4fb157b0f2a0c574b"}, + {file = "fsspec-2024.9.0.tar.gz", hash = "sha256:4b0afb90c2f21832df142f292649035d80b421f60a9e1c027802e5a0da2b04e8"}, +] + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + [[package]] name = "iced-x86" version = "1.21.0" @@ -252,13 +313,13 @@ files = [ [[package]] name = "ipython" -version = "8.27.0" +version = "8.28.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ - {file = "ipython-8.27.0-py3-none-any.whl", hash = "sha256:f68b3cb8bde357a5d7adc9598d57e22a45dfbea19eb6b98286fa3b288c9cd55c"}, - {file = "ipython-8.27.0.tar.gz", hash = "sha256:0b99a2dc9f15fd68692e898e5568725c6d49c527d36a9fb5960ffbdeaa82ff7e"}, + {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, + {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, ] [package.dependencies] @@ -322,6 +383,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "joblib" +version = "1.4.2" +description = "Lightweight pipelining with Python functions" +optional = false +python-versions = ">=3.8" +files = [ + {file = "joblib-1.4.2-py3-none-any.whl", hash = "sha256:06d478d5674cbc267e7496a410ee875abd68e4340feff4490bcb7afb88060ae6"}, + {file = "joblib-1.4.2.tar.gz", hash = "sha256:2382c5816b2636fbd20a09e0f4e9dad4736765fdfb7dca582943b9c1366b3f0e"}, +] + [[package]] name = "jsonpickle" version = "3.3.0" @@ -463,71 +535,72 @@ files = [ [[package]] name = "markupsafe" -version = "2.1.5" +version = "3.0.1" description = "Safely add untrusted strings to HTML/XML markup." optional = false -python-versions = ">=3.7" +python-versions = ">=3.9" files = [ - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, - {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, - {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, - {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, - {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, - {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, - {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, - {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:db842712984e91707437461930e6011e60b39136c7331e971952bb30465bc1a1"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3ffb4a8e7d46ed96ae48805746755fadd0909fea2306f93d5d8233ba23dda12a"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67c519635a4f64e495c50e3107d9b4075aec33634272b5db1cde839e07367589"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48488d999ed50ba8d38c581d67e496f955821dc183883550a6fbc7f1aefdc170"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f31ae06f1328595d762c9a2bf29dafd8621c7d3adc130cbb46278079758779ca"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80fcbf3add8790caddfab6764bde258b5d09aefbe9169c183f88a7410f0f6dea"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3341c043c37d78cc5ae6e3e305e988532b072329639007fd408a476642a89fd6"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cb53e2a99df28eee3b5f4fea166020d3ef9116fdc5764bc5117486e6d1211b25"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win32.whl", hash = "sha256:db15ce28e1e127a0013dfb8ac243a8e392db8c61eae113337536edb28bdc1f97"}, + {file = "MarkupSafe-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:4ffaaac913c3f7345579db4f33b0020db693f302ca5137f106060316761beea9"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:26627785a54a947f6d7336ce5963569b5d75614619e75193bdb4e06e21d447ad"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b954093679d5750495725ea6f88409946d69cfb25ea7b4c846eef5044194f583"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:973a371a55ce9ed333a3a0f8e0bcfae9e0d637711534bcb11e130af2ab9334e7"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:244dbe463d5fb6d7ce161301a03a6fe744dac9072328ba9fc82289238582697b"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d98e66a24497637dd31ccab090b34392dddb1f2f811c4b4cd80c230205c074a3"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad91738f14eb8da0ff82f2acd0098b6257621410dcbd4df20aaa5b4233d75a50"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7044312a928a66a4c2a22644147bc61a199c1709712069a344a3fb5cfcf16915"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a4792d3b3a6dfafefdf8e937f14906a51bd27025a36f4b188728a73382231d91"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win32.whl", hash = "sha256:fa7d686ed9883f3d664d39d5a8e74d3c5f63e603c2e3ff0abcba23eac6542635"}, + {file = "MarkupSafe-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ba25a71ebf05b9bb0e2ae99f8bc08a07ee8e98c612175087112656ca0f5c8bf"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:8ae369e84466aa70f3154ee23c1451fda10a8ee1b63923ce76667e3077f2b0c4"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40f1e10d51c92859765522cbd79c5c8989f40f0419614bcdc5015e7b6bf97fc5"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a4cb365cb49b750bdb60b846b0c0bc49ed62e59a76635095a179d440540c346"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee3941769bd2522fe39222206f6dd97ae83c442a94c90f2b7a25d847d40f4729"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62fada2c942702ef8952754abfc1a9f7658a4d5460fabe95ac7ec2cbe0d02abc"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4c2d64fdba74ad16138300815cfdc6ab2f4647e23ced81f59e940d7d4a1469d9"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fb532dd9900381d2e8f48172ddc5a59db4c445a11b9fab40b3b786da40d3b56b"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0f84af7e813784feb4d5e4ff7db633aba6c8ca64a833f61d8e4eade234ef0c38"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win32.whl", hash = "sha256:cbf445eb5628981a80f54087f9acdbf84f9b7d862756110d172993b9a5ae81aa"}, + {file = "MarkupSafe-3.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:a10860e00ded1dd0a65b83e717af28845bb7bd16d8ace40fe5531491de76b79f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e81c52638315ff4ac1b533d427f50bc0afc746deb949210bc85f05d4f15fd772"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:312387403cd40699ab91d50735ea7a507b788091c416dd007eac54434aee51da"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ae99f31f47d849758a687102afdd05bd3d3ff7dbab0a8f1587981b58a76152a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c97ff7fedf56d86bae92fa0a646ce1a0ec7509a7578e1ed238731ba13aabcd1c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7420ceda262dbb4b8d839a4ec63d61c261e4e77677ed7c66c99f4e7cb5030dd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45d42d132cff577c92bfba536aefcfea7e26efb975bd455db4e6602f5c9f45e7"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4c8817557d0de9349109acb38b9dd570b03cc5014e8aabf1cbddc6e81005becd"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a54c43d3ec4cf2a39f4387ad044221c66a376e58c0d0e971d47c475ba79c6b5"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win32.whl", hash = "sha256:c91b394f7601438ff79a4b93d16be92f216adb57d813a78be4446fe0f6bc2d8c"}, + {file = "MarkupSafe-3.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:fe32482b37b4b00c7a52a07211b479653b7fe4f22b2e481b9a9b099d8a430f2f"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:17b2aea42a7280db02ac644db1d634ad47dcc96faf38ab304fe26ba2680d359a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:852dc840f6d7c985603e60b5deaae1d89c56cb038b577f6b5b8c808c97580f1d"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0778de17cff1acaeccc3ff30cd99a3fd5c50fc58ad3d6c0e0c4c58092b859396"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:800100d45176652ded796134277ecb13640c1a537cad3b8b53da45aa96330453"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d06b24c686a34c86c8c1fba923181eae6b10565e4d80bdd7bc1c8e2f11247aa4"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:33d1c36b90e570ba7785dacd1faaf091203d9942bc036118fab8110a401eb1a8"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:beeebf760a9c1f4c07ef6a53465e8cfa776ea6a2021eda0d0417ec41043fe984"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bbde71a705f8e9e4c3e9e33db69341d040c827c7afa6789b14c6e16776074f5a"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win32.whl", hash = "sha256:82b5dba6eb1bcc29cc305a18a3c5365d2af06ee71b123216416f7e20d2a84e5b"}, + {file = "MarkupSafe-3.0.1-cp313-cp313t-win_amd64.whl", hash = "sha256:730d86af59e0e43ce277bb83970530dd223bf7f2a838e086b50affa6ec5f9295"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4935dd7883f1d50e2ffecca0aa33dc1946a94c8f3fdafb8df5c330e48f71b132"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e9393357f19954248b00bed7c56f29a25c930593a77630c719653d51e7669c2a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40621d60d0e58aa573b68ac5e2d6b20d44392878e0bfc159012a5787c4e35bc8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94190df587738280d544971500b9cafc9b950d32efcb1fba9ac10d84e6aa4e6"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6a387d61fe41cdf7ea95b38e9af11cfb1a63499af2759444b99185c4ab33f5b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8ad4ad1429cd4f315f32ef263c1342166695fad76c100c5d979c45d5570ed58b"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e24bfe89c6ac4c31792793ad9f861b8f6dc4546ac6dc8f1c9083c7c4f2b335cd"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2a4b34a8d14649315c4bc26bbfa352663eb51d146e35eef231dd739d54a5430a"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win32.whl", hash = "sha256:242d6860f1fd9191aef5fae22b51c5c19767f93fb9ead4d21924e0bcb17619d8"}, + {file = "MarkupSafe-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:93e8248d650e7e9d49e8251f883eed60ecbc0e8ffd6349e18550925e31bd029b"}, + {file = "markupsafe-3.0.1.tar.gz", hash = "sha256:3e683ee4f5d0fa2dde4db77ed8dd8a876686e3fc417655c2ece9a90576905344"}, ] [[package]] @@ -607,6 +680,23 @@ files = [ [package.dependencies] traitlets = "*" +[[package]] +name = "mpmath" +version = "1.3.0" +description = "Python library for arbitrary-precision floating-point arithmetic" +optional = false +python-versions = "*" +files = [ + {file = "mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c"}, + {file = "mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f"}, +] + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +docs = ["sphinx"] +gmpy = ["gmpy2 (>=2.1.0a4)"] +tests = ["pytest (>=4.6)"] + [[package]] name = "networkx" version = "3.3" @@ -627,64 +717,208 @@ test = ["pytest (>=7.2)", "pytest-cov (>=4.0)"] [[package]] name = "numpy" -version = "2.1.1" +version = "2.1.2" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" files = [ - {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, - {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, - {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, - {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, - {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, - {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, - {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, - {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, - {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, - {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, - {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, - {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, - {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, - {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, - {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, - {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, - {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, - {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, - {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, - {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, - {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, - {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, - {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, - {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, - {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, - {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:30d53720b726ec36a7f88dc873f0eec8447fbc93d93a8f079dfac2629598d6ee"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e8d3ca0a72dd8846eb6f7dfe8f19088060fcb76931ed592d29128e0219652884"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:fc44e3c68ff00fd991b59092a54350e6e4911152682b4782f68070985aa9e648"}, + {file = "numpy-2.1.2-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:7c1c60328bd964b53f8b835df69ae8198659e2b9302ff9ebb7de4e5a5994db3d"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6cdb606a7478f9ad91c6283e238544451e3a95f30fb5467fbf715964341a8a86"}, + {file = "numpy-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d666cb72687559689e9906197e3bec7b736764df6a2e58ee265e360663e9baf7"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6eef7a2dbd0abfb0d9eaf78b73017dbfd0b54051102ff4e6a7b2980d5ac1a03"}, + {file = "numpy-2.1.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:12edb90831ff481f7ef5f6bc6431a9d74dc0e5ff401559a71e5e4611d4f2d466"}, + {file = "numpy-2.1.2-cp310-cp310-win32.whl", hash = "sha256:a65acfdb9c6ebb8368490dbafe83c03c7e277b37e6857f0caeadbbc56e12f4fb"}, + {file = "numpy-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:860ec6e63e2c5c2ee5e9121808145c7bf86c96cca9ad396c0bd3e0f2798ccbe2"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b42a1a511c81cc78cbc4539675713bbcf9d9c3913386243ceff0e9429ca892fe"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:faa88bc527d0f097abdc2c663cddf37c05a1c2f113716601555249805cf573f1"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:c82af4b2ddd2ee72d1fc0c6695048d457e00b3582ccde72d8a1c991b808bb20f"}, + {file = "numpy-2.1.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:13602b3174432a35b16c4cfb5de9a12d229727c3dd47a6ce35111f2ebdf66ff4"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ebec5fd716c5a5b3d8dfcc439be82a8407b7b24b230d0ad28a81b61c2f4659a"}, + {file = "numpy-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2b49c3c0804e8ecb05d59af8386ec2f74877f7ca8fd9c1e00be2672e4d399b1"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2cbba4b30bf31ddbe97f1c7205ef976909a93a66bb1583e983adbd155ba72ac2"}, + {file = "numpy-2.1.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8e00ea6fc82e8a804433d3e9cedaa1051a1422cb6e443011590c14d2dea59146"}, + {file = "numpy-2.1.2-cp311-cp311-win32.whl", hash = "sha256:5006b13a06e0b38d561fab5ccc37581f23c9511879be7693bd33c7cd15ca227c"}, + {file = "numpy-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:f1eb068ead09f4994dec71c24b2844f1e4e4e013b9629f812f292f04bd1510d9"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7bf0a4f9f15b32b5ba53147369e94296f5fffb783db5aacc1be15b4bf72f43b"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b1d0fcae4f0949f215d4632be684a539859b295e2d0cb14f78ec231915d644db"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f751ed0a2f250541e19dfca9f1eafa31a392c71c832b6bb9e113b10d050cb0f1"}, + {file = "numpy-2.1.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:bd33f82e95ba7ad632bc57837ee99dba3d7e006536200c4e9124089e1bf42426"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8cde4f11f0a975d1fd59373b32e2f5a562ade7cde4f85b7137f3de8fbb29a0"}, + {file = "numpy-2.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d95f286b8244b3649b477ac066c6906fbb2905f8ac19b170e2175d3d799f4df"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ab4754d432e3ac42d33a269c8567413bdb541689b02d93788af4131018cbf366"}, + {file = "numpy-2.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e585c8ae871fd38ac50598f4763d73ec5497b0de9a0ab4ef5b69f01c6a046142"}, + {file = "numpy-2.1.2-cp312-cp312-win32.whl", hash = "sha256:9c6c754df29ce6a89ed23afb25550d1c2d5fdb9901d9c67a16e0b16eaf7e2550"}, + {file = "numpy-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:456e3b11cb79ac9946c822a56346ec80275eaf2950314b249b512896c0d2505e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a84498e0d0a1174f2b3ed769b67b656aa5460c92c9554039e11f20a05650f00d"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4d6ec0d4222e8ffdab1744da2560f07856421b367928026fb540e1945f2eeeaf"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:259ec80d54999cc34cd1eb8ded513cb053c3bf4829152a2e00de2371bd406f5e"}, + {file = "numpy-2.1.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:675c741d4739af2dc20cd6c6a5c4b7355c728167845e3c6b0e824e4e5d36a6c3"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:05b2d4e667895cc55e3ff2b56077e4c8a5604361fc21a042845ea3ad67465aa8"}, + {file = "numpy-2.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:43cca367bf94a14aca50b89e9bc2061683116cfe864e56740e083392f533ce7a"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:76322dcdb16fccf2ac56f99048af32259dcc488d9b7e25b51e5eca5147a3fb98"}, + {file = "numpy-2.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:32e16a03138cabe0cb28e1007ee82264296ac0983714094380b408097a418cfe"}, + {file = "numpy-2.1.2-cp313-cp313-win32.whl", hash = "sha256:242b39d00e4944431a3cd2db2f5377e15b5785920421993770cddb89992c3f3a"}, + {file = "numpy-2.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:f2ded8d9b6f68cc26f8425eda5d3877b47343e68ca23d0d0846f4d312ecaa445"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ffef621c14ebb0188a8633348504a35c13680d6da93ab5cb86f4e54b7e922b5"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ad369ed238b1959dfbade9018a740fb9392c5ac4f9b5173f420bd4f37ba1f7a0"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d82075752f40c0ddf57e6e02673a17f6cb0f8eb3f587f63ca1eaab5594da5b17"}, + {file = "numpy-2.1.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:1600068c262af1ca9580a527d43dc9d959b0b1d8e56f8a05d830eea39b7c8af6"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a26ae94658d3ba3781d5e103ac07a876b3e9b29db53f68ed7df432fd033358a8"}, + {file = "numpy-2.1.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13311c2db4c5f7609b462bc0f43d3c465424d25c626d95040f073e30f7570e35"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:2abbf905a0b568706391ec6fa15161fad0fb5d8b68d73c461b3c1bab6064dd62"}, + {file = "numpy-2.1.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:ef444c57d664d35cac4e18c298c47d7b504c66b17c2ea91312e979fcfbdfb08a"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:bdd407c40483463898b84490770199d5714dcc9dd9b792f6c6caccc523c00952"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:da65fb46d4cbb75cb417cddf6ba5e7582eb7bb0b47db4b99c9fe5787ce5d91f5"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c193d0b0238638e6fc5f10f1b074a6993cb13b0b431f64079a509d63d3aa8b7"}, + {file = "numpy-2.1.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a7d80b2e904faa63068ead63107189164ca443b42dd1930299e0d1cb041cec2e"}, + {file = "numpy-2.1.2.tar.gz", hash = "sha256:13532a088217fa624c99b843eeb54640de23b3414b14aa66d023805eb731066c"}, +] + +[[package]] +name = "nvidia-cublas-cu12" +version = "12.1.3.1" +description = "CUBLAS native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-manylinux1_x86_64.whl", hash = "sha256:ee53ccca76a6fc08fb9701aa95b6ceb242cdaab118c3bb152af4e579af792728"}, + {file = "nvidia_cublas_cu12-12.1.3.1-py3-none-win_amd64.whl", hash = "sha256:2b964d60e8cf11b5e1073d179d85fa340c120e99b3067558f3cf98dd69d02906"}, +] + +[[package]] +name = "nvidia-cuda-cupti-cu12" +version = "12.1.105" +description = "CUDA profiling tools runtime libs." +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:e54fde3983165c624cb79254ae9818a456eb6e87a7fd4d56a2352c24ee542d7e"}, + {file = "nvidia_cuda_cupti_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:bea8236d13a0ac7190bd2919c3e8e6ce1e402104276e6f9694479e48bb0eb2a4"}, +] + +[[package]] +name = "nvidia-cuda-nvrtc-cu12" +version = "12.1.105" +description = "NVRTC native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:339b385f50c309763ca65456ec75e17bbefcbbf2893f462cb8b90584cd27a1c2"}, + {file = "nvidia_cuda_nvrtc_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:0a98a522d9ff138b96c010a65e145dc1b4850e9ecb75a0172371793752fd46ed"}, +] + +[[package]] +name = "nvidia-cuda-runtime-cu12" +version = "12.1.105" +description = "CUDA Runtime native Libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:6e258468ddf5796e25f1dc591a31029fa317d97a0a94ed93468fc86301d61e40"}, + {file = "nvidia_cuda_runtime_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:dfb46ef84d73fababab44cf03e3b83f80700d27ca300e537f85f636fac474344"}, +] + +[[package]] +name = "nvidia-cudnn-cu12" +version = "9.1.0.70" +description = "cuDNN runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f"}, + {file = "nvidia_cudnn_cu12-9.1.0.70-py3-none-win_amd64.whl", hash = "sha256:6278562929433d68365a07a4a1546c237ba2849852c0d4b2262a486e805b977a"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" + +[[package]] +name = "nvidia-cufft-cu12" +version = "11.0.2.54" +description = "CUFFT native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-manylinux1_x86_64.whl", hash = "sha256:794e3948a1aa71fd817c3775866943936774d1c14e7628c74f6f7417224cdf56"}, + {file = "nvidia_cufft_cu12-11.0.2.54-py3-none-win_amd64.whl", hash = "sha256:d9ac353f78ff89951da4af698f80870b1534ed69993f10a4cf1d96f21357e253"}, +] + +[[package]] +name = "nvidia-curand-cu12" +version = "10.3.2.106" +description = "CURAND native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:9d264c5036dde4e64f1de8c50ae753237c12e0b1348738169cd0f8a536c0e1e0"}, + {file = "nvidia_curand_cu12-10.3.2.106-py3-none-win_amd64.whl", hash = "sha256:75b6b0c574c0037839121317e17fd01f8a69fd2ef8e25853d826fec30bdba74a"}, +] + +[[package]] +name = "nvidia-cusolver-cu12" +version = "11.4.5.107" +description = "CUDA solver native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-manylinux1_x86_64.whl", hash = "sha256:8a7ec542f0412294b15072fa7dab71d31334014a69f953004ea7a118206fe0dd"}, + {file = "nvidia_cusolver_cu12-11.4.5.107-py3-none-win_amd64.whl", hash = "sha256:74e0c3a24c78612192a74fcd90dd117f1cf21dea4822e66d89e8ea80e3cd2da5"}, +] + +[package.dependencies] +nvidia-cublas-cu12 = "*" +nvidia-cusparse-cu12 = "*" +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-cusparse-cu12" +version = "12.1.0.106" +description = "CUSPARSE native runtime libraries" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-manylinux1_x86_64.whl", hash = "sha256:f3b50f42cf363f86ab21f720998517a659a48131e8d538dc02f8768237bd884c"}, + {file = "nvidia_cusparse_cu12-12.1.0.106-py3-none-win_amd64.whl", hash = "sha256:b798237e81b9719373e8fae8d4f091b70a0cf09d9d85c95a557e11df2d8e9a5a"}, +] + +[package.dependencies] +nvidia-nvjitlink-cu12 = "*" + +[[package]] +name = "nvidia-nccl-cu12" +version = "2.20.5" +description = "NVIDIA Collective Communication Library (NCCL) Runtime" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1fc150d5c3250b170b29410ba682384b14581db722b2531b0d8d33c595f33d01"}, + {file = "nvidia_nccl_cu12-2.20.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:057f6bf9685f75215d0c53bf3ac4a10b3e6578351de307abad9e18a99182af56"}, +] + +[[package]] +name = "nvidia-nvjitlink-cu12" +version = "12.6.77" +description = "Nvidia JIT LTO Library" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvjitlink_cu12-12.6.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:3bf10d85bb1801e9c894c6e197e44dd137d2a0a9e43f8450e9ad13f2df0dd52d"}, + {file = "nvidia_nvjitlink_cu12-12.6.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9ae346d16203ae4ea513be416495167a0101d33d2d14935aa9c1829a3fb45142"}, + {file = "nvidia_nvjitlink_cu12-12.6.77-py3-none-win_amd64.whl", hash = "sha256:410718cd44962bed862a31dd0318620f6f9a8b28a6291967bcfcb446a6516771"}, +] + +[[package]] +name = "nvidia-nvtx-cu12" +version = "12.1.105" +description = "NVIDIA Tools Extension" +optional = false +python-versions = ">=3" +files = [ + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-manylinux1_x86_64.whl", hash = "sha256:dc21cf308ca5691e7c04d962e213f8a4aa9bbfa23d95412f452254c2caeb09e5"}, + {file = "nvidia_nvtx_cu12-12.1.105-py3-none-win_amd64.whl", hash = "sha256:65f4d98982b31b60026e0e6de73fbdfc09d08a96f4656dd3665ca616a11e1e82"}, ] [[package]] @@ -700,40 +934,53 @@ files = [ [[package]] name = "pandas" -version = "2.2.2" +version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:90c6fca2acf139569e74e8781709dccb6fe25940488755716d1d354d6bc58bce"}, - {file = "pandas-2.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c7adfc142dac335d8c1e0dcbd37eb8617eac386596eb9e1a1b77791cf2498238"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4abfe0be0d7221be4f12552995e58723c7422c80a659da13ca382697de830c08"}, - {file = "pandas-2.2.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8635c16bf3d99040fdf3ca3db669a7250ddf49c55dc4aa8fe0ae0fa8d6dcc1f0"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:40ae1dffb3967a52203105a077415a86044a2bea011b5f321c6aa64b379a3f51"}, - {file = "pandas-2.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e5a0b00e1e56a842f922e7fae8ae4077aee4af0acb5ae3622bd4b4c30aedf99"}, - {file = "pandas-2.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:ddf818e4e6c7c6f4f7c8a12709696d193976b591cc7dc50588d3d1a6b5dc8772"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:696039430f7a562b74fa45f540aca068ea85fa34c244d0deee539cb6d70aa288"}, - {file = "pandas-2.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8e90497254aacacbc4ea6ae5e7a8cd75629d6ad2b30025a4a8b09aa4faf55151"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58b84b91b0b9f4bafac2a0ac55002280c094dfc6402402332c0913a59654ab2b"}, - {file = "pandas-2.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2123dc9ad6a814bcdea0f099885276b31b24f7edf40f6cdbc0912672e22eee"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2925720037f06e89af896c70bca73459d7e6a4be96f9de79e2d440bd499fe0db"}, - {file = "pandas-2.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0cace394b6ea70c01ca1595f839cf193df35d1575986e484ad35c4aeae7266c1"}, - {file = "pandas-2.2.2-cp311-cp311-win_amd64.whl", hash = "sha256:873d13d177501a28b2756375d59816c365e42ed8417b41665f346289adc68d24"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9dfde2a0ddef507a631dc9dc4af6a9489d5e2e740e226ad426a05cabfbd7c8ef"}, - {file = "pandas-2.2.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e9b79011ff7a0f4b1d6da6a61aa1aa604fb312d6647de5bad20013682d1429ce"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cb51fe389360f3b5a4d57dbd2848a5f033350336ca3b340d1c53a1fad33bcad"}, - {file = "pandas-2.2.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eee3a87076c0756de40b05c5e9a6069c035ba43e8dd71c379e68cab2c20f16ad"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e374f59e440d4ab45ca2fffde54b81ac3834cf5ae2cdfa69c90bc03bde04d76"}, - {file = "pandas-2.2.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:43498c0bdb43d55cb162cdc8c06fac328ccb5d2eabe3cadeb3529ae6f0517c32"}, - {file = "pandas-2.2.2-cp312-cp312-win_amd64.whl", hash = "sha256:d187d355ecec3629624fccb01d104da7d7f391db0311145817525281e2804d23"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:0ca6377b8fca51815f382bd0b697a0814c8bda55115678cbc94c30aacbb6eff2"}, - {file = "pandas-2.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9057e6aa78a584bc93a13f0a9bf7e753a5e9770a30b4d758b8d5f2a62a9433cd"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:001910ad31abc7bf06f49dcc903755d2f7f3a9186c0c040b827e522e9cef0863"}, - {file = "pandas-2.2.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66b479b0bd07204e37583c191535505410daa8df638fd8e75ae1b383851fe921"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a77e9d1c386196879aa5eb712e77461aaee433e54c68cf253053a73b7e49c33a"}, - {file = "pandas-2.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:92fd6b027924a7e178ac202cfbe25e53368db90d56872d20ffae94b96c7acc57"}, - {file = "pandas-2.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:640cef9aa381b60e296db324337a554aeeb883ead99dc8f6c18e81a93942f5f4"}, - {file = "pandas-2.2.2.tar.gz", hash = "sha256:9e79019aba43cb4fda9e4d983f8e88ca0373adbb697ae9c6c43093218de28b54"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, + {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d9c45366def9a3dd85a6454c0e7908f2b3b8e9c138f5dc38fed7ce720d8453ed"}, + {file = "pandas-2.2.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86976a1c5b25ae3f8ccae3a5306e443569ee3c3faf444dfd0f41cda24667ad57"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b8661b0238a69d7aafe156b7fa86c44b881387509653fdf857bebc5e4008ad42"}, + {file = "pandas-2.2.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:37e0aced3e8f539eccf2e099f65cdb9c8aa85109b0be6e93e2baff94264bdc6f"}, + {file = "pandas-2.2.3-cp310-cp310-win_amd64.whl", hash = "sha256:56534ce0746a58afaf7942ba4863e0ef81c9c50d3f0ae93e9497d6a41a057645"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039"}, + {file = "pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698"}, + {file = "pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3"}, + {file = "pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32"}, + {file = "pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9"}, + {file = "pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3"}, + {file = "pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8"}, + {file = "pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a"}, + {file = "pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015"}, + {file = "pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0"}, + {file = "pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659"}, + {file = "pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb"}, + {file = "pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468"}, + {file = "pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2"}, + {file = "pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d"}, + {file = "pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc6b93f9b966093cb0fd62ff1a7e4c09e6d546ad7c1de191767baffc57628f39"}, + {file = "pandas-2.2.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5dbca4c1acd72e8eeef4753eeca07de9b1db4f398669d5994086f788a5d7cc30"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8cd6d7cc958a3910f934ea8dbdf17b2364827bb4dafc38ce6eef6bb3d65ff09c"}, + {file = "pandas-2.2.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99df71520d25fade9db7c1076ac94eb994f4d2673ef2aa2e86ee039b6746d20c"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:31d0ced62d4ea3e231a9f228366919a5ea0b07440d9d4dac345376fd8e1477ea"}, + {file = "pandas-2.2.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7eee9e7cea6adf3e3d24e304ac6b8300646e2a5d1cd3a3c2abed9101b0846761"}, + {file = "pandas-2.2.3-cp39-cp39-win_amd64.whl", hash = "sha256:4850ba03528b6dd51d6c5d273c46f183f39a9baf3f0143e566b89450965b105e"}, + {file = "pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667"}, ] [package.dependencies] @@ -895,13 +1142,13 @@ xmp = ["defusedxml"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] @@ -1001,6 +1248,56 @@ jinja2 = ">=2.9.6" jsonpickle = ">=1.4.1" networkx = ">=1.11" +[[package]] +name = "scikit-learn" +version = "1.5.2" +description = "A set of python modules for machine learning and data mining" +optional = false +python-versions = ">=3.9" +files = [ + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:299406827fb9a4f862626d0fe6c122f5f87f8910b86fe5daa4c32dcd742139b6"}, + {file = "scikit_learn-1.5.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2d4cad1119c77930b235579ad0dc25e65c917e756fe80cab96aa3b9428bd3fb0"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c412ccc2ad9bf3755915e3908e677b367ebc8d010acbb3f182814524f2e5540"}, + {file = "scikit_learn-1.5.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a686885a4b3818d9e62904d91b57fa757fc2bed3e465c8b177be652f4dd37c8"}, + {file = "scikit_learn-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:c15b1ca23d7c5f33cc2cb0a0d6aaacf893792271cddff0edbd6a40e8319bc113"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:03b6158efa3faaf1feea3faa884c840ebd61b6484167c711548fce208ea09445"}, + {file = "scikit_learn-1.5.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:1ff45e26928d3b4eb767a8f14a9a6efbf1cbff7c05d1fb0f95f211a89fd4f5de"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f763897fe92d0e903aa4847b0aec0e68cadfff77e8a0687cabd946c89d17e675"}, + {file = "scikit_learn-1.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8b0ccd4a902836493e026c03256e8b206656f91fbcc4fde28c57a5b752561f1"}, + {file = "scikit_learn-1.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6c16d84a0d45e4894832b3c4d0bf73050939e21b99b01b6fd59cbb0cf39163b6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f932a02c3f4956dfb981391ab24bda1dbd90fe3d628e4b42caef3e041c67707a"}, + {file = "scikit_learn-1.5.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3b923d119d65b7bd555c73be5423bf06c0105678ce7e1f558cb4b40b0a5502b1"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f60021ec1574e56632be2a36b946f8143bf4e5e6af4a06d85281adc22938e0dd"}, + {file = "scikit_learn-1.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:394397841449853c2290a32050382edaec3da89e35b3e03d6cc966aebc6a8ae6"}, + {file = "scikit_learn-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:57cc1786cfd6bd118220a92ede80270132aa353647684efa385a74244a41e3b1"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9a702e2de732bbb20d3bad29ebd77fc05a6b427dc49964300340e4c9328b3f5"}, + {file = "scikit_learn-1.5.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:b0768ad641981f5d3a198430a1d31c3e044ed2e8a6f22166b4d546a5116d7908"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:178ddd0a5cb0044464fc1bfc4cca5b1833bfc7bb022d70b05db8530da4bb3dd3"}, + {file = "scikit_learn-1.5.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7284ade780084d94505632241bf78c44ab3b6f1e8ccab3d2af58e0e950f9c12"}, + {file = "scikit_learn-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:b7b0f9a0b1040830d38c39b91b3a44e1b643f4b36e36567b80b7c6bd2202a27f"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:757c7d514ddb00ae249832fe87100d9c73c6ea91423802872d9e74970a0e40b9"}, + {file = "scikit_learn-1.5.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:52788f48b5d8bca5c0736c175fa6bdaab2ef00a8f536cda698db61bd89c551c1"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:643964678f4b5fbdc95cbf8aec638acc7aa70f5f79ee2cdad1eec3df4ba6ead8"}, + {file = "scikit_learn-1.5.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca64b3089a6d9b9363cd3546f8978229dcbb737aceb2c12144ee3f70f95684b7"}, + {file = "scikit_learn-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:3bed4909ba187aca80580fe2ef370d9180dcf18e621a27c4cf2ef10d279a7efe"}, + {file = "scikit_learn-1.5.2.tar.gz", hash = "sha256:b4237ed7b3fdd0a4882792e68ef2545d5baa50aca3bb45aa7df468138ad8f94d"}, +] + +[package.dependencies] +joblib = ">=1.2.0" +numpy = ">=1.19.5" +scipy = ">=1.6.0" +threadpoolctl = ">=3.1.0" + +[package.extras] +benchmark = ["matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "pandas (>=1.1.5)"] +build = ["cython (>=3.0.10)", "meson-python (>=0.16.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.3.4)", "memory_profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pydata-sphinx-theme (>=0.15.3)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)", "sphinx (>=7.3.7)", "sphinx-copybutton (>=0.5.2)", "sphinx-design (>=0.5.0)", "sphinx-design (>=0.6.0)", "sphinx-gallery (>=0.16.0)", "sphinx-prompt (>=1.4.0)", "sphinx-remove-toctrees (>=1.0.0.post1)", "sphinxcontrib-sass (>=0.3.4)", "sphinxext-opengraph (>=0.9.1)"] +examples = ["matplotlib (>=3.3.4)", "pandas (>=1.1.5)", "plotly (>=5.14.0)", "pooch (>=1.6.0)", "scikit-image (>=0.17.2)", "seaborn (>=0.9.0)"] +install = ["joblib (>=1.2.0)", "numpy (>=1.19.5)", "scipy (>=1.6.0)", "threadpoolctl (>=3.1.0)"] +maintenance = ["conda-lock (==2.5.6)"] +tests = ["black (>=24.3.0)", "matplotlib (>=3.3.4)", "mypy (>=1.9)", "numpydoc (>=1.2.0)", "pandas (>=1.1.5)", "polars (>=0.20.30)", "pooch (>=1.6.0)", "pyamg (>=4.0.0)", "pyarrow (>=12.0.0)", "pytest (>=7.1.2)", "pytest-cov (>=2.9.0)", "ruff (>=0.2.1)", "scikit-image (>=0.17.2)"] + [[package]] name = "scipy" version = "1.14.1" @@ -1084,13 +1381,13 @@ test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata [[package]] name = "setuptools-rust" -version = "1.10.1" +version = "1.10.2" description = "Setuptools Rust extension plugin" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools_rust-1.10.1-py3-none-any.whl", hash = "sha256:3837616cc0a7705b2c44058f626c97f774eeb980f28427c16ece562661bc20c5"}, - {file = "setuptools_rust-1.10.1.tar.gz", hash = "sha256:d79035fc54cdf9342e9edf4b009491ecab06c3a652b37c3c137c7ba85547d3e6"}, + {file = "setuptools_rust-1.10.2-py3-none-any.whl", hash = "sha256:4b39c435ae9670315d522ed08fa0e8cb29f2a6048033966b6be2571a90ce4f1c"}, + {file = "setuptools_rust-1.10.2.tar.gz", hash = "sha256:5d73e7eee5f87a6417285b617c97088a7c20d1a70fcea60e3bdc94ff567c29dc"}, ] [package.dependencies] @@ -1127,6 +1424,88 @@ pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] +[[package]] +name = "sympy" +version = "1.13.3" +description = "Computer algebra system (CAS) in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "sympy-1.13.3-py3-none-any.whl", hash = "sha256:54612cf55a62755ee71824ce692986f23c88ffa77207b30c1368eda4a7060f73"}, + {file = "sympy-1.13.3.tar.gz", hash = "sha256:b27fd2c6530e0ab39e275fc9b683895367e51d5da91baa8d3d64db2565fec4d9"}, +] + +[package.dependencies] +mpmath = ">=1.1.0,<1.4" + +[package.extras] +dev = ["hypothesis (>=6.70.0)", "pytest (>=7.1.0)"] + +[[package]] +name = "threadpoolctl" +version = "3.5.0" +description = "threadpoolctl" +optional = false +python-versions = ">=3.8" +files = [ + {file = "threadpoolctl-3.5.0-py3-none-any.whl", hash = "sha256:56c1e26c150397e58c4926da8eeee87533b1e32bef131bd4bf6a2f45f3185467"}, + {file = "threadpoolctl-3.5.0.tar.gz", hash = "sha256:082433502dd922bf738de0d8bcc4fdcbf0979ff44c42bd40f5af8a282f6fa107"}, +] + +[[package]] +name = "torch" +version = "2.4.1" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "torch-2.4.1-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:362f82e23a4cd46341daabb76fba08f04cd646df9bfaf5da50af97cb60ca4971"}, + {file = "torch-2.4.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:e8ac1985c3ff0f60d85b991954cfc2cc25f79c84545aead422763148ed2759e3"}, + {file = "torch-2.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91e326e2ccfb1496e3bee58f70ef605aeb27bd26be07ba64f37dcaac3d070ada"}, + {file = "torch-2.4.1-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:d36a8ef100f5bff3e9c3cea934b9e0d7ea277cb8210c7152d34a9a6c5830eadd"}, + {file = "torch-2.4.1-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:0b5f88afdfa05a335d80351e3cea57d38e578c8689f751d35e0ff36bce872113"}, + {file = "torch-2.4.1-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:ef503165f2341942bfdf2bd520152f19540d0c0e34961232f134dc59ad435be8"}, + {file = "torch-2.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:092e7c2280c860eff762ac08c4bdcd53d701677851670695e0c22d6d345b269c"}, + {file = "torch-2.4.1-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:ddddbd8b066e743934a4200b3d54267a46db02106876d21cf31f7da7a96f98ea"}, + {file = "torch-2.4.1-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:fdc4fe11db3eb93c1115d3e973a27ac7c1a8318af8934ffa36b0370efe28e042"}, + {file = "torch-2.4.1-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:18835374f599207a9e82c262153c20ddf42ea49bc76b6eadad8e5f49729f6e4d"}, + {file = "torch-2.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:ebea70ff30544fc021d441ce6b219a88b67524f01170b1c538d7d3ebb5e7f56c"}, + {file = "torch-2.4.1-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:72b484d5b6cec1a735bf3fa5a1c4883d01748698c5e9cfdbeb4ffab7c7987e0d"}, + {file = "torch-2.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c99e1db4bf0c5347107845d715b4aa1097e601bdc36343d758963055e9599d93"}, + {file = "torch-2.4.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:b57f07e92858db78c5b72857b4f0b33a65b00dc5d68e7948a8494b0314efb880"}, + {file = "torch-2.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:f18197f3f7c15cde2115892b64f17c80dbf01ed72b008020e7da339902742cf6"}, + {file = "torch-2.4.1-cp38-none-macosx_11_0_arm64.whl", hash = "sha256:5fc1d4d7ed265ef853579caf272686d1ed87cebdcd04f2a498f800ffc53dab71"}, + {file = "torch-2.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:40f6d3fe3bae74efcf08cb7f8295eaddd8a838ce89e9d26929d4edd6d5e4329d"}, + {file = "torch-2.4.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:c9299c16c9743001ecef515536ac45900247f4338ecdf70746f2461f9e4831db"}, + {file = "torch-2.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:6bce130f2cd2d52ba4e2c6ada461808de7e5eccbac692525337cfb4c19421846"}, + {file = "torch-2.4.1-cp39-none-macosx_11_0_arm64.whl", hash = "sha256:a38de2803ee6050309aac032676536c3d3b6a9804248537e38e098d0e14817ec"}, +] + +[package.dependencies] +filelock = "*" +fsspec = "*" +jinja2 = "*" +networkx = "*" +nvidia-cublas-cu12 = {version = "12.1.3.1", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-cupti-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-nvrtc-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cuda-runtime-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cudnn-cu12 = {version = "9.1.0.70", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cufft-cu12 = {version = "11.0.2.54", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-curand-cu12 = {version = "10.3.2.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusolver-cu12 = {version = "11.4.5.107", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-cusparse-cu12 = {version = "12.1.0.106", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nccl-cu12 = {version = "2.20.5", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +nvidia-nvtx-cu12 = {version = "12.1.105", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +setuptools = "*" +sympy = "*" +triton = {version = "3.0.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\" and python_version < \"3.13\""} +typing-extensions = ">=4.8.0" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] +optree = ["optree (>=0.11.0)"] + [[package]] name = "tqdm" version = "4.66.5" @@ -1162,15 +1541,48 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "triton" +version = "3.0.0" +description = "A language and compiler for custom Deep Learning operations" +optional = false +python-versions = "*" +files = [ + {file = "triton-3.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e1efef76935b2febc365bfadf74bcb65a6f959a9872e5bddf44cc9e0adce1e1a"}, + {file = "triton-3.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ce8520437c602fb633f1324cc3871c47bee3b67acf9756c1a66309b60e3216c"}, + {file = "triton-3.0.0-1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:34e509deb77f1c067d8640725ef00c5cbfcb2052a1a3cb6a6d343841f92624eb"}, + {file = "triton-3.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bcbf3b1c48af6a28011a5c40a5b3b9b5330530c3827716b5fbf6d7adcc1e53e9"}, + {file = "triton-3.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6e5727202f7078c56f91ff13ad0c1abab14a0e7f2c87e91b12b6f64f3e8ae609"}, +] + +[package.dependencies] +filelock = "*" + +[package.extras] +build = ["cmake (>=3.20)", "lit"] +tests = ["autopep8", "flake8", "isort", "llnl-hatchet", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -1201,4 +1613,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"] [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "f802131d57996774dddc2b3f1467aa5df28e90f21b6236274f68d192967663ff" +content-hash = "a6ea4e12d476798b1c22e9620cc0fedd2d4dff56f1c47d42dc69b4a1294dcbbb" diff --git a/pyproject.toml b/pyproject.toml index 815a4d7..dbf7fa8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,9 @@ wheel = "^0.44.0" setuptools-rust = "^1.10.1" iced-x86 = "^1.21.0" scipy = "^1.14.0" - +scikit-learn = "^1.5.2" +torch = "^2.4.1" +numpy = "^2.1.2" [build-system] requires = ["poetry-core"] diff --git a/quantization_graph_sage_model.pth.pt b/quantization_graph_sage_model.pth.pt new file mode 100644 index 0000000..00cf6dc Binary files /dev/null and b/quantization_graph_sage_model.pth.pt differ diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000..34d4cf8 Binary files /dev/null and b/src/.DS_Store differ diff --git a/src/cfg_constructor/.DS_Store b/src/cfg_constructor/.DS_Store new file mode 100644 index 0000000..5367f53 Binary files /dev/null and b/src/cfg_constructor/.DS_Store differ diff --git a/cfg_constructor/cfg_creator.py b/src/cfg_constructor/cfg_creator.py similarity index 54% rename from cfg_constructor/cfg_creator.py rename to src/cfg_constructor/cfg_creator.py index 4871343..1553d86 100644 --- a/cfg_constructor/cfg_creator.py +++ b/src/cfg_constructor/cfg_creator.py @@ -9,8 +9,22 @@ @click.command() @click.option('--data_dir', default='data', help='Directory where the neutralized binaries are stored') @click.option('--vis_mode', default=2, type=int, help='Visualization mode: 0 = visualize in window, 1 = save as HTML docs, 2 = save graphs w/o visualizing as edgelists') -@click.option('--job_id', default=0, type=int, help='Job ID for logging') +@click.option('--job-id', default=0, type=int, help='Job ID for logging') def main(data_dir, vis_mode, job_id): + """ + Main function to process binary files, generate their control flow graphs (CFGs), + visualize them based on the specified mode, and log the visualization details. + + Parameters: + data_dir (str): The directory containing the neutralized binaries. + + vis_mode (int): The mode of visualization + 0: visualize in window, + 1: save as HTML docs + 2: save graphs without visualizing + + job_id (int): The unique identifier for the job, used for logging purposes. + """ log_file = setup_logging(job_id) for infile in tqdm(glob.glob(os.path.join(data_dir, '*')), desc="Processing binaries"): diff --git a/cfg_constructor/utils/bin_processing.py b/src/cfg_constructor/utils/bin_processing.py similarity index 73% rename from cfg_constructor/utils/bin_processing.py rename to src/cfg_constructor/utils/bin_processing.py index cc7a08a..9508660 100644 --- a/cfg_constructor/utils/bin_processing.py +++ b/src/cfg_constructor/utils/bin_processing.py @@ -3,22 +3,33 @@ import iced_x86 import networkx as nx +import time FORMATTER = iced_x86.Formatter(iced_x86.FormatterSyntax.INTEL) + OP_SET = { iced_x86.OpKind.IMMEDIATE8, iced_x86.OpKind.IMMEDIATE16, iced_x86.OpKind.IMMEDIATE32, iced_x86.OpKind.IMMEDIATE64 } + BRANCH_SET = { iced_x86.FlowControl.UNCONDITIONAL_BRANCH, iced_x86.FlowControl.CONDITIONAL_BRANCH } def analyze_and_save_binary(infile, vis_mode, log_file): + """ + Analyze a binary file, generate its control flow graph (CFG), + visualize it, and log the visualization details. + + Parameters: + infile (str): The path to the binary file to analyze. + vis_mode (int): The mode of visualization to use. + log_file (str): The path to the log file for recording visualization details. + """ if is_already_visualized(log_file, infile): - print(f"Skipping {infile} as it has already been visualized.") return binary_data = open(infile, 'rb').read() @@ -28,6 +39,8 @@ def analyze_and_save_binary(infile, vis_mode, log_file): instructions = list(decoder) cfg = nx.DiGraph() + start = time.time() + for i, instr in enumerate(instructions): cfg.add_node(instr.ip, instruction=FORMATTER.format(instr)) @@ -41,5 +54,7 @@ def analyze_and_save_binary(infile, vis_mode, log_file): if next_ip: cfg.add_edge(instr.ip, next_ip) + duration = round((time.time() - start) * 1000, 4) + vis(cfg, infile, vis_mode) - log_visualization(log_file, infile, vis_mode) \ No newline at end of file + log_visualization(log_file, infile, vis_mode, duration) \ No newline at end of file diff --git a/src/cfg_constructor/utils/log_utils.py b/src/cfg_constructor/utils/log_utils.py new file mode 100644 index 0000000..b851e98 --- /dev/null +++ b/src/cfg_constructor/utils/log_utils.py @@ -0,0 +1,73 @@ +import logging +import os +import json +import time + +def setup_logging(job_id): + """ + Set up logging for the visualization process with JSON output. + + Parameters: + job_id (str): The unique identifier for the job. + + Returns: + str: The path to the log file created. + """ + log_dir = 'src/cfg_constructor/logs' + os.makedirs(log_dir, exist_ok=True) + log_file = os.path.join(log_dir, f'visualization_log_{job_id}.json') + + # Create a custom JSON formatter + class JsonFormatter(logging.Formatter): + def format(self, record): + log_obj = { + 'filename': record.filename, + 'exename': record.exename, + 'vismode': record.vismode, + 'time': record.time + } + return json.dumps(log_obj) + + # Set up logging + logger = logging.getLogger() + logger.setLevel(logging.INFO) + + # Create a file handler + file_handler = logging.FileHandler(log_file) + file_handler.setFormatter(JsonFormatter()) + logger.addHandler(file_handler) + + return log_file + +def log_visualization(log_file, fname, vis_mode, duration): + """ + Log the visualization details. + + Parameters: + log_file (str): The path to the log file. + fname (str): The name of the file being visualized. + vis_mode (int): The mode of visualization used. + duration (float): The time taken to generate the CFG. + """ + + logging.info('', extra={'exename': fname, 'vismode': vis_mode, 'time': duration}) + +def is_already_visualized(log_file, fname): + """ + Check if a file has already been visualized by searching the log. + + Parameters: + log_file (str): The path to the log file. + fname (str): The name of the file to check. + + Returns: + bool: True if the file has been visualized, False otherwise. + """ + with open(log_file, 'r') as f: + log_data = f.readlines() + for line in log_data: + obj = json.loads(line) + if fname == obj['exename']: + return True + + return False \ No newline at end of file diff --git a/cfg_constructor/utils/vis_utils.py b/src/cfg_constructor/utils/vis_utils.py similarity index 58% rename from cfg_constructor/utils/vis_utils.py rename to src/cfg_constructor/utils/vis_utils.py index 8801c82..e6e58bd 100644 --- a/cfg_constructor/utils/vis_utils.py +++ b/src/cfg_constructor/utils/vis_utils.py @@ -5,6 +5,17 @@ import os def vis(cfg, infile, vis_mode): + """ + Visualize the control flow graph (CFG) based on the specified mode. + + Parameters: + cfg (networkx.DiGraph): The control flow graph to visualize. + infile (str): The input file name for the CFG. + vis_mode (int): The mode of visualization (0: Matplotlib, 1: Pyvis, 2: Save to file). + + Raises: + ValueError: If an invalid visualization mode is provided. + """ f_id = os.path.basename(infile) if vis_mode == 0: @@ -18,6 +29,13 @@ def vis(cfg, infile, vis_mode): def visualize_cfg(cfg, title): + """ + Visualize the control flow graph using Matplotlib. + + Parameters: + cfg (networkx.DiGraph): The control flow graph to visualize. + title (str): The title for the visualization. + """ pos = nx.spring_layout(cfg) nx.draw(cfg, pos, with_labels=True, node_color='lightblue', node_size=500, font_size=8, arrows=True) nx.draw_networkx_labels(cfg, pos, {node: f"{node:x}" for node in cfg.nodes()}) @@ -26,6 +44,13 @@ def visualize_cfg(cfg, title): plt.show() def visualize_cfg_with_visnetwork(cfg, title): + """ + Visualize the control flow graph in an HTML format for browser based viewing. + + Parameters: + cfg (networkx.DiGraph): The control flow graph to visualize. + title (str): The title for the visualization. + """ # Check if the graph is empty if len(cfg.nodes) == 0: print("The graph is empty!") @@ -48,35 +73,33 @@ def visualize_cfg_with_visnetwork(cfg, title): # Display the network in a browser view output_dir = os.path.join('out', 'out_html') output_dir = os.path.join('cfg_constructor', output_dir) + output_dir = os.path.join('src', output_dir) + os.makedirs(output_dir, exist_ok=True) net.save_graph(os.path.join(output_dir, f'{title}.html')) def save_cfg(cfg, title): """ - Save the control flow graph (CFG) to an Edgelist file and node names to a separate file. + Save the control flow graph (CFG) to an adjacency matrix file and node names to a separate file. Parameters: - - cfg: NetworkX graph object representing the control flow graph - - title: String representing the title of the files (without extension) + cfg (networkx.DiGraph): The control flow graph to save. + title (str): The title for the output files (without extension). """ # Check if the graph is empty if len(cfg.nodes) == 0: print("The graph is empty!") return - # Define directory for saving files - output_dir = os.path.join('out', 'out_edgelists') - output_dir = os.path.join('cfg_constructor', output_dir) - os.makedirs(output_dir, exist_ok=True) - - # Define file paths - edgelist_path = os.path.join(output_dir, f'{title}.txt') - node_names_path = os.path.join(output_dir, f'{title}_node_names.csv') - - # Save Edgelist - nx.write_edgelist(cfg, edgelist_path) - + # Create adjacency matrix + adjacency_matrix = nx.to_numpy_array(cfg) + + # Save Adjacency Matrix + adjacency_matrix_path = os.path.join('out', 'out_adjacency_matrices', f"{title}_adjacency_matrix.csv") + pd.DataFrame(adjacency_matrix).to_csv(adjacency_matrix_path, index=False, header=False) + # Save Node Names node_names = {node: f"Node_{node}" for node in cfg.nodes} node_names_df = pd.DataFrame(list(node_names.items()), columns=['Node', 'Name']) + node_names_path = os.path.join('out', 'out_adjacency_matrices', f"{title}_node_names.csv") node_names_df.to_csv(node_names_path, index=False) \ No newline at end of file diff --git a/src/models/.DS_Store b/src/models/.DS_Store new file mode 100644 index 0000000..0c6d77a Binary files /dev/null and b/src/models/.DS_Store differ diff --git a/src/models/GAE/README.md b/src/models/GAE/README.md new file mode 100644 index 0000000..a09c07b --- /dev/null +++ b/src/models/GAE/README.md @@ -0,0 +1,3 @@ +# Graph Autoencoder (GAE) + +To be filled - will contain training, initialization, etc. for a GAE approach to malware classification. Specifically, Encoder-Decoder training and then stitching the Encoder to a FFN (or other model) for the classification task \ No newline at end of file diff --git a/src/models/GAT/README.md b/src/models/GAT/README.md new file mode 100644 index 0000000..6d08e88 --- /dev/null +++ b/src/models/GAT/README.md @@ -0,0 +1,3 @@ +# Graph Attention Network (GAT) + +To be filled - will contain training, initialization, etc. for a GAT approach to malware classification \ No newline at end of file diff --git a/src/models/JAX_SAGE/README_MULTI_DATASET.md b/src/models/JAX_SAGE/README_MULTI_DATASET.md new file mode 100644 index 0000000..722ac81 --- /dev/null +++ b/src/models/JAX_SAGE/README_MULTI_DATASET.md @@ -0,0 +1,185 @@ +# Multi-Dataset GraphSAGE Training + +This directory contains refactored training scripts for training GraphSAGE models on multiple datasets simultaneously. + +## Overview + +The refactored `jax_train.py` script now supports training on multiple datasets by: + +1. **Automatic Dataset Discovery**: Scans directories for edge list and label files +2. **Batch Processing**: Loads and processes multiple datasets in sequence +3. **Sequential Training**: Trains the model on each dataset for a specified number of epochs +4. **Progress Tracking**: Shows training progress across all datasets + +## File Structure + +``` +src/models/JAX_SAGE/ +├── jax_train.py # Main training script (refactored) +├── train_multiple_datasets.py # Command-line interface helper +├── README_MULTI_DATASET.md # This documentation +└── ... (other existing files) +``` + +## Expected Data Format + +### Directory Structure +``` +edge_folder/ +├── 10010_edgelist.csv +├── 10011_edgelist.csv +├── 10012_edgelist.csv +└── ... + +label_folder/ +├── 10010_node_names.csv +├── 10011_node_names.csv +├── 10012_node_names.csv +└── ... +``` + +### File Formats + +**Edge List Files** (`*_edgelist.csv`): +- CSV format with no header +- Two columns: source_node, target_node +- Example: +``` +1001,1002 +1002,1003 +1001,1003 +``` + +**Label Files** (`*_node_names.csv`): +- CSV format with header +- Must contain a 'node' column +- Example: +``` +node,instruction_count +1001,50 +1002,30 +1003,25 +``` + +## Usage + +### Method 1: Direct Python Script + +```python +from jax_train import runGSageTraining + +# Train on multiple datasets +runGSageTraining( + num_classes=1, + learning_rate=0.01, + num_epochs=100, + embed_dim=128, + edge_folder="/path/to/edge/lists", + label_folder="/path/to/label/files" +) +``` + +### Method 2: Command Line Interface + +```bash +python train_multiple_datasets.py \ + --edge_folder /path/to/edge/lists \ + --label_folder /path/to/label/files \ + --num_epochs 50 \ + --learning_rate 0.01 \ + --embed_dim 128 +``` + +### Command Line Options + +- `--edge_folder`: Directory containing edge list files (required) +- `--label_folder`: Directory containing label files (required) +- `--num_classes`: Number of output classes (default: 1) +- `--embed_dim`: Embedding dimension (default: 128) +- `--learning_rate`: Learning rate (default: 0.01) +- `--num_epochs`: Number of epochs per dataset (default: 100) +- `--output_file`: Output file for trained model (default: graph_sage_model.pkl) + +## Training Process + +1. **Dataset Discovery**: The script scans the specified directories for matching file pairs +2. **Data Loading**: Each dataset is loaded and preprocessed individually +3. **Model Initialization**: The model is initialized using the first dataset's structure +4. **Sequential Training**: For each dataset: + - Create fixed embedding for the dataset + - Initialize encoder for the dataset + - Train for specified number of epochs + - Move to next dataset +5. **Model Saving**: Final trained parameters are saved to a pickle file + +## Output + +- **Model File**: `graph_sage_model.pkl` (or custom filename) +- **Console Output**: Training progress, loss values, and timing information + +## Example Output + +``` +Found 5 dataset pairs +Loading dataset 1/5: 10010_edgelist.csv +Successfully loaded dataset 10010 with 150 nodes +Loading dataset 2/5: 10011_edgelist.csv +Successfully loaded dataset 10011 with 200 nodes +... + +Successfully loaded 5 datasets + +Training on 5 datasets... + +Training on dataset 10010 (1/5) +Dataset 10010 - Epoch [1/100], Loss: 0.6931 (Overall: 1/500) +Dataset 10010 - Epoch [2/100], Loss: 0.6823 (Overall: 2/500) +... + +Model saved to graph_sage_model.pkl +Total training time: 45.23 seconds +Trained on 5 datasets +``` + +## Error Handling + +The script includes robust error handling: + +- **Missing Files**: Skips datasets with missing or corrupted files +- **Invalid Data**: Continues training even if some datasets fail to load +- **Path Validation**: Checks that specified directories exist +- **File Format Validation**: Ensures expected file patterns are found + +## Performance Considerations + +- **Memory Usage**: Each dataset is loaded individually to manage memory +- **Training Time**: Total training time scales with number of datasets × epochs per dataset +- **JIT Compilation**: Uses JAX JIT for efficient training steps +- **Progress Tracking**: Shows overall progress across all datasets + +## Troubleshooting + +### Common Issues + +1. **No datasets found**: Check file naming patterns (`*_edgelist.csv`, `*_node_names.csv`) +2. **Memory errors**: Reduce `embed_dim` or number of datasets +3. **Training divergence**: Reduce `learning_rate` or increase `num_epochs` +4. **File format errors**: Ensure CSV files have correct format and encoding + +### Debug Mode + +For debugging, you can modify the script to print more detailed information: + +```python +# Add debug prints in load_single_dataset function +print(f"Loading {edge_list_file} and {node_labels_file}") +print(f"Found {num_nodes} nodes") +``` + +## Future Enhancements + +- **Parallel Training**: Support for training on multiple datasets in parallel +- **Validation**: Add validation dataset support +- **Checkpointing**: Save intermediate model states +- **Early Stopping**: Implement early stopping based on validation loss +- **Data Augmentation**: Support for data augmentation techniques \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_eval.py b/src/models/JAX_SAGE/jax_eval.py new file mode 100644 index 0000000..176dd0d --- /dev/null +++ b/src/models/JAX_SAGE/jax_eval.py @@ -0,0 +1,115 @@ +#!/usr/local/bin/python3 +""" +jax_eval.py + +Evaluation script for the JAX/Flax GraphSAGE model. +It loads the test data, the saved model parameters, and computes evaluation metrics. +""" + +import jax +import jax.numpy as jnp +import numpy as np +import pandas as pd +import pickle +from flax import linen as nn + +from jax_sage import GraphSAGE +from jax_utils.fixed_embedding import FixedEmbedding + + +def load_data(edge_list_file, node_labels_file, embed_dim): + """ + Loads the edge list and node labels from CSV files. + Returns: + - feature_weights: numpy array of shape (num_nodes, embed_dim) + - labels: numpy array of shape (num_nodes, 1) (binary labels) + - adj_lists: dictionary mapping node id (as string) to set of neighbor indices + - edge_index: numpy array representing the edge index (if needed) + - node_id_to_index, index_to_node_id: mapping dictionaries + """ + # Load CSV files with pandas. + edges_df = pd.read_csv(edge_list_file, header=None, names=["source", "target"]) + node_labels_df = pd.read_csv(node_labels_file) + nodes = node_labels_df['Node'].values + num_nodes = len(nodes) + + # Create mappings. + node_id_to_index = {node_id: idx for idx, node_id in enumerate(nodes)} + index_to_node_id = {idx: node_id for idx, node_id in enumerate(nodes)} + + # Create a fixed feature matrix. + rng_np = np.random.default_rng(0) + feature_weights = rng_np.normal(scale=0.1, size=(num_nodes, embed_dim)).astype(np.float32) + + # Create labels array. + labels = np.zeros((num_nodes, 1), dtype=np.float32) + # (In practice, load or compute the actual labels here.) + + # Create an adjacency list with string keys. + adj_lists = {str(idx): set() for idx in range(num_nodes)} + edge_index_list = [] + for _, row in edges_df.iterrows(): + src, tgt = row["source"], row["target"] + if src in node_id_to_index and tgt in node_id_to_index: + i, j = node_id_to_index[src], node_id_to_index[tgt] + edge_index_list.append([i, j]) + adj_lists[str(i)].add(j) + adj_lists[str(j)].add(i) + + edge_index = np.array(edge_index_list, dtype=np.int32).T + return feature_weights, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id + + +def evaluate_model(model, params, labels, nodes, rng): + """ + Applies the model to the given nodes and computes: + - binary cross-entropy loss, + - binary accuracy. + Assumes a sigmoid output for binary classification. + """ + # Forward pass to get predictions. + predictions = model.apply(params, nodes, rng=rng) + # Compute binary cross-entropy loss. + loss = -jnp.mean( + labels * jnp.log(predictions + 1e-8) + + (1 - labels) * jnp.log(1 - predictions + 1e-8) + ) + # Compute binary accuracy. + predicted_labels = predictions > 0.5 + true_labels = labels > 0.5 + accuracy = jnp.mean(predicted_labels == true_labels) + return loss, accuracy, predictions + + +def main(): + test_edges = '' + test_labels_file = '' + embed_dim = 128 + num_classes = 1 + + feature_weights, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id = load_data( + test_edges, test_labels_file, embed_dim + ) + + with open("graph_sage_model.pkl", "rb") as f: + params = pickle.load(f) + + features = FixedEmbedding(weights=jnp.array(feature_weights)) + + # Rebuild the model (encoder and full model) exactly as in training. + enc = GraphSAGE.initialize_encoder(features, adj_lists, embed_dim) + model = GraphSAGE.initialize_model(num_classes, enc, embed_dim) + + # Define nodes to evaluate on. Here we use all nodes from the test set. + # Use an immutable tuple as static argument. + nodes = tuple(range(labels.shape[0])) + rng = jax.random.PRNGKey(42) + + # Evaluate the model. + loss, accuracy, predictions = evaluate_model(model, params, jnp.array(labels), nodes, rng) + print("Evaluation Loss: {:.4f}".format(loss)) + print("Evaluation Accuracy: {:.4f}".format(accuracy)) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_sage.py b/src/models/JAX_SAGE/jax_sage.py new file mode 100644 index 0000000..cefd8e1 --- /dev/null +++ b/src/models/JAX_SAGE/jax_sage.py @@ -0,0 +1,46 @@ +# sage.py +from flax import linen as nn +import jax +import jax.numpy as jnp + +class GraphSAGE(nn.Module): + encoder: nn.Module + embed_dim: int + num_classes: int = 1 + + def setup(self): + self.cls = nn.Dense(features=1) + self.weight = self.param('weight', nn.initializers.xavier_uniform(), (self.num_classes, self.embed_dim)) + + def __call__(self, nodes, rng=None): + """ + nodes: list or array of node indices. + """ + embeds = self.encoder(nodes, rng=rng) + embeds = embeds.T # now (batch_size, embed_dim) + pred = self.cls(embeds) # (batch_size, 1) + prob = jax.nn.sigmoid(pred) + return prob + + def loss(self, nodes, labels, rng=None): + scores = self.__call__(nodes, rng=rng) + loss = - jnp.mean(labels * jnp.log(scores + 1e-8) + (1 - labels) * jnp.log(1 - scores + 1e-8)) + return loss + + @classmethod + def initialize_model(cls, num_classes, encoder, embed_dim): + return cls(encoder=encoder, embed_dim=embed_dim, num_classes=num_classes) + + @staticmethod + def initialize_encoder(features, adj_lists, embed_dim): + from jax_utils.jax_aggregators import MeanAggregator + from jax_utils.jax_encoders import Encoder + agg = MeanAggregator(features=features, gcn=True, num_sample=10) + enc = Encoder(features=features, + feat_dim=embed_dim, + embed_dim=embed_dim, + adj_lists=adj_lists, + aggregator=agg, + num_sample=10, + gcn=True) + return enc \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_train.py b/src/models/JAX_SAGE/jax_train.py new file mode 100644 index 0000000..8922671 --- /dev/null +++ b/src/models/JAX_SAGE/jax_train.py @@ -0,0 +1,303 @@ +#!/usr/local/bin/python3 +""" +jax_train.py + +A complete training script for the JAX/Flax GraphSAGE model. +It loads data from multiple datasets, initializes the model with a fixed embedding, +trains the model using Optax, and saves the trained parameters. +""" + +import jax +import jax.numpy as jnp +import optax +import numpy as np +import pandas as pd +import pickle +from flax import linen as nn +import time +import os +from pathlib import Path +import glob +from typing import List, Tuple, Dict, Any + +from jax_sage import GraphSAGE +from jax_utils.fixed_embedding import FixedEmbedding + + +def find_dataset_files(edge_folder: str, feat_folder: str, dataset: pd.DataFrame) -> List[Tuple[str, str]]: + """ + Find all matching edge list and feature file pairs in the given directories. + + Args: + edge_folder: Directory containing edge list files + feat_folder: Directory containing feature files + dataset: Dataframe dataset + + Returns: + List of tuples (edge_file, feat_file) for each dataset + """ + edge_files = glob.glob(os.path.join(edge_folder, "*_edgelist.csv")) + feat_files = glob.glob(os.path.join(feat_folder, "*_block_lens.csv")) + + # Create a mapping of file IDs to their paths + edge_file_map = {} + for edge_file in edge_files: + file_id = os.path.basename(edge_file).split("_")[0] + edge_file_map[file_id] = edge_file + + feat_file_map = {} + for feat_file in feat_files: + file_id = os.path.basename(feat_file).split("_")[0] + feat_file_map[file_id] = feat_file + + overlap = set(edge_file_map.keys()).intersection(set(feat_file_map.keys())) + overlap = set([int(x) for x in list(overlap)]) + overlap = overlap.intersection(set(dataset['id'])) + + # Find matching pairs + dataset_pairs = [] + # guaranteed that each file_id has an edgelist, label, feature file + for file_id in list(overlap): + file_id = str(file_id) + dataset_pairs.append((edge_file_map[file_id], feat_file_map[file_id])) + + print(f"Found {len(dataset_pairs)} dataset pairs") + return dataset_pairs + + +def load_single_dataset(edge_list_file: str, node_features_file: str, embed_dim: int, dataset_master: pd.DataFrame) -> Tuple[np.ndarray, np.ndarray, Dict, np.ndarray, Dict, Dict]: + """ + Loads a single dataset from edge list and node features files. + + Args: + edge_list_file: Path to edge list CSV file + node_features_file: Path to node features CSV file + embed_dim: Embedding dimension + + Returns: + Tuple of (feature_weights, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id) + """ + edges_df = pd.read_csv(edge_list_file, header=None, names=["source", "target"]) + node_features_df = pd.read_csv(node_features_file) + # Expecting columns: 'node', 'list', ... (other columns) + nodes = node_features_df['node'].values + num_nodes = len(nodes) + + node_id_to_index = {node_id: idx for idx, node_id in enumerate(nodes)} + index_to_node_id = {idx: node_id for idx, node_id in enumerate(nodes)} + + # Create a fixed feature matrix + feature_weights = np.ones(shape=(num_nodes, embed_dim)) + + for i in range(num_nodes): + node = index_to_node_id[i] + # n_instr = node_features_df.iloc[i] # This was not correct, n_instr should be a scalar or feature + # For now, just use 0 as placeholder for n_instr, or you can extract a feature if available + n_instr = 0 + num_in = sum(edges_df["source"] == node) + num_out = sum(edges_df["target"] == node) + vec = [node, n_instr, num_in, num_out] + # in case manual features extended in future + vec.extend([0] * (embed_dim - len(vec))) + feature_weights[i] = np.array(vec) + + file_id = edge_list_file.split("/")[-1].replace('.csv','').split("_")[0] + file_id = int(file_id) + + # Create labels array: label is 1 if entry['list'] == 'Blacklist', else 0 + if 'list' not in dataset_master.columns: + raise ValueError(f"Expected column 'list' in feature file {dataset_master}") + + entry = dataset_master[dataset_master['id'] == file_id] + if len(entry) != 1: + raise ValueError(f"Expected exactly one row in dataset_master for id={file_id}, but found {len(entry)}") + + # 1 if malicious, 0 if benign + labels = np.array([[1.0 if entry.iloc[0]['list'] == 'Blacklist' else 0.0]], dtype=np.float32) + + # Create an adjacency list with string keys + adj_lists = {str(idx): set() for idx in range(num_nodes)} + edge_index_list = [] + for _, row in edges_df.iterrows(): + src, tgt = row["source"], row["target"] + if src in node_id_to_index and tgt in node_id_to_index: + i, j = node_id_to_index[src], node_id_to_index[tgt] + edge_index_list.append([i, j]) + adj_lists[str(i)].add(j) + adj_lists[str(j)].add(i) + + edge_index = np.array(edge_index_list, dtype=np.int32).T + + return feature_weights, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id + + +def load_all_datasets(dataset_pairs: List[Tuple[str, str]], dataset_master: pd.DataFrame, embed_dim: int) -> List[Dict[str, Any]]: + """ + Load all datasets and return them as a list of dictionaries. + + Args: + dataset_pairs: List of (edge_file, feat_file) tuples + embed_dim: Embedding dimension + + Returns: + List of dataset dictionaries + """ + datasets = [] + + for i, (edge_file, feat_file) in enumerate(dataset_pairs): + print(f"Loading dataset {i+1}/{len(dataset_pairs)}: {os.path.basename(edge_file)}") + feature_weights, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id = load_single_dataset( + edge_file, feat_file, embed_dim, dataset_master + ) + + dataset = { + 'feature_weights': feature_weights, + 'labels': labels, + 'adj_lists': adj_lists, + 'edge_index': edge_index, + 'node_id_to_index': node_id_to_index, + 'index_to_node_id': index_to_node_id, + 'name': os.path.basename(edge_file).split('_')[0] + } + datasets.append(dataset) + print(f"Successfully loaded dataset {dataset['name']} with {len(feature_weights)} nodes") + + + return datasets + + +def train_model_on_datasets(model, params, datasets: List[Dict[str, Any]], num_epochs: int, learning_rate: float, rng): + """ + Trains the model on multiple datasets using Optax's Adam optimizer. + + Args: + model: The GraphSAGE model + params: Model parameters + datasets: List of dataset dictionaries + num_epochs: Number of training epochs per dataset + learning_rate: Learning rate for optimizer + rng: Random number generator key + + Returns: + Trained model parameters + """ + print(f"Training on {len(datasets)} datasets...") + optimizer = optax.adam(learning_rate) + opt_state = optimizer.init(params) + + def train_step(params, opt_state, nodes, labels, rng): + def loss_fn(params): + pred = model.apply(params, nodes, rng=rng) + loss = -jnp.mean( + labels * jnp.log(pred + 1e-8) + + (1 - labels) * jnp.log(1 - pred + 1e-8) + ) + return loss + + loss, grads = jax.value_and_grad(loss_fn)(params) + updates, opt_state = optimizer.update(grads, opt_state) + params = optax.apply_updates(params, updates) + return params, opt_state, loss + + train_step = jax.jit(train_step, static_argnums=(2,)) + + total_epochs = len(datasets) * num_epochs + current_epoch = 0 + + for dataset_idx, dataset in enumerate(datasets): + print(f"\nTraining on dataset {dataset['name']} ({dataset_idx+1}/{len(datasets)})") + + # Create fixed embedding for this dataset + features = FixedEmbedding(weights=jnp.array(dataset['feature_weights'])) + + # Initialize encoder for this dataset + enc = GraphSAGE.initialize_encoder(features, dataset['adj_lists'], dataset['feature_weights'].shape[1]) + + # Get nodes for this dataset + nodes = tuple(range(dataset['labels'].shape[0])) + labels = jnp.array(dataset['labels']) + + for epoch in range(num_epochs): + rng, step_rng = jax.random.split(rng) + params, opt_state, loss = train_step(params, opt_state, nodes, labels, step_rng) + current_epoch += 1 + print(f"Dataset {dataset['name']} - Epoch [{epoch+1}/{num_epochs}], Loss: {loss:.4f} (Overall: {current_epoch}/{total_epochs})") + + return params + + +def runGSageTraining(num_classes=1, learning_rate=0.01, num_epochs=100, embed_dim=128, + edge_folder="", feat_folder="", label_dataset=None): + """ + Main function to load data from multiple datasets, initialize the model, train it, and save the parameters. + + Args: + num_classes: Number of output classes + learning_rate: Learning rate for training + num_epochs: Number of epochs per dataset + embed_dim: Embedding dimension + edge_folder: Directory containing edge list files + feat_folder: Directory containing feature files + label_dataset: pd.DataFrame containing full dataset + """ + assert embed_dim >= 4, "Cannot handle an embed dim smaller than 4" + assert label_dataset is not None, "Cannot create a dataset with no label dataset" + + start_time = time.time() + + # Find all dataset pairs + dataset_pairs = find_dataset_files(edge_folder, feat_folder, label_dataset) + if not dataset_pairs: + print("No dataset pairs found. Please check the edge_folder and feat_folder paths.") + return + + # Load all datasets + datasets = load_all_datasets(dataset_pairs, label_dataset, embed_dim) + if not datasets: + print("No datasets were successfully loaded.") + return + + print(f"\nSuccessfully loaded {len(datasets)} datasets") + + # Use the first dataset to initialize the model (assuming all datasets have similar structure) + first_dataset = datasets[0] + features = FixedEmbedding(weights=jnp.array(first_dataset['feature_weights'])) + + # Initialize the encoder and the GraphSAGE model + enc = GraphSAGE.initialize_encoder(features, first_dataset['adj_lists'], embed_dim) + model = GraphSAGE.initialize_model(num_classes, enc, embed_dim) + + rng = jax.random.PRNGKey(0) + dummy_nodes = tuple(range(first_dataset['labels'].shape[0])) + params = model.init(rng, dummy_nodes) + + # Train the model on all datasets + params = train_model_on_datasets(model, params, datasets, num_epochs, learning_rate, rng) + + end_time = time.time() + total_time = end_time - start_time + + # Save the trained model parameters + with open("graph_sage_model.pkl", "wb") as f: + pickle.dump(params, f) + print(f"\nModel saved to graph_sage_model.pkl") + print(f"Total training time: {total_time:.2f} seconds") + print(f"Trained on {len(datasets)} datasets") + + +if __name__ == "__main__": + # Example usage - update these paths to your actual data directories + edge_folder = "/Users/thegupta/Desktop/out/out_edgelists" + feat_folder = "/Users/thegupta/Desktop/out/out_file_feats" + labelfile = "/Users/thegupta/Desktop/out/sample_train_ids.csv" + labelfile = pd.read_csv(labelfile) + + runGSageTraining( + num_classes=1, + learning_rate=0.01, + num_epochs=100, + embed_dim=128, + edge_folder=edge_folder, + feat_folder=feat_folder, + label_dataset=labelfile + ) \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_utils/fixed_embedding.py b/src/models/JAX_SAGE/jax_utils/fixed_embedding.py new file mode 100644 index 0000000..6cd3b11 --- /dev/null +++ b/src/models/JAX_SAGE/jax_utils/fixed_embedding.py @@ -0,0 +1,14 @@ +# utils/fixed_embedding.py +import jax.numpy as jnp +from flax import linen as nn + +class FixedEmbedding(nn.Module): + weights: jnp.ndarray + + @nn.compact + def __call__(self, indices): + """ + indices: a jnp.array of indices. + Returns the corresponding rows of the fixed embedding matrix. + """ + return self.weights[indices] \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_utils/jax_aggregators.py b/src/models/JAX_SAGE/jax_utils/jax_aggregators.py new file mode 100644 index 0000000..f610721 --- /dev/null +++ b/src/models/JAX_SAGE/jax_utils/jax_aggregators.py @@ -0,0 +1,58 @@ +# utils/aggregators.py +import jax +import jax.numpy as jnp +import numpy as np +import random +from flax import linen as nn + +class MeanAggregator(nn.Module): + features: any + gcn: bool = False + num_sample: int = None + + @nn.compact + def __call__(self, nodes, to_neighs, rng=None): + """ + nodes: a list of node indices. + to_neighs: a list of sets (neighbors for each node). + rng: an optional JAX PRNG key. + """ + sampled = [] + for i, neigh in enumerate(to_neighs): + neigh = list(neigh) + if self.num_sample is not None and len(neigh) >= self.num_sample: + if rng is not None: + key, rng = jax.random.split(rng) + neigh_array = jnp.array(neigh) + chosen = jax.random.choice(key, neigh_array, shape=(self.num_sample,), replace=False) + chosen = list(np.array(chosen)) + else: + chosen = random.sample(neigh, self.num_sample) + else: + chosen = neigh + sampled.append(set(chosen)) + + if self.gcn: + sampled = [s.union({nodes[i]}) for i, s in enumerate(sampled)] + + sampled_lists = [list(s) for s in sampled] + unique_nodes = set() + for s in sampled_lists: + unique_nodes.update(s) + unique_nodes_list = list(unique_nodes) + unique_nodes_dict = {n: i for i, n in enumerate(unique_nodes_list)} + + mask_np = np.zeros((len(sampled_lists), len(unique_nodes_list)), dtype=np.float32) + for i, s in enumerate(sampled_lists): + for node in s: + j = unique_nodes_dict[node] + mask_np[i, j] = 1.0 + mask = jnp.array(mask_np) + num_neigh = jnp.sum(mask, axis=1, keepdims=True) + num_neigh = jnp.where(num_neigh == 0, 1, num_neigh) + mask = mask / num_neigh + + unique_nodes_array = jnp.array(unique_nodes_list, dtype=jnp.int32) + embed_matrix = self.features(unique_nodes_array) + to_feats = jnp.matmul(mask, embed_matrix) + return to_feats \ No newline at end of file diff --git a/src/models/JAX_SAGE/jax_utils/jax_encoders.py b/src/models/JAX_SAGE/jax_utils/jax_encoders.py new file mode 100644 index 0000000..8ecf00d --- /dev/null +++ b/src/models/JAX_SAGE/jax_utils/jax_encoders.py @@ -0,0 +1,36 @@ +# utils/encoders.py +import jax +import jax.numpy as jnp +import numpy as np +from flax import linen as nn +from flax import struct # import struct to mark static fields +import jax.nn as jnn + +class Encoder(nn.Module): + features: nn.Module + feat_dim: int + embed_dim: int + adj_lists: dict = struct.field(pytree_node=False) + aggregator: nn.Module + num_sample: int = 10 + gcn: bool = False + + def setup(self): + in_dim = self.feat_dim if self.gcn else 2 * self.feat_dim + self.weight = self.param('weight', nn.initializers.xavier_uniform(), (self.embed_dim, in_dim)) + + def __call__(self, nodes, rng=None): + if isinstance(nodes, jnp.ndarray): + nodes_list = list(np.array(nodes).tolist()) + else: + nodes_list = nodes + to_neighs = [self.adj_lists[str(node)] for node in nodes_list] + neigh_feats = self.aggregator(nodes_list, to_neighs, rng=rng) + if not self.gcn: + self_feats = self.features(jnp.array(nodes_list, dtype=jnp.int32)) + combined = jnp.concatenate([self_feats, neigh_feats], axis=1) + else: + combined = neigh_feats + hidden = jnp.dot(self.weight, combined.T) + hidden = jnp.maximum(hidden, 0) + return hidden \ No newline at end of file diff --git a/src/models/JAX_SAGE/loader.py b/src/models/JAX_SAGE/loader.py new file mode 100644 index 0000000..00ea528 --- /dev/null +++ b/src/models/JAX_SAGE/loader.py @@ -0,0 +1,249 @@ +import time +# import torch +import shutil +import numpy as np +from pathlib import Path +import pandas as pd +import jax.numpy as jnp +# from torch.utils import data +# from torch_geometric.data import Data, DataLoader +import tqdm + +# device = torch.device("cuda" if torch.cuda.is_available() else "cpu") + +def load_dataset_jax(paths: list[str]) -> tuple: + start_load = time.perf_counter() + + """ + Loads the dataset and returns a list of samples (each a dict with keys: + "x", "edge_index", "batch", "y", and "num_graphs"), + along with the number of features and number of classes. + + This version is adapted for use with JAX/Flax. + """ + num_features, num_classes = 0, 2 + max_edgelist, max_nodes = 0, 0 + + # Dictionary to hold data for each file id: {file_id: [edge_index, node_features]} + files = {} + labels = None + for path in sorted(paths): + fname = Path(path).name + if "edgelist" in fname: + # Expect file names like "_edgelist..." + curr_id = int(fname.split("_")[0]) + print(f"Loading edgelist from: {path}") + edges = pd.read_csv(path, header=None).values.astype(np.int32) + max_edgelist = edges.shape[0] if edges.shape[0] > max_edgelist else max_edgelist + edge_index = edges.T # shape: (2, num_edges) + if curr_id not in files: + files[curr_id] = [edge_index, None] + else: + _, curr_features = files[curr_id] + files[curr_id] = [edge_index, curr_features] + elif "node_names" in fname: + curr_id = int(fname.split("_")[0]) + print(f"Loading node features from: {path}") + node_features = pd.read_csv(path, header=None).values + # Remove first row and second column as in your original code. + node_features = np.delete(np.delete(node_features, 0, axis=0), 1, axis=1).astype(np.int32) + if curr_id not in files: + files[curr_id] = [None, node_features] + else: + curr_edge, _ = files[curr_id] + files[curr_id] = [curr_edge, node_features] + # num_features = max(num_features, node_features.shape[1]) + num_features = max(num_features, node_features.shape[0]) + max_nodes = max(max_nodes, node_features.shape[0]) + elif "samplekey" in fname: + print(f"Loading labels from: {path}") + labels = pd.read_csv(path, header=None) + elif "removed" in fname: + print(f"Skipping removed file: {path}") + + print(f"Max number of features: {num_features}") + print(f"Number of classes: {num_classes}") + print(f"Largest edgelist (number of edges): {max_edgelist}") + print(f"Largest number of nodes: {max_nodes}") + + data_set = [] + for file_id, (edges, feature_vector) in files.items(): + if feature_vector is None or edges is None: + print(f"Incomplete data for file id {file_id}, skipping.") + continue + + # Pad node feature matrix to have max_nodes rows. + x = np.pad(feature_vector, ((0, max_nodes - feature_vector.shape[0]), (0, 0)), + mode="constant").astype(np.float32) + if file_id in labels[1].values: + y_val = int(labels.loc[labels[1] == file_id].iloc[0, 12]) + else: + print(f"Label for file id {file_id} not found; skipping.") + continue + + if np.max(edges) >= x.shape[0]: + print("Edge list references a node outside the padded feature matrix; skipping.") + continue + + print(f"Creating data sample from file id: {file_id}") + sample = { + "x": jnp.array(x), # shape: [num_nodes, num_features] + "edge_index": (jnp.array(edges[0], dtype=jnp.int32), + jnp.array(edges[1], dtype=jnp.int32)), + "y": jnp.array([y_val], dtype=jnp.int32), # shape: [1] + "batch": jnp.zeros(x.shape[0], dtype=jnp.int32), # all nodes in graph 0 + "num_graphs": 1 + } + data_set.append(sample) + print(f"Created dataset with {len(data_set)} samples.") + + end_load = time.perf_counter() + print("\n\n") + print(f"Time to load data: {(end_load - start_load):.6f} seconds") + + return data_set, num_features, num_classes + +def load_dataset_jax_new(paths: list[str], max_files=20) -> tuple: + start_load = time.perf_counter() + + paths = [Path(path) for path in paths] # NOTE: this could just be done in the get_paths function + paths_set = set([path.name for path in paths]) # for quick verification that all 3 files for any given id exist + + labels_path = None + if max_files is None: + max_files = float('inf') + + ids = {} # {id: [node_names_path, edgelist_path, block_lens_path]} + num_files = 0 + for path in paths: + file_name = path.name + + if "sample" in file_name: + labels_path = path + + if "edgelist.csv" in file_name and num_files < max_files: + #print(file_name) + file_id = int(file_name.split("/")[-1].split("_")[0]) + if (file_id not in ids) and (str(file_id) + "_edgelist.csv") in paths_set and (str(file_id) + "_block_lens.csv") in paths_set: + base_path = path.parents[0] + ids[file_id] = [base_path / file_name, base_path / (str(file_id) + "_edgelist.csv"), base_path / (str(file_id) + "_block_lens.csv")] + num_files += 1 + # if num_files >= max_files: # NOTE: if you uncomment this line you will have to add a line of code to set labels_path manually + # break + + samples = {} # {id: [node_features, edges]} + max_nodes, max_edges, max_blocks = 0, 0, 0 + num_classes = 2 + for file_id, (node_names_path, edgelist_path, block_lens_path) in tqdm.tqdm(ids.items(), desc="Building Dataset"): + # print("#"*30) + bp = str(block_lens_path) + bp = bp.replace("out_edgelists", "out_file_feats") + block_lens_path = Path(bp) + + # print(node_names_path, edgelist_path, block_lens_path) + + # node_features = pd.read_csv(node_names_path, header=None, low_memory=False).values + # print("read feats") + # node_features = np.delete(np.delete(node_features, 0, axis=0), 1, axis=1).astype(np.int32) # shape: num_nodes x 1 data: (node_id) + # print(node_features.shape) + # max_nodes = max(max_nodes, node_features.shape[0]) + + I32_MAX = (2**(31)) - 1 + + block_lens = pd.read_csv(block_lens_path, header=None, low_memory=False).values + # print("read blocklens") + block_lens = np.delete(block_lens, 0, axis=0) + block_lens = block_lens.astype(np.uint64) # shape: num_blocks x 2 data: (node_id, num_instructions) + + block_lens = block_lens % I32_MAX + block_lens = block_lens.astype(np.int32) + + # print(np.max(block_lens, axis=0)[1], np.min(block_lens, axis=0)[1]) + + # print(block_lens.shape) + + edges = None + try: + edges = pd.read_csv(edgelist_path, header=None) # shape: num_edges x 2 data: (node_id, node_id) + except: + # most likely "No columns to parse"; single big block in file i think (or series of big blocks) + # introduce series of self-edges + edges = pd.DataFrame([[node,node] for node,_ in block_lens]) + + edges = edges % I32_MAX + edges = edges.astype(np.int32).values + + # node_features = np.delete(np.delete(node_features, 0, axis=0), 1, axis=1).astype(np.int32) # shape: num_nodes x 1 data: (node_id) + # print(node_features.shape) + # max_nodes = max(max_nodes, node_features.shape[0]) + + # print(edges.shape) + # print("read edges") + # max_edges = max(max_edges, edges.shape[0]) + # samples[file_id] = [node_features, edges] + + # if node_features.shape[0] != block_lens.shape[0]: + # print(f"node_features shape {node_features.shape} is not equal to block_lens shape {block_lens.shape}, file_id {file_id}") + + samples[file_id] = [block_lens, edges] + + print(labels_path) + print(f"{len(samples.items())} files in sample dict") + labels = pd.read_csv(labels_path) + + dataset = [] + + max_nodes, max_edges, max_blocks = 0, 0, 0 + + for file_id, (features, edges) in tqdm.tqdm(samples.items(), desc="Dataset Sample Adding"): + matching_row = labels[labels['id'] == file_id] + if not matching_row.empty: + # print(matching_row) + # file_label = matching_row.iloc[1, 7] + file_label = matching_row.iloc[0]['list'] + # print(f"File id: {file_id} has label {file_label}") + y = 0 if file_label == "Whitelist" else 1 + max_edges = max(max_edges, edges.shape[0]) + + max_blocks = max(max_blocks, features.shape[0]) + max_nodes = max_blocks # each block should be distinct node + + # TODO: do we even need to pad? + # x = np.pad(features, ((0, max_nodes - features.shape[0]), (0, 0)), mode="constant").astype(np.float32) + x = features + #print(file_id) + # print(edges) + #second_idx = 1 if len(edges) > 1 else 0 + # print(type(edges)) + # print(edges.shape) + curr_sample = { + "x": jnp.array(x), + "edge_index": (jnp.array(edges[:,0], dtype=jnp.int32), jnp.array(edges[:,1], dtype=jnp.int32)), + "y": jnp.array([y], dtype=jnp.int32), + "batch": jnp.zeros(x.shape[0], dtype=jnp.int32), + "num_graphs": 1 + } + + dataset.append(curr_sample) + + else: + # print(f"File id: {file_id} does not have a label. Skipping.") + continue + + + print(f"Created dataset with {len(dataset)} samples.") + + end_load = time.perf_counter() + print("\n\n") + print(f"Time to load data: {(end_load - start_load):.6f} seconds") + print(max_nodes, max_edges, max_blocks) + + + return dataset, max_nodes, num_classes + + +def get_paths(samples_2000=False) -> list[str]: + data_dir = Path(__file__).resolve().parent.parent / "out" + #data_dir = data_dir / "2kds" / "found_files" if samples_2000 else data_dir + print(f"Collecting file paths from directory: {data_dir}") + return list([str(path) for path in data_dir.rglob("*")]) \ No newline at end of file diff --git a/src/models/JAX_SAGE/train_multiple_datasets.py b/src/models/JAX_SAGE/train_multiple_datasets.py new file mode 100644 index 0000000..f5d8528 --- /dev/null +++ b/src/models/JAX_SAGE/train_multiple_datasets.py @@ -0,0 +1,97 @@ +#!/usr/local/bin/python3 +""" +train_multiple_datasets.py + +A helper script for training GraphSAGE on multiple datasets. +This script provides a convenient interface for running the multi-dataset training +with different configurations and data paths. +""" + +import argparse +import os +from pathlib import Path +from jax_train import runGSageTraining + + +def main(): + parser = argparse.ArgumentParser(description='Train GraphSAGE on multiple datasets') + + # Data paths + parser.add_argument('--edge_folder', type=str, required=True, + help='Directory containing edge list files (*_edgelist.csv)') + parser.add_argument('--label_folder', type=str, required=True, + help='Directory containing label files (*_node_names.csv)') + + # Model parameters + parser.add_argument('--num_classes', type=int, default=1, + help='Number of output classes (default: 1)') + parser.add_argument('--embed_dim', type=int, default=128, + help='Embedding dimension (default: 128)') + + # Training parameters + parser.add_argument('--learning_rate', type=float, default=0.01, + help='Learning rate (default: 0.01)') + parser.add_argument('--num_epochs', type=int, default=100, + help='Number of epochs per dataset (default: 100)') + + # Output + parser.add_argument('--output_file', type=str, default='graph_sage_model.pkl', + help='Output file for trained model (default: graph_sage_model.pkl)') + + args = parser.parse_args() + + # Validate paths + if not os.path.exists(args.edge_folder): + print(f"Error: Edge folder '{args.edge_folder}' does not exist") + return + + if not os.path.exists(args.label_folder): + print(f"Error: Label folder '{args.label_folder}' does not exist") + return + + # Check if folders contain expected files + edge_files = list(Path(args.edge_folder).glob("*_edgelist.csv")) + label_files = list(Path(args.label_folder).glob("*_node_names.csv")) + + if not edge_files: + print(f"Warning: No edge list files found in '{args.edge_folder}'") + print("Expected files with pattern: *_edgelist.csv") + + if not label_files: + print(f"Warning: No label files found in '{args.label_folder}'") + print("Expected files with pattern: *_node_names.csv") + + if not edge_files or not label_files: + print("No valid dataset files found. Exiting.") + return + + print(f"Found {len(edge_files)} edge list files and {len(label_files)} label files") + + # Run training + print("\nStarting multi-dataset training...") + print(f"Edge folder: {args.edge_folder}") + print(f"Label folder: {args.label_folder}") + print(f"Embedding dimension: {args.embed_dim}") + print(f"Learning rate: {args.learning_rate}") + print(f"Epochs per dataset: {args.num_epochs}") + print(f"Number of classes: {args.num_classes}") + + try: + runGSageTraining( + num_classes=args.num_classes, + learning_rate=args.learning_rate, + num_epochs=args.num_epochs, + embed_dim=args.embed_dim, + edge_folder=args.edge_folder, + label_folder=args.label_folder + ) + print(f"\nTraining completed successfully!") + print(f"Model saved to: {args.output_file}") + + except Exception as e: + print(f"Error during training: {e}") + raise + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/models/MODEL_MAPPING.py b/src/models/MODEL_MAPPING.py new file mode 100644 index 0000000..134a15f --- /dev/null +++ b/src/models/MODEL_MAPPING.py @@ -0,0 +1,5 @@ +from SAGE.sage import GraphSAGE + +MAP = { + 'graphsage': GraphSAGE +} \ No newline at end of file diff --git a/src/models/SAGE/.DS_Store b/src/models/SAGE/.DS_Store new file mode 100644 index 0000000..f981943 Binary files /dev/null and b/src/models/SAGE/.DS_Store differ diff --git a/src/models/SAGE/sage.py b/src/models/SAGE/sage.py new file mode 100644 index 0000000..3a5433e --- /dev/null +++ b/src/models/SAGE/sage.py @@ -0,0 +1,47 @@ +# all hail William Hamilton for the Torch implementation +# https://github.com/williamleif/graphsage-simple + +import torch.nn as nn +import torch +from torch.nn import init +from utils.encoders import Encoder # Import the Encoder class +from utils.aggregators import MeanAggregator # Import the aggregator + +class GraphSAGE(nn.Module): + + def __init__(self, num_classes, enc): + super(GraphSAGE, self).__init__() + self.enc = enc + self.bxent = nn.BCELoss() # Using BCELoss for binary classification + self.weight = nn.Parameter(torch.FloatTensor(num_classes, enc.embed_dim)) + self.cls = nn.Linear(enc.embed_dim, 1) + init.xavier_uniform_(self.weight) + + def forward(self, nodes): + if isinstance(nodes, torch.Tensor): + nodes = nodes.tolist() + embeds = self.enc(nodes) + embeds = embeds.t() # Transpose to match dimensions + pred = self.cls(embeds) + # Debugging statement + print(f"Pred min: {pred.min().item()}, max: {pred.max().item()}") + prob = torch.sigmoid(pred) + return prob + + + def loss(self, nodes, labels): + scores = self.forward(nodes) + # Debugging statement + print(f"Scores min: {scores.min().item()}, max: {scores.max().item()}") + return self.bxent(scores, labels) + + @classmethod + def initialize_model(cls, num_classes, enc): + return cls(num_classes, enc) # Factory method for model initialization + + @staticmethod + def initialize_encoder(features, adj_lists, embed_dim): + agg = MeanAggregator(features, cuda=False) # Adjust 'cuda' as needed + feature_dim = features.embedding_dim + enc = Encoder(features, feature_dim, embed_dim, adj_lists, agg, gcn=True, cuda=False) + return enc diff --git a/src/models/SAGE/train.py b/src/models/SAGE/train.py new file mode 100644 index 0000000..736f7cf --- /dev/null +++ b/src/models/SAGE/train.py @@ -0,0 +1,78 @@ +import torch +import torch.optim as optim +import torch.nn as nn +from sage import GraphSAGE +import numpy as np +from pandas import read_csv +from torch_geometric.data import Data +import os + +def train_model(model, labels, num_epochs, learning_rate): + print("training...") + optimizer = optim.Adam(model.parameters(), lr=learning_rate) + nodes = list(range(len(labels))) + + for epoch in range(num_epochs): + model.train() + optimizer.zero_grad() + loss = model.loss(nodes, labels) + loss.backward() + optimizer.step() + print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}') + +def load_data(edge_list_file, node_labels_file, embed_dim): + # Load edge list + edges_df = read_csv(edge_list_file, header=None, names=["source", "target"]) + node_labels_df = read_csv(node_labels_file) + nodes = node_labels_df['Node'].values + num_nodes = len(nodes) + + # Map node IDs to indices + node_id_to_index = {node_id: idx for idx, node_id in enumerate(nodes)} + index_to_node_id = {idx: node_id for idx, node_id in enumerate(nodes)} + + # Initialize features + features = nn.Embedding(num_nodes, embed_dim) + features.weight = nn.Parameter(features.weight.clone().detach(), requires_grad=False) + + # Initialize labels + labels = torch.zeros((num_nodes, 1), dtype=torch.float) + + # Create edge list and adjacency lists from edge list file + edge_index_list = [] + adj_lists = {idx: set() for idx in range(num_nodes)} + + for _, row in edges_df.iterrows(): + src, tgt = row["source"], row["target"] + if src in node_id_to_index and tgt in node_id_to_index: + i, j = node_id_to_index[src], node_id_to_index[tgt] + edge_index_list.append([i, j]) + adj_lists[i].add(j) + adj_lists[j].add(i) + + edge_index = torch.tensor(edge_index_list, dtype=torch.long).t().contiguous() + + return features, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id + + +def runGSageTraining(num_classes=1, learning_rate=0.01, num_epochs=100, embed_dim=128, num_layers=2): + edges = '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/Malware-Analysis/src/models/SAGE/newset/5124_edgelist.csv' + labels = '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/Malware-Analysis/src/models/SAGE/newset/5124_node_names.csv' + features, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id = load_data( + edges, labels, embed_dim) + + # init encoder + enc = GraphSAGE.initialize_encoder(features, adj_lists, embed_dim) + + # init model + model = GraphSAGE.initialize_model(num_classes, enc) + + # train + train_model(model, labels, num_epochs, learning_rate) + + # save + torch.save(model.state_dict(), 'graph_sage_model.pth') + +if __name__ == "__main__": + runGSageTraining() + diff --git a/src/models/SAGE/utils/aggregators.py b/src/models/SAGE/utils/aggregators.py new file mode 100644 index 0000000..724d8ba --- /dev/null +++ b/src/models/SAGE/utils/aggregators.py @@ -0,0 +1,49 @@ +# In aggregators.py +import torch +import torch.nn as nn +import random + +class MeanAggregator(nn.Module): + def __init__(self, features, cuda=False, gcn=False): + super(MeanAggregator, self).__init__() + self.features = features + self.cuda = cuda + self.gcn = gcn + + def forward(self, nodes, to_neighs, num_sample=None): + """ + nodes --- list of nodes in a batch + to_neighs --- list of sets, each set is the set of neighbors for a node in the batch + num_sample --- number of neighbors to sample. No sampling if None. + """ + _set = set + if num_sample is not None: + _sample = random.sample + samp_neighs = [_set(_sample(to_neigh, num_sample)) if len(to_neigh) >= num_sample else to_neigh + for to_neigh in to_neighs] + else: + samp_neighs = to_neighs + + if self.gcn: + samp_neighs = [samp_neigh.union({nodes[i]}) for i, samp_neigh in enumerate(samp_neighs)] + # Ensure samp_neighs are lists for consistent indexing + samp_neighs = [list(samp_neigh) for samp_neigh in samp_neighs] + unique_nodes_list = list(set.union(*map(set, samp_neighs))) + unique_nodes = {n: i for i, n in enumerate(unique_nodes_list)} + mask = torch.zeros(len(samp_neighs), len(unique_nodes)) + for i, neigh in enumerate(samp_neighs): + for node in neigh: + mask[i, unique_nodes[node]] = 1 + + num_neigh = mask.sum(1, keepdim=True) + # Handle division by zero + num_neigh[num_neigh == 0] = 1 + mask = mask.div(num_neigh) + + if self.cuda: + embed_matrix = self.features(torch.LongTensor(unique_nodes_list).cuda()) + mask = mask.cuda() + else: + embed_matrix = self.features(torch.LongTensor(unique_nodes_list)) + to_feats = mask.mm(embed_matrix) + return to_feats diff --git a/src/models/SAGE/utils/encoders.py b/src/models/SAGE/utils/encoders.py new file mode 100644 index 0000000..3662fb9 --- /dev/null +++ b/src/models/SAGE/utils/encoders.py @@ -0,0 +1,57 @@ +import torch +import torch.nn as nn +from torch.nn import init +import torch.nn.functional as F + +class Encoder(nn.Module): + """ + Encodes a node's using 'convolutional' GraphSage approach + """ + def __init__(self, features, feature_dim, + embed_dim, adj_lists, aggregator, + num_sample=10, + base_model=None, gcn=False, cuda=False, + feature_transform=False): + super(Encoder, self).__init__() + + self.features = features + self.feat_dim = feature_dim + self.adj_lists = adj_lists + self.aggregator = aggregator + self.num_sample = num_sample + if base_model != None: + self.base_model = base_model + + self.gcn = gcn + self.embed_dim = embed_dim + self.cuda = cuda + self.aggregator.cuda = cuda + self.weight = nn.Parameter( + torch.FloatTensor(embed_dim, self.feat_dim if self.gcn else 2 * self.feat_dim)) + init.xavier_uniform_(self.weight) + + def forward(self, nodes): + """ + Generates embeddings for a batch of nodes. + + nodes -- list of nodes + """ + if isinstance(nodes, torch.Tensor): + nodes = nodes.tolist() # catches prev error + elif isinstance(nodes, list) and isinstance(nodes[0], list): + # catches prev error by flattening + nodes = [item for sublist in nodes for item in sublist] + + neigh_feats = self.aggregator.forward(nodes, [self.adj_lists[int(node)] for node in nodes], self.num_sample) + if not self.gcn: + if self.cuda: + self_feats = self.features(torch.LongTensor(nodes).cuda()) + else: + self_feats = self.features(torch.LongTensor(nodes)) + combined = torch.cat([self_feats, neigh_feats], dim=1) + else: + combined = neigh_feats + combined = F.relu(self.weight.mm(combined.t())) + # Debugging statement + # print(f"Combined min: {combined.min().item()}, max: {combined.max().item()}") + return combined \ No newline at end of file diff --git a/src/models/acceleration.py b/src/models/acceleration.py new file mode 100644 index 0000000..0fd0455 --- /dev/null +++ b/src/models/acceleration.py @@ -0,0 +1,124 @@ +import click +import torch +from torch.utils.data import DataLoader, Dataset +from sklearn.metrics import precision_score +import time +import pandas as pd +import torch.quantization as quant +from SAGE.sage import GraphSAGE +from SAGE.train import load_data +import os + +class NodeDataset(Dataset): + def __init__(self, nodes, labels): + self.nodes = nodes + self.labels = labels + + def __len__(self): + return len(self.nodes) + + def __getitem__(self, idx): + return int(self.nodes[idx]), self.labels[idx] + +def collate_fn(batch): + nodes, labels = zip(*batch) + return list(nodes), torch.stack(labels) + +class QuantizationAcceleration: + def __init__(self, model_path: str, data_dir: str, job_id: int): + self.name = "quantization" + print(f"Running acceleration: {self.name}") + + features, labels, adj_lists, edge_index, node_id_to_index, index_to_node_id = load_data( + '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/Malware-Analysis/src/models/SAGE/newset/5124_edgelist.csv', + '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/Malware-Analysis/src/models/SAGE/newset/5124_node_names.csv', + embed_dim=128 + ) + + enc = GraphSAGE.initialize_encoder(features, adj_lists, embed_dim=128) + num_classes = 1 + self.model = GraphSAGE.initialize_model(num_classes, enc) + + self.model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))) + self.model.eval() + self.job_id = job_id + + modelname = model_path.split("/")[-1] + + node_indices = list(range(len(labels))) + dataset = NodeDataset(node_indices, labels) + self.dataloader = DataLoader(dataset, batch_size=32, shuffle=True, collate_fn=collate_fn) + + if self.job_id != -1: + self.outmodel = f"{self.job_id}_{self.name}_{modelname}.pt" + else: + self.outmodel = f"{self.name}_{modelname}.pt" + + def accelerate(self): + torch.backends.quantized.engine = 'qnnpack' + self.model.eval() + self.model = torch.quantization.quantize_dynamic( + self.model, {torch.nn.Linear}, dtype=torch.qint8 + ) + + print("Model dynamically quantized.") + + quantized_model_path = self.outmodel + torch.save(self.model.state_dict(), quantized_model_path) + + def evaluate(self): + accelerated_model = self.model + accelerated_model.eval() + + all_preds = [] + all_labels = [] + times = [] + + with torch.no_grad(): + for nodes, labels in self.dataloader: + start = time.time() + if isinstance(nodes, torch.Tensor): + nodes = nodes.tolist() + outputs = accelerated_model(nodes) + # The model outputs probabilities (after sigmoid). Threshold at 0.5 for binary classification. + preds = (outputs > 0.5).long().view(-1) + all_preds.extend(preds.cpu().numpy()) + all_labels.extend(labels.view(-1).cpu().numpy()) + end = time.time() + times.append(end - start) + + precision = precision_score(all_labels, all_preds, average='weighted') + print(f'Precision: {precision:.4f}') + + avg_latency = (sum(times) * 1000 / len(times)) + print(f"Average Inference Latency: {avg_latency:.2f} ms") + + d = { + 'acceleration': self.name, + 'precision': precision, + 'avg_inference_latency': avg_latency + } + df = pd.DataFrame([d]) + fname = f"{self.job_id}_{self.name}_eval.csv" if self.job_id != -1 else f"{self.name}_eval.csv" + df.to_csv(fname, index=False) + + +@click.command() +@click.option('--model-path', default='', help='Path to the .pt stored model') +@click.option('--data-dir', default='', help='The path to the dataset used to evaluate this model') +@click.option('--job-id', default=-1, help='ID for this job') +def main(model_path, data_dir, job_id): + if not model_path: + model_path = '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/graph_sage_model.pth' + if not data_dir: + data_dir = '/Users/vinay/Desktop/Computer_Science_Projects/georgia_tech/ai_gt/Malware-Analysis/src/models/SAGE/dataset' + + if model_path == '' or data_dir == '': + print("Error: empty model path or data directory passed, unable to accelerate and evaluate") + else: + acceleration = QuantizationAcceleration(model_path, data_dir, job_id) + acceleration.accelerate() + acceleration.evaluate() + +if __name__ == '__main__': + main() \ No newline at end of file