Property Testing Tutorial¶
This tutorial will teach you how to use Matrix Toolkit’s property testing capabilities to verify mathematical properties of matrices.
Introduction¶
Matrix properties are mathematical characteristics that describe the structure and behavior of matrices. Matrix Toolkit provides automated tools to:
Check if a matrix has specific properties
Verify that generated matrices satisfy their expected properties
Search for matrices based on properties
Run comprehensive test suites
Why Property Testing?¶
Property testing is crucial for:
Numerical Algorithm Development: Ensure test matrices have required properties
Reproducibility: Verify matrix generators produce correct outputs
Debugging: Identify when computations violate expected properties
Algorithm Selection: Choose appropriate algorithms based on matrix properties
Tutorial Setup¶
import numpy as np
from matrix_toolkit.anymatrix import AnyMatrix, MatrixProperties
# Initialize
am = AnyMatrix()
Part 1: Basic Property Checking¶
Step 1: Check Single Property¶
# Generate a symmetric matrix
A = am.generate('core/beta', 10)
# Check if symmetric
is_symmetric = MatrixProperties.is_symmetric(A)
print(f"Is symmetric: {is_symmetric}")
# Check if positive definite
is_pd = MatrixProperties.is_positive_definite(A)
print(f"Is positive definite: {is_pd}")
Expected Output:
Is symmetric: True
Is positive definite: True
Step 2: Understanding Tolerance¶
Property checks use floating-point comparisons with tolerance:
# Create a nearly symmetric matrix
A_exact = np.array([[1, 2], [2, 3]])
A_noisy = A_exact + np.random.randn(2, 2) * 1e-12
# Default tolerance (1e-10)
print(f"Symmetric (default): {MatrixProperties.is_symmetric(A_noisy)}")
# Stricter tolerance
print(f"Symmetric (strict): {MatrixProperties.is_symmetric(A_noisy, tol=1e-14)}")
# Looser tolerance
print(f"Symmetric (loose): {MatrixProperties.is_symmetric(A_noisy, tol=1e-10)}")
Key Takeaway: Adjust tolerance based on your numerical precision requirements.
Part 2: Automated Property Verification¶
Step 3: Verify Expected Properties¶
# Generate a matrix
matrix_id = 'core/fourier'
F = am.generate(matrix_id, 8)
# Get expected properties
expected_props = am.properties(matrix_id)
print(f"Expected properties: {expected_props}")
# Verify all properties
results = MatrixProperties.check_properties(F, expected_props)
print("\nVerification Results:")
for prop, passed in results.items():
status = "✓ PASS" if passed else "✗ FAIL"
print(f" {prop:20s}: {status}")
Expected Output:
Expected properties: ['unitary', 'symmetric']
Verification Results:
unitary : ✓ PASS
symmetric : ✓ PASS
Step 4: Handling Failed Checks¶
def verify_and_report(matrix_id, size):
"""Generate matrix and verify properties with detailed reporting"""
# Generate
matrix = am.generate(matrix_id, size)
# Get expected properties
expected = am.properties(matrix_id)
# Verify
results = MatrixProperties.check_properties(matrix, expected, tol=1e-10)
# Report
print(f"\nMatrix: {matrix_id} (size={size})")
print("-" * 50)
all_passed = True
for prop, passed in results.items():
if passed:
print(f" ✓ {prop}")
else:
print(f" ✗ {prop} FAILED")
all_passed = False
if all_passed:
print("\n✓ All properties verified successfully!")
else:
print("\n✗ Some properties failed verification!")
print(" This may indicate:")
print(" - Numerical precision issues")
print(" - Implementation bugs")
print(" - Incorrect property tags")
return all_passed
# Test multiple matrices
test_cases = [
('core/beta', 10),
('matlab/hilbert', 5),
('core/fourier', 16)
]
for matrix_id, size in test_cases:
verify_and_report(matrix_id, size)
Part 3: Comprehensive Testing¶
Step 5: Test Across Sizes¶
def test_across_sizes(matrix_id, sizes):
"""Test matrix properties across different sizes"""
expected_props = am.properties(matrix_id)
results = {}
print(f"\nTesting {matrix_id} across sizes: {sizes}")
print("=" * 60)
for size in sizes:
try:
# Generate
matrix = am.generate(matrix_id, size)
# Verify
prop_results = MatrixProperties.check_properties(
matrix, expected_props
)
# Store
all_passed = all(prop_results.values())
results[size] = {
'success': True,
'all_passed': all_passed,
'details': prop_results
}
# Report
status = "✓" if all_passed else "✗"
print(f" Size {size:4d}: {status}")
except Exception as e:
results[size] = {
'success': False,
'error': str(e)
}
print(f" Size {size:4d}: ERROR - {e}")
return results
# Run tests
results = test_across_sizes('core/beta', [10, 20, 50, 100, 200])
Step 6: Batch Testing¶
def batch_test_matrices(matrix_ids, size=10):
"""Test multiple matrices at once"""
print(f"\nBatch Testing {len(matrix_ids)} matrices (size={size})")
print("=" * 70)
summary = {
'passed': [],
'failed': [],
'errors': []
}
for matrix_id in matrix_ids:
try:
# Generate and verify
matrix = am.generate(matrix_id, size)
expected = am.properties(matrix_id)
results = MatrixProperties.check_properties(matrix, expected)
# Categorize
if all(results.values()):
summary['passed'].append(matrix_id)
print(f" ✓ {matrix_id:30s} PASS")
else:
summary['failed'].append(matrix_id)
failed_props = [p for p, v in results.items() if not v]
print(f" ✗ {matrix_id:30s} FAIL: {failed_props}")
except Exception as e:
summary['errors'].append((matrix_id, str(e)))
print(f" ! {matrix_id:30s} ERROR: {e}")
# Summary
print("\n" + "=" * 70)
print(f"Summary:")
print(f" Passed: {len(summary['passed'])}")
print(f" Failed: {len(summary['failed'])}")
print(f" Errors: {len(summary['errors'])}")
return summary
# Test all core matrices
core_matrices = am.list('core')
summary = batch_test_matrices(core_matrices[:20], size=10)
Part 4: Custom Property Tests¶
Step 7: Define Custom Properties¶
def is_diagonally_dominant(matrix, tol=1e-10):
"""Check if matrix is diagonally dominant"""
n = matrix.shape[0]
for i in range(n):
row_sum = np.sum(np.abs(matrix[i, :])) - np.abs(matrix[i, i])
if np.abs(matrix[i, i]) < row_sum - tol:
return False
return True
def is_m_matrix(matrix, tol=1e-10):
"""Check if matrix is an M-matrix"""
# Non-positive off-diagonals
n = matrix.shape[0]
for i in range(n):
for j in range(n):
if i != j and matrix[i, j] > tol:
return False
# Positive eigenvalues
eigenvalues = np.linalg.eigvals(matrix)
if not np.all(eigenvalues.real > -tol):
return False
return True
# Test custom properties
A = am.generate('regtools/deriv2', 20).toarray()
print(f"Diagonally dominant: {is_diagonally_dominant(A)}")
print(f"M-matrix: {is_m_matrix(-A)}") # Note: deriv2 gives -M-matrix
Step 8: Create Property Test Suite¶
class PropertyTestSuite:
"""Custom test suite for matrix properties"""
def __init__(self):
self.tests = {}
self.results = {}
def add_test(self, name, checker_func, description=""):
"""Add a property test"""
self.tests[name] = {
'checker': checker_func,
'description': description
}
def run_tests(self, matrix, verbose=True):
"""Run all tests on a matrix"""
if verbose:
print(f"\nRunning {len(self.tests)} property tests")
print("=" * 60)
results = {}
for name, test in self.tests.items():
try:
passed = test['checker'](matrix)
results[name] = passed
if verbose:
status = "✓" if passed else "✗"
print(f" {status} {name:30s} : {test['description']}")
except Exception as e:
results[name] = None
if verbose:
print(f" ! {name:30s} : ERROR - {e}")
self.results = results
return results
def summary(self):
"""Print summary of test results"""
if not self.results:
print("No tests have been run yet")
return
passed = sum(1 for v in self.results.values() if v is True)
failed = sum(1 for v in self.results.values() if v is False)
errors = sum(1 for v in self.results.values() if v is None)
print(f"\nTest Summary:")
print(f" Total: {len(self.results)}")
print(f" Passed: {passed}")
print(f" Failed: {failed}")
print(f" Errors: {errors}")
# Create suite
suite = PropertyTestSuite()
# Add standard tests
suite.add_test('symmetric', MatrixProperties.is_symmetric,
'A = A^T')
suite.add_test('positive_definite', MatrixProperties.is_positive_definite,
'x^T A x > 0')
suite.add_test('orthogonal', MatrixProperties.is_orthogonal,
'Q^T Q = I')
# Add custom tests
suite.add_test('diagonally_dominant', is_diagonally_dominant,
'|a_ii| >= sum|a_ij|')
# Run tests
A = am.generate('core/beta', 20)
results = suite.run_tests(A)
suite.summary()
Part 5: Debugging Failed Properties¶
Step 9: Diagnose Property Failures¶
def diagnose_symmetry(matrix, tol=1e-10):
"""Diagnose why a matrix might not be symmetric"""
print("\nSymmetry Diagnosis")
print("=" * 60)
# Compute difference
diff = matrix - matrix.T
# Maximum difference
max_diff = np.max(np.abs(diff))
print(f"Maximum |A - A^T|: {max_diff:.2e}")
# Where is the maximum?
i, j = np.unravel_index(np.argmax(np.abs(diff)), diff.shape)
print(f"Location: ({i}, {j})")
print(f"A[{i},{j}] = {matrix[i,j]:.6f}")
print(f"A[{j},{i}] = {matrix[j,i]:.6f}")
print(f"Difference: {diff[i,j]:.2e}")
# Is it close to symmetric?
if max_diff < tol:
print(f"\n✓ Matrix IS symmetric (within tolerance {tol:.2e})")
elif max_diff < 100 * tol:
print(f"\n~ Matrix is NEARLY symmetric")
print(f" Consider using tolerance >= {max_diff:.2e}")
else:
print(f"\n✗ Matrix is NOT symmetric")
print(f" Maximum difference {max_diff:.2e} >> tolerance {tol:.2e}")
# Test
A = am.generate('core/beta', 10)
A_perturbed = A + np.random.randn(10, 10) * 1e-11
diagnose_symmetry(A_perturbed, tol=1e-10)
Part 6: Real-World Application¶
Step 10: Complete Testing Workflow¶
import json
from datetime import datetime
class MatrixPropertyTester:
"""Complete property testing framework"""
def __init__(self, am):
self.am = am
self.test_history = []
def test_matrix(self, matrix_id, size, custom_tests=None):
"""Run complete test on a matrix"""
# Record start
test_record = {
'matrix_id': matrix_id,
'size': size,
'timestamp': datetime.now().isoformat(),
'results': {}
}
try:
# Generate matrix
matrix = self.am.generate(matrix_id, size)
test_record['generated'] = True
# Get expected properties
expected = self.am.properties(matrix_id)
# Verify expected properties
results = MatrixProperties.check_properties(matrix, expected)
test_record['results']['expected'] = results
# Run custom tests if provided
if custom_tests:
custom_results = {}
for test_name, test_func in custom_tests.items():
try:
custom_results[test_name] = test_func(matrix)
except Exception as e:
custom_results[test_name] = f"ERROR: {e}"
test_record['results']['custom'] = custom_results
# Summary
all_expected_passed = all(results.values())
test_record['summary'] = {
'expected_passed': all_expected_passed,
'total_tests': len(results)
}
except Exception as e:
test_record['generated'] = False
test_record['error'] = str(e)
# Save to history
self.test_history.append(test_record)
return test_record
def generate_report(self, filename=None):
"""Generate test report"""
report = {
'total_tests': len(self.test_history),
'timestamp': datetime.now().isoformat(),
'tests': self.test_history
}
if filename:
with open(filename, 'w') as f:
json.dump(report, f, indent=2)
print(f"Report saved to {filename}")
return report
def print_summary(self):
"""Print summary of all tests"""
print("\n" + "=" * 70)
print("PROPERTY TESTING SUMMARY")
print("=" * 70)
passed = sum(1 for t in self.test_history
if t.get('generated') and
t.get('summary', {}).get('expected_passed'))
print(f"Total tests run: {len(self.test_history)}")
print(f"Passed: {passed}")
print(f"Failed: {len(self.test_history) - passed}")
# Use the tester
tester = MatrixPropertyTester(am)
# Run tests
matrices_to_test = [
('core/beta', 10),
('core/fourier', 16),
('matlab/hilbert', 5),
('regtools/deriv2', 50),
]
for matrix_id, size in matrices_to_test:
result = tester.test_matrix(matrix_id, size)
status = "✓" if result.get('summary', {}).get('expected_passed') else "✗"
print(f"{status} {matrix_id:30s} (size={size})")
# Generate report
tester.print_summary()
tester.generate_report('property_test_report.json')
Exercises¶
Exercise 1: Write a property checker for tridiagonal matrices
Exercise 2: Create a test that verifies orthogonality by checking \(Q^T Q = I\)
Exercise 3: Implement a property checker for Toeplitz matrices
Exercise 4: Write a comprehensive test suite for all nilpotent matrices
Next Steps¶
Read Matrix Properties for complete property reference
Check Anymatrix Examples for more examples
See Anymatrix Module for API documentation
Summary¶
You’ve learned:
✓ How to check individual matrix properties
✓ How to verify expected properties automatically
✓ How to run comprehensive test suites
✓ How to create custom property tests
✓ How to diagnose and debug failed properties
✓ How to implement real-world testing workflows