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 PropertyGenerator from ada_verona/verification_module/property_generator/property_generator.py to define custom verification properties.
  • Implement generate(self, dataset_item) to return List[VnnLibProperty] using base class helpers like _write_vnnlib() and _assemble_vnnlib().
  • Inject via VerificationContext by assigning your generator instance to the property_generator attribute before calling run_verification().
  • Leverage existing implementations such as One2OnePropertyGenerator and One2AnyPropertyGenerator as 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →