Creating Custom PropertyGenerator Implementations for VERONA: A Complete Guide
To create a custom PropertyGenerator for VERONA, subclass the abstract PropertyGenerator base class defined in ada_verona/verification_module/property_generator/property_generator.py, implement the generate(self, dataset_item) method to return List[VnnLibProperty], and inject your implementation into the VerificationContext before running verification.
VERONA is an open-source neural network verification framework developed by ADA Research that modularizes the verification pipeline through pluggable property generators. Creating custom PropertyGenerator implementations allows you to define domain-specific verification properties beyond the standard one-to-one or one-to-any constraints, enabling specialized safety checks and performance optimizations while maintaining compatibility with VNN-LIB solvers.
Understanding the PropertyGenerator Architecture
The verification module in VERONA relies on a clean abstraction where property generators convert concrete verification requests into VNN-LIB format files that solvers can ingest. The architecture decouples property definition from the verification orchestration, allowing you to swap implementations via configuration.
Core Components
| Component | Role | Source Location |
|---|---|---|
PropertyGenerator |
Abstract base class defining the generator API, handling configuration, preprocessing via _apply_preprocessing(), and VNN-LIB file creation |
ada_verona/verification_module/property_generator/property_generator.py |
One2OnePropertyGenerator |
Default implementation creating one property per input-output pair | ada_verona/verification_module/property_generator/one2one_property_generator.py |
One2AnyPropertyGenerator |
Creates properties aggregating multiple output constraints per input region | ada_verona/verification_module/property_generator/one2any_property_generator.py |
VerificationModule |
Orchestrates the flow by receiving a VerificationContext and delegating property creation to the active generator |
ada_verona/verification_module/verification_module.py |
The dependency injection pattern is implemented through ada_verona/database/verification_context.py, which holds a reference to the active PropertyGenerator. This design allows custom generators to plug into the pipeline without modifying the core verification logic in verification_module.py.
Implementing a Custom PropertyGenerator
Creating a custom generator requires subclassing the base class and implementing a single abstract method. The base class provides helper utilities for file handling and preprocessing to ensure consistency with VERONA's conventions.
Step 1: Subclass the Base Generator
Create a new Python file and inherit from PropertyGenerator:
from ada_verona.verification_module.property_generator.property_generator import PropertyGenerator
from ada_verona.database.vnnlib_property import VnnLibProperty
class MyCustomGenerator(PropertyGenerator):
def __init__(self, output_dir: str, margin: float = 0.1, **kwargs):
super().__init__(output_dir=output_dir, **kwargs)
self.margin = margin
def generate(self, dataset_item) -> list[VnnLibProperty]:
# Implementation details in Step 2
pass
Call super().__init__(**kwargs) to ensure the base class stores common settings like output directories and naming schemes.
Step 2: Implement the generate Method
The abstract generate(self, dataset_item) method must return a List[VnnLibProperty]. Use the base class helpers _get_input_region(), _assemble_vnnlib(), and _write_vnnlib() to construct valid VNN-LIB files:
def generate(self, dataset_item) -> list[VnnLibProperty]:
# Retrieve input region (e.g., image + perturbation radius)
input_region = self._get_input_region(dataset_item)
# Define custom constraint: logits[0] - logits[1] >= margin
constraint = f"(>= (+ (read 0) (- (read 1))) {self.margin})"
# Assemble VNN-LIB representation
vnn = self._assemble_vnnlib(input_region, [constraint])
# Write file and return wrapper object
file_path = self._write_vnnlib(vnn, dataset_item.id)
return [VnnLibProperty(file_path=file_path, dataset_id=dataset_item.id)]
Step 3: Configure Additional Parameters
Extend the __init__ signature to accept domain-specific configuration. Store these as instance attributes for use in generate(). The base class handles standard parameters like output_dir through kwargs.
Step 4: Register with VerificationContext
Inject your generator into the verification pipeline by assigning it to a VerificationContext instance:
from ada_verona.database.verification_context import VerificationContext
from ada_verona.verification_module.property_generator.my_custom_generator import MyCustomGenerator
context = VerificationContext(
dataset=dataset,
model_path="models/mnist-net.onnx",
property_generator=MyCustomGenerator(output_dir="props/custom", margin=0.2),
)
When you call context.run_verification(), the VerificationModule delegates property creation to your implementation.
Practical Code Examples
Example: Custom Margin Constraint Generator
This complete implementation adds a custom linear constraint on the logits:
# ada_verona/verification_module/property_generator/my_custom_generator.py
from pathlib import Path
from ada_verona.verification_module.property_generator.property_generator import PropertyGenerator
from ada_verona.database.vnnlib_property import VnnLibProperty
class MyCustomGenerator(PropertyGenerator):
"""
Creates a single VNN-LIB property per dataset item with a custom
linear constraint on the logits.
"""
def __init__(self, output_dir: str, margin: float = 0.1, **kwargs):
super().__init__(output_dir=output_dir, **kwargs)
self.margin = margin
def generate(self, dataset_item) -> list[VnnLibProperty]:
# 1️⃣ Retrieve the input region
input_region = self._get_input_region(dataset_item)
# 2️⃣ Build custom constraint: logits[0] - logits[1] >= margin
constraint = f"(>= (+ (read 0) (- (read 1))) {self.margin})"
# 3️⃣ Assemble the full VNN-LIB representation
vnn = self._assemble_vnnlib(input_region, [constraint])
# 4️⃣ Write the file and return the wrapper object
file_path = self._write_vnnlib(vnn, dataset_item.id)
return [VnnLibProperty(file_path=file_path, dataset_id=dataset_item.id)]
Example: Integration with VerificationContext
This snippet demonstrates plugging the custom generator into a full verification run:
# my_experiment.py
from ada_verona.database.experiment_repository import ExperimentRepository
from ada_verona.database.verification_context import VerificationContext
from ada_verona.verification_module.property_generator.my_custom_generator import MyCustomGenerator
# Load dataset via repository
repo = ExperimentRepository(root_dir="experiments")
dataset = repo.load_dataset("mnist_test")
# Build context with custom generator
context = VerificationContext(
dataset=dataset,
model_path="models/mnist-net.onnx",
property_generator=MyCustomGenerator(output_dir="props/custom", margin=0.2),
)
# Run VERONA - verification module uses your generator
context.run_verification()
Example: Accessing Generated Properties
After verification, inspect the produced VNN-LIB files through the generator instance:
# Access generated properties for debugging
for prop in context.property_generator.generated_properties:
print(f"Property {prop.dataset_id} → {prop.file_path}")
Why Extend VERONA with Custom Generators?
Custom PropertyGenerator implementations solve specific verification challenges that standard generators cannot address:
- Domain-specific constraints – Implement safety envelopes spanning multiple output neurons or time-step constraints for recurrent networks that go beyond simple classification robustness.
- Performance optimizations – Batch-create VNN-LIB files to reduce I/O overhead, or pre-compute auxiliary data structures required by specialized solvers before property generation.
- Alternative property formats – Generate additional metadata annotations for post-processing while preserving the canonical VNN-LIB format that downstream solvers expect.
Summary
- Subclass
PropertyGeneratorfromada_verona/verification_module/property_generator/property_generator.pyto define custom verification properties. - Implement
generate(self, dataset_item)to returnList[VnnLibProperty]using base class helpers like_write_vnnlib()and_assemble_vnnlib(). - Inject via
VerificationContextby assigning your generator instance to theproperty_generatorattribute before callingrun_verification(). - Leverage existing implementations such as
One2OnePropertyGeneratorandOne2AnyPropertyGeneratoras reference patterns for common use cases. - Maintain compatibility with the VNN-LIB standard while adding domain-specific constraints or performance optimizations.
Frequently Asked Questions
What is the PropertyGenerator in VERONA?
The PropertyGenerator is an abstract base class in the VERONA verification framework responsible for converting verification requests into VNN-LIB format property files. It lives in ada_verona/verification_module/property_generator/property_generator.py and defines the interface between dataset items and solver-compatible property specifications.
How do I register a custom PropertyGenerator with the verification pipeline?
Register your implementation by instantiating it and passing it to the VerificationContext constructor or assigning it to the property_generator attribute. The VerificationModule in ada_verona/verification_module/verification_module.py retrieves the generator from this context during run_verification() and calls its generate() method for each dataset item.
Can I customize the VNN-LIB output format in my generator?
Yes, while you must produce valid VNN-LIB content for solver compatibility, you can customize the constraint generation logic, add metadata annotations, or modify file naming schemes. Use the _assemble_vnnlib() helper for standard formatting, or override file writing if you need specialized output formats alongside the standard VNN-LIB files.
What is the difference between One2OnePropertyGenerator and One2AnyPropertyGenerator?
One2OnePropertyGenerator creates a single property file for each input-output pair, mapping one input region to one specific output constraint. One2AnyPropertyGenerator aggregates multiple output constraints into a single property per input region, verifying that any of several output conditions holds for the given input. Choose the base class that matches your verification semantics when extending functionality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →