# Creating Custom PropertyGenerator Implementations for VERONA: A Complete Guide

> Learn to create custom PropertyGenerator implementations for VERONA. Subclass the base class, implement generate, and inject your custom code for advanced verification.

- Repository: [ADA research/verona](https://github.com/ada-research/verona)
- Tags: how-to-guide
- Published: 2026-02-23

---

**To create a custom PropertyGenerator for VERONA, subclass the abstract `PropertyGenerator` base class defined in [`ada_verona/verification_module/property_generator/property_generator.py`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/ada_verona/verification_module/verification_module.py) |

The **dependency injection** pattern is implemented through [`ada_verona/database/verification_context.py`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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`:

```python
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:

```python
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:

```python
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:

```python

# 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:

```python

# 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:

```python

# 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`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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`](https://github.com/ada-research/verona/blob/main/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.