From d1267baa027d8d3cdf22109daa56e776dc29beef Mon Sep 17 00:00:00 2001 From: Hanyuan Li Date: Thu, 4 Jun 2026 11:39:36 +1000 Subject: [PATCH 1/5] fix: add errors return value to validate_spec_pair_with_model output --- act/front_end/torchvision_loader/create_specs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/act/front_end/torchvision_loader/create_specs.py b/act/front_end/torchvision_loader/create_specs.py index b55ce2af8..49f0e3037 100644 --- a/act/front_end/torchvision_loader/create_specs.py +++ b/act/front_end/torchvision_loader/create_specs.py @@ -464,7 +464,7 @@ def _validate_and_filter_specs( for input_spec, output_spec in spec_pairs: try: - is_valid = self.validate_spec_pair_with_model( + is_valid, errors = self.validate_spec_pair_with_model( input_spec, output_spec, pytorch_model, @@ -474,9 +474,9 @@ def _validate_and_filter_specs( if is_valid: valid_pairs.append((input_spec, output_spec)) else: - logger.debug( - f"Spec pair validation failed: " - f"{input_spec.kind}, {output_spec.kind}" + logger.error( + f"Spec pair validation failed: {input_spec.kind}, {output_spec.kind}, with errors:\n" + f"{"\n".join(errors)}" ) except Exception as e: From 85de7d0ff4e51699d7ca18010faf71d04a067101 Mon Sep 17 00:00:00 2001 From: Hanyuan Li Date: Thu, 4 Jun 2026 11:40:14 +1000 Subject: [PATCH 2/5] fix: have y_true class bounds check work with multi-element bool tensors --- act/front_end/spec_creator_base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/act/front_end/spec_creator_base.py b/act/front_end/spec_creator_base.py index 98df08aa4..bd0ff2872 100644 --- a/act/front_end/spec_creator_base.py +++ b/act/front_end/spec_creator_base.py @@ -327,7 +327,9 @@ def _validate_output_spec_shape( (is_valid, error_message) tuple """ if spec.kind in [OutKind.MARGIN_ROBUST, OutKind.TOP1_ROBUST]: - if not (0 <= spec.y_true < num_classes): + y_true_valid_class = (0 <= spec.y_true).logical_and(spec.y_true < num_classes) + + if not y_true_valid_class.all(): return False, f"Class label {spec.y_true} out of range [0, {num_classes})" elif spec.kind == OutKind.LINEAR_LE: From 073dfd2e207c0a33677769c75f4d843f83907014 Mon Sep 17 00:00:00 2001 From: Hanyuan Li Date: Thu, 4 Jun 2026 11:41:26 +1000 Subject: [PATCH 3/5] test: add notebook for dtype testing purposes --- ipynb/frontend_dtype.ipynb | 407 +++++++++++++++++++++++++++++++++++++ 1 file changed, 407 insertions(+) create mode 100644 ipynb/frontend_dtype.ipynb diff --git a/ipynb/frontend_dtype.ipynb b/ipynb/frontend_dtype.ipynb new file mode 100644 index 000000000..2143abe32 --- /dev/null +++ b/ipynb/frontend_dtype.ipynb @@ -0,0 +1,407 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "b566cee2", + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from pathlib import Path\n", + "import sys\n", + "\n", + "import torch\n", + "import torch.nn as nn\n", + "from torch.utils.data import DataLoader\n", + "from torchvision.datasets import CIFAR100\n", + "from torchvision.models import resnet18\n", + "from torchvision.transforms import v2\n", + "\n", + "act_root = os.path.dirname(os.path.dirname(os.path.abspath('__file__')))\n", + "sys.path.insert(0, act_root) if act_root not in sys.path else None\n", + "\n", + "ACT_ROOT = Path(act_root)\n", + "\n", + "BATCH_SIZE = 1\n", + "EPOCHS = 5\n", + "\n", + "MODEL_SAVE_PATH = ACT_ROOT / \"ipynb/resnet18_cifar100.pth\"\n", + "\n", + "# Train model on `float32` dataset\n", + "model = resnet18()\n", + "\n", + "train_dataset = CIFAR100(root=ACT_ROOT / \"data/torchvision/CIFAR100/raw\", transform=v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32, scale=True)]))\n", + "train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n", + "size = len(train_dataloader.dataset)\n", + "\n", + "loss_fn = nn.CrossEntropyLoss()\n", + "optimizer = torch.optim.SGD(model.parameters())\n", + "\n", + "if os.path.exists(MODEL_SAVE_PATH):\n", + " model.load_state_dict(torch.load(MODEL_SAVE_PATH, weights_only=True))\n", + "else:\n", + " model.train()\n", + "\n", + " for _ in range(EPOCHS):\n", + " for batch, (X, y) in enumerate(train_dataloader):\n", + " # Compute prediction and loss\n", + " pred = model(X)\n", + " loss = loss_fn(pred, y)\n", + "\n", + " # Backpropagation\n", + " loss.backward()\n", + " optimizer.step()\n", + " optimizer.zero_grad()\n", + "\n", + " if batch % 100 == 0:\n", + " loss, current = loss.item(), batch * BATCH_SIZE + len(X)\n", + " print(f\"loss: {loss:>7f} [{current:>5d}/{size:>5d}]\")\n", + "\n", + " torch.save(model.state_dict(), MODEL_SAVE_PATH)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "720b20d2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[ACT] Auto-detecting project root: ..\n", + "[ACT] Gurobi license found: ../modules/gurobi/gurobi.lic\n", + "\n", + "🧬 Synthesizing models from 10 spec result(s)...\n", + "\n", + "🎉 Synthesis Complete:\n", + " Total specs: 240\n", + " Wrapped models: 4\n" + ] + } + ], + "source": [ + "from act.front_end.model_synthesis import synthesize_models_from_specs\n", + "from act.front_end.torchvision_loader.create_specs import TorchVisionSpecCreator\n", + "from act.front_end.vnnlib_loader.create_specs import VNNLibSpecCreator\n", + "\n", + "\n", + "USE_VNNLIB = False\n", + "\n", + "# Without specifying `dtype = float32`, both of these spec creators\n", + "# should fail - the default data type for frontends is `float64`\n", + "if USE_VNNLIB:\n", + " spec_creator = VNNLibSpecCreator()\n", + " specs = spec_creator.create_specs_for_data_model_pairs(\n", + " categories=[\"cifar100_2024\"],\n", + " max_instances=10\n", + " )\n", + "else:\n", + " spec_creator = TorchVisionSpecCreator()\n", + " specs = []\n", + "\n", + " for i in range(10):\n", + " spec = spec_creator._create_specs_for_single_instance(\n", + " data_source=\"CIFAR100\",\n", + " model_name=\"resnet18\",\n", + " pytorch_model=model,\n", + " dataloader=train_dataloader,\n", + " num_samples=1,\n", + " start_index=0,\n", + " validate_shapes=True\n", + " )\n", + " specs.append(spec)\n", + "\n", + "synthesis_models = synthesize_models_from_specs(specs)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "d208a03c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[MutationEngine] Adaptive Scalar Perturbation Size:\n", + " - perturb_scale: 0.1 (fraction of range per perturbation)\n", + " - mean_range: 0.093250\n", + " - computed perturb_size: 0.009325\n", + " - steps_to_traverse: ~10.0 steps\n", + " - interpretation: Each mutation perturbation covers 10.0% of the range\n", + "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", + " Output: ../act/pipeline/log/fuzzing_results/traces_0.json\n", + "================================================================================\n", + "ACT: Abstract Constraint Transformer\n", + "Inference-based whitebox fuzzing for neural network verification\n", + "================================================================================\n", + "\n", + "🚀 Starting ACTFuzzer with 10 seeds\n", + " Device: cpu\n", + " Batch size: 80 (from model synthesis)\n", + " Max iterations: 500\n", + " Timeout: 60.0s\n", + "\n", + "📊 Iteration 80 | GlobalCov: 94.00% BestInputCov: 86.15% | Seeds: 90 | Violations: 74 (+74) | Speed: 30.0 it/s (2403 samples/s)\n", + "📊 Iteration 160 | GlobalCov: 94.67% BestInputCov: 86.24% | Seeds: 170 | Violations: 154 (+80) | Speed: 25.8 it/s (2064 samples/s)\n", + "📊 Iteration 240 | GlobalCov: 95.25% BestInputCov: 86.24% | Seeds: 250 | Violations: 234 (+80) | Speed: 24.9 it/s (1994 samples/s)\n", + "📊 Iteration 320 | GlobalCov: 95.31% BestInputCov: 86.24% | Seeds: 328 | Violations: 312 (+78) | Speed: 29.5 it/s (2360 samples/s)\n", + "📊 Iteration 400 | GlobalCov: 95.73% BestInputCov: 86.37% | Seeds: 408 | Violations: 392 (+80) | Speed: 25.9 it/s (2072 samples/s)\n", + "📊 Iteration 480 | GlobalCov: 95.80% BestInputCov: 86.37% | Seeds: 485 | Violations: 469 (+77) | Speed: 17.6 it/s (1410 samples/s)\n", + "📊 Iteration 560 | GlobalCov: 95.99% BestInputCov: 86.37% | Seeds: 565 | Violations: 549 (+80) | Speed: 13.5 it/s (1079 samples/s)\n", + "\n", + "================================================================================\n", + "🎉 ACTFuzzer completed in 41.5s\n", + " Iterations: 560\n", + " Counterexamples: 549\n", + " GlobalCov: 95.99% BestInputCov: 86.37%\n", + " Seeds explored: 565\n", + " Never-activated neurons: 312\n", + " Never-activated sample: model.fc[100], model.fc[106], model.fc[108], model.fc[120], model.fc[123], model.fc[126], model.fc[129], model.fc[130], model.fc[131], model.fc[132]\n", + "================================================================================\n", + "\n", + "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", + "\n", + "================================================================================\n", + "📊 Tracing Statistics\n", + "================================================================================\n", + "Level: 1\n", + "Traces captured: 560\n", + "Sample rate: 1/1\n", + "Output: ../act/pipeline/log/fuzzing_results/traces_0.json\n", + "================================================================================\n", + "\n", + "[MutationEngine] Adaptive Scalar Perturbation Size:\n", + " - perturb_scale: 0.1 (fraction of range per perturbation)\n", + " - mean_range: 0.093250\n", + " - computed perturb_size: 0.009325\n", + " - steps_to_traverse: ~10.0 steps\n", + " - interpretation: Each mutation perturbation covers 10.0% of the range\n", + "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", + " Output: ../act/pipeline/log/fuzzing_results/traces_1.json\n", + "================================================================================\n", + "ACT: Abstract Constraint Transformer\n", + "Inference-based whitebox fuzzing for neural network verification\n", + "================================================================================\n", + "\n", + "🚀 Starting ACTFuzzer with 10 seeds\n", + " Device: cpu\n", + " Batch size: 40 (from model synthesis)\n", + " Max iterations: 500\n", + " Timeout: 60.0s\n", + "\n", + "📊 Iteration 40 | GlobalCov: 95.77% BestInputCov: 86.28% | Seeds: 50 | Violations: 33 (+33) | Speed: 34.5 it/s (1381 samples/s)\n", + "📊 Iteration 80 | GlobalCov: 96.39% BestInputCov: 86.31% | Seeds: 90 | Violations: 73 (+40) | Speed: 28.6 it/s (1146 samples/s)\n", + "📊 Iteration 120 | GlobalCov: 96.98% BestInputCov: 86.43% | Seeds: 130 | Violations: 113 (+40) | Speed: 28.6 it/s (1145 samples/s)\n", + "📊 Iteration 160 | GlobalCov: 97.23% BestInputCov: 86.43% | Seeds: 170 | Violations: 153 (+40) | Speed: 27.5 it/s (1101 samples/s)\n", + "📊 Iteration 200 | GlobalCov: 97.44% BestInputCov: 86.43% | Seeds: 210 | Violations: 193 (+40) | Speed: 26.9 it/s (1075 samples/s)\n", + "📊 Iteration 240 | GlobalCov: 97.44% BestInputCov: 86.43% | Seeds: 250 | Violations: 233 (+40) | Speed: 29.5 it/s (1182 samples/s)\n", + "📊 Iteration 280 | GlobalCov: 97.60% BestInputCov: 86.43% | Seeds: 290 | Violations: 273 (+40) | Speed: 28.6 it/s (1145 samples/s)\n", + "📊 Iteration 320 | GlobalCov: 97.65% BestInputCov: 86.43% | Seeds: 330 | Violations: 313 (+40) | Speed: 30.1 it/s (1206 samples/s)\n", + "📊 Iteration 360 | GlobalCov: 97.83% BestInputCov: 86.43% | Seeds: 370 | Violations: 353 (+40) | Speed: 29.7 it/s (1190 samples/s)\n", + "📊 Iteration 400 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 410 | Violations: 393 (+40) | Speed: 30.6 it/s (1225 samples/s)\n", + "📊 Iteration 440 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 449 | Violations: 432 (+39) | Speed: 32.6 it/s (1303 samples/s)\n", + "📊 Iteration 480 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 489 | Violations: 472 (+40) | Speed: 34.1 it/s (1365 samples/s)\n", + "📊 Iteration 520 | GlobalCov: 97.87% BestInputCov: 86.43% | Seeds: 528 | Violations: 511 (+39) | Speed: 35.5 it/s (1418 samples/s)\n", + "\n", + "================================================================================\n", + "🎉 ACTFuzzer completed in 14.7s\n", + " Iterations: 520\n", + " Counterexamples: 511\n", + " GlobalCov: 97.87% BestInputCov: 86.43%\n", + " Seeds explored: 528\n", + " Never-activated neurons: 166\n", + " Never-activated sample: model.fc[100], model.fc[108], model.fc[125], model.fc[126], model.fc[130], model.fc[131], model.fc[132], model.fc[142], model.fc[143], model.fc[151]\n", + "================================================================================\n", + "\n", + "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", + "\n", + "================================================================================\n", + "📊 Tracing Statistics\n", + "================================================================================\n", + "Level: 1\n", + "Traces captured: 520\n", + "Sample rate: 1/1\n", + "Output: ../act/pipeline/log/fuzzing_results/traces_1.json\n", + "================================================================================\n", + "\n", + "[MutationEngine] Adaptive Scalar Perturbation Size:\n", + " - perturb_scale: 0.1 (fraction of range per perturbation)\n", + " - mean_range: 0.095000\n", + " - computed perturb_size: 0.009500\n", + " - steps_to_traverse: ~10.0 steps\n", + " - interpretation: Each mutation perturbation covers 10.0% of the range\n", + "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", + " Output: ../act/pipeline/log/fuzzing_results/traces_2.json\n", + "================================================================================\n", + "ACT: Abstract Constraint Transformer\n", + "Inference-based whitebox fuzzing for neural network verification\n", + "================================================================================\n", + "\n", + "🚀 Starting ACTFuzzer with 10 seeds\n", + " Device: cpu\n", + " Batch size: 80 (from model synthesis)\n", + " Max iterations: 500\n", + " Timeout: 60.0s\n", + "\n", + "📊 Iteration 80 | GlobalCov: 98.68% BestInputCov: 86.38% | Seeds: 90 | Violations: 79 (+79) | Speed: 36.5 it/s (2919 samples/s)\n", + "📊 Iteration 160 | GlobalCov: 98.81% BestInputCov: 86.38% | Seeds: 170 | Violations: 159 (+80) | Speed: 48.2 it/s (3853 samples/s)\n", + "📊 Iteration 240 | GlobalCov: 98.93% BestInputCov: 86.38% | Seeds: 250 | Violations: 239 (+80) | Speed: 37.7 it/s (3018 samples/s)\n", + "📊 Iteration 320 | GlobalCov: 98.96% BestInputCov: 86.38% | Seeds: 330 | Violations: 319 (+80) | Speed: 38.9 it/s (3116 samples/s)\n", + "📊 Iteration 400 | GlobalCov: 98.96% BestInputCov: 86.38% | Seeds: 410 | Violations: 399 (+80) | Speed: 42.6 it/s (3412 samples/s)\n", + "📊 Iteration 480 | GlobalCov: 98.97% BestInputCov: 86.40% | Seeds: 490 | Violations: 479 (+80) | Speed: 41.2 it/s (3300 samples/s)\n", + "📊 Iteration 560 | GlobalCov: 99.19% BestInputCov: 86.40% | Seeds: 570 | Violations: 559 (+80) | Speed: 36.5 it/s (2921 samples/s)\n", + "\n", + "================================================================================\n", + "🎉 ACTFuzzer completed in 15.3s\n", + " Iterations: 560\n", + " Counterexamples: 559\n", + " GlobalCov: 99.19% BestInputCov: 86.40%\n", + " Seeds explored: 570\n", + " Never-activated neurons: 63\n", + " Never-activated sample: model.fc[100], model.fc[120], model.fc[131], model.fc[152], model.fc[194], model.fc[204], model.fc[232], model.fc[242], model.fc[244], model.fc[258]\n", + "================================================================================\n", + "\n", + "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", + "\n", + "================================================================================\n", + "📊 Tracing Statistics\n", + "================================================================================\n", + "Level: 1\n", + "Traces captured: 560\n", + "Sample rate: 1/1\n", + "Output: ../act/pipeline/log/fuzzing_results/traces_2.json\n", + "================================================================================\n", + "\n", + "[MutationEngine] Adaptive Scalar Perturbation Size:\n", + " - perturb_scale: 0.1 (fraction of range per perturbation)\n", + " - mean_range: 0.095000\n", + " - computed perturb_size: 0.009500\n", + " - steps_to_traverse: ~10.0 steps\n", + " - interpretation: Each mutation perturbation covers 10.0% of the range\n", + "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", + " Output: ../act/pipeline/log/fuzzing_results/traces_3.json\n", + "================================================================================\n", + "ACT: Abstract Constraint Transformer\n", + "Inference-based whitebox fuzzing for neural network verification\n", + "================================================================================\n", + "\n", + "🚀 Starting ACTFuzzer with 10 seeds\n", + " Device: cpu\n", + " Batch size: 40 (from model synthesis)\n", + " Max iterations: 500\n", + " Timeout: 60.0s\n", + "\n", + "📊 Iteration 40 | GlobalCov: 98.42% BestInputCov: 86.36% | Seeds: 50 | Violations: 30 (+30) | Speed: 21.3 it/s (853 samples/s)\n", + "📊 Iteration 80 | GlobalCov: 98.51% BestInputCov: 86.37% | Seeds: 88 | Violations: 66 (+36) | Speed: 29.2 it/s (1166 samples/s)\n", + "📊 Iteration 120 | GlobalCov: 98.83% BestInputCov: 86.37% | Seeds: 128 | Violations: 106 (+40) | Speed: 27.1 it/s (1085 samples/s)\n", + "📊 Iteration 160 | GlobalCov: 98.84% BestInputCov: 86.37% | Seeds: 168 | Violations: 146 (+40) | Speed: 30.7 it/s (1228 samples/s)\n", + "📊 Iteration 200 | GlobalCov: 98.84% BestInputCov: 86.37% | Seeds: 208 | Violations: 186 (+40) | Speed: 34.3 it/s (1371 samples/s)\n", + "📊 Iteration 240 | GlobalCov: 98.86% BestInputCov: 86.37% | Seeds: 248 | Violations: 226 (+40) | Speed: 37.6 it/s (1504 samples/s)\n", + "📊 Iteration 280 | GlobalCov: 99.08% BestInputCov: 86.37% | Seeds: 288 | Violations: 266 (+40) | Speed: 36.0 it/s (1442 samples/s)\n", + "📊 Iteration 320 | GlobalCov: 99.08% BestInputCov: 86.37% | Seeds: 328 | Violations: 306 (+40) | Speed: 31.1 it/s (1245 samples/s)\n", + "📊 Iteration 360 | GlobalCov: 99.11% BestInputCov: 86.37% | Seeds: 368 | Violations: 346 (+40) | Speed: 30.7 it/s (1227 samples/s)\n", + "📊 Iteration 400 | GlobalCov: 99.18% BestInputCov: 86.37% | Seeds: 408 | Violations: 386 (+40) | Speed: 30.7 it/s (1227 samples/s)\n", + "📊 Iteration 440 | GlobalCov: 99.19% BestInputCov: 86.37% | Seeds: 448 | Violations: 425 (+39) | Speed: 32.4 it/s (1295 samples/s)\n", + "📊 Iteration 480 | GlobalCov: 99.19% BestInputCov: 86.37% | Seeds: 486 | Violations: 463 (+38) | Speed: 33.8 it/s (1353 samples/s)\n", + "📊 Iteration 520 | GlobalCov: 99.22% BestInputCov: 86.37% | Seeds: 526 | Violations: 503 (+40) | Speed: 33.4 it/s (1338 samples/s)\n", + "\n", + "================================================================================\n", + "🎉 ACTFuzzer completed in 15.6s\n", + " Iterations: 520\n", + " Counterexamples: 503\n", + " GlobalCov: 99.22% BestInputCov: 86.37%\n", + " Seeds explored: 526\n", + " Never-activated neurons: 61\n", + " Never-activated sample: model.fc[100], model.fc[111], model.fc[120], model.fc[124], model.fc[131], model.fc[132], model.fc[152], model.fc[194], model.fc[204], model.fc[223]\n", + "================================================================================\n", + "\n", + "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", + "\n", + "================================================================================\n", + "📊 Tracing Statistics\n", + "================================================================================\n", + "Level: 1\n", + "Traces captured: 520\n", + "Sample rate: 1/1\n", + "Output: ../act/pipeline/log/fuzzing_results/traces_3.json\n", + "================================================================================\n", + "\n" + ] + } + ], + "source": [ + "from act.front_end.spec_creator_base import LabeledInputTensor\n", + "from act.front_end.verifiable_model import InputLayer, VerifiableModel\n", + "from act.pipeline.fuzzing.actfuzzer import ACTFuzzer, FuzzingConfig\n", + "\n", + "\n", + "config_path = Path(ACT_ROOT) / \"act/pipeline/fuzzing/config.yaml\"\n", + "config = FuzzingConfig.from_yaml(\n", + " config_path=config_path,\n", + " max_iterations=500,\n", + " timeout_seconds=60.0,\n", + " report_interval=20,\n", + " verbose=1,\n", + " # Tracing: capture execution traces for analysis\n", + " trace_level=1,\n", + " trace_storage='json'\n", + ")\n", + "\n", + "fuzzers = []\n", + "reports = []\n", + "\n", + "for key, model in synthesis_models.items():\n", + " verif_model: VerifiableModel = model\n", + " \n", + " input_layer: InputLayer = verif_model.input_layer\n", + " labeled_input = input_layer.labeled_input\n", + "\n", + " images = labeled_input.tensor.to(\"cpu\")\n", + " labels = labeled_input.label.to(\"cpu\")\n", + " B = images.shape[0]\n", + "\n", + " initial_seeds = [\n", + " LabeledInputTensor(tensor=images[i:i+1], label=labels[i:i+1])\n", + " for i in range(B)\n", + " ]\n", + " \n", + " fuzzer = ACTFuzzer(\n", + " wrapped_model=model,\n", + " initial_seeds=initial_seeds,\n", + " config=config\n", + " )\n", + " \n", + " report = fuzzer.fuzz()\n", + "\n", + " fuzzers.append(fuzzer)\n", + " reports.append(report)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "act-slcbm", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 40ae267eb39935a1ea162ab2d7af6661a05a3edf Mon Sep 17 00:00:00 2001 From: Hanyuan Li Date: Thu, 4 Jun 2026 14:28:48 +1000 Subject: [PATCH 4/5] fix: same errors fix on VNNLib spec creator --- act/front_end/torchvision_loader/create_specs.py | 2 +- act/front_end/vnnlib_loader/create_specs.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/act/front_end/torchvision_loader/create_specs.py b/act/front_end/torchvision_loader/create_specs.py index 49f0e3037..eb60c8283 100644 --- a/act/front_end/torchvision_loader/create_specs.py +++ b/act/front_end/torchvision_loader/create_specs.py @@ -474,7 +474,7 @@ def _validate_and_filter_specs( if is_valid: valid_pairs.append((input_spec, output_spec)) else: - logger.error( + logger.debug( f"Spec pair validation failed: {input_spec.kind}, {output_spec.kind}, with errors:\n" f"{"\n".join(errors)}" ) diff --git a/act/front_end/vnnlib_loader/create_specs.py b/act/front_end/vnnlib_loader/create_specs.py index 21dae0eac..f1036ba05 100644 --- a/act/front_end/vnnlib_loader/create_specs.py +++ b/act/front_end/vnnlib_loader/create_specs.py @@ -281,7 +281,7 @@ def _validate_and_filter_specs( for input_spec, output_spec in spec_pairs: try: - is_valid = self.validate_spec_pair_with_model( + is_valid, errors = self.validate_spec_pair_with_model( input_spec, output_spec, pytorch_model, @@ -292,8 +292,8 @@ def _validate_and_filter_specs( valid_pairs.append((input_spec, output_spec)) else: logger.debug( - f"Spec pair validation failed: " - f"{input_spec.kind}, {output_spec.kind}" + f"Spec pair validation failed: {input_spec.kind}, {output_spec.kind}, with errors:\n" + f"{"\n".join(errors)}" ) except Exception as e: From a1098cc99bfe771c6f6d364d1b4e7f51c44b6773 Mon Sep 17 00:00:00 2001 From: Hanyuan Li Date: Thu, 4 Jun 2026 14:29:20 +1000 Subject: [PATCH 5/5] test: remove notebook --- ipynb/frontend_dtype.ipynb | 407 ------------------------------------- 1 file changed, 407 deletions(-) delete mode 100644 ipynb/frontend_dtype.ipynb diff --git a/ipynb/frontend_dtype.ipynb b/ipynb/frontend_dtype.ipynb deleted file mode 100644 index 2143abe32..000000000 --- a/ipynb/frontend_dtype.ipynb +++ /dev/null @@ -1,407 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "id": "b566cee2", - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "from pathlib import Path\n", - "import sys\n", - "\n", - "import torch\n", - "import torch.nn as nn\n", - "from torch.utils.data import DataLoader\n", - "from torchvision.datasets import CIFAR100\n", - "from torchvision.models import resnet18\n", - "from torchvision.transforms import v2\n", - "\n", - "act_root = os.path.dirname(os.path.dirname(os.path.abspath('__file__')))\n", - "sys.path.insert(0, act_root) if act_root not in sys.path else None\n", - "\n", - "ACT_ROOT = Path(act_root)\n", - "\n", - "BATCH_SIZE = 1\n", - "EPOCHS = 5\n", - "\n", - "MODEL_SAVE_PATH = ACT_ROOT / \"ipynb/resnet18_cifar100.pth\"\n", - "\n", - "# Train model on `float32` dataset\n", - "model = resnet18()\n", - "\n", - "train_dataset = CIFAR100(root=ACT_ROOT / \"data/torchvision/CIFAR100/raw\", transform=v2.Compose([v2.ToImage(), v2.ToDtype(torch.float32, scale=True)]))\n", - "train_dataloader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)\n", - "size = len(train_dataloader.dataset)\n", - "\n", - "loss_fn = nn.CrossEntropyLoss()\n", - "optimizer = torch.optim.SGD(model.parameters())\n", - "\n", - "if os.path.exists(MODEL_SAVE_PATH):\n", - " model.load_state_dict(torch.load(MODEL_SAVE_PATH, weights_only=True))\n", - "else:\n", - " model.train()\n", - "\n", - " for _ in range(EPOCHS):\n", - " for batch, (X, y) in enumerate(train_dataloader):\n", - " # Compute prediction and loss\n", - " pred = model(X)\n", - " loss = loss_fn(pred, y)\n", - "\n", - " # Backpropagation\n", - " loss.backward()\n", - " optimizer.step()\n", - " optimizer.zero_grad()\n", - "\n", - " if batch % 100 == 0:\n", - " loss, current = loss.item(), batch * BATCH_SIZE + len(X)\n", - " print(f\"loss: {loss:>7f} [{current:>5d}/{size:>5d}]\")\n", - "\n", - " torch.save(model.state_dict(), MODEL_SAVE_PATH)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "720b20d2", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[ACT] Auto-detecting project root: ..\n", - "[ACT] Gurobi license found: ../modules/gurobi/gurobi.lic\n", - "\n", - "🧬 Synthesizing models from 10 spec result(s)...\n", - "\n", - "🎉 Synthesis Complete:\n", - " Total specs: 240\n", - " Wrapped models: 4\n" - ] - } - ], - "source": [ - "from act.front_end.model_synthesis import synthesize_models_from_specs\n", - "from act.front_end.torchvision_loader.create_specs import TorchVisionSpecCreator\n", - "from act.front_end.vnnlib_loader.create_specs import VNNLibSpecCreator\n", - "\n", - "\n", - "USE_VNNLIB = False\n", - "\n", - "# Without specifying `dtype = float32`, both of these spec creators\n", - "# should fail - the default data type for frontends is `float64`\n", - "if USE_VNNLIB:\n", - " spec_creator = VNNLibSpecCreator()\n", - " specs = spec_creator.create_specs_for_data_model_pairs(\n", - " categories=[\"cifar100_2024\"],\n", - " max_instances=10\n", - " )\n", - "else:\n", - " spec_creator = TorchVisionSpecCreator()\n", - " specs = []\n", - "\n", - " for i in range(10):\n", - " spec = spec_creator._create_specs_for_single_instance(\n", - " data_source=\"CIFAR100\",\n", - " model_name=\"resnet18\",\n", - " pytorch_model=model,\n", - " dataloader=train_dataloader,\n", - " num_samples=1,\n", - " start_index=0,\n", - " validate_shapes=True\n", - " )\n", - " specs.append(spec)\n", - "\n", - "synthesis_models = synthesize_models_from_specs(specs)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "d208a03c", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[MutationEngine] Adaptive Scalar Perturbation Size:\n", - " - perturb_scale: 0.1 (fraction of range per perturbation)\n", - " - mean_range: 0.093250\n", - " - computed perturb_size: 0.009325\n", - " - steps_to_traverse: ~10.0 steps\n", - " - interpretation: Each mutation perturbation covers 10.0% of the range\n", - "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", - " Output: ../act/pipeline/log/fuzzing_results/traces_0.json\n", - "================================================================================\n", - "ACT: Abstract Constraint Transformer\n", - "Inference-based whitebox fuzzing for neural network verification\n", - "================================================================================\n", - "\n", - "🚀 Starting ACTFuzzer with 10 seeds\n", - " Device: cpu\n", - " Batch size: 80 (from model synthesis)\n", - " Max iterations: 500\n", - " Timeout: 60.0s\n", - "\n", - "📊 Iteration 80 | GlobalCov: 94.00% BestInputCov: 86.15% | Seeds: 90 | Violations: 74 (+74) | Speed: 30.0 it/s (2403 samples/s)\n", - "📊 Iteration 160 | GlobalCov: 94.67% BestInputCov: 86.24% | Seeds: 170 | Violations: 154 (+80) | Speed: 25.8 it/s (2064 samples/s)\n", - "📊 Iteration 240 | GlobalCov: 95.25% BestInputCov: 86.24% | Seeds: 250 | Violations: 234 (+80) | Speed: 24.9 it/s (1994 samples/s)\n", - "📊 Iteration 320 | GlobalCov: 95.31% BestInputCov: 86.24% | Seeds: 328 | Violations: 312 (+78) | Speed: 29.5 it/s (2360 samples/s)\n", - "📊 Iteration 400 | GlobalCov: 95.73% BestInputCov: 86.37% | Seeds: 408 | Violations: 392 (+80) | Speed: 25.9 it/s (2072 samples/s)\n", - "📊 Iteration 480 | GlobalCov: 95.80% BestInputCov: 86.37% | Seeds: 485 | Violations: 469 (+77) | Speed: 17.6 it/s (1410 samples/s)\n", - "📊 Iteration 560 | GlobalCov: 95.99% BestInputCov: 86.37% | Seeds: 565 | Violations: 549 (+80) | Speed: 13.5 it/s (1079 samples/s)\n", - "\n", - "================================================================================\n", - "🎉 ACTFuzzer completed in 41.5s\n", - " Iterations: 560\n", - " Counterexamples: 549\n", - " GlobalCov: 95.99% BestInputCov: 86.37%\n", - " Seeds explored: 565\n", - " Never-activated neurons: 312\n", - " Never-activated sample: model.fc[100], model.fc[106], model.fc[108], model.fc[120], model.fc[123], model.fc[126], model.fc[129], model.fc[130], model.fc[131], model.fc[132]\n", - "================================================================================\n", - "\n", - "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", - "\n", - "================================================================================\n", - "📊 Tracing Statistics\n", - "================================================================================\n", - "Level: 1\n", - "Traces captured: 560\n", - "Sample rate: 1/1\n", - "Output: ../act/pipeline/log/fuzzing_results/traces_0.json\n", - "================================================================================\n", - "\n", - "[MutationEngine] Adaptive Scalar Perturbation Size:\n", - " - perturb_scale: 0.1 (fraction of range per perturbation)\n", - " - mean_range: 0.093250\n", - " - computed perturb_size: 0.009325\n", - " - steps_to_traverse: ~10.0 steps\n", - " - interpretation: Each mutation perturbation covers 10.0% of the range\n", - "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", - " Output: ../act/pipeline/log/fuzzing_results/traces_1.json\n", - "================================================================================\n", - "ACT: Abstract Constraint Transformer\n", - "Inference-based whitebox fuzzing for neural network verification\n", - "================================================================================\n", - "\n", - "🚀 Starting ACTFuzzer with 10 seeds\n", - " Device: cpu\n", - " Batch size: 40 (from model synthesis)\n", - " Max iterations: 500\n", - " Timeout: 60.0s\n", - "\n", - "📊 Iteration 40 | GlobalCov: 95.77% BestInputCov: 86.28% | Seeds: 50 | Violations: 33 (+33) | Speed: 34.5 it/s (1381 samples/s)\n", - "📊 Iteration 80 | GlobalCov: 96.39% BestInputCov: 86.31% | Seeds: 90 | Violations: 73 (+40) | Speed: 28.6 it/s (1146 samples/s)\n", - "📊 Iteration 120 | GlobalCov: 96.98% BestInputCov: 86.43% | Seeds: 130 | Violations: 113 (+40) | Speed: 28.6 it/s (1145 samples/s)\n", - "📊 Iteration 160 | GlobalCov: 97.23% BestInputCov: 86.43% | Seeds: 170 | Violations: 153 (+40) | Speed: 27.5 it/s (1101 samples/s)\n", - "📊 Iteration 200 | GlobalCov: 97.44% BestInputCov: 86.43% | Seeds: 210 | Violations: 193 (+40) | Speed: 26.9 it/s (1075 samples/s)\n", - "📊 Iteration 240 | GlobalCov: 97.44% BestInputCov: 86.43% | Seeds: 250 | Violations: 233 (+40) | Speed: 29.5 it/s (1182 samples/s)\n", - "📊 Iteration 280 | GlobalCov: 97.60% BestInputCov: 86.43% | Seeds: 290 | Violations: 273 (+40) | Speed: 28.6 it/s (1145 samples/s)\n", - "📊 Iteration 320 | GlobalCov: 97.65% BestInputCov: 86.43% | Seeds: 330 | Violations: 313 (+40) | Speed: 30.1 it/s (1206 samples/s)\n", - "📊 Iteration 360 | GlobalCov: 97.83% BestInputCov: 86.43% | Seeds: 370 | Violations: 353 (+40) | Speed: 29.7 it/s (1190 samples/s)\n", - "📊 Iteration 400 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 410 | Violations: 393 (+40) | Speed: 30.6 it/s (1225 samples/s)\n", - "📊 Iteration 440 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 449 | Violations: 432 (+39) | Speed: 32.6 it/s (1303 samples/s)\n", - "📊 Iteration 480 | GlobalCov: 97.85% BestInputCov: 86.43% | Seeds: 489 | Violations: 472 (+40) | Speed: 34.1 it/s (1365 samples/s)\n", - "📊 Iteration 520 | GlobalCov: 97.87% BestInputCov: 86.43% | Seeds: 528 | Violations: 511 (+39) | Speed: 35.5 it/s (1418 samples/s)\n", - "\n", - "================================================================================\n", - "🎉 ACTFuzzer completed in 14.7s\n", - " Iterations: 520\n", - " Counterexamples: 511\n", - " GlobalCov: 97.87% BestInputCov: 86.43%\n", - " Seeds explored: 528\n", - " Never-activated neurons: 166\n", - " Never-activated sample: model.fc[100], model.fc[108], model.fc[125], model.fc[126], model.fc[130], model.fc[131], model.fc[132], model.fc[142], model.fc[143], model.fc[151]\n", - "================================================================================\n", - "\n", - "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", - "\n", - "================================================================================\n", - "📊 Tracing Statistics\n", - "================================================================================\n", - "Level: 1\n", - "Traces captured: 520\n", - "Sample rate: 1/1\n", - "Output: ../act/pipeline/log/fuzzing_results/traces_1.json\n", - "================================================================================\n", - "\n", - "[MutationEngine] Adaptive Scalar Perturbation Size:\n", - " - perturb_scale: 0.1 (fraction of range per perturbation)\n", - " - mean_range: 0.095000\n", - " - computed perturb_size: 0.009500\n", - " - steps_to_traverse: ~10.0 steps\n", - " - interpretation: Each mutation perturbation covers 10.0% of the range\n", - "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", - " Output: ../act/pipeline/log/fuzzing_results/traces_2.json\n", - "================================================================================\n", - "ACT: Abstract Constraint Transformer\n", - "Inference-based whitebox fuzzing for neural network verification\n", - "================================================================================\n", - "\n", - "🚀 Starting ACTFuzzer with 10 seeds\n", - " Device: cpu\n", - " Batch size: 80 (from model synthesis)\n", - " Max iterations: 500\n", - " Timeout: 60.0s\n", - "\n", - "📊 Iteration 80 | GlobalCov: 98.68% BestInputCov: 86.38% | Seeds: 90 | Violations: 79 (+79) | Speed: 36.5 it/s (2919 samples/s)\n", - "📊 Iteration 160 | GlobalCov: 98.81% BestInputCov: 86.38% | Seeds: 170 | Violations: 159 (+80) | Speed: 48.2 it/s (3853 samples/s)\n", - "📊 Iteration 240 | GlobalCov: 98.93% BestInputCov: 86.38% | Seeds: 250 | Violations: 239 (+80) | Speed: 37.7 it/s (3018 samples/s)\n", - "📊 Iteration 320 | GlobalCov: 98.96% BestInputCov: 86.38% | Seeds: 330 | Violations: 319 (+80) | Speed: 38.9 it/s (3116 samples/s)\n", - "📊 Iteration 400 | GlobalCov: 98.96% BestInputCov: 86.38% | Seeds: 410 | Violations: 399 (+80) | Speed: 42.6 it/s (3412 samples/s)\n", - "📊 Iteration 480 | GlobalCov: 98.97% BestInputCov: 86.40% | Seeds: 490 | Violations: 479 (+80) | Speed: 41.2 it/s (3300 samples/s)\n", - "📊 Iteration 560 | GlobalCov: 99.19% BestInputCov: 86.40% | Seeds: 570 | Violations: 559 (+80) | Speed: 36.5 it/s (2921 samples/s)\n", - "\n", - "================================================================================\n", - "🎉 ACTFuzzer completed in 15.3s\n", - " Iterations: 560\n", - " Counterexamples: 559\n", - " GlobalCov: 99.19% BestInputCov: 86.40%\n", - " Seeds explored: 570\n", - " Never-activated neurons: 63\n", - " Never-activated sample: model.fc[100], model.fc[120], model.fc[131], model.fc[152], model.fc[194], model.fc[204], model.fc[232], model.fc[242], model.fc[244], model.fc[258]\n", - "================================================================================\n", - "\n", - "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", - "\n", - "================================================================================\n", - "📊 Tracing Statistics\n", - "================================================================================\n", - "Level: 1\n", - "Traces captured: 560\n", - "Sample rate: 1/1\n", - "Output: ../act/pipeline/log/fuzzing_results/traces_2.json\n", - "================================================================================\n", - "\n", - "[MutationEngine] Adaptive Scalar Perturbation Size:\n", - " - perturb_scale: 0.1 (fraction of range per perturbation)\n", - " - mean_range: 0.095000\n", - " - computed perturb_size: 0.009500\n", - " - steps_to_traverse: ~10.0 steps\n", - " - interpretation: Each mutation perturbation covers 10.0% of the range\n", - "📊 Tracing enabled: Level 1, sampling every 1 iteration(s)\n", - " Output: ../act/pipeline/log/fuzzing_results/traces_3.json\n", - "================================================================================\n", - "ACT: Abstract Constraint Transformer\n", - "Inference-based whitebox fuzzing for neural network verification\n", - "================================================================================\n", - "\n", - "🚀 Starting ACTFuzzer with 10 seeds\n", - " Device: cpu\n", - " Batch size: 40 (from model synthesis)\n", - " Max iterations: 500\n", - " Timeout: 60.0s\n", - "\n", - "📊 Iteration 40 | GlobalCov: 98.42% BestInputCov: 86.36% | Seeds: 50 | Violations: 30 (+30) | Speed: 21.3 it/s (853 samples/s)\n", - "📊 Iteration 80 | GlobalCov: 98.51% BestInputCov: 86.37% | Seeds: 88 | Violations: 66 (+36) | Speed: 29.2 it/s (1166 samples/s)\n", - "📊 Iteration 120 | GlobalCov: 98.83% BestInputCov: 86.37% | Seeds: 128 | Violations: 106 (+40) | Speed: 27.1 it/s (1085 samples/s)\n", - "📊 Iteration 160 | GlobalCov: 98.84% BestInputCov: 86.37% | Seeds: 168 | Violations: 146 (+40) | Speed: 30.7 it/s (1228 samples/s)\n", - "📊 Iteration 200 | GlobalCov: 98.84% BestInputCov: 86.37% | Seeds: 208 | Violations: 186 (+40) | Speed: 34.3 it/s (1371 samples/s)\n", - "📊 Iteration 240 | GlobalCov: 98.86% BestInputCov: 86.37% | Seeds: 248 | Violations: 226 (+40) | Speed: 37.6 it/s (1504 samples/s)\n", - "📊 Iteration 280 | GlobalCov: 99.08% BestInputCov: 86.37% | Seeds: 288 | Violations: 266 (+40) | Speed: 36.0 it/s (1442 samples/s)\n", - "📊 Iteration 320 | GlobalCov: 99.08% BestInputCov: 86.37% | Seeds: 328 | Violations: 306 (+40) | Speed: 31.1 it/s (1245 samples/s)\n", - "📊 Iteration 360 | GlobalCov: 99.11% BestInputCov: 86.37% | Seeds: 368 | Violations: 346 (+40) | Speed: 30.7 it/s (1227 samples/s)\n", - "📊 Iteration 400 | GlobalCov: 99.18% BestInputCov: 86.37% | Seeds: 408 | Violations: 386 (+40) | Speed: 30.7 it/s (1227 samples/s)\n", - "📊 Iteration 440 | GlobalCov: 99.19% BestInputCov: 86.37% | Seeds: 448 | Violations: 425 (+39) | Speed: 32.4 it/s (1295 samples/s)\n", - "📊 Iteration 480 | GlobalCov: 99.19% BestInputCov: 86.37% | Seeds: 486 | Violations: 463 (+38) | Speed: 33.8 it/s (1353 samples/s)\n", - "📊 Iteration 520 | GlobalCov: 99.22% BestInputCov: 86.37% | Seeds: 526 | Violations: 503 (+40) | Speed: 33.4 it/s (1338 samples/s)\n", - "\n", - "================================================================================\n", - "🎉 ACTFuzzer completed in 15.6s\n", - " Iterations: 520\n", - " Counterexamples: 503\n", - " GlobalCov: 99.22% BestInputCov: 86.37%\n", - " Seeds explored: 526\n", - " Never-activated neurons: 61\n", - " Never-activated sample: model.fc[100], model.fc[111], model.fc[120], model.fc[124], model.fc[131], model.fc[132], model.fc[152], model.fc[194], model.fc[204], model.fc[223]\n", - "================================================================================\n", - "\n", - "✅ Report saved to ../act/pipeline/log/fuzzing_results\n", - "\n", - "================================================================================\n", - "📊 Tracing Statistics\n", - "================================================================================\n", - "Level: 1\n", - "Traces captured: 520\n", - "Sample rate: 1/1\n", - "Output: ../act/pipeline/log/fuzzing_results/traces_3.json\n", - "================================================================================\n", - "\n" - ] - } - ], - "source": [ - "from act.front_end.spec_creator_base import LabeledInputTensor\n", - "from act.front_end.verifiable_model import InputLayer, VerifiableModel\n", - "from act.pipeline.fuzzing.actfuzzer import ACTFuzzer, FuzzingConfig\n", - "\n", - "\n", - "config_path = Path(ACT_ROOT) / \"act/pipeline/fuzzing/config.yaml\"\n", - "config = FuzzingConfig.from_yaml(\n", - " config_path=config_path,\n", - " max_iterations=500,\n", - " timeout_seconds=60.0,\n", - " report_interval=20,\n", - " verbose=1,\n", - " # Tracing: capture execution traces for analysis\n", - " trace_level=1,\n", - " trace_storage='json'\n", - ")\n", - "\n", - "fuzzers = []\n", - "reports = []\n", - "\n", - "for key, model in synthesis_models.items():\n", - " verif_model: VerifiableModel = model\n", - " \n", - " input_layer: InputLayer = verif_model.input_layer\n", - " labeled_input = input_layer.labeled_input\n", - "\n", - " images = labeled_input.tensor.to(\"cpu\")\n", - " labels = labeled_input.label.to(\"cpu\")\n", - " B = images.shape[0]\n", - "\n", - " initial_seeds = [\n", - " LabeledInputTensor(tensor=images[i:i+1], label=labels[i:i+1])\n", - " for i in range(B)\n", - " ]\n", - " \n", - " fuzzer = ACTFuzzer(\n", - " wrapped_model=model,\n", - " initial_seeds=initial_seeds,\n", - " config=config\n", - " )\n", - " \n", - " report = fuzzer.fuzz()\n", - "\n", - " fuzzers.append(fuzzer)\n", - " reports.append(report)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "act-slcbm", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.11" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -}