diff --git a/Deep-SAD-PyTorch/devenv.nix b/Deep-SAD-PyTorch/devenv.nix index 65a4815..bc5b8ff 100644 --- a/Deep-SAD-PyTorch/devenv.nix +++ b/Deep-SAD-PyTorch/devenv.nix @@ -4,9 +4,11 @@ let torch-bin torchvision-bin aggdraw # for visualtorch + nvidia-ml-py ]; tools = with pkgs; [ ruff + dmidecode ]; in { diff --git a/Deep-SAD-PyTorch/src/datasets/main.py b/Deep-SAD-PyTorch/src/datasets/main.py index cb3d0c3..41a3716 100644 --- a/Deep-SAD-PyTorch/src/datasets/main.py +++ b/Deep-SAD-PyTorch/src/datasets/main.py @@ -21,6 +21,7 @@ def load_dataset( k_fold_num: int = None, num_known_normal: int = 0, num_known_outlier: int = 0, + split: float = 0.7, ): """Loads the dataset.""" @@ -49,6 +50,7 @@ def load_dataset( k_fold_num=k_fold_num, num_known_normal=num_known_normal, num_known_outlier=num_known_outlier, + split=split, ) if dataset_name == "subtersplit": diff --git a/Deep-SAD-PyTorch/src/main.py b/Deep-SAD-PyTorch/src/main.py index 31671f9..00433b5 100644 --- a/Deep-SAD-PyTorch/src/main.py +++ b/Deep-SAD-PyTorch/src/main.py @@ -152,6 +152,12 @@ from utils.visualization.plot_images_grid import plot_images_grid default=0.001, help="Initial learning rate for Deep SAD network training. Default=0.001", ) +@click.option( + "--train_test_split", + type=float, + default=0.7, + help="Ratio of training data in the train-test split (default: 0.7).", +) @click.option("--n_epochs", type=int, default=50, help="Number of epochs to train.") @click.option( "--lr_milestone", @@ -307,6 +313,7 @@ def main( seed, optimizer_name, lr, + train_test_split, n_epochs, lr_milestone, batch_size, @@ -416,6 +423,7 @@ def main( k_fold_num=k_fold_num, num_known_normal=num_known_normal, num_known_outlier=num_known_outlier, + split=train_test_split, ) # Log random sample of known anomaly classes if more than 1 class if n_known_outlier_classes > 1: @@ -694,6 +702,7 @@ def main( ratio_pollution, random_state=np.random.RandomState(cfg.settings["seed"]), k_fold_num=k_fold_num, + split=train_test_split, ) # Set up k-fold passes @@ -804,12 +813,14 @@ def main( k_fold_num=cfg.settings["k_fold_num"], num_known_normal=cfg.settings["num_known_normal"], num_known_outlier=cfg.settings["num_known_outlier"], + split=train_test_split, ) train_passes = ( range(cfg.settings["k_fold_num"]) if cfg.settings["k_fold"] else [None] ) + retest_autoencoder = False retest_isoforest = True retest_ocsvm = True retest_deepsad = True @@ -865,6 +876,25 @@ def main( k_fold_idx=fold_idx, ) + if retest_autoencoder: + # Initialize DeepSAD model and set neural network phi + deepSAD = DeepSAD(cfg.settings["latent_space_dim"], cfg.settings["eta"]) + deepSAD.set_network(cfg.settings["net_name"]) + deepSAD.load_model( + model_path=ae_model_path, load_ae=True, map_location=device + ) + logger.info("Loading model from %s." % load_model) + # Save pretraining results + if fold_idx is None: + deepSAD.save_ae_results( + export_pkl=load_model / "results_ae_retest.pkl" + ) + else: + deepSAD.save_ae_results( + export_pkl=load_model / f"results_ae_retest_{fold_idx}.pkl" + ) + del deepSAD + # Initialize DeepSAD model and set neural network phi if retest_deepsad: deepSAD = DeepSAD(cfg.settings["latent_space_dim"], cfg.settings["eta"]) diff --git a/thesis/Main.pdf b/thesis/Main.pdf index 3ddfad2..9bb977b 100644 Binary files a/thesis/Main.pdf and b/thesis/Main.pdf differ diff --git a/thesis/Main.tex b/thesis/Main.tex index a046efa..6820159 100755 --- a/thesis/Main.tex +++ b/thesis/Main.tex @@ -71,6 +71,11 @@ \usepackage[colorinlistoftodos]{todonotes} %\usepackage[disable]{todonotes} \usepackage{makecell} +\usepackage{longtable} +\usepackage{array} +\usepackage{tabularx} + +\newcolumntype{Y}{>{\centering\arraybackslash}X} \DeclareRobustCommand{\threadtodo}[4]{% \todo[inline, @@ -1251,6 +1256,114 @@ Table~\ref{tab:exp_grid} summarizes the full experiment matrix. {table of hardware and of how long different trainings took} {experiment setup understood $\rightarrow$ what were the experiments' results} + +\begin{table}[p] + \centering + \caption{Computational Environment (Hardware \& Software)} \label{tab:system_setup} + \begin{tabular}{p{0.34\linewidth} p{0.62\linewidth}} + \toprule + \textbf{Item} & \textbf{Details} \\ + \midrule + \multicolumn{2}{l}{\textbf{System}} \\ + Operating System & \ttfamily NixOS 25.11 (Xantusia) \\ + Kernel & \ttfamily 6.12.45 \\ + Architecture & \ttfamily x86\_64 \\ + CPU Model & \ttfamily AMD Ryzen 5 3600 6-Core Processor \\ + CPU Cores (physical) & \ttfamily 6 × 1 \\ + CPU Threads (logical) & \ttfamily 12 \\ + CPU Base Frequency & \ttfamily 2200 MHz \\ + CPU Max Frequency & \ttfamily 4208 MHz \\ + Total RAM & \ttfamily 31.29 GiB \\ + \addlinespace + \multicolumn{2}{l}{\textbf{GPU}} \\ + GPU Name & \ttfamily NVIDIA GeForce RTX 2070 SUPER \\ + GPU Memory & \ttfamily 8.00 GiB \\ + GPU Compute Capability & \ttfamily 7.5 \\ + NVIDIA Driver Version & \ttfamily 570.181 \\ + CUDA (Driver) Version & \ttfamily 12.8 \\ + \addlinespace + \multicolumn{2}{l}{\textbf{Software Environment}} \\ + Python & \ttfamily 3.12.11 \\ + PyTorch & \ttfamily 2.7.1+cu128 \\ + PyTorch Built CUDA & \ttfamily 12.8 \\ + cuDNN (PyTorch build) & \ttfamily 91100 \\ + scikit-learn & \ttfamily 1.7.0 \\ + NumPy & \ttfamily 2.3.1 \\ + SciPy & \ttfamily 1.16.0 \\ + NumPy Build Config & \begin{minipage}[t]{\linewidth}\ttfamily\small blas: + name: blas + openblas configuration: unknown + pc file directory: /nix/store/x19i4pf7zs1pp96mikj8azyn6v891i33-blas-3-dev/lib/pkgconfig + lapack: + name: lapack + openblas configuration: unknown + pc file directory: /nix/store/g819v6ri55f2gdczsi8s8bljkh0lkgwb-lapack-3-dev/lib/pkgconfig\end{minipage} \\ + \addlinespace + \bottomrule + \end{tabular} +\end{table} + +\begin{table} + \centering + \caption{Autoencoder pretraining runtime (seconds): mean ± std across folds.} + \label{tab:ae_pretrain_runtimes} + \begin{tabularx}{\textwidth}{cYY} + \toprule + & Autoencoder Efficient & Autoencoder LeNet \\ + Latent Dim. & & \\ + \midrule + 32 & 1175.03 ± 35.87 s & 384.90 ± 34.59 s \\ + 64 & 1212.53 ± 35.76 s & 398.22 ± 41.25 s \\ + 128 & 1240.86 ± 11.51 s & 397.98 ± 33.43 s \\ + 256 & 1169.72 ± 33.26 s & 399.40 ± 38.20 s \\ + 512 & 1173.34 ± 34.99 s & 430.31 ± 38.02 s \\ + 768 & 1204.45 ± 37.52 s & 436.49 ± 37.13 s \\ + 1024 & 1216.79 ± 34.82 s & 411.69 ± 34.82 s \\ + \bottomrule + \end{tabularx} +\end{table} + +\begin{table} + \centering + \caption{Training runtime: total seconds (mean ± std). DeepSAD cells also show \textit{seconds per epoch} in parentheses.} + \label{tab:train_runtimes_compact} + \begin{tabularx}{\textwidth}{crrrr} + \toprule + & DeepSAD LeNet & DeepSAD Efficient & IsoForest & OCSVM \\ + Latent Dim. & & & & \\ + \midrule + 32 & 765.37 ± 91.74 s & 1026.18 ± 84.13 s & 0.55 ± 0.02 s & 1.07 ± 00.29 s \\ + 64 & 815.88 ± 93.07 s & 1124.48 ± 60.84 s & 0.55 ± 0.02 s & 1.98 ± 01.57 s \\ + 128 & 828.53 ± 63.00 s & 1164.94 ± 02.13 s & 0.55 ± 0.02 s & 3.17 ± 02.63 s \\ + 256 & 794.54 ± 97.04 s & 986.88 ± 82.98 s & 0.55 ± 0.02 s & 12.81 ± 14.19 s \\ + 512 & 806.63 ± 99.83 s & 998.23 ± 80.34 s & 0.55 ± 0.02 s & 22.76 ± 23.52 s \\ + 768 & 818.56 ± 86.38 s & 1053.64 ± 78.72 s & 0.55 ± 0.02 s & 14.24 ± 01.21 s \\ + 1024 & 770.05 ± 86.22 s & 1054.92 ± 87.49 s & 0.55 ± 0.02 s & 28.20 ± 24.04 s \\ + \bottomrule + \end{tabularx} +\end{table} + + +\begin{table} + \centering + \caption{Inference latency (ms/sample): mean ± std across folds; baselines collapsed across networks and semi-labeling.} + \label{tab:inference_latency_compact} + \begin{tabularx}{\textwidth}{cYcYY} + \toprule + & DeepSAD LeNet & DeepSAD Efficient & IsoForest & OCSVM \\ + Latent Dim. & & & & \\ + \midrule + 32 & 0.31 ± 0.04 ms & 0.36 ± 0.05 ms & 0.02 ± 0.00 ms & 0.07 ± 0.02 ms \\ + 64 & 0.33 ± 0.06 ms & 0.43 ± 0.04 ms & 0.02 ± 0.00 ms & 0.10 ± 0.06 ms \\ + 128 & 0.31 ± 0.04 ms & 0.45 ± 0.02 ms & 0.02 ± 0.00 ms & 0.16 ± 0.09 ms \\ + 256 & 0.30 ± 0.04 ms & 0.33 ± 0.02 ms & 0.02 ± 0.00 ms & 0.30 ± 0.21 ms \\ + 512 & 0.32 ± 0.04 ms & 0.33 ± 0.02 ms & 0.02 ± 0.00 ms & 0.63 ± 0.65 ms \\ + 768 & 0.33 ± 0.03 ms & 0.41 ± 0.06 ms & 0.02 ± 0.00 ms & 0.39 ± 0.07 ms \\ + 1024 & 0.27 ± 0.02 ms & 0.39 ± 0.05 ms & 0.02 ± 0.00 ms & 0.94 ± 0.98 ms \\ + \bottomrule + \end{tabularx} +\end{table} + \newchapter{results_discussion}{Results and Discussion} \newsection{results}{Results} \todo[inline]{some results, ROC curves, for both global and local} diff --git a/tools/demo_loaded_data.py b/tools/demo_loaded_data.py index 4e66515..86c7c66 100644 --- a/tools/demo_loaded_data.py +++ b/tools/demo_loaded_data.py @@ -2,7 +2,10 @@ from pathlib import Path import polars as pl -from load_results import load_pretraining_results_dataframe, load_results_dataframe +from plot_scripts.load_results import ( + load_pretraining_results_dataframe, + load_results_dataframe, +) # ------------------------------------------------------------ diff --git a/tools/load_results.py b/tools/load_results.py deleted file mode 100644 index 20e6c44..0000000 --- a/tools/load_results.py +++ /dev/null @@ -1,651 +0,0 @@ -from __future__ import annotations - -import json -import pickle -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple - -import numpy as np -import polars as pl -from polars.testing import assert_frame_equal - -from diff_df import recursive_diff_frames - -# ------------------------------------------------------------ -# Config you can tweak -# ------------------------------------------------------------ -MODELS = ["deepsad", "isoforest", "ocsvm"] -EVALS = ["exp_based", "manual_based"] - -SCHEMA_STATIC = { - # identifiers / dims - "network": pl.Utf8, # e.g. "LeNet", "efficient" - "latent_dim": pl.Int32, - "semi_normals": pl.Int32, - "semi_anomalous": pl.Int32, - "model": pl.Utf8, # "deepsad" | "isoforest" | "ocsvm" - "eval": pl.Utf8, # "exp_based" | "manual_based" - "fold": pl.Int32, - # metrics - "auc": pl.Float64, - "ap": pl.Float64, - # per-sample scores: list of (idx, label, score) - "scores": pl.List( - pl.Struct( - { - "sample_idx": pl.Int32, # dataloader idx - "orig_label": pl.Int8, # {-1,0,1} - "score": pl.Float64, # anomaly score - } - ) - ), - # curves (normalized) - "roc_curve": pl.Struct( - { - "fpr": pl.List(pl.Float64), - "tpr": pl.List(pl.Float64), - "thr": pl.List(pl.Float64), - } - ), - "prc_curve": pl.Struct( - { - "precision": pl.List(pl.Float64), - "recall": pl.List(pl.Float64), - "thr": pl.List(pl.Float64), # may be len(precision)-1 - } - ), - # deepsad-only per-eval arrays (None for other models) - "sample_indices": pl.List(pl.Int32), - "sample_labels": pl.List(pl.Int8), - "valid_mask": pl.List(pl.Boolean), - # timings / housekeeping - "train_time": pl.Float64, - "test_time": pl.Float64, - "folder": pl.Utf8, - "k_fold_num": pl.Int32, - "config_json": pl.Utf8, # full config.json as string (for reference) -} - -# Pretraining-only (AE) schema -# Pretraining-only (AE) schema — lighter defaults -PRETRAIN_SCHEMA = { - # identifiers / dims - "network": pl.Utf8, # e.g. "LeNet", "efficient" - "latent_dim": pl.Int32, - "semi_normals": pl.Int32, - "semi_anomalous": pl.Int32, - "model": pl.Utf8, # always "ae" - "fold": pl.Int32, - "split": pl.Utf8, # "train" | "test" - # timings and optimization - "time": pl.Float64, - "loss": pl.Float64, - # per-sample arrays (as lists) - "indices": pl.List(pl.Int32), - "labels_exp_based": pl.List(pl.Int32), - "labels_manual_based": pl.List(pl.Int32), - "semi_targets": pl.List(pl.Int32), - "file_ids": pl.List(pl.Int32), - "frame_ids": pl.List(pl.Int32), - "scores": pl.List(pl.Float32), # <— use Float32 to match source and save space - # file id -> name mapping from the result dict - "file_names": pl.List(pl.Struct({"file_id": pl.Int32, "name": pl.Utf8})), - # housekeeping - "folder": pl.Utf8, - "k_fold_num": pl.Int32, - "config_json": pl.Utf8, # full config.json as string (for reference) -} - - -# ------------------------------------------------------------ -# Helpers: curve/scores normalizers (tuples/ndarrays -> dict/list) -# ------------------------------------------------------------ -def _tolist(x): - if x is None: - return None - if isinstance(x, np.ndarray): - return x.tolist() - if isinstance(x, (list, tuple)): - return list(x) - # best-effort scalar wrap - try: - return [x] - except Exception: - return None - - -def normalize_float_list(a) -> Optional[List[float]]: - if a is None: - return None - if isinstance(a, np.ndarray): - a = a.tolist() - return [None if x is None else float(x) for x in a] - - -def normalize_file_names(d) -> Optional[List[dict]]: - """ - Convert the 'file_names' dict (keys like numpy.int64 -> str) to a - list[ {file_id:int, name:str} ], sorted by file_id. - """ - if not isinstance(d, dict): - return None - out: List[dict] = [] - for k, v in d.items(): - try: - file_id = int(k) - except Exception: - # keys are printed as np.int64 in the structure; best-effort cast - continue - out.append({"file_id": file_id, "name": str(v)}) - out.sort(key=lambda x: x["file_id"]) - return out - - -def normalize_roc(obj: Any) -> Optional[dict]: - if obj is None: - return None - fpr = tpr = thr = None - if isinstance(obj, (tuple, list)): - if len(obj) >= 2: - fpr, tpr = _tolist(obj[0]), _tolist(obj[1]) - if len(obj) >= 3: - thr = _tolist(obj[2]) - elif isinstance(obj, dict): - fpr = _tolist(obj.get("fpr") or obj.get("x")) - tpr = _tolist(obj.get("tpr") or obj.get("y")) - thr = _tolist(obj.get("thr") or obj.get("thresholds")) - else: - return None - if fpr is None or tpr is None: - return None - return {"fpr": fpr, "tpr": tpr, "thr": thr} - - -def normalize_prc(obj: Any) -> Optional[dict]: - if obj is None: - return None - precision = recall = thr = None - if isinstance(obj, (tuple, list)): - if len(obj) >= 2: - precision, recall = _tolist(obj[0]), _tolist(obj[1]) - if len(obj) >= 3: - thr = _tolist(obj[2]) - elif isinstance(obj, dict): - precision = _tolist(obj.get("precision") or obj.get("y")) - recall = _tolist(obj.get("recall") or obj.get("x")) - thr = _tolist(obj.get("thr") or obj.get("thresholds")) - else: - return None - if precision is None or recall is None: - return None - return {"precision": precision, "recall": recall, "thr": thr} - - -def normalize_scores_to_struct(seq) -> Optional[List[dict]]: - """ - Input: list of (idx, label, score) tuples (as produced in your test()). - Output: list of dicts with keys sample_idx, orig_label, score. - """ - if seq is None: - return None - if isinstance(seq, np.ndarray): - seq = seq.tolist() - if not isinstance(seq, (list, tuple)): - return None - out: List[dict] = [] - for item in seq: - if isinstance(item, (list, tuple)) and len(item) >= 3: - idx, lab, sc = item[0], item[1], item[2] - out.append( - { - "sample_idx": None if idx is None else int(idx), - "orig_label": None if lab is None else int(lab), - "score": None if sc is None else float(sc), - } - ) - else: - # fallback: single numeric -> score - sc = ( - float(item) - if isinstance(item, (int, float, np.integer, np.floating)) - else None - ) - out.append({"sample_idx": None, "orig_label": None, "score": sc}) - return out - - -def normalize_int_list(a) -> Optional[List[int]]: - if a is None: - return None - if isinstance(a, np.ndarray): - a = a.tolist() - return list(a) - - -def normalize_bool_list(a) -> Optional[List[bool]]: - if a is None: - return None - if isinstance(a, np.ndarray): - a = a.tolist() - return [bool(x) for x in a] - - -# ------------------------------------------------------------ -# Low-level: read one experiment folder -# ------------------------------------------------------------ -def read_config(exp_dir: Path) -> dict: - cfg = exp_dir / "config.json" - with cfg.open("r") as f: - c = json.load(f) - if not c.get("k_fold"): - raise ValueError(f"{exp_dir.name}: not trained as k-fold") - return c - - -def read_pickle(p: Path) -> Any: - with p.open("rb") as f: - return pickle.load(f) - - -# ------------------------------------------------------------ -# Extractors for each model -# ------------------------------------------------------------ - -counting = { - (label_method, eval_method): [] - for label_method in ["exp_based", "manual_based"] - for eval_method in ["roc", "prc"] -} - - -def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]: - """ - deepsad under data['test'][eval], with extra per-eval arrays and AP present. - """ - out: Dict[str, dict] = {} - test = data.get("test", {}) - for ev in evals: - evd = test.get(ev) - if not isinstance(evd, dict): - continue - counting[(ev, "roc")].append(len(evd["roc"][0])) - counting[(ev, "prc")].append(len(evd["prc"][0])) - out[ev] = { - "auc": float(evd["auc"]) - if "auc" in evd and evd["auc"] is not None - else None, - "roc": normalize_roc(evd.get("roc")), - "prc": normalize_prc(evd.get("prc")), - "ap": float(evd["ap"]) if "ap" in evd and evd["ap"] is not None else None, - "scores": normalize_scores_to_struct(evd.get("scores")), - "sample_indices": normalize_int_list(evd.get("indices")), - "sample_labels": normalize_int_list(evd.get("labels")), - "valid_mask": normalize_bool_list(evd.get("valid_mask")), - "train_time": data.get("train", {}).get("time"), - "test_time": test.get("time"), - } - return out - - -def rows_from_isoforest(data: dict, evals: List[str]) -> Dict[str, dict]: - """ - Keys: test_auc_, test_roc_, test_prc_, test_ap_, test_scores_. - """ - out: Dict[str, dict] = {} - for ev in evals: - auc = data.get(f"test_auc_{ev}") - if auc is None: - continue - out[ev] = { - "auc": float(auc), - "roc": normalize_roc(data.get(f"test_roc_{ev}")), - "prc": normalize_prc(data.get(f"test_prc_{ev}")), - "ap": float(data.get(f"test_ap_{ev}")) - if data.get(f"test_ap_{ev}") is not None - else None, - "scores": normalize_scores_to_struct(data.get(f"test_scores_{ev}")), - "sample_indices": None, - "sample_labels": None, - "valid_mask": None, - "train_time": data.get("train_time"), - "test_time": data.get("test_time"), - } - return out - - -def rows_from_ocsvm_default(data: dict, evals: List[str]) -> Dict[str, dict]: - """ - Default OCSVM only (ignore linear variant entirely). - """ - out: Dict[str, dict] = {} - for ev in evals: - auc = data.get(f"test_auc_{ev}") - if auc is None: - continue - out[ev] = { - "auc": float(auc), - "roc": normalize_roc(data.get(f"test_roc_{ev}")), - "prc": normalize_prc(data.get(f"test_prc_{ev}")), - "ap": float(data.get(f"test_ap_{ev}")) - if data.get(f"test_ap_{ev}") is not None - else None, - "scores": normalize_scores_to_struct(data.get(f"test_scores_{ev}")), - "sample_indices": None, - "sample_labels": None, - "valid_mask": None, - "train_time": data.get("train_time"), - "test_time": data.get("test_time"), - } - return out - - -# ------------------------------------------------------------ -# Build the Polars DataFrame -# ------------------------------------------------------------ -def load_results_dataframe(root: Path, allow_cache: bool = True) -> pl.DataFrame: - """ - Walks experiment subdirs under `root`. For each (model, fold) it adds rows: - Columns (SCHEMA_STATIC): - network, latent_dim, semi_normals, semi_anomalous, - model, eval, fold, - auc, ap, scores{sample_idx,orig_label,score}, - roc_curve{fpr,tpr,thr}, prc_curve{precision,recall,thr}, - sample_indices, sample_labels, valid_mask, - train_time, test_time, - folder, k_fold_num - """ - if allow_cache: - cache = root / "results_cache.parquet" - if cache.exists(): - try: - df = pl.read_parquet(cache) - print(f"[info] loaded cached results frame from {cache}") - return df - except Exception as e: - print(f"[warn] failed to load cache {cache}: {e}") - - rows: List[dict] = [] - - exp_dirs = [p for p in root.iterdir() if p.is_dir()] - for exp_dir in sorted(exp_dirs): - try: - cfg = read_config(exp_dir) - cfg_json = json.dumps(cfg, sort_keys=True) - except Exception as e: - print(f"[warn] skipping {exp_dir.name}: {e}") - continue - - network = cfg.get("net_name") - latent_dim = int(cfg.get("latent_space_dim")) - semi_normals = int(cfg.get("num_known_normal")) - semi_anomalous = int(cfg.get("num_known_outlier")) - k = int(cfg.get("k_fold_num")) - - for model in MODELS: - for fold in range(k): - pkl = exp_dir / f"results_{model}_{fold}.pkl" - if not pkl.exists(): - continue - - try: - data = read_pickle(pkl) - except Exception as e: - print(f"[warn] failed to read {pkl.name}: {e}") - continue - - if model == "deepsad": - per_eval = rows_from_deepsad(data, EVALS) # eval -> dict - elif model == "isoforest": - per_eval = rows_from_isoforest(data, EVALS) # eval -> dict - elif model == "ocsvm": - per_eval = rows_from_ocsvm_default(data, EVALS) # eval -> dict - else: - per_eval = {} - - for ev, vals in per_eval.items(): - rows.append( - { - "network": network, - "latent_dim": latent_dim, - "semi_normals": semi_normals, - "semi_anomalous": semi_anomalous, - "model": model, - "eval": ev, - "fold": fold, - "auc": vals["auc"], - "ap": vals["ap"], - "scores": vals["scores"], - "roc_curve": vals["roc"], - "prc_curve": vals["prc"], - "sample_indices": vals.get("sample_indices"), - "sample_labels": vals.get("sample_labels"), - "valid_mask": vals.get("valid_mask"), - "train_time": vals["train_time"], - "test_time": vals["test_time"], - "folder": str(exp_dir), - "k_fold_num": k, - "config_json": cfg_json, - } - ) - - # If empty, return a typed empty frame - if not rows: - return pl.DataFrame(schema=SCHEMA_STATIC) - - df = pl.DataFrame(rows, schema=SCHEMA_STATIC) - - # Cast to efficient dtypes (categoricals etc.) – no extra sanitation - df = df.with_columns( - pl.col("network", "model", "eval").cast(pl.Categorical), - pl.col( - "latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num" - ).cast(pl.Int32), - pl.col("auc", "ap", "train_time", "test_time").cast(pl.Float64), - # NOTE: no cast on 'scores' here; it's already List(Struct) per schema. - ) - - if allow_cache: - try: - df.write_parquet(cache) - print(f"[info] cached results frame to {cache}") - except Exception as e: - print(f"[warn] failed to write cache {cache}: {e}") - - return df - - -def load_pretraining_results_dataframe( - root: Path, - allow_cache: bool = True, - include_train: bool = False, # <— default: store only TEST to keep cache tiny - keep_file_names: bool = False, # <— drop file_names by default; they’re repeated - parquet_compression: str = "zstd", - parquet_compression_level: int = 7, # <— stronger compression than default -) -> pl.DataFrame: - """ - Loads only AE pretraining results: files named `results_ae_.pkl`. - Produces one row per (experiment, fold, split). By default we: - - include only the TEST split (include_train=False) - - store scores as Float32 - - drop the repeated file_names mapping to save space - - write Parquet with zstd(level=7) - """ - if allow_cache: - cache = root / "pretraining_results_cache.parquet" - if cache.exists(): - try: - df = pl.read_parquet(cache) - print(f"[info] loaded cached pretraining frame from {cache}") - return df - except Exception as e: - print(f"[warn] failed to load pretraining cache {cache}: {e}") - - rows: List[dict] = [] - - exp_dirs = [p for p in root.iterdir() if p.is_dir()] - for exp_dir in sorted(exp_dirs): - try: - cfg = read_config(exp_dir) - cfg_json = json.dumps(cfg, sort_keys=True) - except Exception as e: - print(f"[warn] skipping {exp_dir.name} (pretraining): {e}") - continue - - network = cfg.get("net_name") - latent_dim = int(cfg.get("latent_space_dim")) - semi_normals = int(cfg.get("num_known_normal")) - semi_anomalous = int(cfg.get("num_known_outlier")) - k = int(cfg.get("k_fold_num")) - - # Only test split by default (include_train=False) - splits = ("train", "test") if include_train else ("test",) - - for fold in range(k): - pkl = exp_dir / f"results_ae_{fold}.pkl" - if not pkl.exists(): - continue - - try: - data = read_pickle(pkl) # expected: {"train": {...}, "test": {...}} - except Exception as e: - print(f"[warn] failed to read {pkl.name}: {e}") - continue - - for split in splits: - splitd = data.get(split) - if not isinstance(splitd, dict): - continue - - rows.append( - { - "network": network, - "latent_dim": latent_dim, - "semi_normals": semi_normals, - "semi_anomalous": semi_anomalous, - "model": "ae", - "fold": fold, - "split": split, - "time": float(splitd.get("time")) - if splitd.get("time") is not None - else None, - "loss": float(splitd.get("loss")) - if splitd.get("loss") is not None - else None, - # ints as Int32, scores as Float32 to save space - "indices": normalize_int_list(splitd.get("indices")), - "labels_exp_based": normalize_int_list( - splitd.get("labels_exp_based") - ), - "labels_manual_based": normalize_int_list( - splitd.get("labels_manual_based") - ), - "semi_targets": normalize_int_list(splitd.get("semi_targets")), - "file_ids": normalize_int_list(splitd.get("file_ids")), - "frame_ids": normalize_int_list(splitd.get("frame_ids")), - "scores": ( - None - if splitd.get("scores") is None - else [ - float(x) - for x in ( - splitd["scores"].tolist() - if isinstance(splitd["scores"], np.ndarray) - else splitd["scores"] - ) - ] - ), - "file_names": normalize_file_names(splitd.get("file_names")) - if keep_file_names - else None, - "folder": str(exp_dir), - "k_fold_num": k, - "config_json": cfg_json, - } - ) - - if not rows: - return pl.DataFrame(schema=PRETRAIN_SCHEMA) - - df = pl.DataFrame(rows, schema=PRETRAIN_SCHEMA) - - # Cast/optimize a bit (categoricals, ints, floats) - df = df.with_columns( - pl.col("network", "model", "split").cast(pl.Categorical), - pl.col( - "latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num" - ).cast(pl.Int32), - pl.col("time", "loss").cast(pl.Float64), - pl.col("scores").cast(pl.List(pl.Float32)), # ensure downcast took - ) - - if allow_cache: - try: - cache = root / "pretraining_results_cache.parquet" - df.write_parquet( - cache, - compression=parquet_compression, - compression_level=parquet_compression_level, - statistics=True, - ) - print( - f"[info] cached pretraining frame to {cache} " - f"({parquet_compression}, level={parquet_compression_level})" - ) - except Exception as e: - print(f"[warn] failed to write pretraining cache {cache}: {e}") - - return df - - -def main(): - root = Path("/home/fedex/mt/results/copy") - df1 = load_results_dataframe(root, allow_cache=True) - exit(0) - - retest_root = Path("/home/fedex/mt/results/copy/retest_nodrop") - df2 = load_results_dataframe(retest_root, allow_cache=False).drop("folder") - - # exact schema & shape first (optional but helpful messages) - assert df1.shape == df2.shape, f"Shape differs: {df1.shape} vs {df2.shape}" - assert set(df1.columns) == set(df2.columns), ( - f"Column sets differ: {df1.columns} vs {df2.columns}" - ) - - # allow small float diffs, ignore column order differences if you want - df1_sorted = df1.select(sorted(df1.columns)) - df2_sorted = df2.select(sorted(df2.columns)) - - # Optionally pre-align/sort both frames by a stable key before diffing. - summary, leaves = recursive_diff_frames( - df1, - df2, - ignore=["timestamp"], # columns to ignore - float_atol=0.1, # absolute tolerance for floats - float_rtol=0.0, # relative tolerance for floats - max_rows_per_column=20, # limit expansion per column - max_leafs_per_row=200, # cap leaves per row - ) - - pl.Config.set_fmt_table_cell_list_len(100) - pl.Config.set_tbl_rows(100) - - print(summary) # which columns differ & how many rows - print(leaves) # exact nested paths + scalar diffs - - # check_exact=False lets us use atol/rtol for floats - assert_frame_equal( - df1_sorted, - df2_sorted, - check_exact=False, - atol=0.1, # absolute tolerance for floats - rtol=0.0, # relative tolerance (set if you want % based) - check_dtypes=True, # set False if you only care about values - ) - print("DataFrames match within tolerance ✅") - - # df_pre = load_pretraining_results_dataframe(root, allow_cache=True) - # print("pretraining:", df_pre.shape, df_pre.head()) - - -if __name__ == "__main__": - main() diff --git a/tools/plot_scripts/ae_elbow_lenet.py b/tools/plot_scripts/ae_elbow_lenet.py index d5f805b..bf11e46 100644 --- a/tools/plot_scripts/ae_elbow_lenet.py +++ b/tools/plot_scripts/ae_elbow_lenet.py @@ -12,7 +12,7 @@ import numpy as np import polars as pl # CHANGE THIS IMPORT IF YOUR LOADER MODULE IS NAMED DIFFERENTLY -from load_results import load_pretraining_results_dataframe +from plot_scripts.load_results import load_pretraining_results_dataframe # ---------------------------- # Config @@ -212,7 +212,7 @@ def plot_multi_loss_curve(arch_results, title, output_path, colors=None): def main(): # Load AE DF (uses your cache if enabled in the loader) - df = load_pretraining_results_dataframe(ROOT, allow_cache=True, include_train=False) + df = load_pretraining_results_dataframe(ROOT, allow_cache=True) # Optional: filter to just LeNet vs Efficient; drop this set() to plot all nets wanted_nets = {"LeNet", "Efficient"} diff --git a/tools/diff_df.py b/tools/plot_scripts/diff_df.py similarity index 100% rename from tools/diff_df.py rename to tools/plot_scripts/diff_df.py diff --git a/tools/plot_scripts/load_results.py b/tools/plot_scripts/load_results.py index 32430cb..8e7080d 100644 --- a/tools/plot_scripts/load_results.py +++ b/tools/plot_scripts/load_results.py @@ -3,10 +3,12 @@ from __future__ import annotations import json import pickle from pathlib import Path -from typing import Any, Dict, List, Optional +from typing import Any, Dict, List, Optional, Tuple import numpy as np import polars as pl +from diff_df import recursive_diff_frames +from polars.testing import assert_frame_equal # ------------------------------------------------------------ # Config you can tweak @@ -75,7 +77,8 @@ PRETRAIN_SCHEMA = { "fold": pl.Int32, "split": pl.Utf8, # "train" | "test" # timings and optimization - "time": pl.Float64, + "train_time": pl.Float64, + "test_time": pl.Float64, "loss": pl.Float64, # per-sample arrays (as lists) "indices": pl.List(pl.Int32), @@ -247,6 +250,14 @@ def read_pickle(p: Path) -> Any: # ------------------------------------------------------------ # Extractors for each model # ------------------------------------------------------------ + +counting = { + (label_method, eval_method): [] + for label_method in ["exp_based", "manual_based"] + for eval_method in ["roc", "prc"] +} + + def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]: """ deepsad under data['test'][eval], with extra per-eval arrays and AP present. @@ -257,6 +268,8 @@ def rows_from_deepsad(data: dict, evals: List[str]) -> Dict[str, dict]: evd = test.get(ev) if not isinstance(evd, dict): continue + counting[(ev, "roc")].append(len(evd["roc"][0])) + counting[(ev, "prc")].append(len(evd["prc"][0])) out[ev] = { "auc": float(evd["auc"]) if "auc" in evd and evd["auc"] is not None @@ -444,7 +457,6 @@ def load_results_dataframe(root: Path, allow_cache: bool = True) -> pl.DataFrame def load_pretraining_results_dataframe( root: Path, allow_cache: bool = True, - include_train: bool = False, # <— default: store only TEST to keep cache tiny keep_file_names: bool = False, # <— drop file_names by default; they’re repeated parquet_compression: str = "zstd", parquet_compression_level: int = 7, # <— stronger compression than default @@ -484,9 +496,6 @@ def load_pretraining_results_dataframe( semi_anomalous = int(cfg.get("num_known_outlier")) k = int(cfg.get("k_fold_num")) - # Only test split by default (include_train=False) - splits = ("train", "test") if include_train else ("test",) - for fold in range(k): pkl = exp_dir / f"results_ae_{fold}.pkl" if not pkl.exists(): @@ -498,57 +507,53 @@ def load_pretraining_results_dataframe( print(f"[warn] failed to read {pkl.name}: {e}") continue - for split in splits: - splitd = data.get(split) - if not isinstance(splitd, dict): - continue + train_time = data.get("train", {}).get("time") + data = data.get("test", {}) - rows.append( - { - "network": network, - "latent_dim": latent_dim, - "semi_normals": semi_normals, - "semi_anomalous": semi_anomalous, - "model": "ae", - "fold": fold, - "split": split, - "time": float(splitd.get("time")) - if splitd.get("time") is not None - else None, - "loss": float(splitd.get("loss")) - if splitd.get("loss") is not None - else None, - # ints as Int32, scores as Float32 to save space - "indices": normalize_int_list(splitd.get("indices")), - "labels_exp_based": normalize_int_list( - splitd.get("labels_exp_based") - ), - "labels_manual_based": normalize_int_list( - splitd.get("labels_manual_based") - ), - "semi_targets": normalize_int_list(splitd.get("semi_targets")), - "file_ids": normalize_int_list(splitd.get("file_ids")), - "frame_ids": normalize_int_list(splitd.get("frame_ids")), - "scores": ( - None - if splitd.get("scores") is None - else [ - float(x) - for x in ( - splitd["scores"].tolist() - if isinstance(splitd["scores"], np.ndarray) - else splitd["scores"] - ) - ] - ), - "file_names": normalize_file_names(splitd.get("file_names")) - if keep_file_names - else None, - "folder": str(exp_dir), - "k_fold_num": k, - "config_json": cfg_json, - } - ) + rows.append( + { + "network": network, + "latent_dim": latent_dim, + "semi_normals": semi_normals, + "semi_anomalous": semi_anomalous, + "model": "ae", + "fold": fold, + "train_time": train_time, + "test_time": data.get("time"), + "loss": float(data.get("loss")) + if data.get("loss") is not None + else None, + # ints as Int32, scores as Float32 to save space + "indices": normalize_int_list(data.get("indices")), + "labels_exp_based": normalize_int_list( + data.get("labels_exp_based") + ), + "labels_manual_based": normalize_int_list( + data.get("labels_manual_based") + ), + "semi_targets": normalize_int_list(data.get("semi_targets")), + "file_ids": normalize_int_list(data.get("file_ids")), + "frame_ids": normalize_int_list(data.get("frame_ids")), + "scores": ( + None + if data.get("scores") is None + else [ + float(x) + for x in ( + data["scores"].tolist() + if isinstance(data["scores"], np.ndarray) + else data["scores"] + ) + ] + ), + "file_names": normalize_file_names(data.get("file_names")) + if keep_file_names + else None, + "folder": str(exp_dir), + "k_fold_num": k, + "config_json": cfg_json, + } + ) if not rows: return pl.DataFrame(schema=PRETRAIN_SCHEMA) @@ -561,7 +566,7 @@ def load_pretraining_results_dataframe( pl.col( "latent_dim", "semi_normals", "semi_anomalous", "fold", "k_fold_num" ).cast(pl.Int32), - pl.col("time", "loss").cast(pl.Float64), + pl.col("test_time", "train_time", "loss").cast(pl.Float64), pl.col("scores").cast(pl.List(pl.Float32)), # ensure downcast took ) @@ -585,12 +590,53 @@ def load_pretraining_results_dataframe( def main(): - root = Path("/home/fedex/mt/results/done") - df = load_results_dataframe(root, allow_cache=True) - print(df.shape, df.head()) + root = Path("/home/fedex/mt/results/copy") + df1 = load_results_dataframe(root, allow_cache=True) + exit(0) - df_pre = load_pretraining_results_dataframe(root, allow_cache=True) - print("pretraining:", df_pre.shape, df_pre.head()) + retest_root = Path("/home/fedex/mt/results/copy/retest_nodrop") + df2 = load_results_dataframe(retest_root, allow_cache=False).drop("folder") + + # exact schema & shape first (optional but helpful messages) + assert df1.shape == df2.shape, f"Shape differs: {df1.shape} vs {df2.shape}" + assert set(df1.columns) == set(df2.columns), ( + f"Column sets differ: {df1.columns} vs {df2.columns}" + ) + + # allow small float diffs, ignore column order differences if you want + df1_sorted = df1.select(sorted(df1.columns)) + df2_sorted = df2.select(sorted(df2.columns)) + + # Optionally pre-align/sort both frames by a stable key before diffing. + summary, leaves = recursive_diff_frames( + df1, + df2, + ignore=["timestamp"], # columns to ignore + float_atol=0.1, # absolute tolerance for floats + float_rtol=0.0, # relative tolerance for floats + max_rows_per_column=20, # limit expansion per column + max_leafs_per_row=200, # cap leaves per row + ) + + pl.Config.set_fmt_table_cell_list_len(100) + pl.Config.set_tbl_rows(100) + + print(summary) # which columns differ & how many rows + print(leaves) # exact nested paths + scalar diffs + + # check_exact=False lets us use atol/rtol for floats + assert_frame_equal( + df1_sorted, + df2_sorted, + check_exact=False, + atol=0.1, # absolute tolerance for floats + rtol=0.0, # relative tolerance (set if you want % based) + check_dtypes=True, # set False if you only care about values + ) + print("DataFrames match within tolerance ✅") + + # df_pre = load_pretraining_results_dataframe(root, allow_cache=True) + # print("pretraining:", df_pre.shape, df_pre.head()) if __name__ == "__main__": diff --git a/tools/plot_scripts/results_latent_space_comparisons.py b/tools/plot_scripts/results_latent_space_comparisons.py index 72d3f75..e9303f9 100644 --- a/tools/plot_scripts/results_latent_space_comparisons.py +++ b/tools/plot_scripts/results_latent_space_comparisons.py @@ -10,7 +10,7 @@ import polars as pl from matplotlib.lines import Line2D # CHANGE THIS IMPORT IF YOUR LOADER MODULE IS NAMED DIFFERENTLY -from load_results import load_results_dataframe +from plot_scripts.load_results import load_results_dataframe # ---------------------------- # Config diff --git a/tools/plot_scripts/results_semi_labels_comparison.py b/tools/plot_scripts/results_semi_labels_comparison.py index f5eaa04..91fa313 100644 --- a/tools/plot_scripts/results_semi_labels_comparison.py +++ b/tools/plot_scripts/results_semi_labels_comparison.py @@ -12,7 +12,7 @@ from matplotlib.lines import Line2D from scipy.stats import sem, t # CHANGE THIS IMPORT IF YOUR LOADER MODULE NAME IS DIFFERENT -from load_results import load_results_dataframe +from plot_scripts.load_results import load_results_dataframe # --------------------------------- # Config diff --git a/tools/plot_scripts/setup_runtime_tables.py b/tools/plot_scripts/setup_runtime_tables.py new file mode 100644 index 0000000..589812a --- /dev/null +++ b/tools/plot_scripts/setup_runtime_tables.py @@ -0,0 +1,704 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +import json +import shutil +from datetime import datetime +from pathlib import Path +from typing import Any, Dict, Optional + +import numpy as np +import pandas as pd +import polars as pl +from load_results import ( + load_pretraining_results_dataframe, + load_results_dataframe, +) + +# ---------------------------- +# Config +# ---------------------------- +RESULTS_ROOT = Path("/home/fedex/mt/results/done") # folder with experiment subdirs +OUTPUT_DIR = Path("/home/fedex/mt/plots/setup_runtime_tables") # where .tex goes + +# If you want to optionally prefer a specific network label for baselines in column names, +# set to a substring to detect (e.g. "efficient"). If None, keep network as-is. +BASELINE_NETWORK_HINT: Optional[str] = None # e.g., "efficient" or None + + +# ---------------------------- +# Helpers +# ---------------------------- +def _net_label_for_display(net: str | None) -> str: + s = (net or "").lower() + if "effic" in s: + return "Efficient" + if "lenet" in s: + return "LeNet" + return net or "" + + +def _fmt_mean_std_n( + mean: float | None, std: float | None, n: int | None, unit: str = "" +) -> str: + if mean is None or (isinstance(mean, float) and (np.isnan(mean) or np.isinf(mean))): + return "-" + base = f"{mean:.2f}" + if std is not None and not ( + isinstance(std, float) and (np.isnan(std) or np.isinf(std)) + ): + base = f"{base} ± {std:.2f}" + if unit: + base = f"{base} {unit}" + if n is not None and n > 0: + base = f"{base} (n={n})" + return base + + +def _fmt_pair(n: int, m: int) -> str: + return f"{n}/{m}" + + +def _fmt_mean_std(mean: float | None, std: float | None, n: int | None) -> str: + if mean is None or (isinstance(mean, float) and (np.isnan(mean) or np.isinf(mean))): + return "-" + if std is None or (isinstance(std, float) and (np.isnan(std) or np.isinf(std))): + return f"{mean:.2f}" + if n is None or n < 1: + return f"{mean:.2f} ± {std:.2f}" + return f"{mean:.2f} ± {std:.2f} (n={n})" + + +def _parse_cfg(cfg_json: Optional[str]) -> Dict[str, Any]: + if not cfg_json: + return {} + try: + return json.loads(cfg_json) + except Exception: + return {} + + +def _key_params(model: str, cfg: Dict[str, Any]) -> str: + """Compact, model-specific parameter string for the table.""" + if model == "deepsad": + bs = cfg.get("batch_size") + ne = cfg.get("n_epochs") + lr = cfg.get("lr") + wd = cfg.get("weight_decay") + return f"bs={bs}, epochs={ne}, lr={lr}, wd={wd}" + if model == "isoforest": + est = cfg.get("isoforest_n_estimators") + ms = cfg.get("isoforest_max_samples") + cont = cfg.get("isoforest_contamination") + return f"n_estimators={est}, max_samples={ms}, cont={cont}" + if model == "ocsvm": + ker = cfg.get("ocsvm_kernel") + nu = cfg.get("ocsvm_nu") + return f"kernel={ker}, nu={nu}" + return "-" + + +def _method_col_name(model: str, network: str) -> str: + """ + Column heading for pivot tables: + - deepsad carries the network (e.g., 'DeepSAD / LeNet') + - baselines carry their own model name; optionally annotate network + """ + label = model.lower() + if label == "deepsad": + return f"DeepSAD / {network}" + # baselines; optionally simplify/standardize network name + if ( + BASELINE_NETWORK_HINT + and BASELINE_NETWORK_HINT.lower() not in (network or "").lower() + ): + # If you want to collapse baseline duplicates to a single name, you can force it here + return model.capitalize() + # Otherwise, keep network variant explicit + return f"{model.capitalize()} / {network}" + + +def _prepare_per_fold_metrics(df: pl.DataFrame) -> pl.DataFrame: + """ + Returns one row per (folder, model, fold) with: + - train_time, test_time + - n_test (len(scores)) + - n_epochs (from config_json; DeepSAD only) + - latency_ms = 1000 * test_time / n_test + - time_per_epoch = train_time / n_epochs (DeepSAD only) + """ + base = ( + df.select( + "folder", + "network", + "model", + "latent_dim", + "semi_normals", + "semi_anomalous", + "fold", + "train_time", + "test_time", + "scores", + "config_json", + ) + .with_columns( + n_test=pl.col("scores").list.len(), + n_epochs=pl.col("config_json") + .str.json_path_match("$.n_epochs") + .cast(pl.Int64), + ) + .drop("scores") + ) + + # de-dup across evals + uniq = base.unique(subset=["folder", "model", "fold"]) + + # derived metrics + uniq = uniq.with_columns( + latency_ms=pl.when((pl.col("test_time") > 0) & (pl.col("n_test") > 0)) + .then(1000.0 * pl.col("test_time") / pl.col("n_test")) + .otherwise(None) + .cast(pl.Float64), + time_per_epoch=pl.when( + (pl.col("model") == "deepsad") & (pl.col("n_epochs") > 0) + ) + .then(pl.col("train_time") / pl.col("n_epochs")) + .otherwise(None) + .cast(pl.Float64), + network_disp=pl.col("network") + .cast(pl.Utf8) + .map_elements(_net_label_for_display, return_dtype=pl.Utf8), + ) + return uniq + + +def _prepare_aggregates(df: pl.DataFrame) -> pl.DataFrame: + """ + Deduplicate across evals, then aggregate times across folds for each + (network, model, latent_dim, semi_normals, semi_anomalous). + """ + # Keep only columns we need + base = df.select( + "folder", + "network", + "model", + "latent_dim", + "semi_normals", + "semi_anomalous", + "fold", + "train_time", + "test_time", + "config_json", + ) + + # Drop duplicates across evals: same (folder, model, fold) should have identical timings + uniq = base.unique(subset=["folder", "model", "fold"]).with_columns( + # Normalize network to a simpler display label, if your config used long names + pl.col("network").cast(pl.Utf8) + ) + + # Group across folds + agg = ( + uniq.group_by( + ["network", "model", "latent_dim", "semi_normals", "semi_anomalous"] + ) + .agg( + pl.len().alias("n_folds"), + pl.col("train_time").mean().alias("train_mean"), + pl.col("train_time").std(ddof=1).alias("train_std"), + pl.col("test_time").mean().alias("test_mean"), + pl.col("test_time").std(ddof=1).alias("test_std"), + pl.col("config_json") + .first() + .alias("config_json"), # one exemplar cfg per group + ) + .sort(["semi_normals", "semi_anomalous", "latent_dim", "network", "model"]) + ) + return agg + + +def make_training_runtime_table(df: pl.DataFrame) -> str: + """ + Returns a LaTeX table (string) for TRAIN runtimes: mean ± std (seconds) across folds. + Rows: Semi (N/O), Latent Dim + Columns: methods split (DeepSAD/LeNet, DeepSAD/Efficient, IsoForest[/net], OCSVM[/net]) + """ + agg = _prepare_aggregates(df) + + # Prepare display strings and column keys + tbl = agg.with_columns( + pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias( + "semi" + ), + pl.col("model").cast(pl.Utf8), + pl.col("network").cast(pl.Utf8), + pl.col("latent_dim").cast(pl.Int64), + # ADD return_dtype here + pl.struct(["train_mean", "train_std", "n_folds"]) + .map_elements( + lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n_folds"]), + return_dtype=pl.Utf8, + ) + .alias("train_fmt"), + # And here + pl.struct(["model", "network"]) + .map_elements( + lambda s: _method_col_name(s["model"], s["network"]), + return_dtype=pl.Utf8, + ) + .alias("method"), + ).select("semi", "latent_dim", "method", "train_fmt") + + # Pivot to wide form: one cell per (semi, latent_dim, method) + wide = tbl.pivot( + values="train_fmt", + index=["semi", "latent_dim"], + columns="method", + aggregate_function="first", + ).sort(["semi", "latent_dim"]) + + # Fill missing with '-' and export + pdf = wide.fill_null("-").to_pandas() + pdf.index = pd.MultiIndex.from_frame(pdf[["semi", "latent_dim"]]) + pdf = pdf.drop(columns=["semi", "latent_dim"]) + latex = pdf.to_latex( + index=True, + escape=True, + na_rep="-", + multicolumn=True, + multicolumn_format="c", + bold_rows=False, + caption="Training runtime (seconds): mean ± std across folds (n in parentheses).", + label="tab:train_runtimes", + ) + return latex + + +def make_inference_runtime_table(df: pl.DataFrame) -> str: + """ + Returns a LaTeX table (string) for TEST/INFERENCE runtimes: mean ± std (seconds) across folds. + Same layout as training table. + """ + agg = _prepare_aggregates(df) + + tbl = agg.with_columns( + pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias( + "semi" + ), + pl.col("model").cast(pl.Utf8), + pl.col("network").cast(pl.Utf8), + pl.col("latent_dim").cast(pl.Int64), + pl.struct(["test_mean", "test_std", "n_folds"]) + .map_elements( + lambda s: _fmt_mean_std(s["test_mean"], s["test_std"], s["n_folds"]), + return_dtype=pl.Utf8, + ) + .alias("test_fmt"), + pl.struct(["model", "network"]) + .map_elements( + lambda s: _method_col_name(s["model"], s["network"]), + return_dtype=pl.Utf8, + ) + .alias("method"), + ).select("semi", "latent_dim", "method", "test_fmt") + + wide = tbl.pivot( + values="test_fmt", + index=["semi", "latent_dim"], + columns="method", + aggregate_function="first", + ).sort(["semi", "latent_dim"]) + + pdf = wide.fill_null("-").to_pandas() + pdf.index = pd.MultiIndex.from_frame(pdf[["semi", "latent_dim"]]) + pdf = pdf.drop(columns=["semi", "latent_dim"]) + latex = pdf.to_latex( + index=True, + escape=True, + na_rep="-", + multicolumn=True, + multicolumn_format="c", + bold_rows=False, + caption="Inference/Test runtime (seconds): mean ± std across folds (n in parentheses).", + label="tab:test_runtimes", + ) + return latex + + +def make_longform_train_table_with_params(df: pl.DataFrame) -> str: + """ + (Optional) Long-form table that includes a 'Params' column extracted from config_json. + Useful if you want to show per-model settings alongside the runtimes. + """ + agg = _prepare_aggregates(df) + # Build params column from JSON for readability + long = ( + agg.with_columns( + pl.format("{}/{}", pl.col("semi_normals"), pl.col("semi_anomalous")).alias( + "semi" + ), + pl.col("latent_dim").cast(pl.Int64), + pl.struct(["model", "config_json"]) + .map_elements( + lambda s: _key_params(s["model"], _parse_cfg(s["config_json"])), + return_dtype=pl.Utf8, + ) + .alias("params"), + pl.struct(["train_mean", "train_std", "n_folds"]) + .map_elements( + lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n_folds"]) + ) + .alias("train_time_fmt"), + ) + .select( + "network", + "model", + "latent_dim", + "semi", + "params", + "train_time_fmt", + ) + .sort(["semi", "latent_dim", "network", "model"]) + ) + + pdf = long.to_pandas() + pdf.rename( + columns={ + "network": "Network", + "model": "Method", + "latent_dim": "Latent Dim", + "semi": "Semi (N/O)", + "params": "Params", + "train_time_fmt": "Train time [s] (mean ± std)", + }, + inplace=True, + ) + latex = pdf.to_latex( + index=False, + escape=True, + longtable=False, + caption="Training runtime with key parameters.", + label="tab:train_runtimes_params", + ) + return latex + + +def make_training_runtime_table_compact(df: pl.DataFrame) -> str: + per_fold = _prepare_per_fold_metrics(df) + + # DeepSAD: keep LeNet vs Efficient, collapse semis + ds = ( + per_fold.filter(pl.col("model") == "deepsad") + .group_by(["model", "network_disp", "latent_dim"]) + .agg( + n=pl.len(), + train_mean=pl.mean("train_time"), + train_std=pl.std("train_time", ddof=1), + tpe_mean=pl.mean("time_per_epoch"), + tpe_std=pl.std("time_per_epoch", ddof=1), + ) + .with_columns( + method=pl.format("DeepSAD / {}", pl.col("network_disp")), + ) + ) + + # Baselines: collapse networks & semis; only vary by latent_dim + bl = ( + per_fold.filter(pl.col("model").is_in(["isoforest", "ocsvm"])) + .group_by(["model", "latent_dim"]) + .agg( + n=pl.len(), + train_mean=pl.mean("train_time"), + train_std=pl.std("train_time", ddof=1), + ) + .with_columns( + method=pl.when(pl.col("model") == "isoforest") + .then(pl.lit("IsoForest")) + .when(pl.col("model") == "ocsvm") + .then(pl.lit("OCSVM")) + .otherwise(pl.lit("Baseline")) + ) + ) + + # --- Standardize schemas before concat --- + ds_std = ds.select( + pl.col("latent_dim").cast(pl.Int64), + pl.col("method").cast(pl.Utf8), + pl.col("train_mean").cast(pl.Float64), + pl.col("train_std").cast(pl.Float64), + pl.col("tpe_mean").cast(pl.Float64), + pl.col("tpe_std").cast(pl.Float64), + pl.col("n").cast(pl.Int64), + ) + + bl_std = bl.select( + pl.col("latent_dim").cast(pl.Int64), + pl.col("method").cast(pl.Utf8), + pl.col("train_mean").cast(pl.Float64), + pl.col("train_std").cast(pl.Float64), + pl.lit(None, dtype=pl.Float64).alias("tpe_mean"), + pl.lit(None, dtype=pl.Float64).alias("tpe_std"), + pl.col("n").cast(pl.Int64), + ) + + agg = pl.concat([ds_std, bl_std], how="vertical") + + # Format cell: total [s]; DeepSAD also appends (italic) per-epoch + def _fmt_train_cell(s: dict) -> str: + total = _fmt_mean_std_n(s["train_mean"], s["train_std"], s["n"], "s") + if s.get("tpe_mean") is None or ( + isinstance(s.get("tpe_mean"), float) and np.isnan(s["tpe_mean"]) + ): + return total + tpe = _fmt_mean_std_n(s["tpe_mean"], s["tpe_std"], None, "s/epoch") + return f"{total} (\\textit{{{tpe}}})" + + tbl = agg.with_columns( + pl.struct(["train_mean", "train_std", "tpe_mean", "tpe_std", "n"]) + .map_elements(_fmt_train_cell, return_dtype=pl.Utf8) + .alias("train_fmt"), + ).select("latent_dim", "method", "train_fmt") + + # Pivot and order columns nicely + wide = tbl.pivot( + values="train_fmt", + index=["latent_dim"], + columns="method", + aggregate_function="first", + ).sort("latent_dim") + + pdf = wide.fill_null("-").to_pandas().set_index("latent_dim") + desired_cols = [ + c + for c in ["DeepSAD / LeNet", "DeepSAD / Efficient", "IsoForest", "OCSVM"] + if c in pdf.columns + ] + if desired_cols: + pdf = pdf.reindex(columns=desired_cols) + + latex = pdf.to_latex( + index=True, + escape=True, + na_rep="-", + multicolumn=True, + multicolumn_format="c", + bold_rows=False, + caption="Training runtime: total seconds (mean ± std). DeepSAD cells also show \\textit{seconds per epoch} in parentheses.", + label="tab:train_runtimes_compact", + ) + return latex + + +def make_inference_latency_table_compact(df: pl.DataFrame) -> str: + per_fold = _prepare_per_fold_metrics(df) + + # DeepSAD: keep networks; collapse semis + ds = ( + per_fold.filter(pl.col("model") == "deepsad") + .group_by(["model", "network_disp", "latent_dim"]) + .agg( + n=pl.len(), + lat_mean=pl.mean("latency_ms"), + lat_std=pl.std("latency_ms", ddof=1), + ) + .with_columns( + method=pl.format("DeepSAD / {}", pl.col("network_disp")), + ) + ) + + # Baselines: collapse networks & semis + bl = ( + per_fold.filter(pl.col("model").is_in(["isoforest", "ocsvm"])) + .group_by(["model", "latent_dim"]) + .agg( + n=pl.len(), + lat_mean=pl.mean("latency_ms"), + lat_std=pl.std("latency_ms", ddof=1), + ) + .with_columns( + method=pl.when(pl.col("model") == "isoforest") + .then(pl.lit("IsoForest")) + .when(pl.col("model") == "ocsvm") + .then(pl.lit("OCSVM")) + .otherwise(pl.lit("Baseline")) + ) + ) + + # --- Standardize schemas before concat --- + ds_std = ds.select( + pl.col("latent_dim").cast(pl.Int64), + pl.col("method").cast(pl.Utf8), + pl.col("lat_mean").cast(pl.Float64), + pl.col("lat_std").cast(pl.Float64), + pl.col("n").cast(pl.Int64), + ) + + bl_std = bl.select( + pl.col("latent_dim").cast(pl.Int64), + pl.col("method").cast(pl.Utf8), + pl.col("lat_mean").cast(pl.Float64), + pl.col("lat_std").cast(pl.Float64), + pl.col("n").cast(pl.Int64), + ) + + agg = pl.concat([ds_std, bl_std], how="vertical") + + def _fmt_lat_cell(s: dict) -> str: + return _fmt_mean_std_n(s["lat_mean"], s["lat_std"], s["n"], "ms") + + tbl = agg.with_columns( + pl.struct(["lat_mean", "lat_std", "n"]) + .map_elements(_fmt_lat_cell, return_dtype=pl.Utf8) + .alias("lat_fmt"), + ).select("latent_dim", "method", "lat_fmt") + + wide = tbl.pivot( + values="lat_fmt", + index=["latent_dim"], + columns="method", + aggregate_function="first", + ).sort("latent_dim") + + pdf = wide.fill_null("-").to_pandas().set_index("latent_dim") + desired_cols = [ + c + for c in ["DeepSAD / LeNet", "DeepSAD / Efficient", "IsoForest", "OCSVM"] + if c in pdf.columns + ] + if desired_cols: + pdf = pdf.reindex(columns=desired_cols) + + latex = pdf.to_latex( + index=True, + escape=True, + na_rep="-", + multicolumn=True, + multicolumn_format="c", + bold_rows=False, + caption="Inference latency (ms/sample): mean ± std across folds; baselines collapsed across networks and semi-labeling.", + label="tab:inference_latency_compact", + ) + return latex + + +def make_ae_pretraining_runtime_table(df_pre: pl.DataFrame) -> str: + """ + LaTeX table: Autoencoder (pretraining) runtime per latent dim. + Rows: latent_dim + Cols: AE / LeNet, AE / Efficient (mean ± std seconds across folds) + """ + # minimal columns we need + base = df_pre.select( + pl.col("network").cast(pl.Utf8), + pl.col("latent_dim").cast(pl.Int64), + pl.col("fold").cast(pl.Int64), + pl.col("train_time").cast(pl.Float64), + ).drop_nulls(subset=["network", "latent_dim", "train_time"]) + + # Nice display label for network + network_disp = ( + pl.when(pl.col("network").str.contains("efficient")) + .then(pl.lit("Efficient")) + .when(pl.col("network").str.contains("LeNet")) + .then(pl.lit("LeNet")) + .otherwise(pl.col("network")) + .alias("network_disp") + ) + + agg = ( + base.with_columns(network_disp) + .group_by(["network_disp", "latent_dim"]) + .agg( + n=pl.len(), + train_mean=pl.mean("train_time"), + train_std=pl.std("train_time", ddof=1), + ) + .with_columns( + pl.format("AE / {}", pl.col("network_disp")).alias("method"), + pl.struct(["train_mean", "train_std", "n"]) + .map_elements( + lambda s: _fmt_mean_std(s["train_mean"], s["train_std"], s["n"]), + return_dtype=pl.Utf8, + ) + .alias("train_fmt"), + ) + .select("latent_dim", "method", "train_fmt") + .sort(["latent_dim", "method"]) + ) + + wide = agg.pivot( + values="train_fmt", + index=["latent_dim"], + columns="method", + aggregate_function="first", + ).sort("latent_dim") + + pdf = wide.fill_null("-").to_pandas().set_index("latent_dim") + + # Order columns if both exist + desired = [ + c for c in ["Autoencoder LeNet", "Autoencoder Efficient"] if c in pdf.columns + ] + if desired: + pdf = pdf.reindex(columns=desired) + + latex = pdf.to_latex( + index=True, + escape=True, + na_rep="-", + multicolumn=True, + multicolumn_format="c", + bold_rows=False, + caption="Autoencoder pretraining runtime (seconds): mean ± std across folds.", + label="tab:ae_pretrain_runtimes", + ) + return latex + + +# ---------------------------- +# Main +# ---------------------------- +def main(): + OUTPUT_DIR.mkdir(parents=True, exist_ok=True) + + # Main results + df = load_results_dataframe(RESULTS_ROOT, allow_cache=True) + if "config_json" not in df.columns: + df = df.with_columns(pl.lit(None).alias("config_json")) + + # AE pretraining results + df_pre = load_pretraining_results_dataframe(RESULTS_ROOT, allow_cache=True) + + # Build LaTeX tables + latex_train = make_training_runtime_table(df) + latex_test = make_inference_runtime_table(df) + latex_train_params = make_longform_train_table_with_params(df) + latex_ae = make_ae_pretraining_runtime_table(df_pre) + + # Timestamped output dirs + ts = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") + ts_dir = OUTPUT_DIR / "archive" / ts + ts_dir.mkdir(parents=True, exist_ok=True) + + # Write files + (ts_dir / "train_runtimes.tex").write_text(latex_train) + (ts_dir / "test_runtimes.tex").write_text(latex_test) + (ts_dir / "train_runtimes_with_params.tex").write_text(latex_train_params) + (ts_dir / "ae_pretraining_runtimes.tex").write_text(latex_ae) + + # Save script & mirror latest + script_path = Path(__file__) + shutil.copy2(script_path, ts_dir) + + latest = OUTPUT_DIR / "latest" + latest.mkdir(exist_ok=True, parents=True) + for f in ts_dir.iterdir(): + if f.is_file(): + shutil.copy2(f, latest / f.name) + + print(f"Saved LaTeX tables to: {ts_dir}") + print(f"Also updated: {latest}") + + +if __name__ == "__main__": + main() diff --git a/tools/print_mat.py b/tools/print_mat.py new file mode 100644 index 0000000..02e14e5 --- /dev/null +++ b/tools/print_mat.py @@ -0,0 +1,20 @@ +rows = 5 +cols = 4 + +mat = [range(0 + (cols * i), cols + (cols * i), 1) for i in range(rows)] + + +def print_mat(mat): + for s in mat: + print(*s) + + +def rotate_mat(mat): + mat = [[mat[row][col] for row in range(rows - 1, -1, -1)] for col in range(cols)] + return mat + + +print_mat(mat) +mat = rotate_mat(mat) +print("rotated:") +print_mat(mat) diff --git a/tools/pyproject.toml b/tools/pyproject.toml index 69f1edf..14f57a9 100644 --- a/tools/pyproject.toml +++ b/tools/pyproject.toml @@ -5,5 +5,7 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.11.9" dependencies = [ + "pandas>=2.3.2", "polars>=1.33.0", + "pyarrow>=21.0.0", ] diff --git a/tools/uv.lock b/tools/uv.lock index b97d2f0..fe4e65b 100644 --- a/tools/uv.lock +++ b/tools/uv.lock @@ -1,6 +1,132 @@ version = 1 revision = 2 requires-python = ">=3.11.9" +resolution-markers = [ + "python_full_version >= '3.12'", + "python_full_version < '3.12'", +] + +[[package]] +name = "numpy" +version = "2.3.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/19/95b3d357407220ed24c139018d2518fab0a61a948e68286a25f1a4d049ff/numpy-2.3.3.tar.gz", hash = "sha256:ddc7c39727ba62b80dfdbedf400d1c10ddfa8eefbd7ec8dcb118be8b56d31029", size = 20576648, upload-time = "2025-09-09T16:54:12.543Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/45/e80d203ef6b267aa29b22714fb558930b27960a0c5ce3c19c999232bb3eb/numpy-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ffc4f5caba7dfcbe944ed674b7eef683c7e94874046454bb79ed7ee0236f59d", size = 21259253, upload-time = "2025-09-09T15:56:02.094Z" }, + { url = "https://files.pythonhosted.org/packages/52/18/cf2c648fccf339e59302e00e5f2bc87725a3ce1992f30f3f78c9044d7c43/numpy-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7e946c7170858a0295f79a60214424caac2ffdb0063d4d79cb681f9aa0aa569", size = 14450980, upload-time = "2025-09-09T15:56:05.926Z" }, + { url = "https://files.pythonhosted.org/packages/93/fb/9af1082bec870188c42a1c239839915b74a5099c392389ff04215dcee812/numpy-2.3.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:cd4260f64bc794c3390a63bf0728220dd1a68170c169088a1e0dfa2fde1be12f", size = 5379709, upload-time = "2025-09-09T15:56:07.95Z" }, + { url = "https://files.pythonhosted.org/packages/75/0f/bfd7abca52bcbf9a4a65abc83fe18ef01ccdeb37bfb28bbd6ad613447c79/numpy-2.3.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:f0ddb4b96a87b6728df9362135e764eac3cfa674499943ebc44ce96c478ab125", size = 6913923, upload-time = "2025-09-09T15:56:09.443Z" }, + { url = "https://files.pythonhosted.org/packages/79/55/d69adad255e87ab7afda1caf93ca997859092afeb697703e2f010f7c2e55/numpy-2.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:afd07d377f478344ec6ca2b8d4ca08ae8bd44706763d1efb56397de606393f48", size = 14589591, upload-time = "2025-09-09T15:56:11.234Z" }, + { url = "https://files.pythonhosted.org/packages/10/a2/010b0e27ddeacab7839957d7a8f00e91206e0c2c47abbb5f35a2630e5387/numpy-2.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc92a5dedcc53857249ca51ef29f5e5f2f8c513e22cfb90faeb20343b8c6f7a6", size = 16938714, upload-time = "2025-09-09T15:56:14.637Z" }, + { url = "https://files.pythonhosted.org/packages/1c/6b/12ce8ede632c7126eb2762b9e15e18e204b81725b81f35176eac14dc5b82/numpy-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7af05ed4dc19f308e1d9fc759f36f21921eb7bbfc82843eeec6b2a2863a0aefa", size = 16370592, upload-time = "2025-09-09T15:56:17.285Z" }, + { url = "https://files.pythonhosted.org/packages/b4/35/aba8568b2593067bb6a8fe4c52babb23b4c3b9c80e1b49dff03a09925e4a/numpy-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:433bf137e338677cebdd5beac0199ac84712ad9d630b74eceeb759eaa45ddf30", size = 18884474, upload-time = "2025-09-09T15:56:20.943Z" }, + { url = "https://files.pythonhosted.org/packages/45/fa/7f43ba10c77575e8be7b0138d107e4f44ca4a1ef322cd16980ea3e8b8222/numpy-2.3.3-cp311-cp311-win32.whl", hash = "sha256:eb63d443d7b4ffd1e873f8155260d7f58e7e4b095961b01c91062935c2491e57", size = 6599794, upload-time = "2025-09-09T15:56:23.258Z" }, + { url = "https://files.pythonhosted.org/packages/0a/a2/a4f78cb2241fe5664a22a10332f2be886dcdea8784c9f6a01c272da9b426/numpy-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:ec9d249840f6a565f58d8f913bccac2444235025bbb13e9a4681783572ee3caa", size = 13088104, upload-time = "2025-09-09T15:56:25.476Z" }, + { url = "https://files.pythonhosted.org/packages/79/64/e424e975adbd38282ebcd4891661965b78783de893b381cbc4832fb9beb2/numpy-2.3.3-cp311-cp311-win_arm64.whl", hash = "sha256:74c2a948d02f88c11a3c075d9733f1ae67d97c6bdb97f2bb542f980458b257e7", size = 10460772, upload-time = "2025-09-09T15:56:27.679Z" }, + { url = "https://files.pythonhosted.org/packages/51/5d/bb7fc075b762c96329147799e1bcc9176ab07ca6375ea976c475482ad5b3/numpy-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cfdd09f9c84a1a934cde1eec2267f0a43a7cd44b2cca4ff95b7c0d14d144b0bf", size = 20957014, upload-time = "2025-09-09T15:56:29.966Z" }, + { url = "https://files.pythonhosted.org/packages/6b/0e/c6211bb92af26517acd52125a237a92afe9c3124c6a68d3b9f81b62a0568/numpy-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cb32e3cf0f762aee47ad1ddc6672988f7f27045b0783c887190545baba73aa25", size = 14185220, upload-time = "2025-09-09T15:56:32.175Z" }, + { url = "https://files.pythonhosted.org/packages/22/f2/07bb754eb2ede9073f4054f7c0286b0d9d2e23982e090a80d478b26d35ca/numpy-2.3.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:396b254daeb0a57b1fe0ecb5e3cff6fa79a380fa97c8f7781a6d08cd429418fe", size = 5113918, upload-time = "2025-09-09T15:56:34.175Z" }, + { url = "https://files.pythonhosted.org/packages/81/0a/afa51697e9fb74642f231ea36aca80fa17c8fb89f7a82abd5174023c3960/numpy-2.3.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:067e3d7159a5d8f8a0b46ee11148fc35ca9b21f61e3c49fbd0a027450e65a33b", size = 6647922, upload-time = "2025-09-09T15:56:36.149Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f5/122d9cdb3f51c520d150fef6e87df9279e33d19a9611a87c0d2cf78a89f4/numpy-2.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c02d0629d25d426585fb2e45a66154081b9fa677bc92a881ff1d216bc9919a8", size = 14281991, upload-time = "2025-09-09T15:56:40.548Z" }, + { url = "https://files.pythonhosted.org/packages/51/64/7de3c91e821a2debf77c92962ea3fe6ac2bc45d0778c1cbe15d4fce2fd94/numpy-2.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9192da52b9745f7f0766531dcfa978b7763916f158bb63bdb8a1eca0068ab20", size = 16641643, upload-time = "2025-09-09T15:56:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/30/e4/961a5fa681502cd0d68907818b69f67542695b74e3ceaa513918103b7e80/numpy-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cd7de500a5b66319db419dc3c345244404a164beae0d0937283b907d8152e6ea", size = 16056787, upload-time = "2025-09-09T15:56:46.141Z" }, + { url = "https://files.pythonhosted.org/packages/99/26/92c912b966e47fbbdf2ad556cb17e3a3088e2e1292b9833be1dfa5361a1a/numpy-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:93d4962d8f82af58f0b2eb85daaf1b3ca23fe0a85d0be8f1f2b7bb46034e56d7", size = 18579598, upload-time = "2025-09-09T15:56:49.844Z" }, + { url = "https://files.pythonhosted.org/packages/17/b6/fc8f82cb3520768718834f310c37d96380d9dc61bfdaf05fe5c0b7653e01/numpy-2.3.3-cp312-cp312-win32.whl", hash = "sha256:5534ed6b92f9b7dca6c0a19d6df12d41c68b991cef051d108f6dbff3babc4ebf", size = 6320800, upload-time = "2025-09-09T15:56:52.499Z" }, + { url = "https://files.pythonhosted.org/packages/32/ee/de999f2625b80d043d6d2d628c07d0d5555a677a3cf78fdf868d409b8766/numpy-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:497d7cad08e7092dba36e3d296fe4c97708c93daf26643a1ae4b03f6294d30eb", size = 12786615, upload-time = "2025-09-09T15:56:54.422Z" }, + { url = "https://files.pythonhosted.org/packages/49/6e/b479032f8a43559c383acb20816644f5f91c88f633d9271ee84f3b3a996c/numpy-2.3.3-cp312-cp312-win_arm64.whl", hash = "sha256:ca0309a18d4dfea6fc6262a66d06c26cfe4640c3926ceec90e57791a82b6eee5", size = 10195936, upload-time = "2025-09-09T15:56:56.541Z" }, + { url = "https://files.pythonhosted.org/packages/7d/b9/984c2b1ee61a8b803bf63582b4ac4242cf76e2dbd663efeafcb620cc0ccb/numpy-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f5415fb78995644253370985342cd03572ef8620b934da27d77377a2285955bf", size = 20949588, upload-time = "2025-09-09T15:56:59.087Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e4/07970e3bed0b1384d22af1e9912527ecbeb47d3b26e9b6a3bced068b3bea/numpy-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d00de139a3324e26ed5b95870ce63be7ec7352171bc69a4cf1f157a48e3eb6b7", size = 14177802, upload-time = "2025-09-09T15:57:01.73Z" }, + { url = "https://files.pythonhosted.org/packages/35/c7/477a83887f9de61f1203bad89cf208b7c19cc9fef0cebef65d5a1a0619f2/numpy-2.3.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9dc13c6a5829610cc07422bc74d3ac083bd8323f14e2827d992f9e52e22cd6a6", size = 5106537, upload-time = "2025-09-09T15:57:03.765Z" }, + { url = "https://files.pythonhosted.org/packages/52/47/93b953bd5866a6f6986344d045a207d3f1cfbad99db29f534ea9cee5108c/numpy-2.3.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d79715d95f1894771eb4e60fb23f065663b2298f7d22945d66877aadf33d00c7", size = 6640743, upload-time = "2025-09-09T15:57:07.921Z" }, + { url = "https://files.pythonhosted.org/packages/23/83/377f84aaeb800b64c0ef4de58b08769e782edcefa4fea712910b6f0afd3c/numpy-2.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:952cfd0748514ea7c3afc729a0fc639e61655ce4c55ab9acfab14bda4f402b4c", size = 14278881, upload-time = "2025-09-09T15:57:11.349Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a5/bf3db6e66c4b160d6ea10b534c381a1955dfab34cb1017ea93aa33c70ed3/numpy-2.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5b83648633d46f77039c29078751f80da65aa64d5622a3cd62aaef9d835b6c93", size = 16636301, upload-time = "2025-09-09T15:57:14.245Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/1287924242eb4fa3f9b3a2c30400f2e17eb2707020d1c5e3086fe7330717/numpy-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b001bae8cea1c7dfdb2ae2b017ed0a6f2102d7a70059df1e338e307a4c78a8ae", size = 16053645, upload-time = "2025-09-09T15:57:16.534Z" }, + { url = "https://files.pythonhosted.org/packages/e6/93/b3d47ed882027c35e94ac2320c37e452a549f582a5e801f2d34b56973c97/numpy-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e9aced64054739037d42fb84c54dd38b81ee238816c948c8f3ed134665dcd86", size = 18578179, upload-time = "2025-09-09T15:57:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/20/d9/487a2bccbf7cc9d4bfc5f0f197761a5ef27ba870f1e3bbb9afc4bbe3fcc2/numpy-2.3.3-cp313-cp313-win32.whl", hash = "sha256:9591e1221db3f37751e6442850429b3aabf7026d3b05542d102944ca7f00c8a8", size = 6312250, upload-time = "2025-09-09T15:57:21.296Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b5/263ebbbbcede85028f30047eab3d58028d7ebe389d6493fc95ae66c636ab/numpy-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f0dadeb302887f07431910f67a14d57209ed91130be0adea2f9793f1a4f817cf", size = 12783269, upload-time = "2025-09-09T15:57:23.034Z" }, + { url = "https://files.pythonhosted.org/packages/fa/75/67b8ca554bbeaaeb3fac2e8bce46967a5a06544c9108ec0cf5cece559b6c/numpy-2.3.3-cp313-cp313-win_arm64.whl", hash = "sha256:3c7cf302ac6e0b76a64c4aecf1a09e51abd9b01fc7feee80f6c43e3ab1b1dbc5", size = 10195314, upload-time = "2025-09-09T15:57:25.045Z" }, + { url = "https://files.pythonhosted.org/packages/11/d0/0d1ddec56b162042ddfafeeb293bac672de9b0cfd688383590090963720a/numpy-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:eda59e44957d272846bb407aad19f89dc6f58fecf3504bd144f4c5cf81a7eacc", size = 21048025, upload-time = "2025-09-09T15:57:27.257Z" }, + { url = "https://files.pythonhosted.org/packages/36/9e/1996ca6b6d00415b6acbdd3c42f7f03ea256e2c3f158f80bd7436a8a19f3/numpy-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:823d04112bc85ef5c4fda73ba24e6096c8f869931405a80aa8b0e604510a26bc", size = 14301053, upload-time = "2025-09-09T15:57:30.077Z" }, + { url = "https://files.pythonhosted.org/packages/05/24/43da09aa764c68694b76e84b3d3f0c44cb7c18cdc1ba80e48b0ac1d2cd39/numpy-2.3.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:40051003e03db4041aa325da2a0971ba41cf65714e65d296397cc0e32de6018b", size = 5229444, upload-time = "2025-09-09T15:57:32.733Z" }, + { url = "https://files.pythonhosted.org/packages/bc/14/50ffb0f22f7218ef8af28dd089f79f68289a7a05a208db9a2c5dcbe123c1/numpy-2.3.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ee9086235dd6ab7ae75aba5662f582a81ced49f0f1c6de4260a78d8f2d91a19", size = 6738039, upload-time = "2025-09-09T15:57:34.328Z" }, + { url = "https://files.pythonhosted.org/packages/55/52/af46ac0795e09657d45a7f4db961917314377edecf66db0e39fa7ab5c3d3/numpy-2.3.3-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94fcaa68757c3e2e668ddadeaa86ab05499a70725811e582b6a9858dd472fb30", size = 14352314, upload-time = "2025-09-09T15:57:36.255Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b1/dc226b4c90eb9f07a3fff95c2f0db3268e2e54e5cce97c4ac91518aee71b/numpy-2.3.3-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da1a74b90e7483d6ce5244053399a614b1d6b7bc30a60d2f570e5071f8959d3e", size = 16701722, upload-time = "2025-09-09T15:57:38.622Z" }, + { url = "https://files.pythonhosted.org/packages/9d/9d/9d8d358f2eb5eced14dba99f110d83b5cd9a4460895230f3b396ad19a323/numpy-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2990adf06d1ecee3b3dcbb4977dfab6e9f09807598d647f04d385d29e7a3c3d3", size = 16132755, upload-time = "2025-09-09T15:57:41.16Z" }, + { url = "https://files.pythonhosted.org/packages/b6/27/b3922660c45513f9377b3fb42240bec63f203c71416093476ec9aa0719dc/numpy-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ed635ff692483b8e3f0fcaa8e7eb8a75ee71aa6d975388224f70821421800cea", size = 18651560, upload-time = "2025-09-09T15:57:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/5b/8e/3ab61a730bdbbc201bb245a71102aa609f0008b9ed15255500a99cd7f780/numpy-2.3.3-cp313-cp313t-win32.whl", hash = "sha256:a333b4ed33d8dc2b373cc955ca57babc00cd6f9009991d9edc5ddbc1bac36bcd", size = 6442776, upload-time = "2025-09-09T15:57:45.793Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3a/e22b766b11f6030dc2decdeff5c2fb1610768055603f9f3be88b6d192fb2/numpy-2.3.3-cp313-cp313t-win_amd64.whl", hash = "sha256:4384a169c4d8f97195980815d6fcad04933a7e1ab3b530921c3fef7a1c63426d", size = 12927281, upload-time = "2025-09-09T15:57:47.492Z" }, + { url = "https://files.pythonhosted.org/packages/7b/42/c2e2bc48c5e9b2a83423f99733950fbefd86f165b468a3d85d52b30bf782/numpy-2.3.3-cp313-cp313t-win_arm64.whl", hash = "sha256:75370986cc0bc66f4ce5110ad35aae6d182cc4ce6433c40ad151f53690130bf1", size = 10265275, upload-time = "2025-09-09T15:57:49.647Z" }, + { url = "https://files.pythonhosted.org/packages/6b/01/342ad585ad82419b99bcf7cebe99e61da6bedb89e213c5fd71acc467faee/numpy-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cd052f1fa6a78dee696b58a914b7229ecfa41f0a6d96dc663c1220a55e137593", size = 20951527, upload-time = "2025-09-09T15:57:52.006Z" }, + { url = "https://files.pythonhosted.org/packages/ef/d8/204e0d73fc1b7a9ee80ab1fe1983dd33a4d64a4e30a05364b0208e9a241a/numpy-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:414a97499480067d305fcac9716c29cf4d0d76db6ebf0bf3cbce666677f12652", size = 14186159, upload-time = "2025-09-09T15:57:54.407Z" }, + { url = "https://files.pythonhosted.org/packages/22/af/f11c916d08f3a18fb8ba81ab72b5b74a6e42ead4c2846d270eb19845bf74/numpy-2.3.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:50a5fe69f135f88a2be9b6ca0481a68a136f6febe1916e4920e12f1a34e708a7", size = 5114624, upload-time = "2025-09-09T15:57:56.5Z" }, + { url = "https://files.pythonhosted.org/packages/fb/11/0ed919c8381ac9d2ffacd63fd1f0c34d27e99cab650f0eb6f110e6ae4858/numpy-2.3.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:b912f2ed2b67a129e6a601e9d93d4fa37bef67e54cac442a2f588a54afe5c67a", size = 6642627, upload-time = "2025-09-09T15:57:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/ee/83/deb5f77cb0f7ba6cb52b91ed388b47f8f3c2e9930d4665c600408d9b90b9/numpy-2.3.3-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9e318ee0596d76d4cb3d78535dc005fa60e5ea348cd131a51e99d0bdbe0b54fe", size = 14296926, upload-time = "2025-09-09T15:58:00.035Z" }, + { url = "https://files.pythonhosted.org/packages/77/cc/70e59dcb84f2b005d4f306310ff0a892518cc0c8000a33d0e6faf7ca8d80/numpy-2.3.3-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce020080e4a52426202bdb6f7691c65bb55e49f261f31a8f506c9f6bc7450421", size = 16638958, upload-time = "2025-09-09T15:58:02.738Z" }, + { url = "https://files.pythonhosted.org/packages/b6/5a/b2ab6c18b4257e099587d5b7f903317bd7115333ad8d4ec4874278eafa61/numpy-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e6687dc183aa55dae4a705b35f9c0f8cb178bcaa2f029b241ac5356221d5c021", size = 16071920, upload-time = "2025-09-09T15:58:05.029Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f1/8b3fdc44324a259298520dd82147ff648979bed085feeacc1250ef1656c0/numpy-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d8f3b1080782469fdc1718c4ed1d22549b5fb12af0d57d35e992158a772a37cf", size = 18577076, upload-time = "2025-09-09T15:58:07.745Z" }, + { url = "https://files.pythonhosted.org/packages/f0/a1/b87a284fb15a42e9274e7fcea0dad259d12ddbf07c1595b26883151ca3b4/numpy-2.3.3-cp314-cp314-win32.whl", hash = "sha256:cb248499b0bc3be66ebd6578b83e5acacf1d6cb2a77f2248ce0e40fbec5a76d0", size = 6366952, upload-time = "2025-09-09T15:58:10.096Z" }, + { url = "https://files.pythonhosted.org/packages/70/5f/1816f4d08f3b8f66576d8433a66f8fa35a5acfb3bbd0bf6c31183b003f3d/numpy-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:691808c2b26b0f002a032c73255d0bd89751425f379f7bcd22d140db593a96e8", size = 12919322, upload-time = "2025-09-09T15:58:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/8c/de/072420342e46a8ea41c324a555fa90fcc11637583fb8df722936aed1736d/numpy-2.3.3-cp314-cp314-win_arm64.whl", hash = "sha256:9ad12e976ca7b10f1774b03615a2a4bab8addce37ecc77394d8e986927dc0dfe", size = 10478630, upload-time = "2025-09-09T15:58:14.64Z" }, + { url = "https://files.pythonhosted.org/packages/d5/df/ee2f1c0a9de7347f14da5dd3cd3c3b034d1b8607ccb6883d7dd5c035d631/numpy-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9cc48e09feb11e1db00b320e9d30a4151f7369afb96bd0e48d942d09da3a0d00", size = 21047987, upload-time = "2025-09-09T15:58:16.889Z" }, + { url = "https://files.pythonhosted.org/packages/d6/92/9453bdc5a4e9e69cf4358463f25e8260e2ffc126d52e10038b9077815989/numpy-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:901bf6123879b7f251d3631967fd574690734236075082078e0571977c6a8e6a", size = 14301076, upload-time = "2025-09-09T15:58:20.343Z" }, + { url = "https://files.pythonhosted.org/packages/13/77/1447b9eb500f028bb44253105bd67534af60499588a5149a94f18f2ca917/numpy-2.3.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:7f025652034199c301049296b59fa7d52c7e625017cae4c75d8662e377bf487d", size = 5229491, upload-time = "2025-09-09T15:58:22.481Z" }, + { url = "https://files.pythonhosted.org/packages/3d/f9/d72221b6ca205f9736cb4b2ce3b002f6e45cd67cd6a6d1c8af11a2f0b649/numpy-2.3.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:533ca5f6d325c80b6007d4d7fb1984c303553534191024ec6a524a4c92a5935a", size = 6737913, upload-time = "2025-09-09T15:58:24.569Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5f/d12834711962ad9c46af72f79bb31e73e416ee49d17f4c797f72c96b6ca5/numpy-2.3.3-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0edd58682a399824633b66885d699d7de982800053acf20be1eaa46d92009c54", size = 14352811, upload-time = "2025-09-09T15:58:26.416Z" }, + { url = "https://files.pythonhosted.org/packages/a1/0d/fdbec6629d97fd1bebed56cd742884e4eead593611bbe1abc3eb40d304b2/numpy-2.3.3-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:367ad5d8fbec5d9296d18478804a530f1191e24ab4d75ab408346ae88045d25e", size = 16702689, upload-time = "2025-09-09T15:58:28.831Z" }, + { url = "https://files.pythonhosted.org/packages/9b/09/0a35196dc5575adde1eb97ddfbc3e1687a814f905377621d18ca9bc2b7dd/numpy-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8f6ac61a217437946a1fa48d24c47c91a0c4f725237871117dea264982128097", size = 16133855, upload-time = "2025-09-09T15:58:31.349Z" }, + { url = "https://files.pythonhosted.org/packages/7a/ca/c9de3ea397d576f1b6753eaa906d4cdef1bf97589a6d9825a349b4729cc2/numpy-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:179a42101b845a816d464b6fe9a845dfaf308fdfc7925387195570789bb2c970", size = 18652520, upload-time = "2025-09-09T15:58:33.762Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c2/e5ed830e08cd0196351db55db82f65bc0ab05da6ef2b72a836dcf1936d2f/numpy-2.3.3-cp314-cp314t-win32.whl", hash = "sha256:1250c5d3d2562ec4174bce2e3a1523041595f9b651065e4a4473f5f48a6bc8a5", size = 6515371, upload-time = "2025-09-09T15:58:36.04Z" }, + { url = "https://files.pythonhosted.org/packages/47/c7/b0f6b5b67f6788a0725f744496badbb604d226bf233ba716683ebb47b570/numpy-2.3.3-cp314-cp314t-win_amd64.whl", hash = "sha256:b37a0b2e5935409daebe82c1e42274d30d9dd355852529eab91dab8dcca7419f", size = 13112576, upload-time = "2025-09-09T15:58:37.927Z" }, + { url = "https://files.pythonhosted.org/packages/06/b9/33bba5ff6fb679aa0b1f8a07e853f002a6b04b9394db3069a1270a7784ca/numpy-2.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:78c9f6560dc7e6b3990e32df7ea1a50bbd0e2a111e05209963f5ddcab7073b0b", size = 10545953, upload-time = "2025-09-09T15:58:40.576Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f2/7e0a37cfced2644c9563c529f29fa28acbd0960dde32ece683aafa6f4949/numpy-2.3.3-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1e02c7159791cd481e1e6d5ddd766b62a4d5acf8df4d4d1afe35ee9c5c33a41e", size = 21131019, upload-time = "2025-09-09T15:58:42.838Z" }, + { url = "https://files.pythonhosted.org/packages/1a/7e/3291f505297ed63831135a6cc0f474da0c868a1f31b0dd9a9f03a7a0d2ed/numpy-2.3.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:dca2d0fc80b3893ae72197b39f69d55a3cd8b17ea1b50aa4c62de82419936150", size = 14376288, upload-time = "2025-09-09T15:58:45.425Z" }, + { url = "https://files.pythonhosted.org/packages/bf/4b/ae02e985bdeee73d7b5abdefeb98aef1207e96d4c0621ee0cf228ddfac3c/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:99683cbe0658f8271b333a1b1b4bb3173750ad59c0c61f5bbdc5b318918fffe3", size = 5305425, upload-time = "2025-09-09T15:58:48.6Z" }, + { url = "https://files.pythonhosted.org/packages/8b/eb/9df215d6d7250db32007941500dc51c48190be25f2401d5b2b564e467247/numpy-2.3.3-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d9d537a39cc9de668e5cd0e25affb17aec17b577c6b3ae8a3d866b479fbe88d0", size = 6819053, upload-time = "2025-09-09T15:58:50.401Z" }, + { url = "https://files.pythonhosted.org/packages/57/62/208293d7d6b2a8998a4a1f23ac758648c3c32182d4ce4346062018362e29/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8596ba2f8af5f93b01d97563832686d20206d303024777f6dfc2e7c7c3f1850e", size = 14420354, upload-time = "2025-09-09T15:58:52.704Z" }, + { url = "https://files.pythonhosted.org/packages/ed/0c/8e86e0ff7072e14a71b4c6af63175e40d1e7e933ce9b9e9f765a95b4e0c3/numpy-2.3.3-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1ec5615b05369925bd1125f27df33f3b6c8bc10d788d5999ecd8769a1fa04db", size = 16760413, upload-time = "2025-09-09T15:58:55.027Z" }, + { url = "https://files.pythonhosted.org/packages/af/11/0cc63f9f321ccf63886ac203336777140011fb669e739da36d8db3c53b98/numpy-2.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2e267c7da5bf7309670523896df97f93f6e469fb931161f483cd6882b3b1a5dc", size = 12971844, upload-time = "2025-09-09T15:58:57.359Z" }, +] + +[[package]] +name = "pandas" +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/79/8e/0e90233ac205ad182bd6b422532695d2b9414944a280488105d598c70023/pandas-2.3.2.tar.gz", hash = "sha256:ab7b58f8f82706890924ccdfb5f48002b83d2b5a3845976a9fb705d36c34dcdb", size = 4488684, upload-time = "2025-08-21T10:28:29.257Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7a/59/f3e010879f118c2d400902d2d871c2226cef29b08c09fb8dc41111730400/pandas-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1333e9c299adcbb68ee89a9bb568fc3f20f9cbb419f1dd5225071e6cddb2a743", size = 11563308, upload-time = "2025-08-21T10:26:56.656Z" }, + { url = "https://files.pythonhosted.org/packages/38/18/48f10f1cc5c397af59571d638d211f494dba481f449c19adbd282aa8f4ca/pandas-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:76972bcbd7de8e91ad5f0ca884a9f2c477a2125354af624e022c49e5bd0dfff4", size = 10820319, upload-time = "2025-08-21T10:26:59.162Z" }, + { url = "https://files.pythonhosted.org/packages/95/3b/1e9b69632898b048e223834cd9702052bcf06b15e1ae716eda3196fb972e/pandas-2.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b98bdd7c456a05eef7cd21fd6b29e3ca243591fe531c62be94a2cc987efb5ac2", size = 11790097, upload-time = "2025-08-21T10:27:02.204Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/0e2ffb30b1f7fbc9a588bd01e3c14a0d96854d09a887e15e30cc19961227/pandas-2.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d81573b3f7db40d020983f78721e9bfc425f411e616ef019a10ebf597aedb2e", size = 12397958, upload-time = "2025-08-21T10:27:05.409Z" }, + { url = "https://files.pythonhosted.org/packages/23/82/e6b85f0d92e9afb0e7f705a51d1399b79c7380c19687bfbf3d2837743249/pandas-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e190b738675a73b581736cc8ec71ae113d6c3768d0bd18bffa5b9a0927b0b6ea", size = 13225600, upload-time = "2025-08-21T10:27:07.791Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f1/f682015893d9ed51611948bd83683670842286a8edd4f68c2c1c3b231eef/pandas-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c253828cb08f47488d60f43c5fc95114c771bbfff085da54bfc79cb4f9e3a372", size = 13879433, upload-time = "2025-08-21T10:27:10.347Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e7/ae86261695b6c8a36d6a4c8d5f9b9ede8248510d689a2f379a18354b37d7/pandas-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:9467697b8083f9667b212633ad6aa4ab32436dcbaf4cd57325debb0ddef2012f", size = 11336557, upload-time = "2025-08-21T10:27:12.983Z" }, + { url = "https://files.pythonhosted.org/packages/ec/db/614c20fb7a85a14828edd23f1c02db58a30abf3ce76f38806155d160313c/pandas-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fbb977f802156e7a3f829e9d1d5398f6192375a3e2d1a9ee0803e35fe70a2b9", size = 11587652, upload-time = "2025-08-21T10:27:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/99/b0/756e52f6582cade5e746f19bad0517ff27ba9c73404607c0306585c201b3/pandas-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b9b52693123dd234b7c985c68b709b0b009f4521000d0525f2b95c22f15944b", size = 10717686, upload-time = "2025-08-21T10:27:18.486Z" }, + { url = "https://files.pythonhosted.org/packages/37/4c/dd5ccc1e357abfeee8353123282de17997f90ff67855f86154e5a13b81e5/pandas-2.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bd281310d4f412733f319a5bc552f86d62cddc5f51d2e392c8787335c994175", size = 11278722, upload-time = "2025-08-21T10:27:21.149Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a4/f7edcfa47e0a88cda0be8b068a5bae710bf264f867edfdf7b71584ace362/pandas-2.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96d31a6b4354e3b9b8a2c848af75d31da390657e3ac6f30c05c82068b9ed79b9", size = 11987803, upload-time = "2025-08-21T10:27:23.767Z" }, + { url = "https://files.pythonhosted.org/packages/f6/61/1bce4129f93ab66f1c68b7ed1c12bac6a70b1b56c5dab359c6bbcd480b52/pandas-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:df4df0b9d02bb873a106971bb85d448378ef14b86ba96f035f50bbd3688456b4", size = 12766345, upload-time = "2025-08-21T10:27:26.6Z" }, + { url = "https://files.pythonhosted.org/packages/8e/46/80d53de70fee835531da3a1dae827a1e76e77a43ad22a8cd0f8142b61587/pandas-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:213a5adf93d020b74327cb2c1b842884dbdd37f895f42dcc2f09d451d949f811", size = 13439314, upload-time = "2025-08-21T10:27:29.213Z" }, + { url = "https://files.pythonhosted.org/packages/28/30/8114832daff7489f179971dbc1d854109b7f4365a546e3ea75b6516cea95/pandas-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:8c13b81a9347eb8c7548f53fd9a4f08d4dfe996836543f805c987bafa03317ae", size = 10983326, upload-time = "2025-08-21T10:27:31.901Z" }, + { url = "https://files.pythonhosted.org/packages/27/64/a2f7bf678af502e16b472527735d168b22b7824e45a4d7e96a4fbb634b59/pandas-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0c6ecbac99a354a051ef21c5307601093cb9e0f4b1855984a084bfec9302699e", size = 11531061, upload-time = "2025-08-21T10:27:34.647Z" }, + { url = "https://files.pythonhosted.org/packages/54/4c/c3d21b2b7769ef2f4c2b9299fcadd601efa6729f1357a8dbce8dd949ed70/pandas-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c6f048aa0fd080d6a06cc7e7537c09b53be6642d330ac6f54a600c3ace857ee9", size = 10668666, upload-time = "2025-08-21T10:27:37.203Z" }, + { url = "https://files.pythonhosted.org/packages/50/e2/f775ba76ecfb3424d7f5862620841cf0edb592e9abd2d2a5387d305fe7a8/pandas-2.3.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0064187b80a5be6f2f9c9d6bdde29372468751dfa89f4211a3c5871854cfbf7a", size = 11332835, upload-time = "2025-08-21T10:27:40.188Z" }, + { url = "https://files.pythonhosted.org/packages/8f/52/0634adaace9be2d8cac9ef78f05c47f3a675882e068438b9d7ec7ef0c13f/pandas-2.3.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac8c320bded4718b298281339c1a50fb00a6ba78cb2a63521c39bec95b0209b", size = 12057211, upload-time = "2025-08-21T10:27:43.117Z" }, + { url = "https://files.pythonhosted.org/packages/0b/9d/2df913f14b2deb9c748975fdb2491da1a78773debb25abbc7cbc67c6b549/pandas-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:114c2fe4f4328cf98ce5716d1532f3ab79c5919f95a9cfee81d9140064a2e4d6", size = 12749277, upload-time = "2025-08-21T10:27:45.474Z" }, + { url = "https://files.pythonhosted.org/packages/87/af/da1a2417026bd14d98c236dba88e39837182459d29dcfcea510b2ac9e8a1/pandas-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:48fa91c4dfb3b2b9bfdb5c24cd3567575f4e13f9636810462ffed8925352be5a", size = 13415256, upload-time = "2025-08-21T10:27:49.885Z" }, + { url = "https://files.pythonhosted.org/packages/22/3c/f2af1ce8840ef648584a6156489636b5692c162771918aa95707c165ad2b/pandas-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:12d039facec710f7ba305786837d0225a3444af7bbd9c15c32ca2d40d157ed8b", size = 10982579, upload-time = "2025-08-21T10:28:08.435Z" }, + { url = "https://files.pythonhosted.org/packages/f3/98/8df69c4097a6719e357dc249bf437b8efbde808038268e584421696cbddf/pandas-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c624b615ce97864eb588779ed4046186f967374185c047070545253a52ab2d57", size = 12028163, upload-time = "2025-08-21T10:27:52.232Z" }, + { url = "https://files.pythonhosted.org/packages/0e/23/f95cbcbea319f349e10ff90db488b905c6883f03cbabd34f6b03cbc3c044/pandas-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0cee69d583b9b128823d9514171cabb6861e09409af805b54459bd0c821a35c2", size = 11391860, upload-time = "2025-08-21T10:27:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1b/6a984e98c4abee22058aa75bfb8eb90dce58cf8d7296f8bc56c14bc330b0/pandas-2.3.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2319656ed81124982900b4c37f0e0c58c015af9a7bbc62342ba5ad07ace82ba9", size = 11309830, upload-time = "2025-08-21T10:27:56.957Z" }, + { url = "https://files.pythonhosted.org/packages/15/d5/f0486090eb18dd8710bf60afeaf638ba6817047c0c8ae5c6a25598665609/pandas-2.3.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37205ad6f00d52f16b6d09f406434ba928c1a1966e2771006a9033c736d30d2", size = 11883216, upload-time = "2025-08-21T10:27:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/10/86/692050c119696da19e20245bbd650d8dfca6ceb577da027c3a73c62a047e/pandas-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:837248b4fc3a9b83b9c6214699a13f069dc13510a6a6d7f9ba33145d2841a012", size = 12699743, upload-time = "2025-08-21T10:28:02.447Z" }, + { url = "https://files.pythonhosted.org/packages/cd/d7/612123674d7b17cf345aad0a10289b2a384bff404e0463a83c4a3a59d205/pandas-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d2c3554bd31b731cd6490d94a28f3abb8dd770634a9e06eb6d2911b9827db370", size = 13186141, upload-time = "2025-08-21T10:28:05.377Z" }, +] [[package]] name = "polars" @@ -16,13 +142,94 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/58/13/824a81b43199202fc859c24515cd5b227930d6dce0dea488e4b415edbaba/polars-1.33.0-cp39-abi3-win_arm64.whl", hash = "sha256:c7d614644eda028907965f8203ac54b9a4f5b90303de2723bf1c1087433a0914", size = 35033820, upload-time = "2025-09-01T16:32:08.116Z" }, ] +[[package]] +name = "pyarrow" +version = "21.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/c2/ea068b8f00905c06329a3dfcd40d0fcc2b7d0f2e355bdb25b65e0a0e4cd4/pyarrow-21.0.0.tar.gz", hash = "sha256:5051f2dccf0e283ff56335760cbc8622cf52264d67e359d5569541ac11b6d5bc", size = 1133487, upload-time = "2025-07-18T00:57:31.761Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/dc/80564a3071a57c20b7c32575e4a0120e8a330ef487c319b122942d665960/pyarrow-21.0.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c077f48aab61738c237802836fc3844f85409a46015635198761b0d6a688f87b", size = 31243234, upload-time = "2025-07-18T00:55:03.812Z" }, + { url = "https://files.pythonhosted.org/packages/ea/cc/3b51cb2db26fe535d14f74cab4c79b191ed9a8cd4cbba45e2379b5ca2746/pyarrow-21.0.0-cp311-cp311-macosx_12_0_x86_64.whl", hash = "sha256:689f448066781856237eca8d1975b98cace19b8dd2ab6145bf49475478bcaa10", size = 32714370, upload-time = "2025-07-18T00:55:07.495Z" }, + { url = "https://files.pythonhosted.org/packages/24/11/a4431f36d5ad7d83b87146f515c063e4d07ef0b7240876ddb885e6b44f2e/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:479ee41399fcddc46159a551705b89c05f11e8b8cb8e968f7fec64f62d91985e", size = 41135424, upload-time = "2025-07-18T00:55:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/74/dc/035d54638fc5d2971cbf1e987ccd45f1091c83bcf747281cf6cc25e72c88/pyarrow-21.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:40ebfcb54a4f11bcde86bc586cbd0272bac0d516cfa539c799c2453768477569", size = 42823810, upload-time = "2025-07-18T00:55:16.301Z" }, + { url = "https://files.pythonhosted.org/packages/2e/3b/89fced102448a9e3e0d4dded1f37fa3ce4700f02cdb8665457fcc8015f5b/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8d58d8497814274d3d20214fbb24abcad2f7e351474357d552a8d53bce70c70e", size = 43391538, upload-time = "2025-07-18T00:55:23.82Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bb/ea7f1bd08978d39debd3b23611c293f64a642557e8141c80635d501e6d53/pyarrow-21.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:585e7224f21124dd57836b1530ac8f2df2afc43c861d7bf3d58a4870c42ae36c", size = 45120056, upload-time = "2025-07-18T00:55:28.231Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0b/77ea0600009842b30ceebc3337639a7380cd946061b620ac1a2f3cb541e2/pyarrow-21.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:555ca6935b2cbca2c0e932bedd853e9bc523098c39636de9ad4693b5b1df86d6", size = 26220568, upload-time = "2025-07-18T00:55:32.122Z" }, + { url = "https://files.pythonhosted.org/packages/ca/d4/d4f817b21aacc30195cf6a46ba041dd1be827efa4a623cc8bf39a1c2a0c0/pyarrow-21.0.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:3a302f0e0963db37e0a24a70c56cf91a4faa0bca51c23812279ca2e23481fccd", size = 31160305, upload-time = "2025-07-18T00:55:35.373Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9c/dcd38ce6e4b4d9a19e1d36914cb8e2b1da4e6003dd075474c4cfcdfe0601/pyarrow-21.0.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:b6b27cf01e243871390474a211a7922bfbe3bda21e39bc9160daf0da3fe48876", size = 32684264, upload-time = "2025-07-18T00:55:39.303Z" }, + { url = "https://files.pythonhosted.org/packages/4f/74/2a2d9f8d7a59b639523454bec12dba35ae3d0a07d8ab529dc0809f74b23c/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e72a8ec6b868e258a2cd2672d91f2860ad532d590ce94cdf7d5e7ec674ccf03d", size = 41108099, upload-time = "2025-07-18T00:55:42.889Z" }, + { url = "https://files.pythonhosted.org/packages/ad/90/2660332eeb31303c13b653ea566a9918484b6e4d6b9d2d46879a33ab0622/pyarrow-21.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:b7ae0bbdc8c6674259b25bef5d2a1d6af5d39d7200c819cf99e07f7dfef1c51e", size = 42829529, upload-time = "2025-07-18T00:55:47.069Z" }, + { url = "https://files.pythonhosted.org/packages/33/27/1a93a25c92717f6aa0fca06eb4700860577d016cd3ae51aad0e0488ac899/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:58c30a1729f82d201627c173d91bd431db88ea74dcaa3885855bc6203e433b82", size = 43367883, upload-time = "2025-07-18T00:55:53.069Z" }, + { url = "https://files.pythonhosted.org/packages/05/d9/4d09d919f35d599bc05c6950095e358c3e15148ead26292dfca1fb659b0c/pyarrow-21.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:072116f65604b822a7f22945a7a6e581cfa28e3454fdcc6939d4ff6090126623", size = 45133802, upload-time = "2025-07-18T00:55:57.714Z" }, + { url = "https://files.pythonhosted.org/packages/71/30/f3795b6e192c3ab881325ffe172e526499eb3780e306a15103a2764916a2/pyarrow-21.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf56ec8b0a5c8c9d7021d6fd754e688104f9ebebf1bf4449613c9531f5346a18", size = 26203175, upload-time = "2025-07-18T00:56:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/16/ca/c7eaa8e62db8fb37ce942b1ea0c6d7abfe3786ca193957afa25e71b81b66/pyarrow-21.0.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:e99310a4ebd4479bcd1964dff9e14af33746300cb014aa4a3781738ac63baf4a", size = 31154306, upload-time = "2025-07-18T00:56:04.42Z" }, + { url = "https://files.pythonhosted.org/packages/ce/e8/e87d9e3b2489302b3a1aea709aaca4b781c5252fcb812a17ab6275a9a484/pyarrow-21.0.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:d2fe8e7f3ce329a71b7ddd7498b3cfac0eeb200c2789bd840234f0dc271a8efe", size = 32680622, upload-time = "2025-07-18T00:56:07.505Z" }, + { url = "https://files.pythonhosted.org/packages/84/52/79095d73a742aa0aba370c7942b1b655f598069489ab387fe47261a849e1/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f522e5709379d72fb3da7785aa489ff0bb87448a9dc5a75f45763a795a089ebd", size = 41104094, upload-time = "2025-07-18T00:56:10.994Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/7782438b551dbb0468892a276b8c789b8bbdb25ea5c5eb27faadd753e037/pyarrow-21.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:69cbbdf0631396e9925e048cfa5bce4e8c3d3b41562bbd70c685a8eb53a91e61", size = 42825576, upload-time = "2025-07-18T00:56:15.569Z" }, + { url = "https://files.pythonhosted.org/packages/b3/62/0f29de6e0a1e33518dec92c65be0351d32d7ca351e51ec5f4f837a9aab91/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:731c7022587006b755d0bdb27626a1a3bb004bb56b11fb30d98b6c1b4718579d", size = 43368342, upload-time = "2025-07-18T00:56:19.531Z" }, + { url = "https://files.pythonhosted.org/packages/90/c7/0fa1f3f29cf75f339768cc698c8ad4ddd2481c1742e9741459911c9ac477/pyarrow-21.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc56bc708f2d8ac71bd1dcb927e458c93cec10b98eb4120206a4091db7b67b99", size = 45131218, upload-time = "2025-07-18T00:56:23.347Z" }, + { url = "https://files.pythonhosted.org/packages/01/63/581f2076465e67b23bc5a37d4a2abff8362d389d29d8105832e82c9c811c/pyarrow-21.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:186aa00bca62139f75b7de8420f745f2af12941595bbbfa7ed3870ff63e25636", size = 26087551, upload-time = "2025-07-18T00:56:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ab/357d0d9648bb8241ee7348e564f2479d206ebe6e1c47ac5027c2e31ecd39/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:a7a102574faa3f421141a64c10216e078df467ab9576684d5cd696952546e2da", size = 31290064, upload-time = "2025-07-18T00:56:30.214Z" }, + { url = "https://files.pythonhosted.org/packages/3f/8a/5685d62a990e4cac2043fc76b4661bf38d06efed55cf45a334b455bd2759/pyarrow-21.0.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:1e005378c4a2c6db3ada3ad4c217b381f6c886f0a80d6a316fe586b90f77efd7", size = 32727837, upload-time = "2025-07-18T00:56:33.935Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/c0828ee09525c2bafefd3e736a248ebe764d07d0fd762d4f0929dbc516c9/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:65f8e85f79031449ec8706b74504a316805217b35b6099155dd7e227eef0d4b6", size = 41014158, upload-time = "2025-07-18T00:56:37.528Z" }, + { url = "https://files.pythonhosted.org/packages/6e/26/a2865c420c50b7a3748320b614f3484bfcde8347b2639b2b903b21ce6a72/pyarrow-21.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:3a81486adc665c7eb1a2bde0224cfca6ceaba344a82a971ef059678417880eb8", size = 42667885, upload-time = "2025-07-18T00:56:41.483Z" }, + { url = "https://files.pythonhosted.org/packages/0a/f9/4ee798dc902533159250fb4321267730bc0a107d8c6889e07c3add4fe3a5/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:fc0d2f88b81dcf3ccf9a6ae17f89183762c8a94a5bdcfa09e05cfe413acf0503", size = 43276625, upload-time = "2025-07-18T00:56:48.002Z" }, + { url = "https://files.pythonhosted.org/packages/5a/da/e02544d6997037a4b0d22d8e5f66bc9315c3671371a8b18c79ade1cefe14/pyarrow-21.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6299449adf89df38537837487a4f8d3bd91ec94354fdd2a7d30bc11c48ef6e79", size = 44951890, upload-time = "2025-07-18T00:56:52.568Z" }, + { url = "https://files.pythonhosted.org/packages/e5/4e/519c1bc1876625fe6b71e9a28287c43ec2f20f73c658b9ae1d485c0c206e/pyarrow-21.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:222c39e2c70113543982c6b34f3077962b44fca38c0bd9e68bb6781534425c10", size = 26371006, upload-time = "2025-07-18T00:56:56.379Z" }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, +] + +[[package]] +name = "pytz" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, +] + +[[package]] +name = "six" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, +] + [[package]] name = "tools" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "pandas" }, { name = "polars" }, + { name = "pyarrow" }, ] [package.metadata] -requires-dist = [{ name = "polars", specifier = ">=1.33.0" }] +requires-dist = [ + { name = "pandas", specifier = ">=2.3.2" }, + { name = "polars", specifier = ">=1.33.0" }, + { name = "pyarrow", specifier = ">=21.0.0" }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, +] diff --git a/tools/verify_loaded_results.py b/tools/verify_loaded_results.py index dbad4f3..a3bd9c1 100644 --- a/tools/verify_loaded_results.py +++ b/tools/verify_loaded_results.py @@ -6,7 +6,7 @@ from typing import Sequence import polars as pl -from load_results import load_results_dataframe +from plot_scripts.load_results import load_results_dataframe # --- configure your intended grid here (use the *canonical* strings used in df) --- NETWORKS_EXPECTED = ["subter_LeNet", "subter_efficient"]