| name | invariant-inference |
| description | Automatically infer loop invariants for code verification and correctness proofs. Use when analyzing loops to identify properties that hold throughout execution, generating assertions for verification, proving loop correctness, or documenting loop behavior. Supports Python, Java, C/C++, and language-agnostic analysis. Generates invariants as code assertions (assert statements). Triggers when users ask to infer invariants, find loop properties, generate loop assertions, prove loop correctness, or verify loop behavior. |
Invariant Inference
Overview
Analyze loops and automatically infer invariants—properties that remain true throughout loop execution. Generate these as code assertions for verification and correctness proofs.
Workflow
1. Identify the Loop
First, locate and understand the loop to analyze:
Loop types to recognize:
for loops with index variables
while loops with conditions
do-while loops
- Iterator-based loops
- Recursive functions (treated as implicit loops)
Extract key information:
- Loop variable(s) and their initial values
- Loop condition (when it terminates)
- Loop body (what happens each iteration)
- Variables modified in the loop
- Variables read but not modified
2. Analyze Loop Structure
Understand what the loop does:
Categorize the loop:
- Accumulation: Building up a sum, product, or collection
- Search: Looking for an element or condition
- Transformation: Modifying elements in a data structure
- Generation: Creating new data based on input
- Traversal: Visiting all elements
- Sorting/Partitioning: Rearranging elements
Identify patterns:
- Array/list iteration with bounds
- Counter increments/decrements
- Pointer advancement
- Collection building
- Flag-based early termination
3. Infer Invariant Categories
Generate invariants for each applicable category. See invariant-patterns.md for comprehensive patterns.
Bounds Invariants
Properties about variable ranges:
assert 0 <= i < n
assert 0 <= i <= len(arr)
while left < right:
assert 0 <= left <= right < len(arr)
Relationship Invariants
Properties relating variables:
Sum/Accumulation:
total = 0
for i in range(len(arr)):
assert total == sum(arr[0:i])
total += arr[i]
assert total == sum(arr)
Max/Min:
max_val = arr[0]
for i in range(1, len(arr)):
assert max_val == max(arr[0:i])
if arr[i] > max_val:
max_val = arr[i]
Product:
product = 1
for i in range(len(arr)):
assert product == arr[0] * arr[1] * ... * arr[i-1]
product *= arr[i]
Progress Invariants
Properties showing termination:
while n > 0:
assert n > 0
n -= 1
assert n >= 0
i = 0
while i < n:
assert i < n
i += 1
assert i <= n
Data Structure Invariants
Properties about structure integrity:
Sorted sublists:
for i in range(1, len(arr)):
assert is_sorted(arr[0:i])
Partition property:
while left < right:
assert all(arr[j] <= pivot for j in range(0, left))
assert all(arr[j] >= pivot for j in range(right, len(arr)))
Size invariants:
result = []
for i in range(len(items)):
assert len(result) == i
if condition(items[i]):
result.append(items[i])
4. Generate Assertions
Convert inferred invariants into code assertions:
Python Format
def find_maximum(arr):
"""Find maximum element in array."""
assert len(arr) > 0, "Array must not be empty"
max_val = arr[0]
for i in range(1, len(arr)):
assert 0 < i < len(arr), "Index in valid range"
assert max_val == max(arr[0:i]), "max_val is maximum so far"
assert max_val in arr[0:i], "max_val is from processed elements"
if arr[i] > max_val:
max_val = arr[i]
assert max_val == max(arr), "max_val is maximum of entire array"
return max_val
Java Format
public int findMaximum(int[] arr) {
assert arr.length > 0 : "Array must not be empty";
int maxVal = arr[0];
for (int i = 1; i < arr.length; i++) {
assert i > 0 && i < arr.length : "Index in valid range";
assert maxVal == max(arr, 0, i) : "maxVal is maximum so far";
if (arr[i] > maxVal) {
maxVal = arr[i];
}
}
assert maxVal == max(arr, 0, arr.length) : "maxVal is maximum";
return maxVal;
}
C/C++ Format
int find_maximum(int arr[], int n) {
assert(n > 0);
int max_val = arr[0];
for (int i = 1; i < n; i++) {
assert(i >= 1 && i < n);
assert(max_val >= arr[0]);
if (arr[i] > max_val) {
max_val = arr[i];
}
}
return max_val;
}
5. Verify Invariants
Check that inferred invariants are correct:
Initialization
Invariant must be true before the loop starts:
Maintenance
Invariant remains true after each iteration:
Termination
Invariant + termination condition proves post-condition:
6. Handle Complex Cases
Nested Loops
Generate invariants for each nesting level:
total = 0
for i in range(rows):
assert total == sum(matrix[0:i, :])
for j in range(cols):
assert total == sum(matrix[0:i, :]) + sum(matrix[i, 0:j])
total += matrix[i][j]
Multiple Variables
Track relationships between variables:
left, right = 0, len(arr) - 1
while left < right:
assert 0 <= left <= right < len(arr)
assert left + right == len(arr) - 1
left += 1
right -= 1
Early Exit
Handle break/return statements:
found = False
for i in range(len(arr)):
assert not found or arr[i-1] == target
if arr[i] == target:
found = True
break
assert not found or target in arr[0:i+1]
Loop with Side Effects
Track effects on external state:
for i in range(len(arr)):
assert len(arr) remains constant
assert arr[0:i] are processed correctly
arr[i] = transform(arr[i])
Example Workflows
Example 1: Simple Accumulation
User request:
"Infer invariants for this sum loop"
def array_sum(arr):
total = 0
for i in range(len(arr)):
total += arr[i]
return total
Response:
def array_sum(arr):
assert len(arr) >= 0
total = 0
for i in range(len(arr)):
assert 0 <= i < len(arr), "Index in valid range"
assert total == sum(arr[0:i]), "total is sum of elements so far"
total += arr[i]
assert total == sum(arr), "total is sum of entire array"
return total
Explanation:
- Bounds:
0 <= i < len(arr)
- Relationship:
total == sum(arr[0:i])
- Progress:
i increases each iteration
- Post-condition follows from invariant + termination
Example 2: Search Loop
User request:
"What are the invariants for binary search?"
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
Response:
def binary_search(arr, target):
assert is_sorted(arr), "Array must be sorted"
left, right = 0, len(arr) - 1
while left <= right:
assert 0 <= left <= len(arr), "left in valid range"
assert -1 <= right < len(arr), "right in valid range"
assert left <= right + 1, "Valid search range"
assert (target not in arr or
any(arr[i] == target for i in range(left, right + 1))), \
"Target in current range if it exists"
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
assert arr[mid] < target, "All elements left of mid < target"
left = mid + 1
else:
assert arr[mid] > target, "All elements right of mid > target"
right = mid - 1
assert left > right, "Search range exhausted"
target arr,
-
Example 3: Insertion Sort
User request:
"Generate invariants for insertion sort"
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
Response:
def insertion_sort(arr):
for i in range(1, len(arr)):
assert is_sorted(arr[0:i]), "arr[0:i] is sorted"
assert set(arr) == set(original_arr), "Elements unchanged (only reordered)"
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
assert -1 <= j < i, "j in valid range"
assert arr[j] > key, "Current element larger than key"
assert is_sorted(arr[0:j]), "Left part still sorted"
assert arr[j+2:i+1] are shifted right and sorted
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
assert is_sorted(arr[0:i+1]), "arr[0:i+1] is now sorted"
assert is_sorted(arr), "Entire array is sorted"
Tips for Effective Invariant Inference
Start with obvious properties:
- Variable bounds (0 <= i < n)
- Loop counter relationships
- Data structure sizes
Look for accumulation patterns:
- Sums, products, counts
- Max/min tracking
- Collection building
Identify preservation properties:
- What stays constant? (array length, set of elements)
- What grows/shrinks monotonically?
- What relationships are maintained?
Think about the loop's purpose:
- Why is this loop here?
- What should be true when it finishes?
- What must be true for each iteration to work?
Verify your invariants:
- Check initialization (true before loop)
- Check maintenance (preserved by loop body)
- Check that invariant + termination ⟹ post-condition
Be specific:
- Weak:
i >= 0
- Better:
0 <= i < len(arr)
- Best:
0 <= i < len(arr) and sum_val == sum(arr[0:i])
Use helper predicates for clarity:
def is_sorted(arr):
return all(arr[i] <= arr[i+1] for i in range(len(arr)-1))
def is_partition(arr, pivot, left, right):
return (all(arr[i] <= pivot for i in range(left)) and
all(arr[i] >= pivot for i in range(right, len(arr))))
Reference
For comprehensive invariant patterns across different loop types and languages, see invariant-patterns.md.