From 42cab09464fdd8239252ad146a02653350f066ef Mon Sep 17 00:00:00 2001
From: Matthew K Defenderfer <mdefende@uab.edu>
Date: Tue, 22 Oct 2024 15:36:01 -0500
Subject: [PATCH 1/2] Merge branch 'main' of gitlab.rc.uab.edu:rc/gpfs-policy
 into main

---
 src/run-policy/run-mmpol.sh          | 154 ++++++++++++++++++++++-----
 src/run-policy/run-submit-pol-job.py |  16 +--
 src/run-policy/submit-pol-job        |  45 +++++---
 3 files changed, 166 insertions(+), 49 deletions(-)

diff --git a/src/run-policy/run-mmpol.sh b/src/run-policy/run-mmpol.sh
index 7eaed0d..b20175e 100755
--- a/src/run-policy/run-mmpol.sh
+++ b/src/run-policy/run-mmpol.sh
@@ -4,43 +4,143 @@ set -euxo pipefail
 
 # run an mmapply policy across the cluster via slurm
 
-# gather info to map mmapplypolicy to runtime configuration
-# arguments passed via job env and runtime context
+############################################################
+# Default Values                                           #
+############################################################
 
-filesystem=${FILESYSTEM:-scratch}
-policyfile=$POLICYFILE
-tmpglobal=$DIR/slurm-tmp-${SLURM_JOBID}
-tmpscratch=$DIR/slurm-tmp-${SLURM_JOBID}
-mkdir -p $tmpglobal
+outdir="/data/rc/gpfs-policy/data"
+policy_file="./policy-def/list-path-external"
+output_log_prefix=""
+dry_run=""
 
-nodes=`scontrol show hostnames "${SLURM_JOB_NODELIST}" | tr '\n' ',' | sed -e 's/,$//'`
+############################################################
+# Help                                                     #
+############################################################
+
+usage()
+{
+>&2 cat << EOF
+Usage: $0 [ -h ] [ -o | --outdir ] [ -f | --output-prefix ] [ -P | --policy-file] device
+EOF
+exit 1
+}
+
+help()
+{
+>&2 cat << EOF
+Runs mmapplypolicy on the specified device/fileset. The policy file dictates the actions performed including list, delete, add, etc. This is most often called by the submit-pol-job wrapper instead of invoked directly
+
+Usage: $0 [ -h ] [ -o | --outdir ] [ -f | --output-prefix ] [ -P | --policy-file ] device
+
+options:
+    -h|--help           Print this Help.
+    --dry-run           Do not run the policy command or any command that modifies any files or directories such as 
+                            mkdir
+
+Required:
+    device              GPFS fileset/directory apply the policy to. Can be 
+                            specified as either the name of the fileset or the 
+                            full path to the directory 
+                            (Examples: scratch, /data/user/[username])
+
+Path:   
+    -o|--outdir         Parent directory to save policy output to 
+                            (default: /data/rc/gpfs-policy/data)
+    -f|--output-prefix  Prefix of the policy output file. Appended with a metadata string containing the policy name, 
+                            job ID, and date
+
+Policy Options:
+    -P|--policy-file    Path to policy file to apply to the given GPFS device
+EOF
+exit 0
+}
+
+args=$(getopt -a -o ho:f:P: --long help,outdir:,output-prefix:,policy-file:,dry-run -- "$@")
+
+if [[ $? -gt 0 ]]; then
+  usage
+fi
+
+eval set -- ${args}
+
+while :
+do
+  case $1 in
+    -h | --help)            help                    ;;
+    -o | --outdir)          outdir=$2               ; shift 2 ;;
+    -f | --output-prefix)   output_log_prefix=$2    ; shift 2 ;;
+    -P | --policy-file)     policy_file=$2          ; shift 2 ;;
+    --dry-run)              dry_run=true            ; shift 1 ;;
+    --) shift; break ;;
+    *) >&2 echo Unsupported option: $1
+       usage ;;
+  esac
+done
+
+if [[ $# -eq 0 ]]; then
+  usage
+fi
+
+device="$1"
+
+# Ensure device is specified
+if [[ -z "${device}" ]]; then
+    echo "Error: Specify either the name of a fileset or a directory path"
+    usage
+fi
+
+# set default output_log_prefix if not specified in the arguments
+if [[ -z "${output_log_prefix}" ]]; then
+    modified_device=$(echo "${device}" | sed -e 's|^/||' -e 's|/$||' -e 's|/|-|g')
+    output_log_prefix="list-policy_${modified_device}"
+fi
+
+# create temporary working directory for list aggregation
+tmpglobal="${outdir}/slurm-tmp-${SLURM_JOBID}"
+tmpscratch="${outdir}/slurm-tmp-${SLURM_JOBID}"
+
+nodes=$(scontrol show hostnames "${SLURM_JOB_NODELIST}" | tr '\n' ',' | sed -e 's/,$//')
 cores="${SLURM_CPUS_PER_TASK}"
 
-DATESTR=`date +'%Y-%m-%d-%H:%M:%S'`
+DATESTR=$(date +'%Y-%m-%dT%H:%M:%S')
 
-policy=`basename $policyfile`
+policy=$(basename ${policy_file})
 filetag="${policy}_slurm-${SLURM_JOBID}_${DATESTR}"
 
-cmd="mmapplypolicy ${filesystem} -I defer \
-  -P $policyfile \
-  -g $tmpglobal \
-  -s $tmpscratch \
-  -f ${DIR}/list-${SLURM_JOBID} \
-  -M FILEPATH=${filesystem} \
+cmd="mmapplypolicy ${device} -I defer \
+  -P ${policy_file} \
+  -g ${tmpglobal} \
+  -s ${tmpscratch} \
+  -f ${outdir}/list-${SLURM_JOBID} \
+  -M FILEPATH=${device} \
   -M JOBID=${SLURM_JOBID} \
-  -M LIST_OUTPUT_FILE=${OUTFILE:-/tmp/gpfs-list-policy}
+  -M LIST_OUTPUT_FILE=${output_log_prefix} \
   -N ${nodes} -n ${cores} -m ${cores}"
 
-# report final command in job log
-echo $cmd
+if [[ ! ${dry_run} ]]; then
+    mkdir -p ${tmpglobal}
+    
+    # run policy command
+    ${cmd}
+
+    log_name="${output_log_prefix}_${filetag}"
+    log_dir="${outdir}/${log_name}"
 
-# run policy command
-$cmd
+    mkdir -p ${log_dir}/raw
+    chmod 1770 ${log_dir}
 
-# tag output file with run metadata
-outfile=`ls -t $tmpglobal | head -1`
-if [[ "$outfile" != "" ]]
-then
-   mv -n $tmpglobal/$outfile $tmpglobal/../${outfile}_$filetag
+    # tag output file with run metadata
+    raw_log_file=$(find ${outdir} -maxdepth 1 -name "list-${SLURM_JOBID}*" -type f | head -1)
+    if [[ "$raw_log_file" != "" ]]; then
+        mv -n ${raw_log_file} ${log_dir}/raw/${log_name}
+        gzip ${log_dir}/raw/${log_name}
+
+        chmod 440 ${log_dir}/raw/${log_name}.gz
+        chmod 550 ${log_dir}/raw
+    fi
+
+    chown -R ${USER}:atlab ${log_dir}
+    
+    rmdir ${tmpglobal}
 fi
-rmdir $tmpglobal
+
diff --git a/src/run-policy/run-submit-pol-job.py b/src/run-policy/run-submit-pol-job.py
index c69cd95..064edc0 100755
--- a/src/run-policy/run-submit-pol-job.py
+++ b/src/run-policy/run-submit-pol-job.py
@@ -36,6 +36,10 @@ def parse_args():
                         help='Time limit for job formatted as [D-]HH:MM:SS')
     sbatch.add_argument('-m','--mem-per-cpu',type=str,default='8G',
                         help='Amount of RAM to allocate per core')
+
+    parser.add_argument('--dry-run', action='store_true',
+                        help="Do not submit any jobs, run any policies, or create or remove any files or directories."
+                             "Used for testing")
     
     parser.add_argument('device',type=str,
                         help="GPFS fileset/directory apply the policy to. Can be specified as either the name of the"
@@ -106,11 +110,8 @@ def validate_output_directory(outdir):
     return p
 
 def create_default_log_prefix(device):
-    if device.match('/data/user'):
-        log_prefix = 'list-policy_data_user'
-    else:
-        log_prefix = f'list-policy_{device.stem}'
-    return log_prefix
+    mod_device = str(device).strip('/').replace('/','-')
+    return f"list-policy_{mod_device}"
 
 def main():
     args = parse_args()
@@ -135,7 +136,10 @@ def main():
     else:
         args['policy'] = './policy-def/list-path-external'
 
-    cmd = "./submit-pol-job -o {outdir} -f {log_prefix} -P {policy} -N {nodes} -c {cores} -p {partition} -t {time} -m {mem_per_cpu} {device}".format(**args)
+    if args['dry_run']:
+        cmd = "./submit-pol-job -o {outdir} -f {log_prefix} -P {policy} -N {nodes} -c {cores} -p {partition} -t {time} -m {mem_per_cpu} --dry-run {device}".format(**args)
+    else:
+        cmd = "./submit-pol-job -o {outdir} -f {log_prefix} -P {policy} -N {nodes} -c {cores} -p {partition} -t {time} -m {mem_per_cpu} {device}".format(**args)
     
     print(f"Command: {cmd}")
     subprocess.run(cmd,shell=True)
diff --git a/src/run-policy/submit-pol-job b/src/run-policy/submit-pol-job
index 1589c24..0bad4ea 100755
--- a/src/run-policy/submit-pol-job
+++ b/src/run-policy/submit-pol-job
@@ -14,6 +14,7 @@ partition="amd-hdr100,medium"
 outdir="/data/rc/gpfs-policy/data"
 policy="./policy-def/list-path-external"
 outfile=""
+dry_run=""
 
 ############################################################
 # Help                                                     #
@@ -23,7 +24,7 @@ usage()
 >&2 cat << EOF
 Usage: $0 [ -h ] [ -o | --outdir ] [ -f | --outfile ] [ --with-dirs ] 
           [ -N | --nodes ] [ -c | --cores ] [ -p | --partition] 
-          [ -t | --time ] [ -m | --mem-per-cpu ]
+          [ -t | --time ] [ -m | --mem-per-cpu ] [ --dry_run ]
           device
 EOF
 exit 1
@@ -38,11 +39,13 @@ as root or via the run-submit-pol-job.py script. The default policy file is
 
 Usage: $0 [ -h ] [ -o | --outdir ] [ -f | --outfile ] [ -P | --policy ] 
           [ -N | --nodes ] [ -c | --cores ] [ -p | --partition ] 
-          [ -t | --time ] [ -m | --mem ]
+          [ -t | --time ] [ -m | --mem ] [ --dry-run ]
           device
 
 options:
-    -h|--help       Print this Help.
+    -h|--help           Print this Help.
+    --dry-run           Do not submit a Slurm job running the policy. Instead, pass --dry-run to run-mmpol.sh and call 
+                            it normally to just print the output to STDOUT
 
 Required:
     device              GPFS fileset/directory apply the policy to. Can be 
@@ -69,7 +72,8 @@ EOF
 exit 0
 }
 
-args=$(getopt -a -o ho:f:P:N:c:p:t:m: --long help,outdir:,outfile:,policy:,nodes:,cores:,partition:,time:,mem: -- "$@")
+args=$(getopt -a -o ho:f:P:N:c:p:t:m: \
+                 --long help,outdir:,outfile:,policy:,nodes:,cores:,partition:,time:,mem:,dry-run -- "$@")
 
 if [[ $? -gt 0 ]]; then
   usage
@@ -89,6 +93,7 @@ do
     -p | --partition)   partition=$2    ; shift 2 ;;
     -t | --time)        time=$2         ; shift 2 ;;
     -m | --mem-per-cpu) mem_per_cpu=$2  ; shift 2 ;;
+    --dry-run)          dry_run=true    ; shift 1 ;;
     --) shift; break ;;
     *) >&2 echo Unsupported option: $1
        usage ;;
@@ -108,15 +113,23 @@ if [[ -z "$device" ]]; then
 fi
 
 slurm_out="out/pol-%A-$(basename ${policy})-$(basename ${device}).out"
-mkdir -p out
-
-DIR=$outdir POLICYFILE=$policy FILESYSTEM=${device} OUTFILE=${outfile} && \
-DIR=$DIR POLICYFILE=$POLICYFILE FILESYSTEM=${FILESYSTEM} OUTFILE=${OUTFILE} \
-sbatch \
-   -N $nodes \
-   -c $cores \
-   -t $time \
-   --mem-per-cpu=$mem_per_cpu \
-   -p $partition \
-   -o ${slurm_out} \
-   ./run-mmpol.sh
+
+run_mmpol_cmd_base="./run-mmpol.sh -o ${outdir} -f ${outfile} -P ${policy}"
+
+if [[ -z "${dry_run}" ]]; then
+    mkdir -p out
+
+    run_mmpol_cmd="${run_mmpol_cmd_base} ${device}"
+
+    sbatch \
+       -N $nodes \
+       -c $cores \
+       -t $time \
+       --mem-per-cpu=$mem_per_cpu \
+       -p $partition \
+       -o ${slurm_out} \
+       --wrap "${run_mmpol_cmd}"
+else
+    run_mmpol_cmd="${run_mmpol_cmd_base} --dry-run ${device}"
+    ${run_mmpol_cmd}
+fi
-- 
GitLab


From 14956a91c7317531af8fffe3ab6eb57054fb3720 Mon Sep 17 00:00:00 2001
From: Matthew K Defenderfer <mdefende@uab.edu>
Date: Fri, 6 Dec 2024 14:21:15 -0600
Subject: [PATCH 2/2] Reorganize code to python package and add functionality
 for processing and visualizing policy logs

---
 .gitignore                                    |    9 +-
 create_symlinks.sh                            |    9 +
 cronwrapper => legacy-scripts/cronwrapper     |    0
 ...r-of-last-access-projects-2024-05-03.ipynb |    0
 ...-validate-galaxy-tar-data-2024-05-03.ipynb |    0
 .../gather-info.sh                            |    0
 .../last-access-per-user.py                   |    0
 .../max-access-per-user-merged.ipynb          |    0
 .../max-access-per-user.ipynb                 |    0
 .../parquet-list-policy-data.ipynb            |    0
 .../pickle-list-policy-data.ipynb             |    0
 ...eport-grouby-tld-year-of-last-access.ipynb |    0
 .../run-list-pickle-nb.sh                     |    0
 .../run-max-atime-per-user                    |    0
 .../scratch-log-explorations.ipynb            |    0
 .../wrap-list-pickle.sh                       |    0
 poetry.lock                                   | 1718 +++++++++++++++++
 pyproject.toml                                |   51 +
 src/rc_gpfs/__init__.py                       |    2 +
 src/rc_gpfs/cli/__init__.py                   |    0
 src/rc_gpfs/cli/gpfs_preproc.py               |   89 +
 src/rc_gpfs/compute/__init__.py               |    2 +
 src/rc_gpfs/compute/backend.py                |  173 ++
 src/rc_gpfs/compute/backend_defs.py           |   42 +
 src/rc_gpfs/compute/utils.py                  |  187 ++
 src/rc_gpfs/policy/__init__.py                |    0
 .../policy}/convert-to-parquet.py             |    0
 .../policy}/run-convert-to-parquet.sh         |    0
 src/{ => rc_gpfs/policy}/split-info-file.sh   |    0
 src/rc_gpfs/process/__init__.py               |    1 +
 src/rc_gpfs/process/factory.py                |  198 ++
 src/rc_gpfs/process/process.py                |  109 ++
 src/rc_gpfs/process/utils.py                  |   29 +
 src/rc_gpfs/report/__init__.py                |    0
 src/rc_gpfs/report/general-report.qmd         |   59 +
 src/rc_gpfs/report/plotting.py                |  135 ++
 src/rc_gpfs/report/reports.ipynb              |  398 ++++
 src/rc_gpfs/utils.py                          |   62 +
 38 files changed, 3272 insertions(+), 1 deletion(-)
 create mode 100755 create_symlinks.sh
 rename cronwrapper => legacy-scripts/cronwrapper (100%)
 rename dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb => legacy-scripts/dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb (100%)
 rename dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb => legacy-scripts/dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb (100%)
 rename gather-info.sh => legacy-scripts/gather-info.sh (100%)
 rename last-access-per-user.py => legacy-scripts/last-access-per-user.py (100%)
 rename max-access-per-user-merged.ipynb => legacy-scripts/max-access-per-user-merged.ipynb (100%)
 rename max-access-per-user.ipynb => legacy-scripts/max-access-per-user.ipynb (100%)
 rename parquet-list-policy-data.ipynb => legacy-scripts/parquet-list-policy-data.ipynb (100%)
 rename pickle-list-policy-data.ipynb => legacy-scripts/pickle-list-policy-data.ipynb (100%)
 rename report-grouby-tld-year-of-last-access.ipynb => legacy-scripts/report-grouby-tld-year-of-last-access.ipynb (100%)
 rename run-list-pickle-nb.sh => legacy-scripts/run-list-pickle-nb.sh (100%)
 rename run-max-atime-per-user => legacy-scripts/run-max-atime-per-user (100%)
 rename scratch-log-explorations.ipynb => legacy-scripts/scratch-log-explorations.ipynb (100%)
 rename wrap-list-pickle.sh => legacy-scripts/wrap-list-pickle.sh (100%)
 create mode 100644 poetry.lock
 create mode 100644 pyproject.toml
 create mode 100644 src/rc_gpfs/__init__.py
 create mode 100644 src/rc_gpfs/cli/__init__.py
 create mode 100644 src/rc_gpfs/cli/gpfs_preproc.py
 create mode 100644 src/rc_gpfs/compute/__init__.py
 create mode 100644 src/rc_gpfs/compute/backend.py
 create mode 100644 src/rc_gpfs/compute/backend_defs.py
 create mode 100644 src/rc_gpfs/compute/utils.py
 create mode 100644 src/rc_gpfs/policy/__init__.py
 rename src/{convert-to-parquet => rc_gpfs/policy}/convert-to-parquet.py (100%)
 rename src/{convert-to-parquet => rc_gpfs/policy}/run-convert-to-parquet.sh (100%)
 rename src/{ => rc_gpfs/policy}/split-info-file.sh (100%)
 create mode 100644 src/rc_gpfs/process/__init__.py
 create mode 100644 src/rc_gpfs/process/factory.py
 create mode 100644 src/rc_gpfs/process/process.py
 create mode 100644 src/rc_gpfs/process/utils.py
 create mode 100644 src/rc_gpfs/report/__init__.py
 create mode 100644 src/rc_gpfs/report/general-report.qmd
 create mode 100644 src/rc_gpfs/report/plotting.py
 create mode 100644 src/rc_gpfs/report/reports.ipynb
 create mode 100644 src/rc_gpfs/utils.py

diff --git a/.gitignore b/.gitignore
index ef59c08..e979021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,14 @@
 data
+local-data/
 joblogs/
 slurm-*
 out/
 err/
 *.sif
-__pycache__
\ No newline at end of file
+__pycache__
+quarto*
+cufile.log
+*.html
+general-report_files
+poetry.toml
+.vscode
\ No newline at end of file
diff --git a/create_symlinks.sh b/create_symlinks.sh
new file mode 100755
index 0000000..5f36987
--- /dev/null
+++ b/create_symlinks.sh
@@ -0,0 +1,9 @@
+#!/bin/bash
+mkdir -p bin
+# Find all .py and .sh files in the src directory and its subdirectories
+find src/ -type f \( -name "*.py" -o -name "*.sh" \) | while read -r file; do
+  # Get the base name of the file
+  base=$(basename "$file")
+  # Create a symbolic link in the bin directory
+  ln -sf "../$file" "bin/$base"
+done
\ No newline at end of file
diff --git a/cronwrapper b/legacy-scripts/cronwrapper
similarity index 100%
rename from cronwrapper
rename to legacy-scripts/cronwrapper
diff --git a/dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb b/legacy-scripts/dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb
similarity index 100%
rename from dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb
rename to legacy-scripts/dask-mpi-report-grouby-tld-year-of-last-access-projects-2024-05-03.ipynb
diff --git a/dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb b/legacy-scripts/dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb
similarity index 100%
rename from dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb
rename to legacy-scripts/dask-mpi-validate-galaxy-tar-data-2024-05-03.ipynb
diff --git a/gather-info.sh b/legacy-scripts/gather-info.sh
similarity index 100%
rename from gather-info.sh
rename to legacy-scripts/gather-info.sh
diff --git a/last-access-per-user.py b/legacy-scripts/last-access-per-user.py
similarity index 100%
rename from last-access-per-user.py
rename to legacy-scripts/last-access-per-user.py
diff --git a/max-access-per-user-merged.ipynb b/legacy-scripts/max-access-per-user-merged.ipynb
similarity index 100%
rename from max-access-per-user-merged.ipynb
rename to legacy-scripts/max-access-per-user-merged.ipynb
diff --git a/max-access-per-user.ipynb b/legacy-scripts/max-access-per-user.ipynb
similarity index 100%
rename from max-access-per-user.ipynb
rename to legacy-scripts/max-access-per-user.ipynb
diff --git a/parquet-list-policy-data.ipynb b/legacy-scripts/parquet-list-policy-data.ipynb
similarity index 100%
rename from parquet-list-policy-data.ipynb
rename to legacy-scripts/parquet-list-policy-data.ipynb
diff --git a/pickle-list-policy-data.ipynb b/legacy-scripts/pickle-list-policy-data.ipynb
similarity index 100%
rename from pickle-list-policy-data.ipynb
rename to legacy-scripts/pickle-list-policy-data.ipynb
diff --git a/report-grouby-tld-year-of-last-access.ipynb b/legacy-scripts/report-grouby-tld-year-of-last-access.ipynb
similarity index 100%
rename from report-grouby-tld-year-of-last-access.ipynb
rename to legacy-scripts/report-grouby-tld-year-of-last-access.ipynb
diff --git a/run-list-pickle-nb.sh b/legacy-scripts/run-list-pickle-nb.sh
similarity index 100%
rename from run-list-pickle-nb.sh
rename to legacy-scripts/run-list-pickle-nb.sh
diff --git a/run-max-atime-per-user b/legacy-scripts/run-max-atime-per-user
similarity index 100%
rename from run-max-atime-per-user
rename to legacy-scripts/run-max-atime-per-user
diff --git a/scratch-log-explorations.ipynb b/legacy-scripts/scratch-log-explorations.ipynb
similarity index 100%
rename from scratch-log-explorations.ipynb
rename to legacy-scripts/scratch-log-explorations.ipynb
diff --git a/wrap-list-pickle.sh b/legacy-scripts/wrap-list-pickle.sh
similarity index 100%
rename from wrap-list-pickle.sh
rename to legacy-scripts/wrap-list-pickle.sh
diff --git a/poetry.lock b/poetry.lock
new file mode 100644
index 0000000..a8358e3
--- /dev/null
+++ b/poetry.lock
@@ -0,0 +1,1718 @@
+# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
+
+[[package]]
+name = "cachetools"
+version = "5.5.0"
+description = "Extensible memoizing collections and decorators"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"},
+    {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.7"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
+    {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "cloudpickle"
+version = "3.1.0"
+description = "Pickler class to extend the standard pickle.Pickler functionality"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "cloudpickle-3.1.0-py3-none-any.whl", hash = "sha256:fe11acda67f61aaaec473e3afe030feb131d78a43461b718185363384f1ba12e"},
+    {file = "cloudpickle-3.1.0.tar.gz", hash = "sha256:81a929b6e3c7335c863c771d673d105f02efdb89dfaba0c90495d1c64796601b"},
+]
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+    {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+    {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "cuda-python"
+version = "12.6.2.post1"
+description = "Python bindings for CUDA"
+optional = false
+python-versions = "*"
+files = [
+    {file = "cuda_python-12.6.2.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be1f268aae08d509e4af2d8b8465b74351de39d8f439a5a98caf9b276e027d9b"},
+    {file = "cuda_python-12.6.2.post1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee91c92f34fc140b8e241a2681747cfb4442fa3a9dc817376d3090f6c73a0c0f"},
+    {file = "cuda_python-12.6.2.post1-cp310-cp310-win_amd64.whl", hash = "sha256:839f32f19dbd496c4ed82398f810472504173b8a583ef13397f681abfaa30550"},
+    {file = "cuda_python-12.6.2.post1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20271f7979495435e2192758ca52a227dc70e04af1453442a44d3149b94c4630"},
+    {file = "cuda_python-12.6.2.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d47ba3464fd890025a6d1929dac5e71f2d8c16d0abfe461123cf8be1f975d3a0"},
+    {file = "cuda_python-12.6.2.post1-cp311-cp311-win_amd64.whl", hash = "sha256:8146b16197f5166775f9d90e67e36ac2d316563f226d9ae64a52b462eb59dee3"},
+    {file = "cuda_python-12.6.2.post1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea1e5b26cdfa424c26f098f6ec17340608b73dddc5fd2059f5b31897eabd9916"},
+    {file = "cuda_python-12.6.2.post1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ccda3b9400dd6568b3db118aae1c783ef26f2457d6635197999fa42b46b5187"},
+    {file = "cuda_python-12.6.2.post1-cp312-cp312-win_amd64.whl", hash = "sha256:3bd77d1964233df464486f7740e9c17b1e3576aba613ba295e0bd096a77cdd19"},
+    {file = "cuda_python-12.6.2.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ea25bf01b08563b2d9ff207db1f6c9beb21cd79528fc3f818cf1771c6a05306"},
+    {file = "cuda_python-12.6.2.post1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfb5bdafdc26b47cd345861e6374843bede99fe81804377404c35c19e59e21d"},
+    {file = "cuda_python-12.6.2.post1-cp39-cp39-win_amd64.whl", hash = "sha256:e2cb76b0ec491bc5afd6412d35dd1e84b3b6cd2c11ab4fda9337b01dffa4018d"},
+]
+
+[package.dependencies]
+pywin32 = {version = "*", markers = "sys_platform == \"win32\""}
+
+[[package]]
+name = "cudf-cu12"
+version = "24.10.1"
+description = "cuDF - GPU Dataframe"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "cudf_cu12-24.10.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:06460fb780a62c1289a6090e52864a5ed82273e370530a572cbd23b1355a793c"},
+    {file = "cudf_cu12-24.10.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ebf8a043a41891162ac7af4dcb6983c4602aee9b29584a35cbe4dcefeb9c012"},
+    {file = "cudf_cu12-24.10.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8670780b1580453b1dac75b5cf2c0ca22879249559ed205cf92cb45470971a5f"},
+    {file = "cudf_cu12-24.10.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e3234eaf3570510e80725ea291ff7be44dccb34cedfbaa0176a3786ba7fa5f8"},
+    {file = "cudf_cu12-24.10.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:abebc6ab505d4c8b5cf50e364542ac1b699526c9dae8fb6587f7852cceb9fdb4"},
+    {file = "cudf_cu12-24.10.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fb1934d908e33b61a4ded6c33f8305b78947d01149ed5c37b07e0c581ebbc136"},
+]
+
+[package.dependencies]
+cachetools = "*"
+cuda-python = ">=12.0,<13.0a0"
+cupy-cuda12x = ">=12.0.0"
+fsspec = ">=0.6.0"
+libcudf-cu12 = "==24.10.*"
+numba = ">=0.57"
+numpy = ">=1.23,<3.0a0"
+nvtx = ">=0.2.1"
+packaging = "*"
+pandas = ">=2.0,<2.2.3dev0"
+pyarrow = ">=14.0.0,<18.0.0a0"
+pylibcudf-cu12 = "==24.10.*"
+pynvjitlink-cu12 = "*"
+rich = "*"
+rmm-cu12 = "==24.10.*"
+typing_extensions = ">=4.0.0"
+
+[package.extras]
+cudf-pandas-tests = ["ipython", "jupyter_client", "nbconvert", "nbformat", "openpyxl"]
+pandas-tests = ["ipython", "pandas[clipboard,compression,computation,excel,feather,fss,hdf5,html,output-formatting,parquet,performance,plot,pyarrow,spss,test,xml]", "pytest-reportlog"]
+test = ["cramjam", "fastavro (>=0.22.9)", "hypothesis", "msgpack", "pytest (<8)", "pytest-benchmark", "pytest-cases (>=3.8.2)", "pytest-cov", "pytest-xdist", "scipy", "tokenizers (==0.15.2)", "transformers (==4.39.3)", "tzdata"]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.nvidia.com"
+reference = "rapids"
+
+[[package]]
+name = "cuml-cu12"
+version = "24.10.0"
+description = "cuML - RAPIDS ML Algorithms"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "cuml_cu12-24.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:2cf26e139a781101a461ad5b615de93411e8084c10a2baaa9a9084814ae001ba"},
+    {file = "cuml_cu12-24.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0481db8e341da62c1d6baf9a00cd3958f89895006a772c3968d129ef71c2344b"},
+    {file = "cuml_cu12-24.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:2bbcc8ac8cbb17d4f9607ef04d64d71d31451f367f429e7eef5a60dabeb76253"},
+    {file = "cuml_cu12-24.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:85a88e55d3ae1e01c0a72a834fba92e5ec9e8b11959c4399449c658b43ccb63d"},
+    {file = "cuml_cu12-24.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:6209cffc7be5090c9abf0bac75d0fad8d72cabfcac4d1e3da8a7090d40757a21"},
+    {file = "cuml_cu12-24.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:47647af4bc997cb2bc8f5713b2c4187f7b427c5d2bc745468697711544c26e76"},
+]
+
+[package.dependencies]
+cudf-cu12 = "==24.10.*"
+cupy-cuda12x = ">=12.0.0"
+cuvs-cu12 = "==24.10.*"
+dask-cuda = "==24.10.*"
+dask-cudf-cu12 = "==24.10.*"
+joblib = ">=0.11"
+numba = ">=0.57"
+numpy = ">=1.23,<3.0a0"
+nvidia-cublas-cu12 = "*"
+nvidia-cufft-cu12 = "*"
+nvidia-curand-cu12 = "*"
+nvidia-cusolver-cu12 = "*"
+nvidia-cusparse-cu12 = "*"
+packaging = "*"
+pylibraft-cu12 = "==24.10.*"
+raft-dask-cu12 = "==24.10.*"
+rapids-dask-dependency = "==24.10.*"
+rmm-cu12 = "==24.10.*"
+scipy = ">=1.8.0"
+treelite = "4.3.0"
+
+[package.extras]
+test = ["cython (>=3.0.0)", "dask-ml", "hdbscan (>=0.8.38,<0.8.39)", "hypothesis (>=6.0,<7)", "nltk", "numpydoc", "pynndescent", "pytest (==7.*)", "pytest-benchmark", "pytest-cases", "pytest-cov", "pytest-xdist", "scikit-learn (==1.5)", "seaborn", "setuptools", "statsmodels", "umap-learn (==0.5.6)"]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.nvidia.com"
+reference = "rapids"
+
+[[package]]
+name = "cupy-cuda12x"
+version = "13.3.0"
+description = "CuPy: NumPy & SciPy for GPU"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "cupy_cuda12x-13.3.0-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:674488e990998042cc54d2486d3c37cae80a12ba3787636be5a10b9446dd6914"},
+    {file = "cupy_cuda12x-13.3.0-cp310-cp310-manylinux2014_x86_64.whl", hash = "sha256:cf4a2a0864364715881b50012927e88bd7ec1e6f1de3987970870861ae5ed25e"},
+    {file = "cupy_cuda12x-13.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:7c0dc8c49d271d1c03e49a5d6c8e42e8fee3114b10f269a5ecc387731d693eaa"},
+    {file = "cupy_cuda12x-13.3.0-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:c0cc095b9a3835fd5db66c45ed3c58ecdc5a3bb14e53e1defbfd4a0ce5c8ecdb"},
+    {file = "cupy_cuda12x-13.3.0-cp311-cp311-manylinux2014_x86_64.whl", hash = "sha256:a0e3bead04e502ebde515f0343444ca3f4f7aed09cbc3a316a946cba97f2ea66"},
+    {file = "cupy_cuda12x-13.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:5f11df1149c7219858b27e4c8be92cb4eaf7364c94af6b78c40dffb98050a61f"},
+    {file = "cupy_cuda12x-13.3.0-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:bbd0d916310391faf0d7dc9c58fff7a6dc996b67e5768199160bbceb5ebdda8c"},
+    {file = "cupy_cuda12x-13.3.0-cp312-cp312-manylinux2014_x86_64.whl", hash = "sha256:e206bd8664f0763732b6012431f484ee535bffd77a5ae95e9bfe1c7c72396625"},
+    {file = "cupy_cuda12x-13.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:88ef1478f00ae252da0026e7f04f70c9bb6a2dc130ba5f1e5bc5e8069a928bf5"},
+    {file = "cupy_cuda12x-13.3.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:3a52aa49ffcc940d034f2bb39728c90e9fa83c7a49e376404507956adb6d6ec4"},
+    {file = "cupy_cuda12x-13.3.0-cp39-cp39-manylinux2014_x86_64.whl", hash = "sha256:3ef13f3cbc449d2a0f816594ab1fa0236e1f06ad1eaa81ad04c75e47cbeb87be"},
+    {file = "cupy_cuda12x-13.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:8f5433eec3e5cd8d39e8fcb82e0fdab7c22eba8e3304fcb0b42f2ea988fef0d6"},
+]
+
+[package.dependencies]
+fastrlock = ">=0.5"
+numpy = ">=1.22,<2.3"
+
+[package.extras]
+all = ["Cython (>=0.29.22,<3)", "optuna (>=2.0)", "scipy (>=1.7,<1.14)"]
+stylecheck = ["autopep8 (==1.5.5)", "flake8 (==3.8.4)", "mypy (==1.4.1)", "pbr (==5.5.1)", "pycodestyle (==2.6.0)", "types-setuptools (==57.4.14)"]
+test = ["hypothesis (>=6.37.2,<6.55.0)", "mpmath", "packaging", "pytest (>=7.2)"]
+
+[[package]]
+name = "cuvs-cu12"
+version = "24.10.0"
+description = "cuVS: Vector Search on the GPU"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "cuvs_cu12-24.10.0.tar.gz", hash = "sha256:7fa7349b6b74557fd6adc8cb6ae912d3bc6f0570e2c85e66484d7c2242d8331c"},
+]
+
+[package.dependencies]
+cuda-python = ">=12.0,<13.0a0"
+numpy = ">=1.23,<3.0a0"
+nvidia-cublas-cu12 = "*"
+nvidia-curand-cu12 = "*"
+nvidia-cusolver-cu12 = "*"
+nvidia-cusparse-cu12 = "*"
+pylibraft-cu12 = "==24.10.*"
+
+[package.extras]
+test = ["cupy-cuda12x (>=12.0.0)", "pytest (==7.*)", "pytest-cov", "scikit-learn"]
+
+[[package]]
+name = "dask"
+version = "2024.9.0"
+description = "Parallel PyData with Task Scheduling"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "dask-2024.9.0-py3-none-any.whl", hash = "sha256:ceede9cfd418178a01ec3d11a0cde3f46678bd4a292ba84b57bbb401ce3f1cb8"},
+    {file = "dask-2024.9.0.tar.gz", hash = "sha256:bfbe5b6c3b7937426539be27029800178ce63cea4da8d7e7de836a98384aa1d6"},
+]
+
+[package.dependencies]
+click = ">=8.1"
+cloudpickle = ">=3.0.0"
+fsspec = ">=2021.09.0"
+importlib-metadata = {version = ">=4.13.0", markers = "python_version < \"3.12\""}
+packaging = ">=20.0"
+partd = ">=1.4.0"
+pyyaml = ">=5.3.1"
+toolz = ">=0.10.0"
+
+[package.extras]
+array = ["numpy (>=1.24)"]
+complete = ["dask[array,dataframe,diagnostics,distributed]", "lz4 (>=4.3.2)", "pyarrow (>=14.0.1)"]
+dataframe = ["dask-expr (>=1.1,<1.2)", "dask[array]", "pandas (>=2.0)"]
+diagnostics = ["bokeh (>=3.1.0)", "jinja2 (>=2.10.3)"]
+distributed = ["distributed (==2024.9.0)"]
+test = ["pandas[test]", "pre-commit", "pytest", "pytest-cov", "pytest-rerunfailures", "pytest-timeout", "pytest-xdist"]
+
+[[package]]
+name = "dask-cuda"
+version = "24.10.0"
+description = "Utilities for Dask and CUDA interactions"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "dask_cuda-24.10.0-py3-none-any.whl", hash = "sha256:3666ba38169c6c4448c698c0fe05d3fe5bb9b1c13b6de86030c7e6ad3e2dc2be"},
+    {file = "dask_cuda-24.10.0.tar.gz", hash = "sha256:503946e95736081bf88afc8e2faa3b2cc8a63cd757528c45ac4cdac822a77560"},
+]
+
+[package.dependencies]
+click = ">=8.1"
+numba = ">=0.57"
+numpy = ">=1.23,<3.0a0"
+pandas = ">=1.3"
+pynvml = ">=11.0.0,<11.5"
+rapids-dask-dependency = "==24.10.*"
+zict = ">=2.0.0"
+
+[package.extras]
+docs = ["numpydoc (>=1.1.0)", "sphinx", "sphinx-click (>=2.7.1)", "sphinx-rtd-theme (>=0.5.1)"]
+test = ["cudf (==24.10.*)", "dask-cudf (==24.10.*)", "kvikio (==24.10.*)", "pytest", "pytest-cov", "ucx-py (==0.40.*)"]
+
+[[package]]
+name = "dask-cudf-cu12"
+version = "24.10.1"
+description = "Utilities for Dask and cuDF interactions"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "dask_cudf_cu12-24.10.1-py3-none-any.whl", hash = "sha256:9f04be126ffc5921298c51d2888572ac0e31ac77a9dd67920ecec1a1b40978e8"},
+]
+
+[package.dependencies]
+cudf-cu12 = "==24.10.*"
+cupy-cuda12x = ">=12.0.0"
+fsspec = ">=0.6.0"
+numpy = ">=1.23,<3.0a0"
+pandas = ">=2.0,<2.2.3dev0"
+rapids-dask-dependency = "==24.10.*"
+
+[package.extras]
+test = ["dask-cuda (==24.10.*)", "numba (>=0.57)", "pytest (<8)", "pytest-cov", "pytest-xdist"]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.nvidia.com"
+reference = "rapids"
+
+[[package]]
+name = "dask-expr"
+version = "1.1.14"
+description = "High Level Expressions for Dask"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "dask_expr-1.1.14-py3-none-any.whl", hash = "sha256:86a59c51a84d88b6a653f5b4e2c578213a349a963206dcd29219e4826560ada2"},
+    {file = "dask_expr-1.1.14.tar.gz", hash = "sha256:c67c0c190d933e253f85674da71615f23ed8ba08f3abcc0cd160080643feb40a"},
+]
+
+[package.dependencies]
+dask = "2024.9.0"
+pandas = ">=2"
+pyarrow = ">=14.0.1"
+
+[package.extras]
+analyze = ["crick", "distributed"]
+
+[[package]]
+name = "distributed"
+version = "2024.9.0"
+description = "Distributed scheduler for Dask"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "distributed-2024.9.0-py3-none-any.whl", hash = "sha256:d3a6407efffd3ab70a7f3a068be0f15c7ba36adc4fc666ed3d3505ced2c79cbe"},
+    {file = "distributed-2024.9.0.tar.gz", hash = "sha256:cb5f76ff230fc2249b15b0a66da46982e23249e5d8389c16dfe427e598b71c57"},
+]
+
+[package.dependencies]
+click = ">=8.0"
+cloudpickle = ">=3.0.0"
+dask = "2024.9.0"
+jinja2 = ">=2.10.3"
+locket = ">=1.0.0"
+msgpack = ">=1.0.2"
+packaging = ">=20.0"
+psutil = ">=5.8.0"
+pyyaml = ">=5.4.1"
+sortedcontainers = ">=2.0.5"
+tblib = ">=1.6.0"
+toolz = ">=0.11.2"
+tornado = ">=6.2.0"
+urllib3 = ">=1.26.5"
+zict = ">=3.0.0"
+
+[[package]]
+name = "distributed-ucxx-cu12"
+version = "0.40.0"
+description = "UCX communication module for Dask Distributed"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "distributed_ucxx_cu12-0.40.0.tar.gz", hash = "sha256:24b83f706eba001016aa82df0d7bea8ff1ef5d5e8eb089ea1673869c19e54072"},
+]
+
+[package.dependencies]
+numba = ">=0.57.1"
+rapids-dask-dependency = "==24.10.*"
+ucxx-cu12 = "==0.40.*"
+
+[package.extras]
+docs = ["sphinx", "sphinx-click (>=2.7.1)", "sphinx-rtd-theme (>=0.5.1)"]
+test = ["cudf-cu12 (==24.10.*)", "cupy-cuda12x (>=12.0.0)", "numpy (>=1.23,<3.0a0)", "pytest (==7.*)", "pytest-rerunfailures"]
+
+[[package]]
+name = "fastrlock"
+version = "0.8.2"
+description = "Fast, re-entrant optimistic lock implemented in Cython"
+optional = false
+python-versions = "*"
+files = [
+    {file = "fastrlock-0.8.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:94e348c72a1fd1f8191f25ea056448e4f5a87b8fbf005b39d290dcb0581a48cd"},
+    {file = "fastrlock-0.8.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2d5595903444c854b99c42122b87edfe8a37cd698a4eae32f4fd1d2a7b6c115d"},
+    {file = "fastrlock-0.8.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e4bbde174a0aff5f6eeba75cf8c4c5d2a316316bc21f03a0bddca0fc3659a6f3"},
+    {file = "fastrlock-0.8.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7a2ccaf88ac0db153e84305d1ef0aa138cea82c6a88309066f6eaa3bc98636cd"},
+    {file = "fastrlock-0.8.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:31a27a2edf482df72b91fe6c6438314d2c65290aa7becc55589d156c9b91f0da"},
+    {file = "fastrlock-0.8.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:e9904b5b37c3e5bb4a245c56bc4b7e497da57ffb8528f4fc39af9dcb168ee2e1"},
+    {file = "fastrlock-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:43a241655e83e4603a152192cf022d5ca348c2f4e56dfb02e5c9c4c1a32f9cdb"},
+    {file = "fastrlock-0.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9121a894d74e65557e47e777060a495ab85f4b903e80dd73a3c940ba042920d7"},
+    {file = "fastrlock-0.8.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:11bbbbc526363955aeddb9eec4cee2a0012322b7b2f15b54f44454fcf4fd398a"},
+    {file = "fastrlock-0.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:27786c62a400e282756ae1b090bcd7cfa35f28270cff65a9e7b27a5327a32561"},
+    {file = "fastrlock-0.8.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:08315bde19d0c2e6b06593d5a418be3dc8f9b1ee721afa96867b9853fceb45cf"},
+    {file = "fastrlock-0.8.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8b49b5743ede51e0bcf6805741f39f5e0e0fd6a172ba460cb39e3097ba803bb"},
+    {file = "fastrlock-0.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b443e73a4dfc7b6e0800ea4c13567b9694358e86f53bb2612a51c9e727cac67b"},
+    {file = "fastrlock-0.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:b3853ed4ce522598dc886160a7bab432a093051af85891fa2f5577c1dcac8ed6"},
+    {file = "fastrlock-0.8.2-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:790fc19bccbd39426060047e53629f171a44745613bf360a045e9f9c8c4a2cea"},
+    {file = "fastrlock-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:dbdce852e6bb66e1b8c36679d482971d69d93acf1785657522e51b7de30c3356"},
+    {file = "fastrlock-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d47713ffe6d4a627fbf078be9836a95ac106b4a0543e3841572c91e292a5d885"},
+    {file = "fastrlock-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:ea96503b918fceaf40443182742b8964d47b65c5ebdea532893cb9479620000c"},
+    {file = "fastrlock-0.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c6bffa978793bea5e1b00e677062e53a62255439339591b70e209fa1552d5ee0"},
+    {file = "fastrlock-0.8.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:75c07726c8b1a52147fd7987d6baaa318c5dced1416c3f25593e40f56e10755b"},
+    {file = "fastrlock-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:88f079335e9da631efa64486c8207564a7bcd0c00526bb9e842e9d5b7e50a6cc"},
+    {file = "fastrlock-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4fb2e77ff04bc4beb71d63c8e064f052ce5a6ea1e001d528d4d7f4b37d736f2e"},
+    {file = "fastrlock-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:b4c9083ea89ab236b06e9ef2263971db3b4b507195fc7d5eecab95828dcae325"},
+    {file = "fastrlock-0.8.2-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:98195866d3a9949915935d40a88e4f1c166e82e378f622c88025f2938624a90a"},
+    {file = "fastrlock-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b22ea9bf5f9fad2b0077e944a7813f91593a4f61adf8faf734a70aed3f2b3a40"},
+    {file = "fastrlock-0.8.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc1bf0ac8a194313cf6e645e300a8a379674ceed8e0b1e910a2de3e3c28989e"},
+    {file = "fastrlock-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a3dcc876050b8f5cbc0ee84ef1e7f0c1dfe7c148f10098828bc4403683c33f10"},
+    {file = "fastrlock-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:685e656048b59d8dfde8c601f188ad53a4d719eb97080cafc8696cda6d75865e"},
+    {file = "fastrlock-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:fb5363cf0fddd9b50525ddbf64a1e1b28ec4c6dfb28670a940cb1cf988a6786b"},
+    {file = "fastrlock-0.8.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:a74f5a92fa6e51c4f3c69b29c4662088b97be12f40652a21109605a175c81824"},
+    {file = "fastrlock-0.8.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ccf39ad5702e33e4d335b48ef9d56e21619b529b7f7471b5211419f380329b62"},
+    {file = "fastrlock-0.8.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:66f2662c640bb71a1016a031eea6eef9d25c2bcdf7ffd1d1ddc5a58f9a1ced04"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:17734e2e5af4c07ddb0fb10bd484e062c22de3be6b67940b9cc6ec2f18fa61ba"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ab91b0c36e95d42e1041a4907e3eefd06c482d53af3c7a77be7e214cc7cd4a63"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b32fdf874868326351a75b1e4c02f97e802147119ae44c52d3d9da193ec34f5b"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:2074548a335fcf7d19ebb18d9208da9e33b06f745754466a7e001d2b1c58dd19"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fb04442b6d1e2b36c774919c6bcbe3339c61b337261d4bd57e27932589095af"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1fed2f4797ad68e9982038423018cf08bec5f4ce9fed63a94a790773ed6a795c"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e380ec4e6d8b26e389713995a43cb7fe56baea2d25fe073d4998c4821a026211"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:25945f962c7bd808415cfde3da624d4399d4ea71ed8918538375f16bceb79e1c"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:2c1719ddc8218b01e82fb2e82e8451bd65076cb96d7bef4477194bbb4305a968"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5460c5ee6ced6d61ec8cd2324ebbe793a4960c4ffa2131ffff480e3b61c99ec5"},
+    {file = "fastrlock-0.8.2-cp36-cp36m-win_amd64.whl", hash = "sha256:33145acbad8317584cd64588131c7e1e286beef6280c0009b4544c91fce171d2"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:59344c1d46b7dec97d3f22f1cc930fafe8980b3c5bc9c9765c56738a5f1559e4"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2a1c354f13f22b737621d914f3b4a8434ae69d3027a775e94b3e671756112f9"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:cf81e0278b645004388873e0a1f9e3bc4c9ab8c18e377b14ed1a544be4b18c9a"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1b15430b93d7eb3d56f6ff690d2ebecb79ed0e58248427717eba150a508d1cd7"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:067edb0a0805bf61e17a251d5046af59f6e9d2b8ad01222e0ef7a0b7937d5548"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eb31fe390f03f7ae886dcc374f1099ec88526631a4cb891d399b68181f154ff0"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:643e1e65b4f5b284427e61a894d876d10459820e93aa1e724dfb415117be24e0"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:5dfb78dd600a12f23fc0c3ec58f81336229fdc74501ecf378d1ce5b3f2f313ea"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8ca0fe21458457077e4cb2d81e1ebdb146a00b3e9e2db6180a773f7ea905032"},
+    {file = "fastrlock-0.8.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d918dfe473291e8bfd8e13223ea5cb9b317bd9f50c280923776c377f7c64b428"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c393af77c659a38bffbca215c0bcc8629ba4299568308dd7e4ff65d62cabed39"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73426f5eb2ecc10626c67cf86bd0af9e00d53e80e5c67d5ce8e18376d6abfa09"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:320fd55bafee3eb069cfb5d6491f811a912758387ef2193840e2663e80e16f48"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c1c91a68926421f5ccbc82c85f83bd3ba593b121a46a1b9a554b3f0dd67a4bf"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ad1bc61c7f6b0e58106aaab034916b6cb041757f708b07fbcdd9d6e1ac629225"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:87f4e01b042c84e6090dbc4fbe3415ddd69f6bc0130382323f9d3f1b8dd71b46"},
+    {file = "fastrlock-0.8.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d34546ad2e4a480b94b6797bcc5a322b3c705c4c74c3e4e545c4a3841c1b2d59"},
+    {file = "fastrlock-0.8.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ebb32d776b61acd49f859a1d16b9e3d84e7b46d0d92aebd58acd54dc38e96664"},
+    {file = "fastrlock-0.8.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:30bdbe4662992348132d03996700e1cf910d141d629179b967b146a22942264e"},
+    {file = "fastrlock-0.8.2-cp38-cp38-win_amd64.whl", hash = "sha256:07ed3c7b3867c05a3d6be4ced200c7767000f3431b9be6da66972822dd86e8be"},
+    {file = "fastrlock-0.8.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:ddf5d247f686aec853ddcc9a1234bfcc6f57b0a0670d2ad82fc25d8ae7e6a15f"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7269bb3fc15587b0c191eecd95831d771a7d80f0c48929e560806b038ff3066c"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adcb9e77aa132cc6c9de2ffe7cf880a20aa8cdba21d367d1da1a412f57bddd5d"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:a3b8b5d2935403f1b4b25ae324560e94b59593a38c0d2e7b6c9872126a9622ed"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2587cedbb36c7988e707d83f0f1175c1f882f362b5ebbee25d70218ea33d220d"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:9af691a9861027181d4de07ed74f0aee12a9650ac60d0a07f4320bff84b5d95f"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:99dd6652bd6f730beadf74ef769d38c6bbd8ee6d1c15c8d138ea680b0594387f"},
+    {file = "fastrlock-0.8.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4d63b6596368dab9e0cc66bf047e7182a56f33b34db141816a4f21f5bf958228"},
+    {file = "fastrlock-0.8.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ff75c90663d6e8996610d435e71487daa853871ad1770dd83dc0f2fc4997241e"},
+    {file = "fastrlock-0.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e27c3cd27fbd25e5223c5c992b300cd4ee8f0a75c6f222ce65838138d853712c"},
+    {file = "fastrlock-0.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:dd961a32a7182c3891cdebca417fda67496d5d5de6ae636962254d22723bdf52"},
+    {file = "fastrlock-0.8.2.tar.gz", hash = "sha256:644ec9215cf9c4df8028d8511379a15d9c1af3e16d80e47f1b6fdc6ba118356a"},
+]
+
+[[package]]
+name = "fsspec"
+version = "2024.10.0"
+description = "File-system specification"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871"},
+    {file = "fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493"},
+]
+
+[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 = "importlib-metadata"
+version = "8.5.0"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"},
+    {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"},
+]
+
+[package.dependencies]
+zipp = ">=3.20"
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+perf = ["ipython"]
+test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"]
+type = ["pytest-mypy"]
+
+[[package]]
+name = "jinja2"
+version = "3.1.4"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
+    {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
+]
+
+[package.dependencies]
+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 = "libcudf-cu12"
+version = "24.10.1"
+description = "cuDF - GPU Dataframe (C++)"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "libcudf_cu12-24.10.1-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:72ce5d7d0a8c3cb67e3617ae07b130dd42d714c4b1be6fe7d343764582b5ae09"},
+    {file = "libcudf_cu12-24.10.1-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:59eaabf315efb7da8e3d6add14806d4f41b4fd664a9d636d72c1638c9574882a"},
+]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.nvidia.com"
+reference = "rapids"
+
+[[package]]
+name = "libucx-cu12"
+version = "1.17.0"
+description = "The Unified Communication X library (UCX)"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "libucx_cu12-1.17.0-py3-none-manylinux_2_28_aarch64.whl", hash = "sha256:e10b88d8952a6d874c39f2a779be14e555c8c4db88324e4adf25e3e185af063b"},
+    {file = "libucx_cu12-1.17.0-py3-none-manylinux_2_28_x86_64.whl", hash = "sha256:242b6b7568e3546b7af7e51b91a0e0f65951bc7c9c8a82431220231b93d802bf"},
+]
+
+[[package]]
+name = "libucxx-cu12"
+version = "0.40.0"
+description = "Python Bindings for the Unified Communication X library (UCX)"
+optional = false
+python-versions = "*"
+files = [
+    {file = "libucxx_cu12-0.40.0.tar.gz", hash = "sha256:5fa1b8014c7455697f624aac752f88b91cca0f3ffc8dcf20e2c515ed1d405f84"},
+]
+
+[package.dependencies]
+libucx-cu12 = ">=1.15.0,<1.18"
+
+[[package]]
+name = "llvmlite"
+version = "0.43.0"
+description = "lightweight wrapper around basic LLVM functionality"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "llvmlite-0.43.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a289af9a1687c6cf463478f0fa8e8aa3b6fb813317b0d70bf1ed0759eab6f761"},
+    {file = "llvmlite-0.43.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6d4fd101f571a31acb1559ae1af30f30b1dc4b3186669f92ad780e17c81e91bc"},
+    {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7d434ec7e2ce3cc8f452d1cd9a28591745de022f931d67be688a737320dfcead"},
+    {file = "llvmlite-0.43.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6912a87782acdff6eb8bf01675ed01d60ca1f2551f8176a300a886f09e836a6a"},
+    {file = "llvmlite-0.43.0-cp310-cp310-win_amd64.whl", hash = "sha256:14f0e4bf2fd2d9a75a3534111e8ebeb08eda2f33e9bdd6dfa13282afacdde0ed"},
+    {file = "llvmlite-0.43.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3e8d0618cb9bfe40ac38a9633f2493d4d4e9fcc2f438d39a4e854f39cc0f5f98"},
+    {file = "llvmlite-0.43.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0a9a1a39d4bf3517f2af9d23d479b4175ead205c592ceeb8b89af48a327ea57"},
+    {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1da416ab53e4f7f3bc8d4eeba36d801cc1894b9fbfbf2022b29b6bad34a7df2"},
+    {file = "llvmlite-0.43.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977525a1e5f4059316b183fb4fd34fa858c9eade31f165427a3977c95e3ee749"},
+    {file = "llvmlite-0.43.0-cp311-cp311-win_amd64.whl", hash = "sha256:d5bd550001d26450bd90777736c69d68c487d17bf371438f975229b2b8241a91"},
+    {file = "llvmlite-0.43.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f99b600aa7f65235a5a05d0b9a9f31150c390f31261f2a0ba678e26823ec38f7"},
+    {file = "llvmlite-0.43.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:35d80d61d0cda2d767f72de99450766250560399edc309da16937b93d3b676e7"},
+    {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eccce86bba940bae0d8d48ed925f21dbb813519169246e2ab292b5092aba121f"},
+    {file = "llvmlite-0.43.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df6509e1507ca0760787a199d19439cc887bfd82226f5af746d6977bd9f66844"},
+    {file = "llvmlite-0.43.0-cp312-cp312-win_amd64.whl", hash = "sha256:7a2872ee80dcf6b5dbdc838763d26554c2a18aa833d31a2635bff16aafefb9c9"},
+    {file = "llvmlite-0.43.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cd2a7376f7b3367019b664c21f0c61766219faa3b03731113ead75107f3b66c"},
+    {file = "llvmlite-0.43.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:18e9953c748b105668487b7c81a3e97b046d8abf95c4ddc0cd3c94f4e4651ae8"},
+    {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74937acd22dc11b33946b67dca7680e6d103d6e90eeaaaf932603bec6fe7b03a"},
+    {file = "llvmlite-0.43.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc9efc739cc6ed760f795806f67889923f7274276f0eb45092a1473e40d9b867"},
+    {file = "llvmlite-0.43.0-cp39-cp39-win_amd64.whl", hash = "sha256:47e147cdda9037f94b399bf03bfd8a6b6b1f2f90be94a454e3386f006455a9b4"},
+    {file = "llvmlite-0.43.0.tar.gz", hash = "sha256:ae2b5b5c3ef67354824fb75517c8db5fbe93bc02cd9671f3c62271626bc041d5"},
+]
+
+[[package]]
+name = "locket"
+version = "1.0.0"
+description = "File-based locks for Python on Linux and Windows"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+    {file = "locket-1.0.0-py2.py3-none-any.whl", hash = "sha256:b6c819a722f7b6bd955b80781788e4a66a55628b858d347536b7e81325a3a5e3"},
+    {file = "locket-1.0.0.tar.gz", hash = "sha256:5c0d4c052a8bbbf750e056a8e65ccd309086f4f0f18a2eac306a8dfa4112a632"},
+]
+
+[[package]]
+name = "markdown-it-py"
+version = "3.0.0"
+description = "Python port of markdown-it. Markdown parsing, done right!"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
+    {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
+]
+
+[package.dependencies]
+mdurl = ">=0.1,<1.0"
+
+[package.extras]
+benchmarking = ["psutil", "pytest", "pytest-benchmark"]
+code-style = ["pre-commit (>=3.0,<4.0)"]
+compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
+linkify = ["linkify-it-py (>=1,<3)"]
+plugins = ["mdit-py-plugins"]
+profiling = ["gprof2dot"]
+rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
+testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
+
+[[package]]
+name = "markupsafe"
+version = "3.0.2"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"},
+    {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"},
+    {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"},
+    {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"},
+    {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"},
+    {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"},
+    {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
+]
+
+[[package]]
+name = "mdurl"
+version = "0.1.2"
+description = "Markdown URL utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+    {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
+    {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
+]
+
+[[package]]
+name = "msgpack"
+version = "1.1.0"
+description = "MessagePack serializer"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7ad442d527a7e358a469faf43fda45aaf4ac3249c8310a82f0ccff9164e5dccd"},
+    {file = "msgpack-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:74bed8f63f8f14d75eec75cf3d04ad581da6b914001b474a5d3cd3372c8cc27d"},
+    {file = "msgpack-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:914571a2a5b4e7606997e169f64ce53a8b1e06f2cf2c3a7273aa106236d43dd5"},
+    {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c921af52214dcbb75e6bdf6a661b23c3e6417f00c603dd2070bccb5c3ef499f5"},
+    {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8ce0b22b890be5d252de90d0e0d119f363012027cf256185fc3d474c44b1b9e"},
+    {file = "msgpack-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:73322a6cc57fcee3c0c57c4463d828e9428275fb85a27aa2aa1a92fdc42afd7b"},
+    {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:e1f3c3d21f7cf67bcf2da8e494d30a75e4cf60041d98b3f79875afb5b96f3a3f"},
+    {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64fc9068d701233effd61b19efb1485587560b66fe57b3e50d29c5d78e7fef68"},
+    {file = "msgpack-1.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:42f754515e0f683f9c79210a5d1cad631ec3d06cea5172214d2176a42e67e19b"},
+    {file = "msgpack-1.1.0-cp310-cp310-win32.whl", hash = "sha256:3df7e6b05571b3814361e8464f9304c42d2196808e0119f55d0d3e62cd5ea044"},
+    {file = "msgpack-1.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:685ec345eefc757a7c8af44a3032734a739f8c45d1b0ac45efc5d8977aa4720f"},
+    {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3d364a55082fb2a7416f6c63ae383fbd903adb5a6cf78c5b96cc6316dc1cedc7"},
+    {file = "msgpack-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:79ec007767b9b56860e0372085f8504db5d06bd6a327a335449508bbee9648fa"},
+    {file = "msgpack-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ad622bf7756d5a497d5b6836e7fc3752e2dd6f4c648e24b1803f6048596f701"},
+    {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e59bca908d9ca0de3dc8684f21ebf9a690fe47b6be93236eb40b99af28b6ea6"},
+    {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1da8f11a3dd397f0a32c76165cf0c4eb95b31013a94f6ecc0b280c05c91b59"},
+    {file = "msgpack-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:452aff037287acb1d70a804ffd022b21fa2bb7c46bee884dbc864cc9024128a0"},
+    {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8da4bf6d54ceed70e8861f833f83ce0814a2b72102e890cbdfe4b34764cdd66e"},
+    {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:41c991beebf175faf352fb940bf2af9ad1fb77fd25f38d9142053914947cdbf6"},
+    {file = "msgpack-1.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a52a1f3a5af7ba1c9ace055b659189f6c669cf3657095b50f9602af3a3ba0fe5"},
+    {file = "msgpack-1.1.0-cp311-cp311-win32.whl", hash = "sha256:58638690ebd0a06427c5fe1a227bb6b8b9fdc2bd07701bec13c2335c82131a88"},
+    {file = "msgpack-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:fd2906780f25c8ed5d7b323379f6138524ba793428db5d0e9d226d3fa6aa1788"},
+    {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d"},
+    {file = "msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2"},
+    {file = "msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420"},
+    {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2"},
+    {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39"},
+    {file = "msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f"},
+    {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247"},
+    {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c"},
+    {file = "msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b"},
+    {file = "msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b"},
+    {file = "msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f"},
+    {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf"},
+    {file = "msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330"},
+    {file = "msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734"},
+    {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e"},
+    {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca"},
+    {file = "msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915"},
+    {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d"},
+    {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434"},
+    {file = "msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c"},
+    {file = "msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc"},
+    {file = "msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f"},
+    {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c40ffa9a15d74e05ba1fe2681ea33b9caffd886675412612d93ab17b58ea2fec"},
+    {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1ba6136e650898082d9d5a5217d5906d1e138024f836ff48691784bbe1adf96"},
+    {file = "msgpack-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0856a2b7e8dcb874be44fea031d22e5b3a19121be92a1e098f46068a11b0870"},
+    {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:471e27a5787a2e3f974ba023f9e265a8c7cfd373632247deb225617e3100a3c7"},
+    {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:646afc8102935a388ffc3914b336d22d1c2d6209c773f3eb5dd4d6d3b6f8c1cb"},
+    {file = "msgpack-1.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:13599f8829cfbe0158f6456374e9eea9f44eee08076291771d8ae93eda56607f"},
+    {file = "msgpack-1.1.0-cp38-cp38-win32.whl", hash = "sha256:8a84efb768fb968381e525eeeb3d92857e4985aacc39f3c47ffd00eb4509315b"},
+    {file = "msgpack-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:879a7b7b0ad82481c52d3c7eb99bf6f0645dbdec5134a4bddbd16f3506947feb"},
+    {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:53258eeb7a80fc46f62fd59c876957a2d0e15e6449a9e71842b6d24419d88ca1"},
+    {file = "msgpack-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7e7b853bbc44fb03fbdba34feb4bd414322180135e2cb5164f20ce1c9795ee48"},
+    {file = "msgpack-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3e9b4936df53b970513eac1758f3882c88658a220b58dcc1e39606dccaaf01c"},
+    {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46c34e99110762a76e3911fc923222472c9d681f1094096ac4102c18319e6468"},
+    {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a706d1e74dd3dea05cb54580d9bd8b2880e9264856ce5068027eed09680aa74"},
+    {file = "msgpack-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:534480ee5690ab3cbed89d4c8971a5c631b69a8c0883ecfea96c19118510c846"},
+    {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8cf9e8c3a2153934a23ac160cc4cba0ec035f6867c8013cc6077a79823370346"},
+    {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3180065ec2abbe13a4ad37688b61b99d7f9e012a535b930e0e683ad6bc30155b"},
+    {file = "msgpack-1.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c5a91481a3cc573ac8c0d9aace09345d989dc4a0202b7fcb312c88c26d4e71a8"},
+    {file = "msgpack-1.1.0-cp39-cp39-win32.whl", hash = "sha256:f80bc7d47f76089633763f952e67f8214cb7b3ee6bfa489b3cb6a84cfac114cd"},
+    {file = "msgpack-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:4d1b7ff2d6146e16e8bd665ac726a89c74163ef8cd39fa8c1087d4e52d3a2325"},
+    {file = "msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e"},
+]
+
+[[package]]
+name = "numba"
+version = "0.60.0"
+description = "compiling Python code using LLVM"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "numba-0.60.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d761de835cd38fb400d2c26bb103a2726f548dc30368853121d66201672e651"},
+    {file = "numba-0.60.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:159e618ef213fba758837f9837fb402bbe65326e60ba0633dbe6c7f274d42c1b"},
+    {file = "numba-0.60.0-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1527dc578b95c7c4ff248792ec33d097ba6bef9eda466c948b68dfc995c25781"},
+    {file = "numba-0.60.0-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe0b28abb8d70f8160798f4de9d486143200f34458d34c4a214114e445d7124e"},
+    {file = "numba-0.60.0-cp310-cp310-win_amd64.whl", hash = "sha256:19407ced081d7e2e4b8d8c36aa57b7452e0283871c296e12d798852bc7d7f198"},
+    {file = "numba-0.60.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a17b70fc9e380ee29c42717e8cc0bfaa5556c416d94f9aa96ba13acb41bdece8"},
+    {file = "numba-0.60.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3fb02b344a2a80efa6f677aa5c40cd5dd452e1b35f8d1c2af0dfd9ada9978e4b"},
+    {file = "numba-0.60.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5f4fde652ea604ea3c86508a3fb31556a6157b2c76c8b51b1d45eb40c8598703"},
+    {file = "numba-0.60.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4142d7ac0210cc86432b818338a2bc368dc773a2f5cf1e32ff7c5b378bd63ee8"},
+    {file = "numba-0.60.0-cp311-cp311-win_amd64.whl", hash = "sha256:cac02c041e9b5bc8cf8f2034ff6f0dbafccd1ae9590dc146b3a02a45e53af4e2"},
+    {file = "numba-0.60.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d7da4098db31182fc5ffe4bc42c6f24cd7d1cb8a14b59fd755bfee32e34b8404"},
+    {file = "numba-0.60.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38d6ea4c1f56417076ecf8fc327c831ae793282e0ff51080c5094cb726507b1c"},
+    {file = "numba-0.60.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:62908d29fb6a3229c242e981ca27e32a6e606cc253fc9e8faeb0e48760de241e"},
+    {file = "numba-0.60.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0ebaa91538e996f708f1ab30ef4d3ddc344b64b5227b67a57aa74f401bb68b9d"},
+    {file = "numba-0.60.0-cp312-cp312-win_amd64.whl", hash = "sha256:f75262e8fe7fa96db1dca93d53a194a38c46da28b112b8a4aca168f0df860347"},
+    {file = "numba-0.60.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:01ef4cd7d83abe087d644eaa3d95831b777aa21d441a23703d649e06b8e06b74"},
+    {file = "numba-0.60.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:819a3dfd4630d95fd574036f99e47212a1af41cbcb019bf8afac63ff56834449"},
+    {file = "numba-0.60.0-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0b983bd6ad82fe868493012487f34eae8bf7dd94654951404114f23c3466d34b"},
+    {file = "numba-0.60.0-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c151748cd269ddeab66334bd754817ffc0cabd9433acb0f551697e5151917d25"},
+    {file = "numba-0.60.0-cp39-cp39-win_amd64.whl", hash = "sha256:3031547a015710140e8c87226b4cfe927cac199835e5bf7d4fe5cb64e814e3ab"},
+    {file = "numba-0.60.0.tar.gz", hash = "sha256:5df6158e5584eece5fc83294b949fd30b9f1125df7708862205217e068aabf16"},
+]
+
+[package.dependencies]
+llvmlite = "==0.43.*"
+numpy = ">=1.22,<2.1"
+
+[[package]]
+name = "numpy"
+version = "1.26.4"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"},
+    {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"},
+    {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"},
+    {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"},
+    {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"},
+    {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"},
+    {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"},
+    {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"},
+    {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"},
+    {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"},
+    {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"},
+    {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"},
+    {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"},
+    {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"},
+    {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"},
+    {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"},
+    {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"},
+    {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"},
+    {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"},
+    {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"},
+    {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"},
+    {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"},
+    {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"},
+    {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"},
+    {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"},
+    {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"},
+    {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"},
+    {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"},
+    {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"},
+    {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"},
+    {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"},
+    {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"},
+    {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"},
+    {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"},
+    {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"},
+    {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"},
+]
+
+[[package]]
+name = "nvidia-cublas-cu12"
+version = "12.6.3.3"
+description = "CUBLAS native runtime libraries"
+optional = false
+python-versions = ">=3"
+files = [
+    {file = "nvidia_cublas_cu12-12.6.3.3-py3-none-manylinux2014_aarch64.whl", hash = "sha256:e531199ca4f1f764fb45bc1dde49a006f6765033f9c89c737e4553b9502ca1f5"},
+    {file = "nvidia_cublas_cu12-12.6.3.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f33fb68e101d99470c82d17f92a0dd9f74de2a21685c217f4716cdd63b1316eb"},
+    {file = "nvidia_cublas_cu12-12.6.3.3-py3-none-win_amd64.whl", hash = "sha256:e1f70bee38b964eac1907293b336bceb24498a4243e61eaf91a52977c59aebc4"},
+]
+
+[[package]]
+name = "nvidia-cufft-cu12"
+version = "11.3.0.4"
+description = "CUFFT native runtime libraries"
+optional = false
+python-versions = ">=3"
+files = [
+    {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_aarch64.whl", hash = "sha256:8510990de9f96c803a051822618d42bf6cb8f069ff3f48d93a8486efdacb48fb"},
+    {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-manylinux2014_x86_64.whl", hash = "sha256:768160ac89f6f7b459bee747e8d175dbf53619cfe74b2a5636264163138013ca"},
+    {file = "nvidia_cufft_cu12-11.3.0.4-py3-none-win_amd64.whl", hash = "sha256:6048ebddfb90d09d2707efb1fd78d4e3a77cb3ae4dc60e19aab6be0ece2ae464"},
+]
+
+[package.dependencies]
+nvidia-nvjitlink-cu12 = "*"
+
+[[package]]
+name = "nvidia-curand-cu12"
+version = "10.3.7.77"
+description = "CURAND native runtime libraries"
+optional = false
+python-versions = ">=3"
+files = [
+    {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_aarch64.whl", hash = "sha256:6e82df077060ea28e37f48a3ec442a8f47690c7499bff392a5938614b56c98d8"},
+    {file = "nvidia_curand_cu12-10.3.7.77-py3-none-manylinux2014_x86_64.whl", hash = "sha256:99f1a32f1ac2bd134897fc7a203f779303261268a65762a623bf30cc9fe79117"},
+    {file = "nvidia_curand_cu12-10.3.7.77-py3-none-win_amd64.whl", hash = "sha256:6d6d935ffba0f3d439b7cd968192ff068fafd9018dbf1b85b37261b13cfc9905"},
+]
+
+[[package]]
+name = "nvidia-cusolver-cu12"
+version = "11.7.1.2"
+description = "CUDA solver native runtime libraries"
+optional = false
+python-versions = ">=3"
+files = [
+    {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:0ce237ef60acde1efc457335a2ddadfd7610b892d94efee7b776c64bb1cac9e0"},
+    {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:6cf28f17f64107a0c4d7802be5ff5537b2130bfc112f25d5a30df227058ca0e6"},
+    {file = "nvidia_cusolver_cu12-11.7.1.2-py3-none-win_amd64.whl", hash = "sha256:6813f9d8073f555444a8705f3ab0296d3e1cb37a16d694c5fc8b862a0d8706d7"},
+]
+
+[package.dependencies]
+nvidia-cublas-cu12 = "*"
+nvidia-cusparse-cu12 = "*"
+nvidia-nvjitlink-cu12 = "*"
+
+[[package]]
+name = "nvidia-cusparse-cu12"
+version = "12.5.4.2"
+description = "CUSPARSE native runtime libraries"
+optional = false
+python-versions = ">=3"
+files = [
+    {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7aa32fa5470cf754f72d1116c7cbc300b4e638d3ae5304cfa4a638a5b87161b1"},
+    {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:23749a6571191a215cb74d1cdbff4a86e7b19f1200c071b3fcf844a5bea23a2f"},
+    {file = "nvidia_cusparse_cu12-12.5.4.2-py3-none-win_amd64.whl", hash = "sha256:4acb8c08855a26d737398cba8fb6f8f5045d93f82612b4cfd84645a2332ccf20"},
+]
+
+[package.dependencies]
+nvidia-nvjitlink-cu12 = "*"
+
+[[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 = "nvtx"
+version = "0.2.10"
+description = "PyNVTX - Python code annotation library"
+optional = false
+python-versions = "*"
+files = [
+    {file = "nvtx-0.2.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e4ac301f89a9267002820725bdcac8ae2f354bd22757e20761d158409177324"},
+    {file = "nvtx-0.2.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b44d57460e3f9bdd4db0c0be89e54c32e3c4e90b03fa8b67c2ecf07394b1f3"},
+    {file = "nvtx-0.2.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828a1d17e53577adf3e24e93b92d68eabcb316b293ce64c5aa03776c1577511c"},
+    {file = "nvtx-0.2.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71a1a641d4db137da8166d689d835a42f92b97cf2658ea069cbed162b8c5dd79"},
+    {file = "nvtx-0.2.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3232fd776dbb9f4ee7735e251f5e844bc4c0bd614521a15abba666b15b12e6e3"},
+    {file = "nvtx-0.2.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:552ee32cadd7a8205833e157f3e161670200b213eb2816fd8631182c3e97c0dc"},
+    {file = "nvtx-0.2.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da89bdb1f9495d24217fdd442589b82388a971e8747c8a83f94a84a52fe02444"},
+    {file = "nvtx-0.2.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:278595902762a259d603a5f40116cd615a56513d92118c291d25cc0e43c6f59c"},
+    {file = "nvtx-0.2.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d28b159057afd7e4f6c3159dddbc97bdc2efddf6d40a6e7284a7ad5c342fdbf"},
+    {file = "nvtx-0.2.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d59655a35941e58ef46fa1297e09628d06b05b94e078e39a02e3dffc09aa823"},
+    {file = "nvtx-0.2.10.tar.gz", hash = "sha256:58b89cd69079fda1ceef8441eec5c5c189d6a1ff94c090a3afe03aedd0bbd140"},
+]
+
+[[package]]
+name = "packaging"
+version = "24.2"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
+    {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
+]
+
+[[package]]
+name = "pandas"
+version = "2.2.2"
+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"},
+]
+
+[package.dependencies]
+numpy = [
+    {version = ">=1.23.2", markers = "python_version == \"3.11\""},
+    {version = ">=1.26.0", markers = "python_version >= \"3.12\""},
+]
+python-dateutil = ">=2.8.2"
+pytz = ">=2020.1"
+tzdata = ">=2022.7"
+
+[package.extras]
+all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"]
+aws = ["s3fs (>=2022.11.0)"]
+clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"]
+compression = ["zstandard (>=0.19.0)"]
+computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"]
+consortium-standard = ["dataframe-api-compat (>=0.1.7)"]
+excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"]
+feather = ["pyarrow (>=10.0.1)"]
+fss = ["fsspec (>=2022.11.0)"]
+gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"]
+hdf5 = ["tables (>=3.8.0)"]
+html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"]
+mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"]
+output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"]
+parquet = ["pyarrow (>=10.0.1)"]
+performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"]
+plot = ["matplotlib (>=3.6.3)"]
+postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"]
+pyarrow = ["pyarrow (>=10.0.1)"]
+spss = ["pyreadstat (>=1.2.0)"]
+sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"]
+test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
+xml = ["lxml (>=4.9.2)"]
+
+[[package]]
+name = "partd"
+version = "1.4.2"
+description = "Appendable key-value storage"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "partd-1.4.2-py3-none-any.whl", hash = "sha256:978e4ac767ec4ba5b86c6eaa52e5a2a3bc748a2ca839e8cc798f1cc6ce6efb0f"},
+    {file = "partd-1.4.2.tar.gz", hash = "sha256:d022c33afbdc8405c226621b015e8067888173d85f7f5ecebb3cafed9a20f02c"},
+]
+
+[package.dependencies]
+locket = "*"
+toolz = "*"
+
+[package.extras]
+complete = ["blosc", "numpy (>=1.20.0)", "pandas (>=1.3)", "pyzmq"]
+
+[[package]]
+name = "plotly"
+version = "5.24.1"
+description = "An open-source, interactive data visualization library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "plotly-5.24.1-py3-none-any.whl", hash = "sha256:f67073a1e637eb0dc3e46324d9d51e2fe76e9727c892dde64ddf1e1b51f29089"},
+    {file = "plotly-5.24.1.tar.gz", hash = "sha256:dbc8ac8339d248a4bcc36e08a5659bacfe1b079390b8953533f4eb22169b4bae"},
+]
+
+[package.dependencies]
+packaging = "*"
+tenacity = ">=6.2.0"
+
+[[package]]
+name = "psutil"
+version = "6.1.0"
+description = "Cross-platform lib for process and system monitoring in Python."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
+files = [
+    {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"},
+    {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"},
+    {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"},
+    {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"},
+    {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"},
+    {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"},
+    {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"},
+    {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"},
+    {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"},
+    {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"},
+    {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"},
+    {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"},
+    {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"},
+    {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"},
+    {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"},
+    {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"},
+    {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"},
+]
+
+[package.extras]
+dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"]
+test = ["pytest", "pytest-xdist", "setuptools"]
+
+[[package]]
+name = "pyarrow"
+version = "16.1.0"
+description = "Python library for Apache Arrow"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pyarrow-16.1.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:17e23b9a65a70cc733d8b738baa6ad3722298fa0c81d88f63ff94bf25eaa77b9"},
+    {file = "pyarrow-16.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4740cc41e2ba5d641071d0ab5e9ef9b5e6e8c7611351a5cb7c1d175eaf43674a"},
+    {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98100e0268d04e0eec47b73f20b39c45b4006f3c4233719c3848aa27a03c1aef"},
+    {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f68f409e7b283c085f2da014f9ef81e885d90dcd733bd648cfba3ef265961848"},
+    {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:a8914cd176f448e09746037b0c6b3a9d7688cef451ec5735094055116857580c"},
+    {file = "pyarrow-16.1.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:48be160782c0556156d91adbdd5a4a7e719f8d407cb46ae3bb4eaee09b3111bd"},
+    {file = "pyarrow-16.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:9cf389d444b0f41d9fe1444b70650fea31e9d52cfcb5f818b7888b91b586efff"},
+    {file = "pyarrow-16.1.0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:d0ebea336b535b37eee9eee31761813086d33ed06de9ab6fc6aaa0bace7b250c"},
+    {file = "pyarrow-16.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e73cfc4a99e796727919c5541c65bb88b973377501e39b9842ea71401ca6c1c"},
+    {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf9251264247ecfe93e5f5a0cd43b8ae834f1e61d1abca22da55b20c788417f6"},
+    {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddf5aace92d520d3d2a20031d8b0ec27b4395cab9f74e07cc95edf42a5cc0147"},
+    {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:25233642583bf658f629eb230b9bb79d9af4d9f9229890b3c878699c82f7d11e"},
+    {file = "pyarrow-16.1.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a33a64576fddfbec0a44112eaf844c20853647ca833e9a647bfae0582b2ff94b"},
+    {file = "pyarrow-16.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:185d121b50836379fe012753cf15c4ba9638bda9645183ab36246923875f8d1b"},
+    {file = "pyarrow-16.1.0-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:2e51ca1d6ed7f2e9d5c3c83decf27b0d17bb207a7dea986e8dc3e24f80ff7d6f"},
+    {file = "pyarrow-16.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:06ebccb6f8cb7357de85f60d5da50e83507954af617d7b05f48af1621d331c9a"},
+    {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b04707f1979815f5e49824ce52d1dceb46e2f12909a48a6a753fe7cafbc44a0c"},
+    {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d32000693deff8dc5df444b032b5985a48592c0697cb6e3071a5d59888714e2"},
+    {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8785bb10d5d6fd5e15d718ee1d1f914fe768bf8b4d1e5e9bf253de8a26cb1628"},
+    {file = "pyarrow-16.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e1369af39587b794873b8a307cc6623a3b1194e69399af0efd05bb202195a5a7"},
+    {file = "pyarrow-16.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:febde33305f1498f6df85e8020bca496d0e9ebf2093bab9e0f65e2b4ae2b3444"},
+    {file = "pyarrow-16.1.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b5f5705ab977947a43ac83b52ade3b881eb6e95fcc02d76f501d549a210ba77f"},
+    {file = "pyarrow-16.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0d27bf89dfc2576f6206e9cd6cf7a107c9c06dc13d53bbc25b0bd4556f19cf5f"},
+    {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d07de3ee730647a600037bc1d7b7994067ed64d0eba797ac74b2bc77384f4c2"},
+    {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbef391b63f708e103df99fbaa3acf9f671d77a183a07546ba2f2c297b361e83"},
+    {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:19741c4dbbbc986d38856ee7ddfdd6a00fc3b0fc2d928795b95410d38bb97d15"},
+    {file = "pyarrow-16.1.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:f2c5fb249caa17b94e2b9278b36a05ce03d3180e6da0c4c3b3ce5b2788f30eed"},
+    {file = "pyarrow-16.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:e6b6d3cd35fbb93b70ade1336022cc1147b95ec6af7d36906ca7fe432eb09710"},
+    {file = "pyarrow-16.1.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:18da9b76a36a954665ccca8aa6bd9f46c1145f79c0bb8f4f244f5f8e799bca55"},
+    {file = "pyarrow-16.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:99f7549779b6e434467d2aa43ab2b7224dd9e41bdde486020bae198978c9e05e"},
+    {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f07fdffe4fd5b15f5ec15c8b64584868d063bc22b86b46c9695624ca3505b7b4"},
+    {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddfe389a08ea374972bd4065d5f25d14e36b43ebc22fc75f7b951f24378bf0b5"},
+    {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:3b20bd67c94b3a2ea0a749d2a5712fc845a69cb5d52e78e6449bbd295611f3aa"},
+    {file = "pyarrow-16.1.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:ba8ac20693c0bb0bf4b238751d4409e62852004a8cf031c73b0e0962b03e45e3"},
+    {file = "pyarrow-16.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:31a1851751433d89a986616015841977e0a188662fcffd1a5677453f1df2de0a"},
+    {file = "pyarrow-16.1.0.tar.gz", hash = "sha256:15fbb22ea96d11f0b5768504a3f961edab25eaf4197c341720c4a387f6c60315"},
+]
+
+[package.dependencies]
+numpy = ">=1.16.6"
+
+[[package]]
+name = "pygments"
+version = "2.18.0"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"},
+    {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"},
+]
+
+[package.extras]
+windows-terminal = ["colorama (>=0.4.6)"]
+
+[[package]]
+name = "pylibcudf-cu12"
+version = "24.10.1"
+description = "pylibcudf - Python bindings for libcudf"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "pylibcudf_cu12-24.10.1-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca391b8b15b6400eb780e502008dc3ea6d2201e3efb16fb3fc507f926ba3d6f6"},
+    {file = "pylibcudf_cu12-24.10.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:afe39ab4e140a832d25121586c47b07284478857c3ea2e1828971143b5906c57"},
+    {file = "pylibcudf_cu12-24.10.1-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d0365c0c0807f3c7747005d85965b552194dec19e11d1370832fddab5f8c34df"},
+    {file = "pylibcudf_cu12-24.10.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7bb06522e23089ea08e03604d865dbafa34fa4a3283c05008ea41fac55d08751"},
+    {file = "pylibcudf_cu12-24.10.1-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca712475c86052658002f366fb74ee8acf20d113619a7672f42e3f509414800e"},
+    {file = "pylibcudf_cu12-24.10.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:717987c16cc8774c8326fc3c94bd2ad1c58acf41e52586052e38dd00ba1bc7e4"},
+]
+
+[package.dependencies]
+cuda-python = ">=12.0,<13.0a0"
+libcudf-cu12 = "==24.10.*"
+nvtx = ">=0.2.1"
+packaging = "*"
+pyarrow = ">=14.0.0,<18.0.0a0"
+rmm-cu12 = "==24.10.*"
+typing_extensions = ">=4.0.0"
+
+[package.extras]
+test = ["fastavro (>=0.22.9)", "hypothesis", "numpy (>=1.23,<3.0a0)", "pandas", "pytest (<8)", "pytest-cov", "pytest-xdist"]
+
+[package.source]
+type = "legacy"
+url = "https://pypi.nvidia.com"
+reference = "rapids"
+
+[[package]]
+name = "pylibraft-cu12"
+version = "24.10.0"
+description = "RAFT: Reusable Algorithms Functions and other Tools"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "pylibraft_cu12-24.10.0.tar.gz", hash = "sha256:18d931b46c57d6c5003c020a6ec5795561f9451e989cc3fb09fb8105487900d2"},
+]
+
+[package.dependencies]
+cuda-python = ">=12.0,<13.0a0"
+numpy = ">=1.23,<3.0a0"
+nvidia-cublas-cu12 = "*"
+nvidia-curand-cu12 = "*"
+nvidia-cusolver-cu12 = "*"
+nvidia-cusparse-cu12 = "*"
+rmm-cu12 = "==24.10.*"
+
+[package.extras]
+test = ["cupy-cuda12x (>=12.0.0)", "pytest (==7.*)", "pytest-cov", "scikit-learn", "scipy"]
+
+[[package]]
+name = "pynvjitlink-cu12"
+version = "0.4.0"
+description = "nvJitLink Python binding"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "pynvjitlink_cu12-0.4.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:91655a6e4819616baeb944fea6adbac7d5dcfe9fe90997f261be232af6ee5893"},
+    {file = "pynvjitlink_cu12-0.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b2dd5ec1d8a3b5b9941abd9932b5e9e0b4dd6f0f765dc2057aaa0e28dc25cfe6"},
+    {file = "pynvjitlink_cu12-0.4.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d413958f9a1aab5c28cced80fa8e3d16f47fd0a84fbe7dd0cc5fecd9dadc5374"},
+    {file = "pynvjitlink_cu12-0.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:892510cb556056e77db33f36a13c7d3290f7db4d39b6a89b8055cf04328afb1e"},
+    {file = "pynvjitlink_cu12-0.4.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e4ae1d7cd31fa3df3456de51a2531ff56eb1ed48dec15dd3539047884d157f8a"},
+    {file = "pynvjitlink_cu12-0.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f6834d84e0fba15b8c0ee39d8c4397e631708f97a8196c2f7b3082f2f35f465"},
+]
+
+[package.extras]
+test = ["cuda-python", "numba (>=0.58)", "psutil", "pytest", "pytest-cov"]
+
+[[package]]
+name = "pynvml"
+version = "11.4.1"
+description = "Python Bindings for the NVIDIA Management Library"
+optional = false
+python-versions = ">=3.6"
+files = [
+    {file = "pynvml-11.4.1-py3-none-any.whl", hash = "sha256:d27be542cd9d06558de18e2deffc8022ccd7355bc7382255d477038e7e424c6c"},
+    {file = "pynvml-11.4.1.tar.gz", hash = "sha256:b2e4a33b80569d093b513f5804db0c7f40cfc86f15a013ae7a8e99c5e175d5dd"},
+]
+
+[[package]]
+name = "python-dateutil"
+version = "2.9.0.post0"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+    {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
+    {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "pytz"
+version = "2024.2"
+description = "World timezone definitions, modern and historical"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"},
+    {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"},
+]
+
+[[package]]
+name = "pywin32"
+version = "308"
+description = "Python for Window Extensions"
+optional = false
+python-versions = "*"
+files = [
+    {file = "pywin32-308-cp310-cp310-win32.whl", hash = "sha256:796ff4426437896550d2981b9c2ac0ffd75238ad9ea2d3bfa67a1abd546d262e"},
+    {file = "pywin32-308-cp310-cp310-win_amd64.whl", hash = "sha256:4fc888c59b3c0bef905ce7eb7e2106a07712015ea1c8234b703a088d46110e8e"},
+    {file = "pywin32-308-cp310-cp310-win_arm64.whl", hash = "sha256:a5ab5381813b40f264fa3495b98af850098f814a25a63589a8e9eb12560f450c"},
+    {file = "pywin32-308-cp311-cp311-win32.whl", hash = "sha256:5d8c8015b24a7d6855b1550d8e660d8daa09983c80e5daf89a273e5c6fb5095a"},
+    {file = "pywin32-308-cp311-cp311-win_amd64.whl", hash = "sha256:575621b90f0dc2695fec346b2d6302faebd4f0f45c05ea29404cefe35d89442b"},
+    {file = "pywin32-308-cp311-cp311-win_arm64.whl", hash = "sha256:100a5442b7332070983c4cd03f2e906a5648a5104b8a7f50175f7906efd16bb6"},
+    {file = "pywin32-308-cp312-cp312-win32.whl", hash = "sha256:587f3e19696f4bf96fde9d8a57cec74a57021ad5f204c9e627e15c33ff568897"},
+    {file = "pywin32-308-cp312-cp312-win_amd64.whl", hash = "sha256:00b3e11ef09ede56c6a43c71f2d31857cf7c54b0ab6e78ac659497abd2834f47"},
+    {file = "pywin32-308-cp312-cp312-win_arm64.whl", hash = "sha256:9b4de86c8d909aed15b7011182c8cab38c8850de36e6afb1f0db22b8959e3091"},
+    {file = "pywin32-308-cp313-cp313-win32.whl", hash = "sha256:1c44539a37a5b7b21d02ab34e6a4d314e0788f1690d65b48e9b0b89f31abbbed"},
+    {file = "pywin32-308-cp313-cp313-win_amd64.whl", hash = "sha256:fd380990e792eaf6827fcb7e187b2b4b1cede0585e3d0c9e84201ec27b9905e4"},
+    {file = "pywin32-308-cp313-cp313-win_arm64.whl", hash = "sha256:ef313c46d4c18dfb82a2431e3051ac8f112ccee1a34f29c263c583c568db63cd"},
+    {file = "pywin32-308-cp37-cp37m-win32.whl", hash = "sha256:1f696ab352a2ddd63bd07430080dd598e6369152ea13a25ebcdd2f503a38f1ff"},
+    {file = "pywin32-308-cp37-cp37m-win_amd64.whl", hash = "sha256:13dcb914ed4347019fbec6697a01a0aec61019c1046c2b905410d197856326a6"},
+    {file = "pywin32-308-cp38-cp38-win32.whl", hash = "sha256:5794e764ebcabf4ff08c555b31bd348c9025929371763b2183172ff4708152f0"},
+    {file = "pywin32-308-cp38-cp38-win_amd64.whl", hash = "sha256:3b92622e29d651c6b783e368ba7d6722b1634b8e70bd376fd7610fe1992e19de"},
+    {file = "pywin32-308-cp39-cp39-win32.whl", hash = "sha256:7873ca4dc60ab3287919881a7d4f88baee4a6e639aa6962de25a98ba6b193341"},
+    {file = "pywin32-308-cp39-cp39-win_amd64.whl", hash = "sha256:71b3322d949b4cc20776436a9c9ba0eeedcbc9c650daa536df63f0ff111bb920"},
+]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.2"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
+    {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
+    {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
+    {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
+    {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
+    {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
+    {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
+    {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
+    {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
+    {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
+    {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
+    {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
+    {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
+    {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
+    {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
+    {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
+    {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
+    {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
+    {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
+    {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
+    {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
+    {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
+    {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
+    {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
+    {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
+    {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
+    {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
+    {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
+    {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
+    {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
+    {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
+    {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
+    {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
+    {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
+    {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
+    {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
+    {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
+    {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
+    {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
+    {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
+    {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
+]
+
+[[package]]
+name = "raft-dask-cu12"
+version = "24.10.0"
+description = "Reusable Accelerated Functions & Tools Dask Infrastructure"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "raft_dask_cu12-24.10.0.tar.gz", hash = "sha256:ac1c47c90fabb573dccc1aed40db6f0cf5dfb978f85acee97f633905c1ff6154"},
+]
+
+[package.dependencies]
+dask-cuda = "==24.10.*"
+distributed-ucxx-cu12 = "==0.40.*"
+joblib = ">=0.11"
+numba = ">=0.57"
+pylibraft-cu12 = "==24.10.*"
+rapids-dask-dependency = "==24.10.*"
+ucx-py-cu12 = "==0.40.*"
+
+[package.extras]
+test = ["pytest (==7.*)", "pytest-cov"]
+
+[[package]]
+name = "rapids-dask-dependency"
+version = "24.10.0"
+description = "Dask and Distributed version pinning for RAPIDS"
+optional = false
+python-versions = "*"
+files = [
+    {file = "rapids_dask_dependency-24.10.0-py3-none-any.whl", hash = "sha256:25242e0b1264e18e97d53b3fa96256d1e75f13865b84ea989f730b54c86d9023"},
+    {file = "rapids_dask_dependency-24.10.0.tar.gz", hash = "sha256:a024bb9d4e91e61e98befaf6324c44a308b25a9a28a2bc7eaa58a35d4f8baf78"},
+]
+
+[package.dependencies]
+dask = "2024.9.0"
+dask-expr = "1.1.14"
+distributed = "2024.9.0"
+
+[package.extras]
+test = ["pytest"]
+
+[[package]]
+name = "rich"
+version = "13.9.4"
+description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
+optional = false
+python-versions = ">=3.8.0"
+files = [
+    {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"},
+    {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"},
+]
+
+[package.dependencies]
+markdown-it-py = ">=2.2.0"
+pygments = ">=2.13.0,<3.0.0"
+
+[package.extras]
+jupyter = ["ipywidgets (>=7.5.1,<9)"]
+
+[[package]]
+name = "rmm-cu12"
+version = "24.10.0"
+description = "rmm - RAPIDS Memory Manager"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "rmm_cu12-24.10.0.tar.gz", hash = "sha256:bfcd896707a385fed5e64d71b1a14f3027de26678f589f8aa5b30d8bd01bd755"},
+]
+
+[package.dependencies]
+cuda-python = ">=12.0,<13.0a0"
+numba = ">=0.57"
+numpy = ">=1.23,<3.0a0"
+
+[package.extras]
+test = ["pytest", "pytest-cov"]
+
+[[package]]
+name = "scipy"
+version = "1.14.1"
+description = "Fundamental algorithms for scientific computing in Python"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"},
+    {file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"},
+    {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"},
+    {file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"},
+    {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"},
+    {file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"},
+    {file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"},
+    {file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"},
+    {file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"},
+    {file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"},
+    {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"},
+    {file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"},
+    {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"},
+    {file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"},
+    {file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"},
+    {file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"},
+    {file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"},
+    {file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"},
+    {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"},
+    {file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"},
+    {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"},
+    {file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"},
+    {file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"},
+    {file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"},
+    {file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"},
+    {file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"},
+    {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"},
+    {file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"},
+    {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"},
+    {file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"},
+    {file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"},
+    {file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"},
+    {file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"},
+]
+
+[package.dependencies]
+numpy = ">=1.23.5,<2.3"
+
+[package.extras]
+dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
+doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"]
+test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+    {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+    {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "sortedcontainers"
+version = "2.4.0"
+description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set"
+optional = false
+python-versions = "*"
+files = [
+    {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
+    {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},
+]
+
+[[package]]
+name = "tblib"
+version = "3.0.0"
+description = "Traceback serialization library."
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tblib-3.0.0-py3-none-any.whl", hash = "sha256:80a6c77e59b55e83911e1e607c649836a69c103963c5f28a46cbeef44acf8129"},
+    {file = "tblib-3.0.0.tar.gz", hash = "sha256:93622790a0a29e04f0346458face1e144dc4d32f493714c6c3dff82a4adb77e6"},
+]
+
+[[package]]
+name = "tenacity"
+version = "9.0.0"
+description = "Retry code until it succeeds"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tenacity-9.0.0-py3-none-any.whl", hash = "sha256:93de0c98785b27fcf659856aa9f54bfbd399e29969b0621bc7f762bd441b4539"},
+    {file = "tenacity-9.0.0.tar.gz", hash = "sha256:807f37ca97d62aa361264d497b0e31e92b8027044942bfa756160d908320d73b"},
+]
+
+[package.extras]
+doc = ["reno", "sphinx"]
+test = ["pytest", "tornado (>=4.5)", "typeguard"]
+
+[[package]]
+name = "toolz"
+version = "1.0.0"
+description = "List processing tools and functional utilities"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236"},
+    {file = "toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02"},
+]
+
+[[package]]
+name = "tornado"
+version = "6.4.1"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"},
+    {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"},
+    {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"},
+    {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"},
+    {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"},
+    {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"},
+    {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"},
+    {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"},
+    {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"},
+    {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"},
+    {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"},
+]
+
+[[package]]
+name = "treelite"
+version = "4.3.0"
+description = "Treelite: Universal model exchange format for decision tree forests"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "treelite-4.3.0-py3-none-macosx_10_15_x86_64.macosx_11_0_x86_64.macosx_12_0_x86_64.whl", hash = "sha256:4d8ee20673bbcc9fe2abd71b27281a232cec0db4223ddb204eeb3632c5f6ad12"},
+    {file = "treelite-4.3.0-py3-none-macosx_12_0_arm64.whl", hash = "sha256:e77bd5f02ac7eac13aa30c23b6f09e7f6827d775621f5b2f057c1e0624404eed"},
+    {file = "treelite-4.3.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:b71ea3158aa1e94dd0bec462df7802ea86e51a4f0e1236641e0d867abc320b40"},
+    {file = "treelite-4.3.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:32e6c09796d11f0107adc35897d85f25c74ad2e53e0666df7fbbf57f1d545b1f"},
+    {file = "treelite-4.3.0-py3-none-win_amd64.whl", hash = "sha256:2d415d4f4e592b4d610cb410fdc58351ec2489e110d6e3bfeb62a1ed7246f154"},
+    {file = "treelite-4.3.0.tar.gz", hash = "sha256:7d0f4cf89826fbf9556b39c9fff2e82ab071b1b16adb98de4e98fcbaf860bc27"},
+]
+
+[package.dependencies]
+numpy = "*"
+packaging = "*"
+scipy = "*"
+
+[package.extras]
+scikit-learn = ["scikit-learn"]
+testing = ["hypothesis", "pandas", "pytest", "scikit-learn"]
+
+[[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.2"
+description = "Provider of IANA time zone data"
+optional = false
+python-versions = ">=2"
+files = [
+    {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"},
+    {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"},
+]
+
+[[package]]
+name = "ucx-py-cu12"
+version = "0.40.0"
+description = "Python Bindings for the Unified Communication X library (UCX)"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "ucx_py_cu12-0.40.0.tar.gz", hash = "sha256:6a6a077fda5f84800e83e609a2a126dc3c0f5cca50ae12d9c0afcc5a151ec09f"},
+]
+
+[package.dependencies]
+libucx-cu12 = ">=1.15.0,<1.18"
+numpy = ">=1.23,<3.0a0"
+pynvml = ">=11.4.1"
+
+[package.extras]
+test = ["cloudpickle", "cudf-cu12 (==24.10.*)", "cupy-cuda12x (>=12.0.0)", "dask", "distributed", "numba (>=0.57)", "pytest (==7.*)", "pytest-asyncio", "pytest-rerunfailures"]
+
+[[package]]
+name = "ucxx-cu12"
+version = "0.40.0"
+description = "Python Bindings for the Unified Communication X library (UCX)"
+optional = false
+python-versions = ">=3.10"
+files = [
+    {file = "ucxx_cu12-0.40.0.tar.gz", hash = "sha256:714b14b0358ed2f6dfd77a49a7eebdfb12dc563f1b8e4d884a6ceb067de7350f"},
+]
+
+[package.dependencies]
+libucxx-cu12 = "==0.40.*"
+numpy = ">=1.23,<3.0a0"
+pynvml = ">=11.4.1"
+rmm-cu12 = "==24.10.*"
+
+[package.extras]
+test = ["cloudpickle", "cudf-cu12 (==24.10.*)", "cupy-cuda12x (>=12.0.0)", "numba (>=0.57.1)", "pytest (==7.*)", "pytest-asyncio", "pytest-rerunfailures", "rapids-dask-dependency (==24.10.*)"]
+
+[[package]]
+name = "urllib3"
+version = "2.2.3"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"},
+    {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+h2 = ["h2 (>=4,<5)"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "zict"
+version = "3.0.0"
+description = "Mutable mapping tools"
+optional = false
+python-versions = ">=3.8"
+files = [
+    {file = "zict-3.0.0-py2.py3-none-any.whl", hash = "sha256:5796e36bd0e0cc8cf0fbc1ace6a68912611c1dbd74750a3f3026b9b9d6a327ae"},
+    {file = "zict-3.0.0.tar.gz", hash = "sha256:e321e263b6a97aafc0790c3cfb3c04656b7066e6738c37fffcca95d803c9fba5"},
+]
+
+[[package]]
+name = "zipp"
+version = "3.21.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.9"
+files = [
+    {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"},
+    {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"},
+]
+
+[package.extras]
+check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"]
+cover = ["pytest-cov"]
+doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+enabler = ["pytest-enabler (>=2.2)"]
+test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"]
+type = ["pytest-mypy"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.11"
+content-hash = "b667e3fe3461713a893fc06bdf1a35bdbb581ac6826e2605b69851c6ee71b972"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..425e24a
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,51 @@
+[tool.poetry]
+name = "rc-gpfs"
+version = "0.0.0"
+description = "GPFS policy aggregation and reporting"
+authors = ["Matthew Defenderfer <mdefende@uab.edu>"]
+readme = "README.md"
+license = "AFL"
+repository = "https://gitlab.rc.uab.edu/rc/gpfs-policy"
+keywords = ["GPFS", "policy", "aggregation", "reporting"]
+classifiers = [
+    "Development Status :: 4 - Beta",
+    "Programming Language :: Python :: 3",
+    "Programming Language :: Python :: 3.11",
+    "Programming Language :: Python :: 3.12"
+]
+packages = [
+    { include = "rc_gpfs", from = "src" }
+]
+
+[tool.poetry.dependencies]
+python = "^3.11"
+pandas = "^2.2.2"
+numpy = "^1.26.4"
+pyarrow = "^16.1.0"
+cudf-cu12 = { version = "^24.10", source = "rapids" }
+dask-cudf-cu12 = { version = "^24.10", source = "rapids" }
+cuml-cu12 = { version = "^24.10", source = "rapids" }
+plotly = "^5.24.1"
+nvidia-ml-py = "^12.560.30"
+
+[[tool.poetry.source]]
+name="rapids"
+url="https://pypi.nvidia.com"
+priority = "supplemental"
+
+[tool.poetry.scripts]
+gpfs-preproc = "report.cli:aggregate_gpfs_dataset"
+gpfs-report = "report.cli:report"
+
+[tool.poetry-dynamic-versioning]
+enable = true
+vcs = "git"
+pattern = "default-unprefixed"
+format = "{base}+{distance}.{commit}"
+style = "semver"
+tag-branch = "main"
+
+[build-system]
+requires = ["poetry-core>=1.0.0","poetry-dynamic-versioning"]
+build-backend = "poetry_dynamic_versioning.backend"
+
diff --git a/src/rc_gpfs/__init__.py b/src/rc_gpfs/__init__.py
new file mode 100644
index 0000000..039b8a2
--- /dev/null
+++ b/src/rc_gpfs/__init__.py
@@ -0,0 +1,2 @@
+from .compute import *
+from .process import *
\ No newline at end of file
diff --git a/src/rc_gpfs/cli/__init__.py b/src/rc_gpfs/cli/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/rc_gpfs/cli/gpfs_preproc.py b/src/rc_gpfs/cli/gpfs_preproc.py
new file mode 100644
index 0000000..00e5c16
--- /dev/null
+++ b/src/rc_gpfs/cli/gpfs_preproc.py
@@ -0,0 +1,89 @@
+import argparse
+from pathlib import Path
+from pandas import to_datetime, Timestamp
+from typing import Literal
+from . import process
+from . import report
+
+__all__ = ['preproc']
+
+def _as_path(p) -> Path:
+    return Path(p).absolute()
+
+def _as_datetime(d) -> Timestamp:
+    return to_datetime(d)
+
+def _parse_preprocessing_args():
+    parser = argparse.ArgumentParser()
+    dataset_path = parser.add_argument(
+        'dataset',
+        type=_as_path,
+        help="Path to parquet file/dataset for processing. This can be given as the path to the containing directory if there are multiple parquet files in the dataset."
+    )
+    run_date = parser.add_argument(
+        '-r','--policy-run-date',
+        type=_as_datetime
+    )
+    delta_vals   = parser.add_argument(
+        '-a','--atime-deltas',
+        nargs='+',
+        type=int
+    )
+    delta_unit   = parser.add_argument(
+        '-u','--delta-unit',
+        type=str,
+        choices=['D','W','M','Y']
+    )
+    report_dir   = parser.add_argument(
+        '-d','--outdir',
+        type=_as_path
+
+    )
+    report_name  = parser.add_argument(
+        '-o','--outfile',
+        type=str,
+    )
+
+    cluster      = parser.add_argument_group(
+        title='Local Cluster Options',
+        description='Arguments to control Dask local cluster behavior'
+    )
+    
+    n_workers    = cluster.add_argument('-n','--n-workers', type = int)
+    with_cuda    = cluster.add_argument('--with-cuda', default = 'infer')
+    with_dask    = cluster.add_argument('--with-dask', default = 'infer')
+    args = parser.parse_args()
+    return vars(args)
+
+def _str_to_bool(val):
+    """Convert a string representation of truth to true (1) or false (0).
+    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
+    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
+    'val' is anything else.
+    """
+    val = val.lower()
+    if val in ('y', 'yes', 't', 'true', 'on', '1'):
+        return True
+    elif val in ('n', 'no', 'f', 'false', 'off', '0'):
+        return False
+    else:
+        return 'infer'
+
+def _convert_int(val):
+    try:
+        return int(val)
+    except:
+        return None
+
+def _fix_cluster_args(args) -> dict:
+    args['with_cuda'] = _str_to_bool(args['with_cuda'])
+    args['with_dask'] = _str_to_bool(args['with_dask'])
+    args['n_workers'] = _convert_int(args['n_workers'])
+    return args
+
+def preproc():
+    args = _parse_preprocessing_args()
+    args = _fix_cluster_args(args)
+
+    df_agg = process.aggregate_gpfs_dataset()
+    return df_agg
\ No newline at end of file
diff --git a/src/rc_gpfs/compute/__init__.py b/src/rc_gpfs/compute/__init__.py
new file mode 100644
index 0000000..3499062
--- /dev/null
+++ b/src/rc_gpfs/compute/__init__.py
@@ -0,0 +1,2 @@
+from .backend import *
+
diff --git a/src/rc_gpfs/compute/backend.py b/src/rc_gpfs/compute/backend.py
new file mode 100644
index 0000000..e38f134
--- /dev/null
+++ b/src/rc_gpfs/compute/backend.py
@@ -0,0 +1,173 @@
+from .backend_defs import backend_options
+from dask_cuda import LocalCUDACluster
+from dask.distributed import Client, LocalCluster
+from .utils import *
+from typing import Literal
+
+__all__ = ['start_backend']
+
+# ENH: Add default parameters for cluster creation based on defined type and available resources. For instance, creating a LocalCluster should default to using all available CPUs and all available RAM.
+class DaskClusterManager:
+    def __init__(self,  cluster_type: LocalCluster | LocalCUDACluster=LocalCluster, **kwargs):
+        """_summary_
+
+        Parameters
+        ----------
+        cluster_type : LocalCluster | LocalCUDACluster, default = LocalCluster
+            Sets the type of cluster to be created. Specifically designed to create either a CPU-only cluster (LocalCluster) or a GPU-enabled cluster (LocalCUDACluster). All specified kwargs will be passed to the cluster creator.
+        """
+
+        # Initialize the cluster (adjust this to the cluster type you are using)
+        self.cluster = cluster_type(**kwargs)
+        
+        # Initialize the client
+        self.client = Client(self.cluster)
+    
+    def scale(self, n_workers):
+        """Scale the cluster to the specified number of workers."""
+        self.cluster.scale(n_workers)
+
+    def close(self):
+        """Close the client and cluster."""
+        print("INFO: Closing Dask client and cluster")
+        self.client.close()
+        self.cluster.close()
+
+    def __getattr__(self, name):
+        """
+        Delegate attribute access to the client or cluster.
+
+        This allows accessing methods and properties from both objects directly
+        through this manager class.
+        """
+        if hasattr(self.client, name):
+            return getattr(self.client, name)
+        elif hasattr(self.cluster, name):
+            return getattr(self.cluster, name)
+        raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
+
+    def __repr__(self):
+        return (f"<DaskClusterManager(cluster={self.cluster}, "
+                f"client={self.client})>")
+
+def start_local_cluster(with_cuda=False,**kwargs) -> DaskClusterManager:
+    if with_cuda:
+        cluster_type = LocalCUDACluster
+        n_workers = kwargs.pop('n_workers',get_gpu_info()[0])
+        local_directory = kwargs.pop('local_directory','/scratch/local')
+        manager = DaskClusterManager(cluster_type=cluster_type, 
+                                     n_workers=n_workers,
+                                     local_directory=local_directory,
+                                     **kwargs)
+    else:
+        cluster_type = LocalCluster
+        n_workers = kwargs.pop('n_workers',parse_scontrol()[0])
+        memory_limit = kwargs.pop('memory_limit',None)
+        manager = DaskClusterManager(cluster_type=cluster_type, 
+                                     n_workers = n_workers,
+                                     memory_limit = memory_limit,
+                                     **kwargs)
+    return manager
+
+def select_backend_type(with_cuda, with_dask) -> Literal['dask_cuda','cudf','dask','pandas']:
+    if with_cuda and with_dask:
+        backend = 'dask_cuda'
+    elif with_cuda and not with_dask:
+        backend = 'cudf'
+    elif not with_cuda and with_dask:
+        backend = 'dask'
+    else:
+        backend = 'pandas'
+    return backend
+    
+def infer_cuda() -> bool:
+    ngpus,_ = get_gpu_info()
+    
+    print(f"INFO: {ngpus} GPUs are available.")
+    if ngpus > 0:
+        print("INFO: Using CUDA compute")
+    else:
+        print("INFO: USING CPU compute")
+    return ngpus > 0
+
+def infer_dask(est_dataset_gb: str | Path, 
+               ram: int | float = 0, 
+               mem_frac: float = 0.9, 
+               ngpus: int = 0, 
+               vram_per_gpu: int | float = 0) -> bool:
+    if ngpus > 1:
+        print("INFO: Multi-GPU detected. Setting Dask CUDA as backend")
+        with_dask = True
+    elif ngpus == 1:
+        with_dask = (vram_per_gpu * mem_frac) < est_dataset_gb
+
+        if with_dask:
+            print(f"INFO: Estimated dataset size ({round(est_dataset_gb,2)} GB) exceeds the GPU VRAM limit ({vram_per_gpu*mem_frac} GB) after deducting {round(vram_per_gpu*(1-mem_frac),2)} GB reserved for compute.")
+            print(f"INFO: Setting Dask CUDA as the backend to process the dataset in partitions. Increasing mem_frac above {mem_frac} may allow complete in-memory processing but increases the likelihood of OOM errors.")
+        else:
+            print(f"INFO: Estimated dataset size ({round(est_dataset_gb,2)} GB) is within the GPU VRAM limit ({vram_per_gpu*mem_frac} GB) after deducting {round(vram_per_gpu*(1-mem_frac),2)} GB reserved for compute.")
+            print(f"Setting cuDF as the compute backend")
+    else:
+        with_dask = (ram * mem_frac) < est_dataset_gb
+
+        if with_dask:
+            print(f"INFO: Estimated dataset size ({round(est_dataset_gb,2)} GB) exceeds the job's allocated RAM ({ram*mem_frac} GB) after deducting {round(ram*(1-mem_frac),2)} GB reserved for compute.")
+            print(f"INFO: Setting Dask as the compute backend to process the dataset in partitions. Increasing mem_frac above {mem_frac} may allow complete in-memory processing but increases the likelihood of OOM errors.")
+        else:
+            print(f"INFO: Estimated dataset size ({round(est_dataset_gb,2)} GB) is within the allocated memory limit ({ram} GB) after deducting {round(ram*(1-mem_frac),2)} GB reserved for compute.")
+            print(f"INFO: Setting pandas as the compute backend")
+    return with_dask
+
+def start_backend(dataset_path: str | Path,
+                  with_cuda: bool | Literal['infer'] = 'infer',
+                  with_dask: bool | Literal['infer'] = 'infer',
+                  **kwargs) -> DaskClusterManager | None:
+    
+    # Collect compute and dataset information to make an informed choice on the backend
+    est_dataset_gb = estimate_dataset_size(dataset_path)
+    cores,mem = parse_scontrol()
+    ngpus,vram_per_gpu = get_gpu_info() if with_cuda in ['infer',True] else [0,0]
+    
+    print(f"cores: {cores} \nmem: {mem} \nngpus: {ngpus} \nvram: {vram_per_gpu}")
+    
+    # If either cuda or dask is set to infer, need to determine which of those is applicable to the current dataset
+    # based on the resources available
+    if with_cuda == 'infer':
+        with_cuda = infer_cuda()
+    if with_dask == 'infer':
+        mem_frac = kwargs.get('mem_frac',0.9)
+        with_dask = infer_dask(est_dataset_gb, 
+                               ram = mem, 
+                               mem_frac=mem_frac, 
+                               ngpus = ngpus, 
+                               vram_per_gpu = vram_per_gpu)
+
+    backend = select_backend_type(with_cuda,with_dask)
+
+    imports = backend_options[backend].get('imports',{})
+    imports_zipped = zip(imports.get('package',[]), imports.get('alias',[]))
+    pre_import_hooks = backend_options[backend].get('pre_import_hooks',{})
+    post_import_hooks = backend_options[backend].get('post_import_hooks',{})
+    
+    for package, alias in imports_zipped:
+        if package in pre_import_hooks.keys():
+            hook = pre_import_hooks[package]
+            wrap_hook(hook)
+        
+        import_package(package,alias)
+
+        if package in post_import_hooks.keys():
+            hook = post_import_hooks[package]
+            wrap_hook(hook)
+
+    guidance_message = backend_options[backend]['guidance_message']
+    if guidance_message is not None:
+        print(f"INFO: {guidance_message}")
+
+    if backend in ['dask','dask_cuda']:
+        n_workers_default = cores if backend == 'dask' else ngpus
+        n_workers = kwargs.pop('n_workers',n_workers_default)
+        manager = start_local_cluster(with_cuda,n_workers=n_workers,**kwargs)
+    else:
+        manager = None
+    return [manager,backend]
\ No newline at end of file
diff --git a/src/rc_gpfs/compute/backend_defs.py b/src/rc_gpfs/compute/backend_defs.py
new file mode 100644
index 0000000..b2694d0
--- /dev/null
+++ b/src/rc_gpfs/compute/backend_defs.py
@@ -0,0 +1,42 @@
+backend_options = {
+    "pandas": {
+        "imports": {
+            "package": ["pandas"],
+            "alias": ["pd"]
+        },
+        "guidance_message": None,
+        "example_usage": "df = pd.read_parquet('dataset')"
+    },
+    "cudf": {
+        "imports": {
+            "package": ["cudf.pandas","pandas"],
+            "alias": [None,"pd"]
+        },
+        "pre_import_hooks": {
+            "pandas": "cudf.pandas.install()"
+        },
+        "guidance_message": (
+            "cuDF and pandas have been loaded into your current environment. All applicable pandas functions will use silently use cuDF when able and will default to pandas otherwise. Use standard pandas functions for processing"
+        ),
+        "example_usage": "df = pd.read_parquet('dataset')"
+    },
+    "dask": {
+        "imports": {
+            "package": ["dask.dataframe"],
+            "alias": ["dd"]
+        },
+        "guidance_message": "Only dask.dataframe has been loaded automatically. All other dask modules must be loaded manually in order to use them.",
+        "example_usage": "df = dd.read_parquet('dataset')"
+    },
+    "dask_cuda": {
+        "imports": {
+            "package": ["dask_cuda","dask.dataframe"],
+            "alias": [None,"dd"]
+        },
+        "post_import_hooks": {
+            "dask.dataframe": "dask.config.set({'dataframe.backend': 'cudf'})"
+        },
+        "guidance_message": "Only dask.dataframe has been loaded automatically. All other dask modules must be loaded manually in order to use them.",
+        "example_usage": "df = dd.read_parquet('dataset')"
+    }
+}
\ No newline at end of file
diff --git a/src/rc_gpfs/compute/utils.py b/src/rc_gpfs/compute/utils.py
new file mode 100644
index 0000000..b6d38b0
--- /dev/null
+++ b/src/rc_gpfs/compute/utils.py
@@ -0,0 +1,187 @@
+import sys
+import ast
+import pyarrow.parquet as pq
+import os
+import subprocess
+import re
+import pynvml
+import importlib
+from typing import Any
+from pathlib import Path
+from ..utils import convert_si
+
+def parse_scontrol():
+    job_id = os.getenv('SLURM_JOB_ID')
+
+    command = f"scontrol show job {job_id} | grep TRES="
+    result = subprocess.run(command, shell=True, capture_output=True, text=True).stdout.strip()
+
+    tres_pattern=r'.*cpu=(?P<cores>[\d]+),mem=(?P<mem>[\d]+[KMGT]?).*'
+    cores,mem = re.search(tres_pattern,result).groupdict().values()
+    
+    cores = int(cores)
+    mem = convert_si(mem,to_unit='G',use_binary=True)
+    return [cores,mem]
+
+def estimate_dataset_size(path: str | Path) -> float:
+    if not isinstance(path,Path):
+        path = Path(path)
+    
+    # only read first parquet file if path leads to directory
+    if path.is_dir():
+        parquet_files = sorted([f for f in path.glob('*.parquet')])
+    else:
+        parquet_files = [path]
+    
+    total_uncompressed_size = 0
+
+    # Loop through each row group in the Parquet file
+    for file_path in parquet_files:
+        with pq.ParquetFile(file_path) as parquet:
+            for i in range(parquet.metadata.num_row_groups):
+                row_group = parquet.metadata.row_group(i)
+            
+                # Sum up the uncompressed sizes for each row group
+                total_uncompressed_size += row_group.total_byte_size
+    
+    return total_uncompressed_size / (1024**3)
+
+def get_gpu_info():
+    try:
+        pynvml.nvmlInit()
+    except Exception as e:
+        print("INFO: No GPU found. Using CPU backend.")
+        return [0,0]
+    
+    gpus = pynvml.nvmlDeviceGetCount()
+    handle = pynvml.nvmlDeviceGetHandleByIndex(0)
+    vram = pynvml.nvmlDeviceGetMemoryInfo(handle).total/(1024**3)
+    pynvml.nvmlShutdown()
+    return [gpus,vram]
+
+def import_package(package_name, alias=None):
+    """
+    Dynamically imports a library and adds it to the main (top-level) namespace.
+
+    Args:
+        library_name (str): The name of the library to import.
+        alias (str, optional): An alias to assign to the library in the main namespace.
+                               Defaults to the library name.
+    """
+    try:
+        # Dynamically import the library or submodule
+        imported_library = importlib.import_module(package_name)
+        
+        # Determine the alias or default name
+        global_name = alias if alias else package_name
+        
+        # Access the main module's global namespace
+        main_namespace = sys.modules['__main__'].__dict__
+        
+        # Add the full library or submodule to the main namespace
+        main_namespace[global_name] = imported_library
+        
+        # Also ensure parent modules are available in the global namespace
+        parts = package_name.split('.')
+        for i in range(1, len(parts)):
+            parent_name = '.'.join(parts[:i])
+            if parent_name not in main_namespace:
+                main_namespace[parts[i - 1]] = importlib.import_module(parent_name)
+        
+        print(f"Successfully imported '{package_name}' into the main namespace as '{global_name}'.")
+    except ImportError as e:
+        print(f"Error importing '{package_name}': {e}")
+        raise
+
+def parse_hook_string(hook_string: str) -> tuple[str, list[Any], dict[str | None, Any]]:
+    """
+    Parses a string representing a function call and extracts the function and its arguments.
+
+    Parameters
+    ----------
+    hook_string : str
+        A string representation of the function call 
+            (e.g., "dask.config.set({'dataframe.backend': 'cudf'})").
+
+    Returns
+    -------
+    tuple[str, list[Any], dict[str | None, Any]]
+        - hook (str): The dotted path to the function (e.g., "dask.config.set").
+        - args (list): A list of positional arguments.
+        - kwargs (dict): A dictionary of keyword arguments.
+
+    Raises
+    ------
+    ValueError
+        If the input string cannot be parsed as a function call.
+    """
+    try:
+        # Parse the string into an AST (Abstract Syntax Tree)
+        tree = ast.parse(hook_string, mode="eval")
+
+        # Ensure the root of the tree is a function call
+        if not isinstance(tree.body, ast.Call):
+            raise ValueError(f"Invalid hook string: {hook_string}")
+
+        # Extract the function name (dotted path)
+        func = tree.body.func
+
+        def get_full_name(node):
+            if isinstance(node, ast.Name):
+                return node.id
+            elif isinstance(node, ast.Attribute):
+                return f"{get_full_name(node.value)}.{node.attr}"
+            raise ValueError(f"Unsupported function structure in: {hook_string}")
+
+        hook = get_full_name(func)
+
+        # Extract positional arguments
+        args = [ast.literal_eval(arg) for arg in tree.body.args]
+
+        # Extract keyword arguments
+        kwargs = {kw.arg: ast.literal_eval(kw.value) for kw in tree.body.keywords}
+
+        return hook, args, kwargs
+    except Exception as e:
+        raise ValueError(f"Error parsing hook string '{hook_string}': {e}")
+
+def run_hook(hook: str, *args, **kwargs):
+    """
+    Executes a hook function specified by its dotted path, with optional arguments and keyword arguments.
+
+    Parameters
+    ----------
+    hook : str
+        The dotted path to the hook function (e.g., 'dask.config.set')
+    *args : Any
+        Positional arguments to pass to the hook function.
+    **kwargs : Any
+        Keyword arguments to pass to the hook function.
+    
+    Raises
+    ------
+    Exception 
+        If the specified hook cannot be imported or executed.
+    """
+
+    try:
+        # Split the hook into module and function parts
+        hook_module_name, hook_func_name = hook.rsplit('.', 1)
+        
+        # Dynamically import the module
+        hook_module = importlib.import_module(hook_module_name)
+        
+        # Get the function from the module
+        hook_func = getattr(hook_module, hook_func_name)
+        
+        # Execute the function with the provided arguments and keyword arguments
+        hook_func(*args, **kwargs)
+        print(f"Executed hook: {hook} with args: {args} and kwargs: {kwargs}")
+    except Exception as e:
+        print(f"Error executing hook '{hook}': {e}")
+        raise
+
+def wrap_hook(hook: str) -> None:
+    func, args, kwargs = parse_hook_string(hook)
+    run_hook(func,*args,**kwargs)
+    return None
\ No newline at end of file
diff --git a/src/rc_gpfs/policy/__init__.py b/src/rc_gpfs/policy/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/convert-to-parquet/convert-to-parquet.py b/src/rc_gpfs/policy/convert-to-parquet.py
similarity index 100%
rename from src/convert-to-parquet/convert-to-parquet.py
rename to src/rc_gpfs/policy/convert-to-parquet.py
diff --git a/src/convert-to-parquet/run-convert-to-parquet.sh b/src/rc_gpfs/policy/run-convert-to-parquet.sh
similarity index 100%
rename from src/convert-to-parquet/run-convert-to-parquet.sh
rename to src/rc_gpfs/policy/run-convert-to-parquet.sh
diff --git a/src/split-info-file.sh b/src/rc_gpfs/policy/split-info-file.sh
similarity index 100%
rename from src/split-info-file.sh
rename to src/rc_gpfs/policy/split-info-file.sh
diff --git a/src/rc_gpfs/process/__init__.py b/src/rc_gpfs/process/__init__.py
new file mode 100644
index 0000000..40cc503
--- /dev/null
+++ b/src/rc_gpfs/process/__init__.py
@@ -0,0 +1 @@
+from .process import *
\ No newline at end of file
diff --git a/src/rc_gpfs/process/factory.py b/src/rc_gpfs/process/factory.py
new file mode 100644
index 0000000..1610f65
--- /dev/null
+++ b/src/rc_gpfs/process/factory.py
@@ -0,0 +1,198 @@
+import cudf
+import pandas as pd
+import dask.dataframe as dd
+import dask_cudf
+from .utils import as_timedelta
+from typing import Literal
+from typeguard import typechecked
+
+__all__ = ['get_aggregator']
+
+# ENH: In the future, probably need to wrap the manager and backend type into a class. That class would contain the 
+# read_parquet function instead of putting it in the aggregation classes. This would separate everything out more 
+# sensibly
+
+@typechecked
+class Aggregator:
+    def __init__(self):
+        self.backend = None
+        self.cuda = None
+    
+    def _cut(
+        self,
+        ser: pd.Series | cudf.Series,
+        bins: list[int | pd.Timestamp],
+        labels: list[str] | None = None,
+        **kwargs
+    ) -> pd.Series | cudf.Series:
+        right = kwargs.pop('right',False)
+               
+        if self.cuda:
+            func = cudf.cut
+            ser = ser.astype('int64')
+        else:
+            func = pd.cut
+
+        grps = func(ser,bins=bins,labels=labels,right=right,**kwargs)
+        if labels is not None:
+            grps = grps.cat.reorder_categories(labels[::-1], ordered = True)
+        return grps
+
+    def create_timedelta_cutoffs(
+        self,
+        delta_vals: int | list[int],
+        delta_unit: Literal['D','W','M','Y'],
+        run_date: pd.Timestamp
+    ) -> list[int | pd.Timestamp]:
+        deltas = pd.Series([as_timedelta(c,delta_unit) for c in delta_vals])
+        cutoffs = pd.to_datetime(run_date - deltas)
+        cutoffs = (
+            pd.concat(
+                [
+                    cutoffs,
+                    pd.Series([pd.to_datetime('today'),pd.to_datetime('1970-01-01')])
+                ]
+            )
+            .sort_values()
+        )
+
+        return cutoffs.astype('int64').to_list() if self.cuda else cutoffs.to_list()
+    
+    def create_timedelta_labels(
+        self,
+        delta_vals: list[int],
+        delta_unit: Literal['D','W','M','Y'],
+    ) -> list[str]:
+        delta_vals.sort(reverse=True)
+        deltas = [f'{d}{delta_unit}' for d in delta_vals]
+        labels = [f'>{deltas[0]}'] + [f'{deltas[i+1]}-{deltas[i]}' for i in range(len(deltas)-1)] + [f'<{deltas[-1]}']
+        return labels
+
+class PandasAggregator(Aggregator):
+    def __init__(self):
+        self.backend = 'pandas'
+        self.cuda = False
+
+    def read_parquet(self,dataset_path,**kwargs) -> pd.DataFrame:
+        return pd.read_parquet(dataset_path,**kwargs)
+        
+    def cut_dt(self,series,*args,**kwargs) -> pd.Series:
+        return self._cut(series,*args,**kwargs)
+
+    def aggregate(
+        self,
+        df: cudf.DataFrame,
+        col: str | list[str],
+        grps: str | list[str],
+        funcs: str | list[str]
+    ) -> pd.DataFrame:
+        
+        df_agg = (
+            df.groupby(grps,observed = True)[col]
+            .agg(funcs)
+            .sort_index(level=[0,1])
+            .reset_index()
+        )
+        return df_agg
+
+    
+class CUDFAggregator(Aggregator):
+    def __init__(self):
+        self.backend = 'cudf'
+        self.cuda = True
+
+    def read_parquet(self,dataset_path,**kwargs) -> cudf.DataFrame:
+        return cudf.read_parquet(dataset_path,**kwargs)
+    
+    def cut_dt(self,series,*args,**kwargs) -> pd.Series:
+        return self._cut(series,*args,**kwargs)
+    
+    def aggregate(
+        self,
+        df: cudf.DataFrame,
+        col: str | list[str],
+        grps: str | list[str],
+        funcs: str | list[str]
+    ) -> pd.DataFrame:
+        df_agg = (
+            df.groupby(grps,observed = True)[col]
+            .agg(funcs)
+            .sort_index(level=[0,1])
+            .to_pandas()
+            .reset_index()
+        )
+        return df_agg
+
+
+class DaskAggregator(Aggregator):
+    def __init__(self):
+        self.backend = 'dask'
+        self.cuda = False
+
+    def cut_dt(self,series,*args,**kwargs) -> cudf.Series:
+        return series.map_partitions(self._cut,*args,**kwargs)
+
+    def aggregate(
+        self,
+        df: dd.DataFrame,
+        col: str | list[str],
+        grps: str | list[str],
+        funcs: str | list[str]
+    ) -> pd.DataFrame:
+        df_agg = (
+            df.groupby(grps,observed = True)[col]
+            .agg(funcs)
+            .compute()
+            .sort_index(level=[0,1])
+            .reset_index()
+        )
+        return df_agg
+    
+    def read_parquet(self,dataset_path,**kwargs) -> dd.DataFrame:
+        split_row_groups = kwargs.pop('split_row_groups',False)
+        return dd.read_parquet(dataset_path,split_row_groups=split_row_groups,**kwargs)
+
+
+class DaskCUDFAggregator(Aggregator):
+    def __init__(self):
+        self.backend = 'dask_cuda'
+        self.cuda = True
+
+    def cut_dt(self,series,*args,**kwargs) -> dask_cudf.Series:
+        return series.map_partitions(self._cut,*args,**kwargs)
+    
+    def aggregate(
+        self,
+        df: dask_cudf.DataFrame,
+        col: str | list[str],
+        grps: str | list[str],
+        funcs: str | list[str]
+    ) -> pd.DataFrame:
+        df_agg = (
+            df.groupby(grps,observed = True)[col]
+            .agg(funcs)
+            .compute()
+            .sort_index(level=[0,1])
+            .to_pandas()
+            .reset_index()
+        )
+        return df_agg
+    
+    def read_parquet(self,dataset_path,**kwargs) -> dd.DataFrame:
+        split_row_groups = kwargs.pop('split_row_groups',False)
+        return dd.read_parquet(dataset_path,split_row_groups=split_row_groups,**kwargs)
+    
+
+
+def get_aggregator(backend) -> PandasAggregator | CUDFAggregator | DaskAggregator | DaskCUDFAggregator:
+    match backend:
+        case 'pandas':
+            return PandasAggregator()
+        case 'cudf':
+            return CUDFAggregator()
+        case 'dask':
+            return DaskAggregator()
+        case 'dask_cuda':
+            return DaskCUDFAggregator()
+        case _:
+            raise ValueError(f"Unsupported backend: {backend}")
\ No newline at end of file
diff --git a/src/rc_gpfs/process/process.py b/src/rc_gpfs/process/process.py
new file mode 100644
index 0000000..4c61b27
--- /dev/null
+++ b/src/rc_gpfs/process/process.py
@@ -0,0 +1,109 @@
+from pathlib import Path
+import pandas as pd
+from ..compute import start_backend
+from .utils import extract_run_date_from_filename
+from .factory import get_aggregator
+from typing import Literal
+from typeguard import typechecked
+
+__all__ = ['aggregate_gpfs_dataset']
+
+def _check_dataset_path(dataset_path) -> Path:
+    if not isinstance(dataset_path,Path):
+        dataset_path = Path(dataset_path)
+
+    if dataset_path.is_file():
+        print(f"INFO: Found 1 file ({dataset_path})")
+    elif dataset_path.is_dir():
+        n_files = len(list(dataset_path.glob('*.parquet')))
+        print(f"INFO: {n_files} parquet files found in dataset")
+        
+    if not dataset_path.exists():
+        raise FileNotFoundError(f"{dataset_path} does not exist. Please check your input")
+    
+    return dataset_path
+
+# ENH: The default naming scheme for the aggregated data needs to be refined. It should describe what aggregations were 
+# performed such as including tld and cutoff date if both were specified
+def _check_report_paths(
+        report_dir: str | Path | None,
+        report_name: str | Path | None,
+        parent_path: Path
+        ) -> Path:
+    if report_dir is None:
+        report_dir = parent_path.joinpath('reports')
+    elif not isinstance(report_dir,Path):
+        report_dir = Path(report_dir)
+    
+    report_dir.mkdir(exist_ok=True,mode=0o2770)
+    
+    if report_name is None:
+        report_name = 'tld_agg.parquet'
+    
+    report_path = report_dir.joinpath(report_name)
+    return report_path
+
+def _check_timedelta_values(vals,unit):
+    if (vals is None) != (unit is None):
+        raise ValueError("Must specify either both or neither of delta_vals and delta_unit")
+    pass
+
+# ENH: Refactor this, it should not automatically create the compute backend, that should be passed to it. Creating the 
+# backend is beyond the scope of the process module and this function specifically. Can wrap this with the backend 
+# creation in a CLI command later for convenience, but that shouldn't be baked in
+
+@typechecked
+def aggregate_gpfs_dataset(
+    dataset_path: str | Path,
+    run_date: pd.Timestamp | None = None,
+    delta_vals: int | list[int] | None = None,
+    delta_unit: Literal['D','W','M','Y'] | None = None,
+    time_val: Literal['access','modify','create'] = 'access',
+    report_dir: str | Path | None = None,
+    report_name: str | Path | None = None,
+    n_workers: int | None = None,
+    with_cuda: Literal['infer'] | bool = 'infer',
+    with_dask: Literal['infer'] | bool = 'infer',
+    **kwargs
+) -> None:
+    # Input checking
+    dataset_path = _check_dataset_path(dataset_path)
+    if dataset_path.is_file():
+        parent_path = dataset_path.parent.parent
+    else:
+        parent_path = dataset_path.parent
+
+    report_path = _check_report_paths(report_dir,report_name,parent_path)
+    _check_timedelta_values(delta_vals,delta_unit)
+
+    if run_date is None:
+        run_date = extract_run_date_from_filename(dataset_path)
+
+    manager,backend = start_backend(
+        dataset_path=dataset_path,
+        with_cuda=with_cuda,
+        with_dask=with_dask,
+        n_workers=n_workers,
+        **kwargs
+        )
+    
+    try:
+        aggregator = get_aggregator(backend)
+        df = aggregator.read_parquet(dataset_path)
+        grps = ['tld']
+        if delta_vals is not None:
+            cutoffs = aggregator.create_timedelta_cutoffs(delta_vals,delta_unit,run_date)
+            labels  = aggregator.create_timedelta_labels(delta_vals,delta_unit)
+            df['dt_grp'] = aggregator.cut_dt(df[time_val],cutoffs,labels)
+            grps.append('dt_grp')
+        else:
+            cutoffs, labels = [None,None]
+        
+        df_agg = aggregator.aggregate(df, col = ['size'], grps = grps, funcs = ['sum','count'])
+        df_agg = df_agg.rename(columns={'sum':'bytes','count':'file_count'})
+        df_agg.to_parquet(report_path)
+    finally:
+        if manager is not None:
+            manager.close()
+    
+    return None
\ No newline at end of file
diff --git a/src/rc_gpfs/process/utils.py b/src/rc_gpfs/process/utils.py
new file mode 100644
index 0000000..14973dd
--- /dev/null
+++ b/src/rc_gpfs/process/utils.py
@@ -0,0 +1,29 @@
+import re
+from pathlib import Path
+from typing import Literal
+from pandas import to_datetime, DataFrame, Timestamp
+from pandas.tseries.offsets import DateOffset
+
+def extract_run_date_from_filename(
+        path: str | Path,
+        pattern: str = r'[\d]{4}-[\d]{2}-[\d]{2}'
+        ) -> Timestamp:
+    if isinstance(path,Path):
+        path = str(path.absolute())
+    run_date = to_datetime(re.search(pattern,path).group(),format='%Y-%m-%d')
+    return run_date
+
+def as_timedelta(
+        val: int,
+        unit: Literal['D','W','M','Y']
+        ) -> DateOffset:
+    if unit == 'D':  # Days
+        return DateOffset(days=val)
+    elif unit == 'W':  # Weeks
+        return DateOffset(weeks=val)
+    elif unit == 'M':  # Months
+        return DateOffset(months=val)
+    elif unit == 'Y':  # Years
+        return DateOffset(years=val)
+    else:
+        raise ValueError(f"{unit} is not a valid unit. Choose one of D, W, M, or Y")
\ No newline at end of file
diff --git a/src/rc_gpfs/report/__init__.py b/src/rc_gpfs/report/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/src/rc_gpfs/report/general-report.qmd b/src/rc_gpfs/report/general-report.qmd
new file mode 100644
index 0000000..4e9fafc
--- /dev/null
+++ b/src/rc_gpfs/report/general-report.qmd
@@ -0,0 +1,59 @@
+---
+title: "Quarto Basics"
+format:
+  html:
+    execute:
+        echo: false
+        warning: false
+jupyter: python3
+---
+
+```{python}
+import pandas as pd
+from pathlib import Path
+import plotting
+```
+
+```{python}
+#| tags: [parameters]
+
+report_dir = '/data/rc/gpfs-policy/data/list-policy_data-user_2024-09-18/reports'
+```
+
+```{python}
+#| label: data-loading
+report_dir = Path(report_dir)
+report = report_dir.joinpath('tld_atime-age_agg.parquet')
+
+df = pd.read_parquet(report)
+```
+
+```{python}
+age_agg = df.groupby('dt_grp',observed=True,as_index=False)[['file_count','bytes']].sum()
+
+exp,unit = plotting.choose_appropriate_storage_unit(age_agg['bytes'])
+
+age_agg[unit] = age_agg['bytes']/(1024**exp)
+age_agg[['file_count_cum',f'{unit}_cum']] = age_agg[['file_count',unit]].cumsum()
+age_agg[[unit,f'{unit}_cum']] = age_agg[[unit,f'{unit}_cum']].round(3)
+```
+
+
+```{python}
+#| label: plot-storage-by-atime-group
+legend_labels = ['Raw','Cumlative']
+cols = [unit,f'{unit}_cum']
+storage_plot = plotting.create_bar_plot(df=age_agg,x='dt_grp',y=cols,legend_labels=legend_labels,textposition='outside',
+                         title=f'{unit} per atime Group', xlabel='Access Time Age', ylabel=f'Storage Used ({unit})')
+storage_plot.show()
+```
+
+
+```{python}
+#| label: plot-file-count-by-atime-group
+cols = ['file_count','file_count_cum']
+file_count_plot = plotting.create_bar_plot(df=age_agg,x='dt_grp',y=cols,legend_labels=legend_labels,
+                                           textposition='outside',text_decimals=0, title='File Count per atime Group', 
+                                           xlabel='Access Time Age', ylabel='File Count')
+file_count_plot.show()
+```
\ No newline at end of file
diff --git a/src/rc_gpfs/report/plotting.py b/src/rc_gpfs/report/plotting.py
new file mode 100644
index 0000000..73c4c12
--- /dev/null
+++ b/src/rc_gpfs/report/plotting.py
@@ -0,0 +1,135 @@
+from plotly.graph_objects import Figure
+import plotly.graph_objects as go
+from plotly import colors
+
+__all__ = ['bar_plot','pareto_chart']
+
+def choose_appropriate_storage_unit(size,starting_unit='B'):
+    if hasattr(size, "__len__"):
+        size = size.max()
+    
+    try:
+        units = ['B','kiB','MiB','GiB','TiB']
+        units_b10 = ['kB','MB','GB','TB']
+        if starting_unit in units_b10:
+            starting_unit = starting_unit.replace('B','iB')
+            # add logging message here saying the specified base 10 unit is being interpreted as base 2
+        exp = units.index(starting_unit)
+    except (ValueError):
+        raise(f"{starting_unit} is not a valid storage unit. Choose from 'B','kB','MB','GB', or 'TB'")
+    
+    while ((size/1024) >= 1) & (exp <= 4):
+        size = size/1024
+        exp += 1
+    return exp,units[exp]
+
+def _format_number(num,dec=2):
+    return f"{num:,.{dec}f}"  # Format with commas and 3 decimal places
+
+def bar_plot(df,x,y,show_legend=True,legend_labels=None,add_text=True,textposition=None,text_decimals=2,
+            group_colors=colors.qualitative.Plotly,title=None,xlabel=None,ylabel=None,enable_text_hover=False) -> Figure:
+    if not isinstance(y,list):
+        y = [y]
+
+    if show_legend and legend_labels is None:
+        legend_labels = y
+    
+    textposition = textposition if add_text else None
+    
+    fig = go.Figure()
+    for idx,c in enumerate(y):
+        text = df[c].apply(_format_number,dec=text_decimals) if add_text else None
+        fig.add_bar(
+            x=df[x],
+            y=df[c],
+            text=text,
+            textposition=textposition,
+            name=legend_labels[idx],
+            marker_color=group_colors[idx],
+            uid=idx
+        )
+
+    # If plotting multiple traces, make some updates to the layout
+    if len(y) > 1:
+        for idx in range(len(y)):
+            fig.update_traces(
+                patch = {'offsetgroup':idx},
+                selector = {'uid':idx}
+            )
+        
+        fig.update_layout(
+            barmode='group',  # Grouped bar chart
+            bargap=0.3
+        )
+
+    fig.update_layout(
+        title_text=title,
+        title_x=0.5,
+        title_xanchor='center',
+        title_font_size = 24,
+        xaxis_title=xlabel,
+        yaxis_title=ylabel,
+        margin=dict(t=100, b=20, l=40, r=40),
+        template='plotly_white',
+        hovermode=enable_text_hover
+    )
+
+    return fig
+
+def pareto_chart(df, x, y, cumsum_col=None, show_legend=True, legend_labels=['Raw','Cumulative'], add_text=True,
+                 textposition_bar=None, textposition_scatter=None, text_decimals=2, 
+                 group_colors=colors.qualitative.Plotly, title=None,xlabel=None,ylabel=None,enable_text_hover=False) -> Figure:
+    df_ = df.copy()
+    if cumsum_col is None:
+        cumsum_col = f'{y}_cumsum'
+        df_[cumsum_col] = df_[y].cumsum()
+    
+    if show_legend and legend_labels is None:
+        legend_labels = [y,cumsum_col]
+    
+    if add_text and textposition_bar is None:
+        textposition_bar = 'outside'
+    
+    if add_text and textposition_scatter is None:
+        textposition_scatter = 'top center'
+    
+    fig = go.Figure()
+    bar_text = df_[y].apply(_format_number,dec=text_decimals) if add_text else None
+    fig.add_bar(
+        x=df_[x],
+        y=df_[y],
+        text=bar_text,
+        textposition=textposition_bar,
+        name=legend_labels[0],
+        marker_color=group_colors[0]
+    )
+
+    if add_text:
+        scatter_text = df_[cumsum_col].apply(_format_number,dec=text_decimals) 
+        scatter_text[0] = None
+    else:
+        scatter_text = None
+    
+    fig.add_scatter(
+        x=df_[x],
+        y=df_[cumsum_col],
+        text=scatter_text,
+        textposition=textposition_scatter,
+        name=legend_labels[1],
+        mode='lines+markers+text',
+        marker_color=group_colors[1]
+    )
+
+    fig.update_layout(
+        title_text=title,
+        title_x=0.5,
+        title_xanchor='center',
+        title_font_size = 24,
+        xaxis_title=xlabel,
+        yaxis_title=ylabel,
+        margin=dict(t=100, b=20, l=40, r=40),
+        template='plotly_white',
+        hovermode=enable_text_hover
+    )
+
+    return fig
\ No newline at end of file
diff --git a/src/rc_gpfs/report/reports.ipynb b/src/rc_gpfs/report/reports.ipynb
new file mode 100644
index 0000000..dab68da
--- /dev/null
+++ b/src/rc_gpfs/report/reports.ipynb
@@ -0,0 +1,398 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "<dask.config.set at 0x2aaab64e3150>"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from dask_cuda import LocalCUDACluster\n",
+    "from dask.distributed import Client\n",
+    "import dask\n",
+    "import dask.dataframe as dd\n",
+    "import cudf\n",
+    "import pandas as pd\n",
+    "from pandas.tseries.offsets import DateOffset\n",
+    "import re\n",
+    "from pathlib import Path\n",
+    "\n",
+    "dask.config.set({\"dataframe.backend\": \"cudf\"})"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "parquet_ds = '/data/rc/gpfs-policy/data/list-policy_data-user_2024-09-18/parquet'\n",
+    "run_date = pd.to_datetime(re.search(r'[\\d]{4}-[\\d]{2}-[\\d]{2}',parquet_ds).group(),format='%Y-%m-%d')\n",
+    "delta_vals = [i for i in range(1,19)]\n",
+    "delta_unit = 'M' # 'D','W','M','Y'\n",
+    "report_dir = Path(parquet_ds).parent.joinpath('reports')\n",
+    "report_dir.mkdir(exist_ok=True)\n",
+    "report_dir.chmod(mode = 0o2770) # 0o2770 sets user/group rwx and group sticky bit"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Setup Cluster and Data"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "cluster = LocalCUDACluster(threads_per_worker=10,device_memory_limit=None)\n",
+    "client=Client(cluster)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ddf = dd.read_parquet(parquet_ds,split_row_groups=False)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Define Cutoffs In DateTimes"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def create_timedelta(val,unit):\n",
+    "    if unit == 'D':  # Days\n",
+    "        return DateOffset(days=val)\n",
+    "    elif unit == 'W':  # Weeks\n",
+    "        return DateOffset(weeks=val)\n",
+    "    elif unit == 'M':  # Months\n",
+    "        return DateOffset(months=val)\n",
+    "    elif unit == 'Y':  # Years\n",
+    "        return DateOffset(years=val)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "deltas = pd.Series([create_timedelta(c,delta_unit) for c in delta_vals])\n",
+    "cutoffs = pd.to_datetime(run_date - deltas)\n",
+    "cutoffs = (\n",
+    "    pd.concat(\n",
+    "        [\n",
+    "            cutoffs,\n",
+    "            pd.Series([run_date,pd.to_datetime('1970-01-01')])\n",
+    "        ]\n",
+    "    )\n",
+    "    .astype('int64')\n",
+    "    .sort_values()\n",
+    "    .to_list()\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def create_cutoff_group_labels(vals,unit):\n",
+    "    vals.sort(reverse=True) # values sorted in descending order since the largest number represents the oldest group\n",
+    "    deltas = [f'{d}{unit}' for d in vals]\n",
+    "    groups = []\n",
+    "    groups.append(f'>{deltas[0]}')\n",
+    "    for i in range(len(deltas)-1):\n",
+    "        groups.append(f'{deltas[i+1]}-{deltas[i]}')\n",
+    "    groups.append(f'<{deltas[-1]}')\n",
+    "    return groups"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "grp_labels = create_cutoff_group_labels(delta_vals,delta_unit)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ddf['access_epoch'] = ddf['access'].astype('int64')"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def _cut(ser,bins,labels,with_cuda,**kwargs) -> pd.Series | cudf.Series:\n",
+    "        right = kwargs.pop('right',False)\n",
+    "        if with_cuda:\n",
+    "            func = cudf.cut\n",
+    "            ser = ser.astype('int64')\n",
+    "        else:\n",
+    "            func = pd.cut\n",
+    "        \n",
+    "        grps = func(ser,bins=bins,labels=labels,right=right,**kwargs)\n",
+    "        if labels is not None:\n",
+    "            grps = grps.cat.reorder_categories(labels[::-1], ordered = True)\n",
+    "        return grps"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 78,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "ddf['dt_grp'] = ddf['access_epoch'].map_partitions(cudf_cut,cutoffs,grp_labels)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 79,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "v = ddf.get_partition(0).compute()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 83,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "4743970     >18M\n",
+       "4744054     >18M\n",
+       "4744053     >18M\n",
+       "4744052     >18M\n",
+       "4744051     >18M\n",
+       "           ...  \n",
+       "523698     4M-5M\n",
+       "523696     5M-6M\n",
+       "523629     5M-6M\n",
+       "523625     5M-6M\n",
+       "523712     4M-5M\n",
+       "Length: 5000000, dtype: category\n",
+       "Categories (19, object): ['>18M' < '17M-18M' < '16M-17M' < '15M-16M' ... '3M-4M' < '2M-3M' < '1M-2M' < '<1M']"
+      ]
+     },
+     "execution_count": 83,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cudf_cut(v['access_epoch'],cutoffs,grp_labels)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Breakdown by TLD and ATime Cutoffs"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg = ddf.groupby(['tld','dt_grp'],observed=True)['size'].agg(['sum','count']).compute().sort_index(level=[0,1]).to_pandas().reset_index()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg['dt_grp'].cat.as_ordered()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg['dt_grp'] = df_agg['dt_grp'].cat.reorder_categories(grp_labels[::-1], ordered = True)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg = df_agg.rename(columns={'sum':'bytes','count':'file_count'})"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg.to_parquet(report_dir.joinpath('tld_atime-age_agg.parquet'))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### TLD and File Age Plots"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "from pathlib import Path\n",
+    "import plotly.express as px\n",
+    "import plotly.graph_objects as go\n",
+    "from plotly import colors\n",
+    "import plotting\n",
+    "import importlib"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_agg = pd.read_parquet('/data/rc/gpfs-policy/data/list-policy_data-user_2024-09-18/reports/tld_atime-age_agg.parquet')\n",
+    "age_agg = df_agg.groupby('dt_grp',observed=True,as_index=False)[['file_count','bytes']].sum()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "exp,unit = plotting.choose_appropriate_storage_unit(age_agg['bytes'])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "age_agg[unit] = age_agg['bytes']/(1024**exp)\n",
+    "age_agg[['file_count_cum',f'{unit}_cum']] = age_agg[['file_count',unit]].cumsum()\n",
+    "age_agg[[unit,f'{unit}_cum']] = age_agg[[unit,f'{unit}_cum']].round(3)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "importlib.reload(plotting)\n",
+    "storage_plot = plotting.create_bar_plot(df=age_agg,x='dt_grp',y=[unit,f'{unit}_cum'],textposition='outside',\n",
+    "                         title=f'{unit} per atime Group', xlabel='Access Time Age', ylabel=f'Storage Used ({unit})')\n",
+    "storage_plot.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "importlib.reload(plotting)\n",
+    "legend_labels = ['Raw','Cumlative']\n",
+    "cols = ['file_count','file_count_cum']\n",
+    "file_count_plot = plotting.create_bar_plot(df=age_agg,x='dt_grp',y=cols,legend_labels=legend_labels,\n",
+    "                                           textposition='outside',text_decimals=0, title='File Count per atime Group', \n",
+    "                                           xlabel='Access Time Age', ylabel='File Count')\n",
+    "file_count_plot.show()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "importlib.reload(plotting)\n",
+    "pareto = plotting.create_pareto_chart(df=age_agg,x='dt_grp',y=unit, title='Storage per atime Group', \n",
+    "                                      xlabel='Access Time Age', ylabel=f'Storage Used {unit}',textposition_scatter='top left')\n",
+    "#pareto.update_layout(width = 800)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "pareto.show()"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.10"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/src/rc_gpfs/utils.py b/src/rc_gpfs/utils.py
new file mode 100644
index 0000000..d109d9a
--- /dev/null
+++ b/src/rc_gpfs/utils.py
@@ -0,0 +1,62 @@
+from typing import Literal
+
+# ENH: if this package becomes merged with noctua, need to replace this function since it's copied directly from there
+def convert_si(value: str | float | int, 
+               unit: Literal['base','K','M','G','T'] | None = None, 
+               to_unit: Literal['base','K','M','G','T'] = 'base', 
+               use_binary: bool = False
+) -> float:
+    """_summary_
+
+    Parameters
+    ----------
+    value : str | float | int
+        Input value to convert. If str, the unit can be embedded in the string.
+    unit : Literal['base','K','M','G','T'] | None, optional
+        The unit of the value if value is a numeric type, by default None
+    to_unit : Literal['base','K','M','G','T'], optional
+        The unit to convert the value to, by default 'base'
+    use_binary : bool, optional
+        Whether to use binary conversion (1024-based) or decimal conversion (1000-based), by default False
+
+    Returns
+    -------
+    float
+        The converted value
+    """
+
+    factor = 1024 if use_binary else 1000
+
+    # Unit multipliers
+    unit_multipliers = {
+        'base': 1,
+        'K': factor,
+        'M': factor ** 2,
+        'G': factor ** 3,
+        'T': factor ** 4,
+    }
+
+    # If value is a string, extract the number and the unit
+    if isinstance(value, str):
+        # Extract numeric part and unit part
+        value = value.strip()
+        for suffix in ['K', 'M', 'G', 'T']:
+            if value.endswith(suffix):
+                unit = suffix
+                value = value[:-1]
+                break
+        else:
+            unit = 'base'
+        value = float(value)
+    
+    # If value is numeric, use the provided unit or default to 'base'
+    if unit is None:
+        unit = 'base'
+
+    # Convert the input value to base
+    base_value = value * unit_multipliers[unit]
+
+    # Convert base value to the target unit
+    converted_value = base_value / unit_multipliers[to_unit]
+    
+    return converted_value
-- 
GitLab