Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
525
examples/prime-radiant/docs/SECURITY_AUDIT.md
Normal file
525
examples/prime-radiant/docs/SECURITY_AUDIT.md
Normal file
@@ -0,0 +1,525 @@
|
||||
# Prime-Radiant Security Audit Report
|
||||
|
||||
**Audit Date:** 2026-01-22
|
||||
**Auditor:** V3 Security Architect
|
||||
**Crate:** prime-radiant (Coherence Engine)
|
||||
**Scope:** Memory safety, input validation, cryptographic concerns, WASM security, dependencies, code quality
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The Prime-Radiant coherence engine demonstrates **strong security fundamentals** with several notable strengths:
|
||||
- `#![deny(unsafe_code)]` enforced crate-wide
|
||||
- Parameterized SQL queries preventing SQL injection
|
||||
- Proper use of Result types throughout public APIs
|
||||
- Well-defined error types with thiserror
|
||||
|
||||
However, **17 security issues** were identified across the following categories:
|
||||
|
||||
| Severity | Count | Description |
|
||||
|----------|-------|-------------|
|
||||
| HIGH | 3 | Input validation gaps, panic-on-invalid-input |
|
||||
| MEDIUM | 8 | Numerical stability, resource exhaustion potential |
|
||||
| LOW | 4 | Code quality improvements, hardening recommendations |
|
||||
| INFO | 2 | Best practice recommendations |
|
||||
|
||||
---
|
||||
|
||||
## 1. Memory Safety Analysis
|
||||
|
||||
### 1.1 Unsafe Code Status: PASS
|
||||
|
||||
The crate explicitly denies unsafe code:
|
||||
```rust
|
||||
// /crates/prime-radiant/src/lib.rs:143
|
||||
#![deny(unsafe_code)]
|
||||
```
|
||||
|
||||
This is excellent and enforced at compile time. No unsafe blocks exist in the codebase.
|
||||
|
||||
### 1.2 Buffer Operations: MOSTLY SAFE
|
||||
|
||||
**SIMD Vector Operations** (`src/simd/vectors.rs`):
|
||||
- Uses `debug_assert!` for length checks (lines 50, 196-197, 286, 369-371)
|
||||
- These assertions only fire in debug mode; release builds skip validation
|
||||
|
||||
**FINDING [MED-1]: Release-Mode Bounds Check Missing**
|
||||
```rust
|
||||
// src/simd/vectors.rs:49-50
|
||||
pub fn dot_product_simd(a: &[f32], b: &[f32]) -> f32 {
|
||||
debug_assert_eq!(a.len(), b.len(), "Vectors must have equal length");
|
||||
// In release mode, mismatched lengths cause undefined behavior
|
||||
```
|
||||
|
||||
**Recommendation:** Replace `debug_assert!` with proper Result-returning validation for public APIs.
|
||||
|
||||
### 1.3 GPU Buffer Operations: SAFE
|
||||
|
||||
Buffer management (`src/gpu/buffer.rs`) properly validates:
|
||||
- Buffer size limits (line 516): `if size > super::MAX_BUFFER_SIZE`
|
||||
- Buffer size mismatches (line 182-187): Returns `GpuError::BufferSizeMismatch`
|
||||
- Pool capacity limits (line 555): Enforces `max_pool_size`
|
||||
|
||||
---
|
||||
|
||||
## 2. Input Validation Analysis
|
||||
|
||||
### 2.1 Graph Size Limits: PARTIAL
|
||||
|
||||
**FINDING [HIGH-1]: No Maximum Graph Size Limit**
|
||||
|
||||
The `SheafGraph` (`src/substrate/graph.rs`) allows unbounded growth:
|
||||
```rust
|
||||
pub fn add_node(&self, node: SheafNode) -> NodeId {
|
||||
// No limit on node count
|
||||
self.nodes.insert(id, node);
|
||||
```
|
||||
|
||||
**DoS Risk:** An attacker could exhaust memory by adding unlimited nodes/edges.
|
||||
|
||||
**Recommendation:** Add configurable limits:
|
||||
```rust
|
||||
pub struct GraphLimits {
|
||||
pub max_nodes: usize, // Default: 1_000_000
|
||||
pub max_edges: usize, // Default: 10_000_000
|
||||
pub max_state_dim: usize, // Default: 65536
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 Matrix Dimension Validation: PARTIAL
|
||||
|
||||
**FINDING [MED-2]: Large Matrix Allocation Without Bounds**
|
||||
|
||||
`RestrictionMap::identity()` allocates `dim * dim` without upper bound:
|
||||
```rust
|
||||
// src/coherence/engine.rs:214-225
|
||||
pub fn identity(dim: usize) -> Self {
|
||||
let mut matrix = vec![0.0; dim * dim]; // Unbounded!
|
||||
```
|
||||
|
||||
With `dim = 2^16`, this allocates 16GB.
|
||||
|
||||
**Recommendation:** Add dimension caps (suggested: 65536 for matrices).
|
||||
|
||||
### 2.3 File Path Validation: SAFE
|
||||
|
||||
PostgreSQL storage (`src/storage/postgres.rs`) uses parameterized queries:
|
||||
```rust
|
||||
// Line 362-377 - properly parameterized
|
||||
sqlx::query("INSERT INTO node_states (node_id, state, dimension, updated_at) VALUES ($1, $2, $3, NOW())")
|
||||
.bind(node_id)
|
||||
.bind(state)
|
||||
```
|
||||
|
||||
File storage (`src/storage/file.rs`) constructs paths but does not sanitize for traversal:
|
||||
|
||||
**FINDING [MED-3]: Potential Path Traversal in FileStorage**
|
||||
```rust
|
||||
// src/storage/file.rs:279-281
|
||||
fn node_path(&self, node_id: &str) -> PathBuf {
|
||||
let ext = if self.format == StorageFormat::Json { "json" } else { "bin" };
|
||||
self.root.join("nodes").join(format!("{}.{}", node_id, ext))
|
||||
}
|
||||
```
|
||||
|
||||
If `node_id = "../../../etc/passwd"`, this creates a traversal vector.
|
||||
|
||||
**Recommendation:** Validate node_id contains only alphanumeric, dash, underscore characters.
|
||||
|
||||
### 2.4 Signal Validation: EXISTS
|
||||
|
||||
The `SignalValidator` (`src/signal/validation.rs`) provides:
|
||||
- Maximum payload size validation (default 1MB)
|
||||
- Signal type allowlisting
|
||||
- Source non-empty validation
|
||||
|
||||
This is good but could be expanded.
|
||||
|
||||
---
|
||||
|
||||
## 3. Numerical Stability Analysis
|
||||
|
||||
### 3.1 NaN/Infinity Handling: INCOMPLETE
|
||||
|
||||
**FINDING [MED-4]: No NaN Checks on Input States**
|
||||
|
||||
State vectors accept NaN/Infinity without validation:
|
||||
```rust
|
||||
// src/substrate/node.rs
|
||||
pub fn update_state_from_slice(&mut self, new_state: &[f32]) {
|
||||
self.state = StateVector::from_slice(new_state);
|
||||
// No NaN check
|
||||
```
|
||||
|
||||
NaN propagates through all coherence computations silently.
|
||||
|
||||
**Locations using special float values:**
|
||||
- `src/hyperbolic/mod.rs:217`: `f32::MAX` for min_depth
|
||||
- `src/mincut/metrics.rs:55`: `f64::INFINITY` for min_cut_value
|
||||
- `src/attention/moe.rs:199`: `f32::NEG_INFINITY` for max logit
|
||||
- `src/ruvllm_integration/confidence.rs:376-379`: NaN for error states
|
||||
|
||||
**Recommendation:** Add validation helper:
|
||||
```rust
|
||||
pub fn validate_state(state: &[f32]) -> Result<(), ValidationError> {
|
||||
if state.iter().any(|x| x.is_nan() || x.is_infinite()) {
|
||||
return Err(ValidationError::InvalidFloat);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 Division Safety: PARTIAL
|
||||
|
||||
Cosine similarity (`src/storage/postgres.rs:861-875`) properly handles zero norms:
|
||||
```rust
|
||||
if norm_a == 0.0 || norm_b == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
```
|
||||
|
||||
However, other locations may divide without checking.
|
||||
|
||||
---
|
||||
|
||||
## 4. Cryptographic Analysis
|
||||
|
||||
### 4.1 Random Number Generation: MIXED
|
||||
|
||||
**Good (Deterministic Seeds):**
|
||||
```rust
|
||||
// src/coherence/engine.rs:248-249
|
||||
use rand::{Rng, SeedableRng};
|
||||
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
|
||||
```
|
||||
|
||||
This is appropriate for reproducible restriction maps.
|
||||
|
||||
**FINDING [MED-5]: Non-Cryptographic RNG for Node IDs**
|
||||
```rust
|
||||
// src/substrate/node.rs:48-49
|
||||
use rand::Rng;
|
||||
let mut rng = rand::thread_rng();
|
||||
```
|
||||
|
||||
`thread_rng()` is not cryptographically secure. While likely used for test data, if node IDs need unpredictability, use `OsRng` or `getrandom`.
|
||||
|
||||
### 4.2 Hash Functions: GOOD
|
||||
|
||||
The crate uses `blake3` for WAL checksums (`src/storage/file.rs:51-52`):
|
||||
```rust
|
||||
let checksum = *blake3::hash(&op_bytes).as_bytes();
|
||||
```
|
||||
|
||||
Blake3 is cryptographically strong and appropriate.
|
||||
|
||||
### 4.3 No Hardcoded Secrets: PASS
|
||||
|
||||
Searched codebase for hardcoded credentials, API keys, passwords - none found.
|
||||
|
||||
---
|
||||
|
||||
## 5. WASM-Specific Security
|
||||
|
||||
### 5.1 Memory Isolation: HANDLED BY WASM RUNTIME
|
||||
|
||||
The tiles module uses 256 WASM tiles. WASM provides:
|
||||
- Linear memory isolation
|
||||
- Control flow integrity
|
||||
- Type safety at boundaries
|
||||
|
||||
### 5.2 Data Cleanup: NOT EXPLICITLY HANDLED
|
||||
|
||||
**FINDING [LOW-1]: No Explicit Memory Zeroization**
|
||||
|
||||
Sensitive data in WASM memory (e.g., state vectors) is not explicitly zeroed after use. While WASM memory is isolated per instance, zeroing before deallocation is defense-in-depth.
|
||||
|
||||
**Recommendation:** For sensitive operations, use `zeroize` crate.
|
||||
|
||||
### 5.3 JS Boundary Error Handling: GOOD
|
||||
|
||||
The GPU module returns proper `GpuResult<T>` types across all boundaries.
|
||||
|
||||
---
|
||||
|
||||
## 6. Dependency Analysis
|
||||
|
||||
### 6.1 Cargo.toml Dependencies
|
||||
|
||||
Based on `/crates/prime-radiant/Cargo.toml`:
|
||||
|
||||
| Dependency | Version | Known CVEs | Status |
|
||||
|------------|---------|------------|--------|
|
||||
| blake3 | 1.5 | None | OK |
|
||||
| bytemuck | 1.21 | None | OK |
|
||||
| chrono | 0.4 | None (0.4.35+) | OK |
|
||||
| dashmap | 6.0 | None | OK |
|
||||
| parking_lot | 0.12 | None | OK |
|
||||
| rayon | 1.10 | None | OK |
|
||||
| serde | 1.0 | None | OK |
|
||||
| sqlx | 0.8 | None | OK |
|
||||
| thiserror | 2.0 | None | OK |
|
||||
| uuid | 1.10 | None | OK |
|
||||
| wgpu | 22.1 | None | OK |
|
||||
| wide | 0.7 | None | OK |
|
||||
| bincode | 2.0.0-rc.3 | None | OK (RC) |
|
||||
|
||||
**FINDING [LOW-2]: Using Release Candidate Dependency**
|
||||
`bincode = "2.0.0-rc.3"` is a release candidate. Consider pinning to stable when available.
|
||||
|
||||
### 6.2 Minimal Dependency Surface: GOOD
|
||||
|
||||
The crate uses feature flags to minimize attack surface:
|
||||
```toml
|
||||
[features]
|
||||
default = []
|
||||
postgres = ["sqlx/postgres"]
|
||||
gpu = ["wgpu"]
|
||||
simd = []
|
||||
parallel = ["rayon"]
|
||||
```
|
||||
|
||||
Only required features are compiled.
|
||||
|
||||
---
|
||||
|
||||
## 7. Code Quality Issues
|
||||
|
||||
### 7.1 Panic-Inducing Code
|
||||
|
||||
**FINDING [HIGH-2]: panic! in Library Code**
|
||||
```rust
|
||||
// src/distributed/adapter.rs:340
|
||||
panic!("Wrong command type");
|
||||
```
|
||||
|
||||
Library code should never panic; use Result instead.
|
||||
|
||||
**FINDING [HIGH-3]: unwrap() in Non-Test Code**
|
||||
```rust
|
||||
// src/governance/witness.rs:564
|
||||
self.head.as_ref().unwrap()
|
||||
```
|
||||
|
||||
This can panic if `head` is `None`.
|
||||
|
||||
**FINDING [MED-6]: expect() in Builders Without Validation**
|
||||
```rust
|
||||
// src/substrate/node.rs:454
|
||||
let state = self.state.expect("State vector is required");
|
||||
```
|
||||
|
||||
Builder pattern should return `Result<T, BuilderError>` instead of panicking.
|
||||
|
||||
### 7.2 Incomplete Error Propagation
|
||||
|
||||
Some locations use `.unwrap()` in test code (acceptable) but several are in production paths. Full list of production unwrap() calls:
|
||||
|
||||
1. `src/storage/file.rs:49` - WAL entry creation (partially justified)
|
||||
2. `src/simd/vectors.rs:499` - SIMD array conversion
|
||||
3. `src/simd/matrix.rs:390` - SIMD array conversion
|
||||
4. `src/simd/energy.rs:523` - SIMD array conversion
|
||||
5. `src/governance/witness.rs:564` - Head access
|
||||
|
||||
### 7.3 Timing Attack Considerations
|
||||
|
||||
**FINDING [MED-7]: Non-Constant-Time Comparisons**
|
||||
|
||||
Hash comparisons in WAL verification use standard equality:
|
||||
```rust
|
||||
// src/storage/file.rs:63
|
||||
fn verify(&self) -> bool {
|
||||
self.checksum == *blake3::hash(&bytes).as_bytes()
|
||||
}
|
||||
```
|
||||
|
||||
For security-critical hash comparisons, use constant-time comparison to prevent timing attacks:
|
||||
```rust
|
||||
use subtle::ConstantTimeEq;
|
||||
self.checksum.ct_eq(&hash).into()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Recommendations Summary
|
||||
|
||||
### Critical (Address Immediately)
|
||||
|
||||
| ID | Issue | File | Line | Fix |
|
||||
|----|-------|------|------|-----|
|
||||
| HIGH-1 | No graph size limits | substrate/graph.rs | 312 | Add `GraphLimits` config |
|
||||
| HIGH-2 | panic! in library | distributed/adapter.rs | 340 | Return Result |
|
||||
| HIGH-3 | unwrap() on Option | governance/witness.rs | 564 | Return Result |
|
||||
|
||||
### High Priority (Address in Phase 1)
|
||||
|
||||
| ID | Issue | File | Fix |
|
||||
|----|-------|------|-----|
|
||||
| MED-1 | Release-mode bounds | simd/vectors.rs | Add runtime validation |
|
||||
| MED-2 | Unbounded matrix allocation | coherence/engine.rs | Add dimension cap |
|
||||
| MED-3 | Path traversal potential | storage/file.rs | Validate node_id |
|
||||
| MED-4 | No NaN/Inf validation | substrate/node.rs | Add float validation |
|
||||
|
||||
### Medium Priority (Address in Phase 2)
|
||||
|
||||
| ID | Issue | File | Fix |
|
||||
|----|-------|------|-----|
|
||||
| MED-5 | Non-crypto RNG | substrate/node.rs | Document or use OsRng |
|
||||
| MED-6 | expect() in builders | substrate/*.rs | Return Result |
|
||||
| MED-7 | Timing attacks | storage/file.rs | Use constant-time |
|
||||
|
||||
### Low Priority (Best Practices)
|
||||
|
||||
| ID | Issue | Fix |
|
||||
|----|-------|-----|
|
||||
| LOW-1 | No memory zeroization | Use `zeroize` for sensitive data |
|
||||
| LOW-2 | RC dependency | Pin bincode to stable when available |
|
||||
|
||||
---
|
||||
|
||||
## 9. Production Deployment Recommendations
|
||||
|
||||
### 9.1 Resource Limits
|
||||
|
||||
Configure these limits before production deployment:
|
||||
|
||||
```rust
|
||||
let config = CoherenceConfig {
|
||||
max_nodes: 1_000_000,
|
||||
max_edges: 10_000_000,
|
||||
max_state_dimension: 4096,
|
||||
max_matrix_dimension: 8192,
|
||||
max_payload_size: 10 * 1024 * 1024, // 10MB
|
||||
max_concurrent_computations: 100,
|
||||
};
|
||||
```
|
||||
|
||||
### 9.2 Input Validation Layer
|
||||
|
||||
Add a validation middleware for all external inputs:
|
||||
|
||||
```rust
|
||||
pub struct SecureInputValidator {
|
||||
pub max_state_dim: usize,
|
||||
pub max_node_id_len: usize,
|
||||
pub allowed_id_chars: Regex,
|
||||
}
|
||||
|
||||
impl SecureInputValidator {
|
||||
pub fn validate_node_id(&self, id: &str) -> Result<(), ValidationError> {
|
||||
if id.len() > self.max_node_id_len {
|
||||
return Err(ValidationError::IdTooLong);
|
||||
}
|
||||
if !self.allowed_id_chars.is_match(id) {
|
||||
return Err(ValidationError::InvalidIdChars);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_state(&self, state: &[f32]) -> Result<(), ValidationError> {
|
||||
if state.len() > self.max_state_dim {
|
||||
return Err(ValidationError::StateTooLarge);
|
||||
}
|
||||
if state.iter().any(|x| x.is_nan() || x.is_infinite()) {
|
||||
return Err(ValidationError::InvalidFloat);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 Monitoring
|
||||
|
||||
Add these security-relevant metrics:
|
||||
- Graph size (nodes, edges)
|
||||
- Failed validation attempts
|
||||
- Memory usage per operation
|
||||
- Unusual pattern detection (rapid adds, large states)
|
||||
|
||||
### 9.4 Rate Limiting
|
||||
|
||||
Implement rate limiting for:
|
||||
- Node/edge additions per client
|
||||
- Energy computation requests
|
||||
- File storage operations
|
||||
|
||||
---
|
||||
|
||||
## 10. Compliance Notes
|
||||
|
||||
### 10.1 Rust Security Best Practices
|
||||
|
||||
| Practice | Status |
|
||||
|----------|--------|
|
||||
| No unsafe code | PASS |
|
||||
| Proper error types | PASS |
|
||||
| Result over panic | PARTIAL |
|
||||
| Input validation | PARTIAL |
|
||||
| Dependency management | PASS |
|
||||
|
||||
### 10.2 OWASP Considerations
|
||||
|
||||
| Risk | Mitigation Status |
|
||||
|------|-------------------|
|
||||
| Injection | PASS (parameterized SQL) |
|
||||
| Broken Auth | N/A (no auth in crate) |
|
||||
| Sensitive Data | PARTIAL (no zeroization) |
|
||||
| XXE | N/A (no XML) |
|
||||
| Access Control | N/A (application layer) |
|
||||
| Misconfig | PARTIAL (needs limits) |
|
||||
| XSS | N/A (no web output) |
|
||||
| Deserialization | PASS (serde/bincode safe) |
|
||||
| Logging | PARTIAL (needs audit logs) |
|
||||
| SSRF | N/A |
|
||||
|
||||
---
|
||||
|
||||
## Appendix A: Files Audited
|
||||
|
||||
```
|
||||
src/
|
||||
├── lib.rs
|
||||
├── error.rs
|
||||
├── coherence/engine.rs
|
||||
├── distributed/adapter.rs
|
||||
├── governance/
|
||||
│ ├── mod.rs
|
||||
│ ├── witness.rs
|
||||
│ ├── lineage.rs
|
||||
│ └── repository.rs
|
||||
├── gpu/
|
||||
│ ├── mod.rs
|
||||
│ └── buffer.rs
|
||||
├── hyperbolic/
|
||||
│ ├── mod.rs
|
||||
│ ├── adapter.rs
|
||||
│ └── energy.rs
|
||||
├── simd/
|
||||
│ ├── mod.rs
|
||||
│ ├── vectors.rs
|
||||
│ ├── matrix.rs
|
||||
│ └── energy.rs
|
||||
├── signal/
|
||||
│ ├── mod.rs
|
||||
│ ├── validation.rs
|
||||
│ └── ingestion.rs
|
||||
├── storage/
|
||||
│ ├── mod.rs
|
||||
│ ├── file.rs
|
||||
│ └── postgres.rs
|
||||
├── substrate/
|
||||
│ ├── graph.rs
|
||||
│ ├── node.rs
|
||||
│ ├── edge.rs
|
||||
│ └── restriction.rs
|
||||
└── tiles/
|
||||
├── mod.rs
|
||||
├── adapter.rs
|
||||
└── coordinator.rs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Report Generated:** 2026-01-22
|
||||
**Next Audit Recommended:** 2026-04-22 (quarterly)
|
||||
333
examples/prime-radiant/docs/adr/ADR-001-sheaf-cohomology.md
Normal file
333
examples/prime-radiant/docs/adr/ADR-001-sheaf-cohomology.md
Normal file
@@ -0,0 +1,333 @@
|
||||
# ADR-001: Sheaf Cohomology for AI Coherence
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Large Language Models and AI agents frequently produce outputs that are locally plausible but globally inconsistent. Traditional approaches to detecting such "hallucinations" rely on:
|
||||
|
||||
1. **Confidence scores**: Unreliable due to overconfidence on out-of-distribution inputs
|
||||
2. **Retrieval augmentation**: Helps but doesn't verify consistency across retrieved facts
|
||||
3. **Chain-of-thought verification**: Manual and prone to same failures as original reasoning
|
||||
4. **Ensemble methods**: Expensive and still vulnerable to correlated errors
|
||||
|
||||
We need a mathematical framework that can:
|
||||
|
||||
- Detect **local-to-global consistency** failures systematically
|
||||
- Provide **quantitative measures** of coherence
|
||||
- Support **incremental updates** as new information arrives
|
||||
- Work across **multiple domains** with the same underlying math
|
||||
|
||||
### Why Sheaf Theory?
|
||||
|
||||
Sheaf theory was developed in algebraic geometry and topology precisely to handle local-to-global problems. A sheaf assigns data to open sets in a way that:
|
||||
|
||||
1. **Locality**: Information at a point is determined by nearby information
|
||||
2. **Gluing**: Locally consistent data can be assembled into global data
|
||||
3. **Restriction**: Global data determines local data uniquely
|
||||
|
||||
These properties exactly match our coherence requirements:
|
||||
|
||||
- AI claims are local (about specific facts)
|
||||
- Coherent knowledge should glue together globally
|
||||
- Contradictions appear when local data fails to extend globally
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement **cellular sheaf cohomology** on graphs as the mathematical foundation for Prime-Radiant's coherence engine.
|
||||
|
||||
### Mathematical Foundation
|
||||
|
||||
#### Definition: Sheaf on a Graph
|
||||
|
||||
A **cellular sheaf** F on a graph G = (V, E) assigns:
|
||||
|
||||
1. To each vertex v, a vector space F(v) (the **stalk** at v)
|
||||
2. To each edge e = (u,v), a vector space F(e)
|
||||
3. For each vertex v incident to edge e, a linear map (the **restriction map**):
|
||||
```
|
||||
rho_{v,e}: F(v) -> F(e)
|
||||
```
|
||||
|
||||
#### Definition: Residual
|
||||
|
||||
For an edge e = (u,v) with vertex states x_u in F(u) and x_v in F(v), the **residual** is:
|
||||
|
||||
```
|
||||
r_e = rho_{u,e}(x_u) - rho_{v,e}(x_v)
|
||||
```
|
||||
|
||||
The residual measures local inconsistency: if states agree through their restriction maps, r_e = 0.
|
||||
|
||||
#### Definition: Sheaf Laplacian
|
||||
|
||||
The **sheaf Laplacian** L is the block matrix:
|
||||
|
||||
```
|
||||
L = D^T W D
|
||||
```
|
||||
|
||||
where:
|
||||
- D is the coboundary map (encodes graph topology and restriction maps)
|
||||
- W is a diagonal weight matrix for edges
|
||||
|
||||
The quadratic form x^T L x = sum_e w_e ||r_e||^2 computes total coherence energy.
|
||||
|
||||
#### Definition: Cohomology Groups
|
||||
|
||||
The **first cohomology group** H^1(G, F) measures obstruction to finding a global section:
|
||||
|
||||
```
|
||||
H^1(G, F) = ker(delta_1) / im(delta_0)
|
||||
```
|
||||
|
||||
where delta_i are coboundary maps. If H^1 is non-trivial, the sheaf admits no global section (global inconsistency exists).
|
||||
|
||||
### Implementation Architecture
|
||||
|
||||
```rust
|
||||
/// A sheaf on a graph with fixed-dimensional stalks
|
||||
pub struct SheafGraph {
|
||||
/// Node stalks: state vectors at each vertex
|
||||
nodes: HashMap<NodeId, StateVector>,
|
||||
|
||||
/// Edge stalks and restriction maps
|
||||
edges: HashMap<EdgeId, SheafEdge>,
|
||||
|
||||
/// Cached Laplacian blocks for incremental updates
|
||||
laplacian_cache: LaplacianCache,
|
||||
}
|
||||
|
||||
/// A restriction map implemented as a matrix
|
||||
pub struct RestrictionMap {
|
||||
/// The linear map as a matrix (output_dim x input_dim)
|
||||
matrix: Array2<f32>,
|
||||
|
||||
/// Input dimension (node stalk dimension)
|
||||
input_dim: usize,
|
||||
|
||||
/// Output dimension (edge stalk dimension)
|
||||
output_dim: usize,
|
||||
}
|
||||
|
||||
impl RestrictionMap {
|
||||
/// Apply the restriction map: rho(x)
|
||||
pub fn apply(&self, x: &[f32]) -> Vec<f32> {
|
||||
self.matrix.dot(&ArrayView1::from(x)).to_vec()
|
||||
}
|
||||
|
||||
/// Identity restriction (node stalk = edge stalk)
|
||||
pub fn identity(dim: usize) -> Self {
|
||||
Self {
|
||||
matrix: Array2::eye(dim),
|
||||
input_dim: dim,
|
||||
output_dim: dim,
|
||||
}
|
||||
}
|
||||
|
||||
/// Projection restriction (edge stalk is subset of node stalk)
|
||||
pub fn projection(input_dim: usize, output_dim: usize) -> Self {
|
||||
let mut matrix = Array2::zeros((output_dim, input_dim));
|
||||
for i in 0..output_dim.min(input_dim) {
|
||||
matrix[[i, i]] = 1.0;
|
||||
}
|
||||
Self { matrix, input_dim, output_dim }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Cohomology Computation
|
||||
|
||||
```rust
|
||||
/// Compute the first cohomology dimension
|
||||
pub fn cohomology_dimension(&self) -> usize {
|
||||
// Build coboundary matrix D
|
||||
let d = self.build_coboundary_matrix();
|
||||
|
||||
// Compute rank using SVD
|
||||
let svd = d.svd(true, true).unwrap();
|
||||
let rank = svd.singular_values
|
||||
.iter()
|
||||
.filter(|&s| *s > 1e-10)
|
||||
.count();
|
||||
|
||||
// dim H^1 = dim(edge stalks) - rank(D)
|
||||
let edge_dim: usize = self.edges.values()
|
||||
.map(|e| e.stalk_dim)
|
||||
.sum();
|
||||
|
||||
edge_dim.saturating_sub(rank)
|
||||
}
|
||||
|
||||
/// Check if sheaf admits a global section
|
||||
pub fn has_global_section(&self) -> bool {
|
||||
self.cohomology_dimension() == 0
|
||||
}
|
||||
```
|
||||
|
||||
### Energy Computation
|
||||
|
||||
The total coherence energy is:
|
||||
|
||||
```rust
|
||||
/// Compute total coherence energy: E = sum_e w_e ||r_e||^2
|
||||
pub fn coherence_energy(&self) -> f32 {
|
||||
self.edges.values()
|
||||
.map(|edge| {
|
||||
let source = &self.nodes[&edge.source];
|
||||
let target = &self.nodes[&edge.target];
|
||||
|
||||
// Apply restriction maps
|
||||
let rho_s = edge.source_restriction.apply(&source.state);
|
||||
let rho_t = edge.target_restriction.apply(&target.state);
|
||||
|
||||
// Compute residual
|
||||
let residual: Vec<f32> = rho_s.iter()
|
||||
.zip(rho_t.iter())
|
||||
.map(|(a, b)| a - b)
|
||||
.collect();
|
||||
|
||||
// Weighted squared norm
|
||||
let norm_sq: f32 = residual.iter().map(|r| r * r).sum();
|
||||
edge.weight * norm_sq
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
```
|
||||
|
||||
### Incremental Updates
|
||||
|
||||
For efficiency, we maintain a **residual cache** and update incrementally:
|
||||
|
||||
```rust
|
||||
/// Update a single node and recompute affected energies
|
||||
pub fn update_node(&mut self, node_id: NodeId, new_state: Vec<f32>) {
|
||||
// Store old state for delta computation
|
||||
let old_state = self.nodes.insert(node_id, new_state.clone());
|
||||
|
||||
// Only recompute residuals for edges incident to this node
|
||||
for edge_id in self.edges_incident_to(node_id) {
|
||||
self.recompute_residual(edge_id);
|
||||
}
|
||||
|
||||
// Update fingerprint
|
||||
self.update_fingerprint(node_id, &old_state, &new_state);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Mathematically Grounded**: Sheaf cohomology provides rigorous foundations for coherence
|
||||
2. **Domain Agnostic**: Same math applies to facts, financial signals, medical data, etc.
|
||||
3. **Local-to-Global Detection**: Naturally captures the essence of hallucination (local OK, global wrong)
|
||||
4. **Incremental Computation**: Residual caching enables real-time updates
|
||||
5. **Spectral Analysis**: Sheaf Laplacian eigenvalues provide drift detection
|
||||
6. **Quantitative Measure**: Energy gives a continuous coherence score, not just binary
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Computational Cost**: Full cohomology computation is O(n^3) for n nodes
|
||||
2. **Restriction Map Design**: Choosing appropriate rho requires domain knowledge
|
||||
3. **Curse of Dimensionality**: High-dimensional stalks increase memory and compute
|
||||
4. **Learning Complexity**: Non-trivial to learn restriction maps from data
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Incremental Updates**: Avoid full recomputation for small changes
|
||||
2. **Learned rho**: GNN-based restriction map learning (see `learned-rho` feature)
|
||||
3. **Dimensional Reduction**: Use projection restriction maps to reduce edge stalk dimension
|
||||
4. **Subpolynomial MinCut**: Use for approximation when full computation is infeasible
|
||||
|
||||
---
|
||||
|
||||
## Mathematical Properties
|
||||
|
||||
### Theorem: Energy Minimization
|
||||
|
||||
If the sheaf Laplacian L has full column rank, the minimum energy configuration is unique:
|
||||
|
||||
```
|
||||
x* = argmin_x ||Dx||^2_W = L^+ b
|
||||
```
|
||||
|
||||
where L^+ is the pseudoinverse and b encodes boundary conditions.
|
||||
|
||||
### Theorem: Cheeger Inequality
|
||||
|
||||
The spectral gap (second smallest eigenvalue) of L relates to graph cuts:
|
||||
|
||||
```
|
||||
lambda_2 / 2 <= h(G) <= sqrt(2 * lambda_2)
|
||||
```
|
||||
|
||||
where h(G) is the Cheeger constant. This enables **cut prediction** from spectral analysis.
|
||||
|
||||
### Theorem: Hodge Decomposition
|
||||
|
||||
The space of edge states decomposes:
|
||||
|
||||
```
|
||||
C^1(G, F) = im(delta_0) + ker(delta_1) + H^1(G, F)
|
||||
```
|
||||
|
||||
This separates gradient flows (consistent), harmonic forms (neutral), and cohomology (obstructions).
|
||||
|
||||
---
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-004: Spectral Invariants](ADR-004-spectral-invariants.md) - Uses sheaf Laplacian eigenvalues
|
||||
- [ADR-002: Category Theory](ADR-002-category-topos.md) - Sheaves are presheaves satisfying gluing
|
||||
- [ADR-003: Homotopy Type Theory](ADR-003-homotopy-type-theory.md) - Higher sheaves and stacks
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Hansen, J., & Ghrist, R. (2019). "Toward a spectral theory of cellular sheaves." Journal of Applied and Computational Topology.
|
||||
|
||||
2. Curry, J. (2014). "Sheaves, Cosheaves and Applications." PhD thesis, University of Pennsylvania.
|
||||
|
||||
3. Robinson, M. (2014). "Topological Signal Processing." Springer.
|
||||
|
||||
4. Bodnar, C., et al. (2022). "Neural Sheaf Diffusion: A Topological Perspective on Heterophily and Oversmoothing in GNNs." NeurIPS.
|
||||
|
||||
5. Ghrist, R. (2014). "Elementary Applied Topology." Createspace.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Worked Example
|
||||
|
||||
Consider a knowledge graph with three facts:
|
||||
|
||||
- F1: "Paris is the capital of France" (state: [1, 0, 0, 1])
|
||||
- F2: "France is in Europe" (state: [0, 1, 1, 0])
|
||||
- F3: "Paris is not in Europe" (state: [1, 0, 0, -1]) -- HALLUCINATION
|
||||
|
||||
Edges with identity restriction maps:
|
||||
- E1: F1 -> F2 (France connection)
|
||||
- E2: F1 -> F3 (Paris connection)
|
||||
- E3: F2 -> F3 (Europe connection)
|
||||
|
||||
Residuals:
|
||||
- r_{E1} = [1,0,0,1] - [0,1,1,0] = [1,-1,-1,1], ||r||^2 = 4
|
||||
- r_{E2} = [1,0,0,1] - [1,0,0,-1] = [0,0,0,2], ||r||^2 = 4
|
||||
- r_{E3} = [0,1,1,0] - [1,0,0,-1] = [-1,1,1,1], ||r||^2 = 4
|
||||
|
||||
Total energy = 4 + 4 + 4 = 12 (HIGH -- indicates hallucination)
|
||||
|
||||
If F3 were corrected to "Paris is in Europe" (state: [1,0,1,1]):
|
||||
- r_{E3} = [0,1,1,0] - [1,0,1,1] = [-1,1,0,-1], ||r||^2 = 3
|
||||
|
||||
Energy decreases, indicating better coherence.
|
||||
492
examples/prime-radiant/docs/adr/ADR-002-category-topos.md
Normal file
492
examples/prime-radiant/docs/adr/ADR-002-category-topos.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# ADR-002: Category Theory and Topos-Theoretic Belief Models
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
While sheaf cohomology (ADR-001) provides the foundation for coherence measurement, we need higher-level abstractions for:
|
||||
|
||||
1. **Functorial Retrieval**: Structure-preserving access to knowledge across different representations
|
||||
2. **Belief Dynamics**: Modeling how beliefs change under new evidence
|
||||
3. **Higher Coherence Laws**: Ensuring consistency not just of facts, but of relationships between facts
|
||||
4. **Intuitionistic Logic**: Handling partial or uncertain knowledge appropriately
|
||||
|
||||
Category theory provides the language for these abstractions, and topos theory extends this to handle logic and set-like constructions in coherent ways.
|
||||
|
||||
### Why Category Theory?
|
||||
|
||||
Category theory is the mathematics of structure and structure-preserving maps. It provides:
|
||||
|
||||
1. **Functors**: Maps between categories that preserve structure
|
||||
2. **Natural Transformations**: Maps between functors that preserve relationships
|
||||
3. **Limits and Colimits**: Universal constructions for combining and decomposing data
|
||||
4. **Adjunctions**: Fundamental optimization principles
|
||||
|
||||
### Why Topos Theory?
|
||||
|
||||
A topos is a category that behaves like the category of sets but with a different internal logic. Topoi enable:
|
||||
|
||||
1. **Intuitionistic Logic**: Handle "not provably true" vs "provably false"
|
||||
2. **Subobject Classifiers**: Generalized truth values beyond {true, false}
|
||||
3. **Internal Languages**: Reason about objects using logical syntax
|
||||
4. **Sheaf Semantics**: Interpret sheaves as generalized sets
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement a **functorial retrieval system** with topos-theoretic belief models for coherence management.
|
||||
|
||||
### Mathematical Foundation
|
||||
|
||||
#### Definition: Category of Knowledge Graphs
|
||||
|
||||
Let **KGraph** be the category where:
|
||||
- Objects are knowledge graphs G = (V, E, F) with sheaf structure F
|
||||
- Morphisms are graph homomorphisms that preserve sheaf structure:
|
||||
```
|
||||
phi: G -> G' such that phi_*(F) -> F'
|
||||
```
|
||||
|
||||
#### Definition: Retrieval Functor
|
||||
|
||||
A **retrieval functor** R: Query -> KGraph assigns:
|
||||
- To each query q, a subgraph R(q) of the knowledge base
|
||||
- To each query refinement q -> q', a graph inclusion R(q) -> R(q')
|
||||
|
||||
Functoriality ensures that refining a query gives a consistent subgraph.
|
||||
|
||||
#### Definition: Belief Topos
|
||||
|
||||
The **belief topos** B(G) over a knowledge graph G is the category:
|
||||
- Objects: Belief states (assignments of credences to nodes/edges)
|
||||
- Morphisms: Belief updates under new evidence
|
||||
- Subobject classifier: Omega = [0, 1] (credence values)
|
||||
|
||||
The internal logic is intuitionistic: for a proposition P,
|
||||
- "P is true" means credence(P) = 1
|
||||
- "P is false" means credence(P) = 0
|
||||
- Otherwise, P has partial truth value
|
||||
|
||||
### Implementation Architecture
|
||||
|
||||
#### Functorial Retrieval
|
||||
|
||||
```rust
|
||||
/// A category of knowledge representations
|
||||
pub trait Category {
|
||||
type Object;
|
||||
type Morphism;
|
||||
|
||||
fn identity(obj: &Self::Object) -> Self::Morphism;
|
||||
fn compose(f: &Self::Morphism, g: &Self::Morphism) -> Self::Morphism;
|
||||
}
|
||||
|
||||
/// A functor between categories
|
||||
pub trait Functor<C: Category, D: Category> {
|
||||
fn map_object(&self, obj: &C::Object) -> D::Object;
|
||||
fn map_morphism(&self, mor: &C::Morphism) -> D::Morphism;
|
||||
|
||||
// Functoriality laws (ensured by implementation)
|
||||
// F(id_A) = id_{F(A)}
|
||||
// F(g . f) = F(g) . F(f)
|
||||
}
|
||||
|
||||
/// Query category: queries with refinement morphisms
|
||||
pub struct QueryCategory;
|
||||
|
||||
impl Category for QueryCategory {
|
||||
type Object = Query;
|
||||
type Morphism = QueryRefinement;
|
||||
|
||||
fn identity(q: &Query) -> QueryRefinement {
|
||||
QueryRefinement::identity(q.clone())
|
||||
}
|
||||
|
||||
fn compose(f: &QueryRefinement, g: &QueryRefinement) -> QueryRefinement {
|
||||
QueryRefinement::compose(f, g)
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieval functor from queries to knowledge subgraphs
|
||||
pub struct RetrievalFunctor {
|
||||
knowledge_base: Arc<SheafGraph>,
|
||||
index: VectorIndex,
|
||||
}
|
||||
|
||||
impl Functor<QueryCategory, KGraphCategory> for RetrievalFunctor {
|
||||
fn map_object(&self, query: &Query) -> SheafSubgraph {
|
||||
// Retrieve relevant subgraph for query
|
||||
let node_ids = self.index.search(&query.embedding, query.k);
|
||||
self.knowledge_base.extract_subgraph(&node_ids, query.hops)
|
||||
}
|
||||
|
||||
fn map_morphism(&self, refinement: &QueryRefinement) -> SubgraphInclusion {
|
||||
// Refinement yields inclusion of subgraphs
|
||||
let source = self.map_object(&refinement.source);
|
||||
let target = self.map_object(&refinement.target);
|
||||
SubgraphInclusion::compute(&source, &target)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Natural Transformations
|
||||
|
||||
```rust
|
||||
/// A natural transformation between functors
|
||||
pub trait NaturalTransformation<C, D, F, G>
|
||||
where
|
||||
C: Category,
|
||||
D: Category,
|
||||
F: Functor<C, D>,
|
||||
G: Functor<C, D>,
|
||||
{
|
||||
/// Component at object A: eta_A: F(A) -> G(A)
|
||||
fn component(&self, obj: &C::Object) -> D::Morphism;
|
||||
|
||||
// Naturality: for f: A -> B,
|
||||
// G(f) . eta_A = eta_B . F(f)
|
||||
}
|
||||
|
||||
/// Coherence preservation transformation
|
||||
pub struct CoherencePreservation {
|
||||
source_functor: RetrievalFunctor,
|
||||
target_functor: CoherenceAwareFunctor,
|
||||
}
|
||||
|
||||
impl NaturalTransformation<QueryCategory, KGraphCategory,
|
||||
RetrievalFunctor, CoherenceAwareFunctor>
|
||||
for CoherencePreservation {
|
||||
fn component(&self, query: &Query) -> SubgraphMap {
|
||||
// Transform retrieval into coherence-filtered retrieval
|
||||
let raw_subgraph = self.source_functor.map_object(query);
|
||||
let filtered = self.filter_incoherent_edges(&raw_subgraph);
|
||||
SubgraphMap::new(raw_subgraph, filtered)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Topos-Theoretic Belief Model
|
||||
|
||||
```rust
|
||||
/// A topos of belief states over a knowledge graph
|
||||
pub struct BeliefTopos {
|
||||
graph: Arc<SheafGraph>,
|
||||
/// Credence assignments: node/edge -> [0, 1]
|
||||
credences: HashMap<EntityId, f32>,
|
||||
/// Update history for rollback
|
||||
history: Vec<BeliefUpdate>,
|
||||
}
|
||||
|
||||
/// The subobject classifier Omega
|
||||
pub struct TruthValue(f32);
|
||||
|
||||
impl TruthValue {
|
||||
pub const TRUE: TruthValue = TruthValue(1.0);
|
||||
pub const FALSE: TruthValue = TruthValue(0.0);
|
||||
pub const UNKNOWN: TruthValue = TruthValue(0.5);
|
||||
|
||||
/// Intuitionistic negation: not(p) = p -> FALSE
|
||||
pub fn not(&self) -> TruthValue {
|
||||
if self.0 == 0.0 {
|
||||
TruthValue::TRUE
|
||||
} else {
|
||||
TruthValue::FALSE
|
||||
}
|
||||
}
|
||||
|
||||
/// Intuitionistic conjunction
|
||||
pub fn and(&self, other: &TruthValue) -> TruthValue {
|
||||
TruthValue(self.0.min(other.0))
|
||||
}
|
||||
|
||||
/// Intuitionistic disjunction
|
||||
pub fn or(&self, other: &TruthValue) -> TruthValue {
|
||||
TruthValue(self.0.max(other.0))
|
||||
}
|
||||
|
||||
/// Intuitionistic implication
|
||||
pub fn implies(&self, other: &TruthValue) -> TruthValue {
|
||||
if self.0 <= other.0 {
|
||||
TruthValue::TRUE
|
||||
} else {
|
||||
other.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BeliefTopos {
|
||||
/// Bayesian update under new evidence
|
||||
pub fn update(&mut self, evidence: Evidence) -> BeliefUpdate {
|
||||
let prior = self.credence(evidence.entity);
|
||||
|
||||
// Compute likelihood based on coherence
|
||||
let likelihood = self.compute_likelihood(&evidence);
|
||||
|
||||
// Bayesian update (simplified)
|
||||
let posterior = (prior * likelihood) /
|
||||
(prior * likelihood + (1.0 - prior) * (1.0 - likelihood));
|
||||
|
||||
let update = BeliefUpdate {
|
||||
entity: evidence.entity,
|
||||
prior,
|
||||
posterior,
|
||||
evidence: evidence.clone(),
|
||||
};
|
||||
|
||||
self.credences.insert(evidence.entity, posterior);
|
||||
self.history.push(update.clone());
|
||||
update
|
||||
}
|
||||
|
||||
/// Compute likelihood based on coherence with existing beliefs
|
||||
fn compute_likelihood(&self, evidence: &Evidence) -> f32 {
|
||||
// High coherence with existing beliefs -> high likelihood
|
||||
let subgraph = self.graph.neighborhood(evidence.entity, 2);
|
||||
let energy = subgraph.compute_energy();
|
||||
|
||||
// Convert energy to probability (lower energy = higher likelihood)
|
||||
(-energy / self.temperature()).exp()
|
||||
}
|
||||
|
||||
/// Check if proposition holds in current belief state
|
||||
pub fn holds(&self, prop: &Proposition) -> TruthValue {
|
||||
match prop {
|
||||
Proposition::Atom(entity) => {
|
||||
TruthValue(self.credence(*entity))
|
||||
}
|
||||
Proposition::And(p, q) => {
|
||||
self.holds(p).and(&self.holds(q))
|
||||
}
|
||||
Proposition::Or(p, q) => {
|
||||
self.holds(p).or(&self.holds(q))
|
||||
}
|
||||
Proposition::Implies(p, q) => {
|
||||
self.holds(p).implies(&self.holds(q))
|
||||
}
|
||||
Proposition::Not(p) => {
|
||||
self.holds(p).not()
|
||||
}
|
||||
Proposition::Coherent(region) => {
|
||||
// Region is coherent if energy below threshold
|
||||
let energy = self.graph.region_energy(region);
|
||||
if energy < COHERENCE_THRESHOLD {
|
||||
TruthValue::TRUE
|
||||
} else if energy > INCOHERENCE_THRESHOLD {
|
||||
TruthValue::FALSE
|
||||
} else {
|
||||
TruthValue(1.0 - energy / INCOHERENCE_THRESHOLD)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Higher Category Structure
|
||||
|
||||
For advanced applications, we model **2-morphisms** (relationships between relationships):
|
||||
|
||||
```rust
|
||||
/// A 2-category with objects, 1-morphisms, and 2-morphisms
|
||||
pub trait TwoCategory {
|
||||
type Object;
|
||||
type Morphism1;
|
||||
type Morphism2;
|
||||
|
||||
fn id_1(obj: &Self::Object) -> Self::Morphism1;
|
||||
fn id_2(mor: &Self::Morphism1) -> Self::Morphism2;
|
||||
|
||||
fn compose_1(f: &Self::Morphism1, g: &Self::Morphism1) -> Self::Morphism1;
|
||||
fn compose_2_vertical(
|
||||
alpha: &Self::Morphism2,
|
||||
beta: &Self::Morphism2
|
||||
) -> Self::Morphism2;
|
||||
fn compose_2_horizontal(
|
||||
alpha: &Self::Morphism2,
|
||||
beta: &Self::Morphism2
|
||||
) -> Self::Morphism2;
|
||||
}
|
||||
|
||||
/// Coherence laws form 2-morphisms in the belief 2-category
|
||||
pub struct CoherenceLaw {
|
||||
/// Source belief update sequence
|
||||
source: Vec<BeliefUpdate>,
|
||||
/// Target belief update sequence
|
||||
target: Vec<BeliefUpdate>,
|
||||
/// Witness that they're equivalent
|
||||
witness: CoherenceWitness,
|
||||
}
|
||||
|
||||
impl CoherenceLaw {
|
||||
/// Associativity: (f . g) . h = f . (g . h)
|
||||
pub fn associativity(f: BeliefUpdate, g: BeliefUpdate, h: BeliefUpdate) -> Self {
|
||||
CoherenceLaw {
|
||||
source: vec![f.clone(), g.clone(), h.clone()], // Left-associated
|
||||
target: vec![f, g, h], // Right-associated
|
||||
witness: CoherenceWitness::Associativity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unit law: id . f = f = f . id
|
||||
pub fn left_unit(f: BeliefUpdate) -> Self {
|
||||
CoherenceLaw {
|
||||
source: vec![BeliefUpdate::identity(), f.clone()],
|
||||
target: vec![f],
|
||||
witness: CoherenceWitness::LeftUnit,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Structure Preservation**: Functors ensure retrieval respects knowledge structure
|
||||
2. **Intuitionistic Reasoning**: Handles partial/uncertain knowledge properly
|
||||
3. **Compositionality**: Complex operations built from simple primitives
|
||||
4. **Higher Coherence**: 2-morphisms capture meta-level consistency
|
||||
5. **Belief Dynamics**: Topos semantics enable principled belief update
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Abstraction Overhead**: Category theory requires learning curve
|
||||
2. **Performance Cost**: Functor laws verification has runtime cost
|
||||
3. **Complexity**: 2-categorical structures can be overwhelming
|
||||
4. **Implementation Fidelity**: Ensuring Rust code matches category theory is subtle
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Gradual Adoption**: Use basic functors first, add higher structures as needed
|
||||
2. **Type-Level Enforcement**: Use Rust's type system to enforce laws statically
|
||||
3. **Documentation**: Extensive examples linking code to mathematical concepts
|
||||
4. **Testing**: Property-based tests for categorical laws
|
||||
|
||||
---
|
||||
|
||||
## Mathematical Properties
|
||||
|
||||
### Theorem: Yoneda Lemma
|
||||
|
||||
For a functor F: C -> Set and object A in C:
|
||||
|
||||
```
|
||||
Nat(Hom(A, -), F) ≅ F(A)
|
||||
```
|
||||
|
||||
Natural transformations from a representable functor to F are determined by elements of F(A).
|
||||
|
||||
**Application**: This allows us to reconstruct knowledge graph structure from query patterns.
|
||||
|
||||
### Theorem: Subobject Classifier in Presheaves
|
||||
|
||||
In the topos of presheaves Set^{C^op}:
|
||||
|
||||
```
|
||||
Omega(c) = {sieves on c}
|
||||
```
|
||||
|
||||
The truth values for an object c are sieves (downward-closed collections of morphisms into c).
|
||||
|
||||
**Application**: Partial truth values are determined by how much of the knowledge graph supports a proposition.
|
||||
|
||||
### Theorem: Adjoint Functors Preserve Limits
|
||||
|
||||
If F ⊣ G (F left adjoint to G), then:
|
||||
- F preserves colimits
|
||||
- G preserves limits
|
||||
|
||||
**Application**: Retrieval (right adjoint) preserves finite products of query results.
|
||||
|
||||
---
|
||||
|
||||
## Integration with Sheaf Cohomology
|
||||
|
||||
The belief topos connects to sheaf cohomology:
|
||||
|
||||
```rust
|
||||
/// Coherence as a global section
|
||||
pub fn coherent_section(&self) -> Option<GlobalSection> {
|
||||
// Check if current beliefs form a global section
|
||||
let cohomology_dim = self.graph.cohomology_dimension();
|
||||
|
||||
if cohomology_dim == 0 {
|
||||
Some(self.construct_global_section())
|
||||
} else {
|
||||
None // Obstruction exists
|
||||
}
|
||||
}
|
||||
|
||||
/// Credence from cohomology class
|
||||
pub fn credence_from_cohomology(&self, node: NodeId) -> f32 {
|
||||
// Higher cohomology -> lower credence
|
||||
let local_cohomology = self.graph.local_cohomology(node);
|
||||
1.0 / (1.0 + local_cohomology as f32)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-001: Sheaf Cohomology](ADR-001-sheaf-cohomology.md) - Mathematical foundation
|
||||
- [ADR-003: Homotopy Type Theory](ADR-003-homotopy-type-theory.md) - Higher categories and paths
|
||||
- [ADR-005: Causal Abstraction](ADR-005-causal-abstraction.md) - Causal categories
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Mac Lane, S. (1978). "Categories for the Working Mathematician." Springer.
|
||||
|
||||
2. Lawvere, F.W. & Schanuel, S. (2009). "Conceptual Mathematics." Cambridge University Press.
|
||||
|
||||
3. Goldblatt, R. (1984). "Topoi: The Categorical Analysis of Logic." North-Holland.
|
||||
|
||||
4. Awodey, S. (2010). "Category Theory." Oxford University Press.
|
||||
|
||||
5. Johnstone, P.T. (2002). "Sketches of an Elephant: A Topos Theory Compendium." Oxford University Press.
|
||||
|
||||
6. Spivak, D.I. (2014). "Category Theory for the Sciences." MIT Press.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Category Theory Primer
|
||||
|
||||
### Objects and Morphisms
|
||||
|
||||
A category C consists of:
|
||||
- A collection ob(C) of **objects**
|
||||
- For each pair of objects A, B, a collection Hom(A, B) of **morphisms**
|
||||
- For each object A, an **identity morphism** id_A: A -> A
|
||||
- **Composition**: For f: A -> B and g: B -> C, g . f: A -> C
|
||||
|
||||
Subject to:
|
||||
- Associativity: (h . g) . f = h . (g . f)
|
||||
- Identity: f . id_A = f = id_B . f
|
||||
|
||||
### Functors
|
||||
|
||||
A functor F: C -> D consists of:
|
||||
- An object map: A |-> F(A)
|
||||
- A morphism map: f |-> F(f)
|
||||
|
||||
Subject to:
|
||||
- F(id_A) = id_{F(A)}
|
||||
- F(g . f) = F(g) . F(f)
|
||||
|
||||
### Natural Transformations
|
||||
|
||||
A natural transformation eta: F => G between functors F, G: C -> D consists of:
|
||||
- For each object A in C, a morphism eta_A: F(A) -> G(A)
|
||||
|
||||
Subject to naturality: For f: A -> B,
|
||||
- G(f) . eta_A = eta_B . F(f)
|
||||
539
examples/prime-radiant/docs/adr/ADR-003-homotopy-type-theory.md
Normal file
539
examples/prime-radiant/docs/adr/ADR-003-homotopy-type-theory.md
Normal file
@@ -0,0 +1,539 @@
|
||||
# ADR-003: Homotopy Type Theory for Verified Reasoning
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
AI systems need to reason about equivalences between different representations of knowledge. Traditional approaches struggle with:
|
||||
|
||||
1. **Representation Independence**: Different encodings of the same knowledge should be interchangeable
|
||||
2. **Proof Transfer**: A proof about one structure should apply to equivalent structures
|
||||
3. **Higher Equalities**: Not just equality of objects, but equality of proofs of equality
|
||||
4. **Constructive Reasoning**: Proofs should be computationally meaningful
|
||||
|
||||
Homotopy Type Theory (HoTT) provides a foundation where:
|
||||
- Types are spaces
|
||||
- Terms are points
|
||||
- Equalities are paths
|
||||
- Higher equalities are higher-dimensional paths (homotopies)
|
||||
|
||||
This geometric intuition enables **proof transport**: any property of a structure transfers automatically to equivalent structures.
|
||||
|
||||
### Why HoTT?
|
||||
|
||||
The **Univalence Axiom** in HoTT states:
|
||||
|
||||
```
|
||||
(A ≃ B) ≃ (A = B)
|
||||
```
|
||||
|
||||
Equivalence of types is equivalent to identity of types. This means:
|
||||
- If two knowledge representations are equivalent, they are the same for all purposes
|
||||
- Proofs about one representation apply to the other
|
||||
- Refactoring doesn't break correctness guarantees
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement a **HoTT-inspired reasoning layer** for verified coherence operations with proof transport.
|
||||
|
||||
### Mathematical Foundation
|
||||
|
||||
#### Definition: Path (Identity Type)
|
||||
|
||||
For a type A and terms a, b : A, the **path type** a =_A b represents proofs that a and b are equal.
|
||||
|
||||
A term p : a =_A b is a **path** from a to b.
|
||||
|
||||
#### Definition: Path Induction (J Eliminator)
|
||||
|
||||
Given:
|
||||
- Type family C : (x : A) -> (y : A) -> (x = y) -> Type
|
||||
- Base case c : (x : A) -> C(x, x, refl_x)
|
||||
|
||||
We can construct:
|
||||
- J(C, c) : (x : A) -> (y : A) -> (p : x = y) -> C(x, y, p)
|
||||
|
||||
This means: to prove something about all paths, it suffices to prove it for reflexivity.
|
||||
|
||||
#### Definition: Univalence
|
||||
|
||||
For types A and B, there is an equivalence:
|
||||
|
||||
```
|
||||
ua : (A ≃ B) -> (A = B)
|
||||
```
|
||||
|
||||
with inverse:
|
||||
|
||||
```
|
||||
idtoeqv : (A = B) -> (A ≃ B)
|
||||
```
|
||||
|
||||
such that ua . idtoeqv = id and idtoeqv . ua = id.
|
||||
|
||||
#### Definition: Transport
|
||||
|
||||
Given a path p : a = b and a type family P : A -> Type, we get:
|
||||
|
||||
```
|
||||
transport_P(p) : P(a) -> P(b)
|
||||
```
|
||||
|
||||
This "transports" data along the path.
|
||||
|
||||
### Implementation Architecture
|
||||
|
||||
#### Path Types
|
||||
|
||||
```rust
|
||||
/// A path (proof of equality) between terms
|
||||
pub struct Path<A> {
|
||||
source: A,
|
||||
target: A,
|
||||
/// The actual proof witness (for computational paths)
|
||||
witness: PathWitness,
|
||||
}
|
||||
|
||||
/// Witness types for different kinds of paths
|
||||
pub enum PathWitness {
|
||||
/// Reflexivity: a = a
|
||||
Refl,
|
||||
/// Path from equivalence via univalence
|
||||
Univalence(EquivalenceWitness),
|
||||
/// Composed path: transitivity
|
||||
Compose(Box<PathWitness>, Box<PathWitness>),
|
||||
/// Inverted path: symmetry
|
||||
Inverse(Box<PathWitness>),
|
||||
/// Applied function: ap
|
||||
Ap {
|
||||
function: String,
|
||||
base_path: Box<PathWitness>,
|
||||
},
|
||||
/// Transport witness
|
||||
Transport {
|
||||
family: String,
|
||||
base_path: Box<PathWitness>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<A: Clone + PartialEq> Path<A> {
|
||||
/// Reflexivity path
|
||||
pub fn refl(x: A) -> Self {
|
||||
Path {
|
||||
source: x.clone(),
|
||||
target: x,
|
||||
witness: PathWitness::Refl,
|
||||
}
|
||||
}
|
||||
|
||||
/// Symmetry: p : a = b implies p^-1 : b = a
|
||||
pub fn inverse(&self) -> Path<A> {
|
||||
Path {
|
||||
source: self.target.clone(),
|
||||
target: self.source.clone(),
|
||||
witness: PathWitness::Inverse(Box::new(self.witness.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Transitivity: p : a = b and q : b = c implies q . p : a = c
|
||||
pub fn compose(&self, other: &Path<A>) -> Option<Path<A>> {
|
||||
if self.target != other.source {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Path {
|
||||
source: self.source.clone(),
|
||||
target: other.target.clone(),
|
||||
witness: PathWitness::Compose(
|
||||
Box::new(self.witness.clone()),
|
||||
Box::new(other.witness.clone()),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Type Families and Transport
|
||||
|
||||
```rust
|
||||
/// A type family (dependent type)
|
||||
pub trait TypeFamily<A> {
|
||||
type Fiber;
|
||||
|
||||
fn fiber(&self, x: &A) -> Self::Fiber;
|
||||
}
|
||||
|
||||
/// Transport along a path
|
||||
pub struct Transport<P: TypeFamily<A>, A> {
|
||||
family: P,
|
||||
_marker: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<P: TypeFamily<A>, A: Clone> Transport<P, A> {
|
||||
/// Transport data along a path
|
||||
pub fn transport(
|
||||
&self,
|
||||
path: &Path<A>,
|
||||
data: P::Fiber,
|
||||
) -> P::Fiber
|
||||
where
|
||||
P::Fiber: Clone,
|
||||
{
|
||||
match &path.witness {
|
||||
PathWitness::Refl => data,
|
||||
PathWitness::Univalence(equiv) => {
|
||||
// Apply the equivalence map
|
||||
self.apply_equivalence(equiv, data)
|
||||
}
|
||||
PathWitness::Compose(p, q) => {
|
||||
// Transport along p, then along q
|
||||
let mid = self.transport_along_witness(p, data);
|
||||
self.transport_along_witness(q, mid)
|
||||
}
|
||||
PathWitness::Inverse(p) => {
|
||||
// Use inverse of equivalence
|
||||
self.transport_inverse(p, data)
|
||||
}
|
||||
_ => data, // Conservative: identity if unknown
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Equivalences
|
||||
|
||||
```rust
|
||||
/// An equivalence between types A and B
|
||||
pub struct Equivalence<A, B> {
|
||||
/// Forward map
|
||||
pub to: Box<dyn Fn(A) -> B>,
|
||||
/// Backward map
|
||||
pub from: Box<dyn Fn(B) -> A>,
|
||||
/// Witness that from . to ~ id_A
|
||||
pub left_inverse: Homotopy<A>,
|
||||
/// Witness that to . from ~ id_B
|
||||
pub right_inverse: Homotopy<B>,
|
||||
}
|
||||
|
||||
/// A homotopy between functions
|
||||
pub struct Homotopy<A> {
|
||||
/// For each x, a path from f(x) to g(x)
|
||||
component: Box<dyn Fn(A) -> PathWitness>,
|
||||
}
|
||||
|
||||
impl<A: Clone, B: Clone> Equivalence<A, B> {
|
||||
/// Convert to path via univalence
|
||||
pub fn to_path(&self) -> Path<TypeId> {
|
||||
Path {
|
||||
source: TypeId::of::<A>(),
|
||||
target: TypeId::of::<B>(),
|
||||
witness: PathWitness::Univalence(
|
||||
EquivalenceWitness::from_equivalence(self)
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Univalence axiom: (A ≃ B) ≃ (A = B)
|
||||
pub fn univalence<A: 'static, B: 'static>(
|
||||
equiv: Equivalence<A, B>
|
||||
) -> Path<TypeId> {
|
||||
equiv.to_path()
|
||||
}
|
||||
|
||||
/// Inverse of univalence: (A = B) -> (A ≃ B)
|
||||
pub fn idtoeqv<A: Clone, B: Clone>(
|
||||
path: Path<TypeId>
|
||||
) -> Option<Equivalence<A, B>> {
|
||||
match path.witness {
|
||||
PathWitness::Refl => {
|
||||
Some(Equivalence::identity())
|
||||
}
|
||||
PathWitness::Univalence(equiv) => {
|
||||
equiv.to_equivalence()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Higher Paths
|
||||
|
||||
```rust
|
||||
/// A 2-path (homotopy between paths)
|
||||
pub struct Path2<A> {
|
||||
source: Path<A>,
|
||||
target: Path<A>,
|
||||
witness: Path2Witness,
|
||||
}
|
||||
|
||||
/// A 3-path (homotopy between homotopies)
|
||||
pub struct Path3<A> {
|
||||
source: Path2<A>,
|
||||
target: Path2<A>,
|
||||
witness: Path3Witness,
|
||||
}
|
||||
|
||||
impl<A: Clone + PartialEq> Path2<A> {
|
||||
/// Identity 2-path
|
||||
pub fn refl(p: Path<A>) -> Self {
|
||||
Path2 {
|
||||
source: p.clone(),
|
||||
target: p,
|
||||
witness: Path2Witness::Refl,
|
||||
}
|
||||
}
|
||||
|
||||
/// Associativity coherence: (p . q) . r = p . (q . r)
|
||||
pub fn associativity(
|
||||
p: &Path<A>,
|
||||
q: &Path<A>,
|
||||
r: &Path<A>,
|
||||
) -> Option<Path2<A>> {
|
||||
let left = p.compose(q)?.compose(r)?; // (p . q) . r
|
||||
let right = q.compose(r).and_then(|qr| p.compose(&qr))?; // p . (q . r)
|
||||
|
||||
Some(Path2 {
|
||||
source: left,
|
||||
target: right,
|
||||
witness: Path2Witness::Associativity,
|
||||
})
|
||||
}
|
||||
|
||||
/// Unit coherence: refl . p = p = p . refl
|
||||
pub fn left_unit(p: &Path<A>) -> Path2<A> {
|
||||
let refl_composed = Path::refl(p.source.clone()).compose(p).unwrap();
|
||||
Path2 {
|
||||
source: refl_composed,
|
||||
target: p.clone(),
|
||||
witness: Path2Witness::LeftUnit,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Application to Coherence
|
||||
|
||||
```rust
|
||||
/// Coherence property as a type family
|
||||
pub struct CoherenceFamily {
|
||||
threshold: f32,
|
||||
}
|
||||
|
||||
impl TypeFamily<SheafGraph> for CoherenceFamily {
|
||||
type Fiber = CoherenceProof;
|
||||
|
||||
fn fiber(&self, graph: &SheafGraph) -> Self::Fiber {
|
||||
let energy = graph.coherence_energy();
|
||||
if energy < self.threshold {
|
||||
CoherenceProof::Coherent(energy)
|
||||
} else {
|
||||
CoherenceProof::Incoherent(energy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proof that coherence transports along equivalences
|
||||
pub fn coherence_transport<A, B>(
|
||||
equiv: &Equivalence<A, B>,
|
||||
coherence_a: CoherenceProof,
|
||||
) -> CoherenceProof
|
||||
where
|
||||
A: IntoSheafGraph,
|
||||
B: IntoSheafGraph,
|
||||
{
|
||||
// Use univalence to get path
|
||||
let path = equiv.to_path();
|
||||
|
||||
// Transport coherence along path
|
||||
let transport = Transport::new(CoherenceFamily::default());
|
||||
transport.transport(&path, coherence_a)
|
||||
}
|
||||
|
||||
/// Verified refactoring: if A ≃ B and A is coherent, B is coherent
|
||||
pub fn verified_refactor<A, B>(
|
||||
source: A,
|
||||
target: B,
|
||||
equiv: Equivalence<A, B>,
|
||||
proof: CoherenceProof,
|
||||
) -> Result<(B, CoherenceProof), RefactorError>
|
||||
where
|
||||
A: IntoSheafGraph,
|
||||
B: IntoSheafGraph,
|
||||
{
|
||||
// Verify equivalence
|
||||
if !equiv.verify() {
|
||||
return Err(RefactorError::InvalidEquivalence);
|
||||
}
|
||||
|
||||
// Transport proof
|
||||
let transported_proof = coherence_transport(&equiv, proof);
|
||||
|
||||
Ok((target, transported_proof))
|
||||
}
|
||||
```
|
||||
|
||||
### Higher Inductive Types
|
||||
|
||||
```rust
|
||||
/// A circle: base point with a loop
|
||||
pub struct Circle {
|
||||
// Type has one point constructor and one path constructor
|
||||
}
|
||||
|
||||
impl Circle {
|
||||
pub const BASE: Circle = Circle {};
|
||||
|
||||
/// The loop: base = base
|
||||
pub fn loop_path() -> Path<Circle> {
|
||||
Path {
|
||||
source: Circle::BASE,
|
||||
target: Circle::BASE,
|
||||
witness: PathWitness::Loop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recursion principle for circle
|
||||
pub fn circle_rec<X>(
|
||||
base_case: X,
|
||||
loop_case: Path<X>,
|
||||
) -> impl Fn(Circle) -> X {
|
||||
move |_c: Circle| base_case.clone()
|
||||
}
|
||||
|
||||
/// Induction principle for circle
|
||||
pub fn circle_ind<P: TypeFamily<Circle>>(
|
||||
base_case: P::Fiber,
|
||||
loop_case: Path<P::Fiber>,
|
||||
) -> impl Fn(Circle) -> P::Fiber
|
||||
where
|
||||
P::Fiber: Clone,
|
||||
{
|
||||
move |_c: Circle| base_case.clone()
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Proof Transport**: Coherence properties transfer across equivalent representations
|
||||
2. **Representation Independence**: Different encodings are provably equivalent
|
||||
3. **Higher Coherence**: 2-paths and 3-paths capture meta-level consistency
|
||||
4. **Constructive**: All proofs are computationally meaningful
|
||||
5. **Verified Refactoring**: Transform code while preserving correctness
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Complexity**: HoTT concepts require significant learning investment
|
||||
2. **Performance**: Path manipulation has runtime overhead
|
||||
3. **Incompleteness**: Not all equivalences are decidable
|
||||
4. **Engineering Challenge**: Implementing univalence faithfully is hard
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Progressive Disclosure**: Use simple paths first, add complexity as needed
|
||||
2. **Lazy Evaluation**: Compute path witnesses on demand
|
||||
3. **Conservative Transport**: Fall back to identity for unknown paths
|
||||
4. **Extensive Testing**: Property tests verify transport correctness
|
||||
|
||||
---
|
||||
|
||||
## Mathematical Properties
|
||||
|
||||
### Theorem: Transport is Functorial
|
||||
|
||||
For paths p : a = b and q : b = c:
|
||||
|
||||
```
|
||||
transport_P(q . p) = transport_P(q) . transport_P(p)
|
||||
```
|
||||
|
||||
### Theorem: Ap Commutes with Composition
|
||||
|
||||
For f : A -> B and paths p : a = a', q : a' = a'':
|
||||
|
||||
```
|
||||
ap_f(q . p) = ap_f(q) . ap_f(p)
|
||||
```
|
||||
|
||||
### Theorem: Function Extensionality
|
||||
|
||||
For functions f, g : A -> B:
|
||||
|
||||
```
|
||||
(f = g) ≃ ((x : A) -> f(x) = g(x))
|
||||
```
|
||||
|
||||
Two functions are equal iff they're pointwise equal.
|
||||
|
||||
### Theorem: Univalence Implies Function Extensionality
|
||||
|
||||
Univalence implies the above, making it a "master" axiom for equality.
|
||||
|
||||
---
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-001: Sheaf Cohomology](ADR-001-sheaf-cohomology.md) - Cohomology as path obstructions
|
||||
- [ADR-002: Category Theory](ADR-002-category-topos.md) - Categories as infinity-groupoids
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Univalent Foundations Program. (2013). "Homotopy Type Theory: Univalent Foundations of Mathematics." Institute for Advanced Study.
|
||||
|
||||
2. Voevodsky, V. (2010). "Univalent Foundations." Talk at IAS.
|
||||
|
||||
3. Awodey, S., & Warren, M. (2009). "Homotopy Theoretic Models of Identity Types." Mathematical Proceedings of the Cambridge Philosophical Society.
|
||||
|
||||
4. Shulman, M. (2015). "Brouwer's Fixed-Point Theorem in Real-Cohesive Homotopy Type Theory."
|
||||
|
||||
5. Rijke, E. (2022). "Introduction to Homotopy Type Theory." arXiv.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: HoTT Computation Rules
|
||||
|
||||
### Beta Rule for Path Induction
|
||||
|
||||
```
|
||||
J(C, c, a, a, refl_a) = c(a)
|
||||
```
|
||||
|
||||
Path induction on reflexivity returns the base case.
|
||||
|
||||
### Computation for Transport
|
||||
|
||||
```
|
||||
transport_P(refl_a, x) = x
|
||||
```
|
||||
|
||||
Transporting along reflexivity is identity.
|
||||
|
||||
### Computation for Ap
|
||||
|
||||
```
|
||||
ap_f(refl_a) = refl_{f(a)}
|
||||
```
|
||||
|
||||
Applying a function to reflexivity gives reflexivity.
|
||||
|
||||
### Univalence Computation
|
||||
|
||||
```
|
||||
transport_{P}(ua(e), x) = e.to(x)
|
||||
```
|
||||
|
||||
Transporting along a univalence path applies the equivalence.
|
||||
320
examples/prime-radiant/docs/adr/ADR-004-spectral-invariants.md
Normal file
320
examples/prime-radiant/docs/adr/ADR-004-spectral-invariants.md
Normal file
@@ -0,0 +1,320 @@
|
||||
# ADR-004: Spectral Invariants for Representation Analysis
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Neural network representations form high-dimensional vector spaces where geometric and spectral properties encode semantic meaning. Understanding these representations requires mathematical tools that can:
|
||||
|
||||
1. **Extract invariant features**: Properties preserved under transformations
|
||||
2. **Detect representation quality**: Distinguish good embeddings from degenerate ones
|
||||
3. **Track representation evolution**: Monitor how representations change during training
|
||||
4. **Compare representations**: Measure similarity between different models
|
||||
|
||||
Traditional approaches focus on:
|
||||
- Cosine similarity (ignores global structure)
|
||||
- t-SNE/UMAP (non-linear, non-invertible projections)
|
||||
- Probing classifiers (task-specific, not general)
|
||||
|
||||
We need invariants that are mathematically well-defined and computationally tractable.
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement **spectral invariants** based on eigenvalue analysis of representation matrices, covariance structures, and graph Laplacians.
|
||||
|
||||
### Core Spectral Invariants
|
||||
|
||||
#### 1. Eigenvalue Spectrum
|
||||
|
||||
For a representation matrix X (n samples x d dimensions):
|
||||
|
||||
```rust
|
||||
/// Compute eigenvalue spectrum of covariance matrix
|
||||
pub struct EigenvalueSpectrum {
|
||||
/// Eigenvalues in descending order
|
||||
pub eigenvalues: Vec<f64>,
|
||||
/// Cumulative explained variance
|
||||
pub cumulative_variance: Vec<f64>,
|
||||
/// Effective dimensionality
|
||||
pub effective_dim: f64,
|
||||
}
|
||||
|
||||
impl EigenvalueSpectrum {
|
||||
pub fn from_covariance(cov: &DMatrix<f64>) -> Result<Self> {
|
||||
let eigen = cov.symmetric_eigenvalues();
|
||||
let mut eigenvalues: Vec<f64> = eigen.iter().cloned().collect();
|
||||
eigenvalues.sort_by(|a, b| b.partial_cmp(a).unwrap());
|
||||
|
||||
let total: f64 = eigenvalues.iter().sum();
|
||||
let cumulative_variance: Vec<f64> = eigenvalues.iter()
|
||||
.scan(0.0, |acc, &x| {
|
||||
*acc += x / total;
|
||||
Some(*acc)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Effective dimensionality via participation ratio
|
||||
let sum_sq: f64 = eigenvalues.iter().map(|x| x * x).sum();
|
||||
let effective_dim = (total * total) / sum_sq;
|
||||
|
||||
Ok(Self { eigenvalues, cumulative_variance, effective_dim })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Spectral Gap
|
||||
|
||||
The spectral gap measures separation between clusters:
|
||||
|
||||
```rust
|
||||
/// Spectral gap analysis
|
||||
pub struct SpectralGap {
|
||||
/// Gap between first and second eigenvalues
|
||||
pub primary_gap: f64,
|
||||
/// Normalized gap (invariant to scale)
|
||||
pub normalized_gap: f64,
|
||||
/// Location of largest gap in spectrum
|
||||
pub largest_gap_index: usize,
|
||||
}
|
||||
|
||||
impl SpectralGap {
|
||||
pub fn from_eigenvalues(eigenvalues: &[f64]) -> Self {
|
||||
let gaps: Vec<f64> = eigenvalues.windows(2)
|
||||
.map(|w| w[0] - w[1])
|
||||
.collect();
|
||||
|
||||
let largest_gap_index = gaps.iter()
|
||||
.enumerate()
|
||||
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap())
|
||||
.map(|(i, _)| i)
|
||||
.unwrap_or(0);
|
||||
|
||||
let primary_gap = gaps.first().copied().unwrap_or(0.0);
|
||||
let normalized_gap = primary_gap / eigenvalues[0].max(1e-10);
|
||||
|
||||
Self { primary_gap, normalized_gap, largest_gap_index }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Condition Number
|
||||
|
||||
Measures numerical stability of representations:
|
||||
|
||||
```rust
|
||||
/// Condition number for representation stability
|
||||
pub fn condition_number(eigenvalues: &[f64]) -> f64 {
|
||||
let max_eig = eigenvalues.first().copied().unwrap_or(1.0);
|
||||
let min_eig = eigenvalues.last().copied().unwrap_or(1e-10).max(1e-10);
|
||||
max_eig / min_eig
|
||||
}
|
||||
```
|
||||
|
||||
### Graph Laplacian Spectrum
|
||||
|
||||
For representation similarity graphs:
|
||||
|
||||
```rust
|
||||
/// Laplacian spectral analysis
|
||||
pub struct LaplacianSpectrum {
|
||||
/// Number of connected components (multiplicity of 0 eigenvalue)
|
||||
pub num_components: usize,
|
||||
/// Fiedler value (second smallest eigenvalue)
|
||||
pub fiedler_value: f64,
|
||||
/// Cheeger constant bound
|
||||
pub cheeger_bound: (f64, f64),
|
||||
}
|
||||
|
||||
impl LaplacianSpectrum {
|
||||
pub fn from_graph(adjacency: &DMatrix<f64>) -> Self {
|
||||
// Compute degree matrix
|
||||
let degrees = adjacency.row_sum();
|
||||
let degree_matrix = DMatrix::from_diagonal(°rees);
|
||||
|
||||
// Laplacian L = D - A
|
||||
let laplacian = °ree_matrix - adjacency;
|
||||
|
||||
// Compute spectrum
|
||||
let eigen = laplacian.symmetric_eigenvalues();
|
||||
let mut eigenvalues: Vec<f64> = eigen.iter().cloned().collect();
|
||||
eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap());
|
||||
|
||||
// Count zero eigenvalues (connected components)
|
||||
let num_components = eigenvalues.iter()
|
||||
.filter(|&&e| e.abs() < 1e-10)
|
||||
.count();
|
||||
|
||||
let fiedler_value = eigenvalues.get(num_components)
|
||||
.copied()
|
||||
.unwrap_or(0.0);
|
||||
|
||||
// Cheeger inequality bounds
|
||||
let cheeger_lower = fiedler_value / 2.0;
|
||||
let cheeger_upper = (2.0 * fiedler_value).sqrt();
|
||||
|
||||
Self {
|
||||
num_components,
|
||||
fiedler_value,
|
||||
cheeger_bound: (cheeger_lower, cheeger_upper),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Invariant Fingerprints
|
||||
|
||||
Combine spectral invariants into a fingerprint for comparison:
|
||||
|
||||
```rust
|
||||
/// Spectral fingerprint for representation comparison
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SpectralFingerprint {
|
||||
/// Top k eigenvalues (normalized)
|
||||
pub top_eigenvalues: Vec<f64>,
|
||||
/// Effective dimensionality
|
||||
pub effective_dim: f64,
|
||||
/// Condition number (log scale)
|
||||
pub log_condition: f64,
|
||||
/// Spectral entropy
|
||||
pub spectral_entropy: f64,
|
||||
}
|
||||
|
||||
impl SpectralFingerprint {
|
||||
pub fn new(spectrum: &EigenvalueSpectrum, k: usize) -> Self {
|
||||
let total: f64 = spectrum.eigenvalues.iter().sum();
|
||||
let top_eigenvalues: Vec<f64> = spectrum.eigenvalues.iter()
|
||||
.take(k)
|
||||
.map(|e| e / total)
|
||||
.collect();
|
||||
|
||||
// Spectral entropy
|
||||
let probs: Vec<f64> = spectrum.eigenvalues.iter()
|
||||
.map(|e| e / total)
|
||||
.filter(|&p| p > 1e-10)
|
||||
.collect();
|
||||
let spectral_entropy: f64 = -probs.iter()
|
||||
.map(|p| p * p.ln())
|
||||
.sum::<f64>();
|
||||
|
||||
Self {
|
||||
top_eigenvalues,
|
||||
effective_dim: spectrum.effective_dim,
|
||||
log_condition: condition_number(&spectrum.eigenvalues).ln(),
|
||||
spectral_entropy,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare two fingerprints
|
||||
pub fn distance(&self, other: &Self) -> f64 {
|
||||
let eigenvalue_dist: f64 = self.top_eigenvalues.iter()
|
||||
.zip(other.top_eigenvalues.iter())
|
||||
.map(|(a, b)| (a - b).powi(2))
|
||||
.sum::<f64>()
|
||||
.sqrt();
|
||||
|
||||
let dim_diff = (self.effective_dim - other.effective_dim).abs();
|
||||
let cond_diff = (self.log_condition - other.log_condition).abs();
|
||||
let entropy_diff = (self.spectral_entropy - other.spectral_entropy).abs();
|
||||
|
||||
// Weighted combination
|
||||
eigenvalue_dist + 0.1 * dim_diff + 0.05 * cond_diff + 0.1 * entropy_diff
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Mathematically rigorous**: Based on linear algebra with well-understood properties
|
||||
2. **Computationally efficient**: SVD/eigendecomposition is O(d^3) but highly optimized
|
||||
3. **Invariant to orthogonal transformations**: Eigenvalues don't change under rotation
|
||||
4. **Interpretable**: Effective dimensionality, spectral gap have clear meanings
|
||||
5. **Composable**: Can combine multiple invariants into fingerprints
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Not invariant to non-orthogonal transforms**: Scaling changes condition number
|
||||
2. **Requires full spectrum**: Approximations lose information
|
||||
3. **Sensitive to outliers**: Single extreme point can dominate covariance
|
||||
4. **Memory intensive**: Storing covariance matrices is O(d^2)
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Normalization**: Pre-normalize representations to unit variance
|
||||
2. **Lanczos iteration**: Compute only top-k eigenvalues for large d
|
||||
3. **Robust covariance**: Use median-of-means or trimmed estimators
|
||||
4. **Streaming updates**: Maintain running covariance estimates
|
||||
|
||||
---
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### Lanczos Algorithm for Large Matrices
|
||||
|
||||
```rust
|
||||
/// Compute top-k eigenvalues using Lanczos iteration
|
||||
pub fn lanczos_eigenvalues(
|
||||
matrix: &DMatrix<f64>,
|
||||
k: usize,
|
||||
max_iter: usize,
|
||||
) -> Vec<f64> {
|
||||
let n = matrix.nrows();
|
||||
let k = k.min(n);
|
||||
|
||||
// Initialize with random vector
|
||||
let mut v = DVector::from_fn(n, |_, _| rand::random::<f64>());
|
||||
v.normalize_mut();
|
||||
|
||||
let mut alpha = Vec::with_capacity(max_iter);
|
||||
let mut beta = Vec::with_capacity(max_iter);
|
||||
let mut v_prev = DVector::zeros(n);
|
||||
|
||||
for i in 0..max_iter {
|
||||
let w = matrix * &v;
|
||||
let a = v.dot(&w);
|
||||
alpha.push(a);
|
||||
|
||||
let mut w = w - a * &v - if i > 0 { beta[i-1] * &v_prev } else { DVector::zeros(n) };
|
||||
let b = w.norm();
|
||||
|
||||
if b < 1e-10 { break; }
|
||||
beta.push(b);
|
||||
|
||||
v_prev = v.clone();
|
||||
v = w / b;
|
||||
}
|
||||
|
||||
// Build tridiagonal matrix and compute eigenvalues
|
||||
tridiagonal_eigenvalues(&alpha, &beta, k)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Decisions
|
||||
|
||||
- [ADR-001: Sheaf Cohomology](ADR-001-sheaf-cohomology.md) - Uses spectral gap for coherence
|
||||
- [ADR-002: Category Theory](ADR-002-category-topos.md) - Spectral invariants as functors
|
||||
- [ADR-006: Quantum Topology](ADR-006-quantum-topology.md) - Density matrix eigenvalues
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Belkin, M., & Niyogi, P. (2003). "Laplacian Eigenmaps for Dimensionality Reduction." Neural Computation.
|
||||
|
||||
2. Von Luxburg, U. (2007). "A Tutorial on Spectral Clustering." Statistics and Computing.
|
||||
|
||||
3. Roy, O., & Vetterli, M. (2007). "The Effective Rank: A Measure of Effective Dimensionality." EUSIPCO.
|
||||
|
||||
4. Kornblith, S., et al. (2019). "Similarity of Neural Network Representations Revisited." ICML.
|
||||
343
examples/prime-radiant/docs/adr/ADR-005-causal-abstraction.md
Normal file
343
examples/prime-radiant/docs/adr/ADR-005-causal-abstraction.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# ADR-005: Causal Abstraction for Mechanistic Interpretability
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
Understanding *why* neural networks produce their outputs requires more than correlation analysis. We need:
|
||||
|
||||
1. **Causal mechanisms**: Which components actually cause specific behaviors
|
||||
2. **Interventional reasoning**: What happens when we modify internal states
|
||||
3. **Abstraction levels**: How low-level computations relate to high-level concepts
|
||||
4. **Alignment verification**: Whether learned mechanisms match intended behavior
|
||||
|
||||
Traditional interpretability approaches provide:
|
||||
- Attention visualization (correlational, not causal)
|
||||
- Gradient-based attribution (local approximations)
|
||||
- Probing classifiers (detect presence, not causation)
|
||||
|
||||
These fail to distinguish "correlates with output" from "causes output."
|
||||
|
||||
### Why Causal Abstraction?
|
||||
|
||||
Causal abstraction theory (Geiger et al., 2021) provides a rigorous framework for:
|
||||
|
||||
1. **Defining interpretations**: Mapping neural computations to high-level concepts
|
||||
2. **Testing interpretations**: Using interventions to verify causal structure
|
||||
3. **Measuring alignment**: Quantifying how well neural mechanisms match intended algorithms
|
||||
4. **Localizing circuits**: Finding minimal subnetworks that implement behaviors
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement **causal abstraction** as the foundation for mechanistic interpretability in Prime-Radiant.
|
||||
|
||||
### Core Concepts
|
||||
|
||||
#### 1. Causal Models
|
||||
|
||||
```rust
|
||||
/// A causal model with variables and structural equations
|
||||
pub struct CausalModel {
|
||||
/// Variable nodes
|
||||
variables: HashMap<VariableId, Variable>,
|
||||
/// Directed edges (cause -> effect)
|
||||
edges: HashSet<(VariableId, VariableId)>,
|
||||
/// Structural equations: V = f(Pa(V), noise)
|
||||
equations: HashMap<VariableId, StructuralEquation>,
|
||||
/// Exogenous noise distributions
|
||||
noise: HashMap<VariableId, NoiseDistribution>,
|
||||
}
|
||||
|
||||
/// A variable in the causal model
|
||||
pub struct Variable {
|
||||
pub id: VariableId,
|
||||
pub name: String,
|
||||
pub domain: VariableDomain,
|
||||
pub level: AbstractionLevel,
|
||||
}
|
||||
|
||||
/// Structural equation defining variable's value
|
||||
pub enum StructuralEquation {
|
||||
/// f(inputs) -> output
|
||||
Function(Box<dyn Fn(&[Value]) -> Value>),
|
||||
/// Neural network component
|
||||
Neural(NeuralComponent),
|
||||
/// Identity (exogenous variable)
|
||||
Exogenous,
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Interventions
|
||||
|
||||
```rust
|
||||
/// An intervention on a causal model
|
||||
pub enum Intervention {
|
||||
/// Set variable to constant value: do(X = x)
|
||||
Hard(VariableId, Value),
|
||||
/// Modify value by function: do(X = f(X))
|
||||
Soft(VariableId, Box<dyn Fn(Value) -> Value>),
|
||||
/// Interchange values between runs
|
||||
Interchange(VariableId, SourceId),
|
||||
/// Activation patching
|
||||
Patch(VariableId, Vec<f32>),
|
||||
}
|
||||
|
||||
impl CausalModel {
|
||||
/// Apply intervention and compute effects
|
||||
pub fn intervene(&self, intervention: &Intervention) -> CausalModel {
|
||||
let mut modified = self.clone();
|
||||
match intervention {
|
||||
Intervention::Hard(var, value) => {
|
||||
// Remove all incoming edges
|
||||
modified.edges.retain(|(_, target)| target != var);
|
||||
// Set constant equation
|
||||
modified.equations.insert(*var, StructuralEquation::constant(*value));
|
||||
}
|
||||
Intervention::Soft(var, f) => {
|
||||
// Compose with existing equation
|
||||
let old_eq = modified.equations.get(var).unwrap();
|
||||
modified.equations.insert(*var, old_eq.compose(f));
|
||||
}
|
||||
// ...
|
||||
}
|
||||
modified
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Causal Abstraction
|
||||
|
||||
```rust
|
||||
/// A causal abstraction between two models
|
||||
pub struct CausalAbstraction {
|
||||
/// Low-level (concrete) model
|
||||
low: CausalModel,
|
||||
/// High-level (abstract) model
|
||||
high: CausalModel,
|
||||
/// Variable mapping: low -> high
|
||||
tau: HashMap<VariableId, VariableId>,
|
||||
/// Intervention mapping
|
||||
intervention_map: Box<dyn Fn(&Intervention) -> Intervention>,
|
||||
}
|
||||
|
||||
impl CausalAbstraction {
|
||||
/// Check if abstraction is valid (interventional consistency)
|
||||
pub fn is_valid(&self, test_interventions: &[Intervention]) -> bool {
|
||||
for intervention in test_interventions {
|
||||
// Map intervention to high level
|
||||
let high_intervention = (self.intervention_map)(intervention);
|
||||
|
||||
// Intervene on both models
|
||||
let low_result = self.low.intervene(intervention);
|
||||
let high_result = self.high.intervene(&high_intervention);
|
||||
|
||||
// Check outputs match (up to tau)
|
||||
let low_output = low_result.output();
|
||||
let high_output = high_result.output();
|
||||
|
||||
if !self.outputs_match(&low_output, &high_output) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Compute interchange intervention accuracy
|
||||
pub fn iia(&self,
|
||||
base_inputs: &[Input],
|
||||
source_inputs: &[Input],
|
||||
target_var: VariableId) -> f64 {
|
||||
let mut correct = 0;
|
||||
let total = base_inputs.len() * source_inputs.len();
|
||||
|
||||
for base in base_inputs {
|
||||
for source in source_inputs {
|
||||
// Run high-level model with intervention
|
||||
let high_base = self.high.run(base);
|
||||
let high_source = self.high.run(source);
|
||||
let high_interchanged = self.high.intervene(
|
||||
&Intervention::Interchange(target_var, high_source.id)
|
||||
).run(base);
|
||||
|
||||
// Run low-level model with corresponding intervention
|
||||
let low_base = self.low.run(base);
|
||||
let low_source = self.low.run(source);
|
||||
let low_intervention = (self.intervention_map)(
|
||||
&Intervention::Interchange(self.tau[&target_var], low_source.id)
|
||||
);
|
||||
let low_interchanged = self.low.intervene(&low_intervention).run(base);
|
||||
|
||||
// Check if behaviors match
|
||||
if self.outputs_match(&low_interchanged, &high_interchanged) {
|
||||
correct += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
correct as f64 / total as f64
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Activation Patching
|
||||
|
||||
```rust
|
||||
/// Activation patching for neural network interpretability
|
||||
pub struct ActivationPatcher {
|
||||
/// Target layer/component
|
||||
target: NeuralComponent,
|
||||
/// Patch source
|
||||
source: PatchSource,
|
||||
}
|
||||
|
||||
pub enum PatchSource {
|
||||
/// From another input's activation
|
||||
OtherInput(InputId),
|
||||
/// Fixed vector
|
||||
Fixed(Vec<f32>),
|
||||
/// Noise ablation
|
||||
Noise(NoiseDistribution),
|
||||
/// Mean ablation
|
||||
Mean,
|
||||
/// Zero ablation
|
||||
Zero,
|
||||
}
|
||||
|
||||
impl ActivationPatcher {
|
||||
/// Measure causal effect of patching
|
||||
pub fn causal_effect(
|
||||
&self,
|
||||
model: &NeuralNetwork,
|
||||
base_input: &Input,
|
||||
metric: &Metric,
|
||||
) -> f64 {
|
||||
// Run without patching
|
||||
let base_output = model.forward(base_input);
|
||||
let base_metric = metric.compute(&base_output);
|
||||
|
||||
// Run with patching
|
||||
let patched_output = model.forward_with_patch(base_input, self);
|
||||
let patched_metric = metric.compute(&patched_output);
|
||||
|
||||
// Causal effect is the difference
|
||||
patched_metric - base_metric
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Circuit Discovery
|
||||
|
||||
```rust
|
||||
/// Discover minimal circuits implementing a behavior
|
||||
pub struct CircuitDiscovery {
|
||||
/// Target behavior to explain
|
||||
behavior: Behavior,
|
||||
/// Candidate components
|
||||
components: Vec<NeuralComponent>,
|
||||
/// Discovered circuits
|
||||
circuits: Vec<Circuit>,
|
||||
}
|
||||
|
||||
pub struct Circuit {
|
||||
/// Components in the circuit
|
||||
components: Vec<NeuralComponent>,
|
||||
/// Edges (data flow)
|
||||
edges: Vec<(NeuralComponent, NeuralComponent)>,
|
||||
/// Faithfulness score (how well circuit explains behavior)
|
||||
faithfulness: f64,
|
||||
/// Completeness score (how much of behavior is captured)
|
||||
completeness: f64,
|
||||
}
|
||||
|
||||
impl CircuitDiscovery {
|
||||
/// Use activation patching to find important components
|
||||
pub fn find_circuit(&mut self, model: &NeuralNetwork, inputs: &[Input]) -> Circuit {
|
||||
let mut important = Vec::new();
|
||||
|
||||
// Test each component
|
||||
for component in &self.components {
|
||||
let patcher = ActivationPatcher {
|
||||
target: component.clone(),
|
||||
source: PatchSource::Zero,
|
||||
};
|
||||
|
||||
let avg_effect: f64 = inputs.iter()
|
||||
.map(|input| patcher.causal_effect(model, input, &self.behavior.metric))
|
||||
.sum::<f64>() / inputs.len() as f64;
|
||||
|
||||
if avg_effect.abs() > IMPORTANCE_THRESHOLD {
|
||||
important.push((component.clone(), avg_effect));
|
||||
}
|
||||
}
|
||||
|
||||
// Build circuit from important components
|
||||
self.build_circuit(important)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Rigorous causality**: Distinguishes correlation from causation
|
||||
2. **Multi-level analysis**: Connects low-level activations to high-level concepts
|
||||
3. **Testable interpretations**: Interventions provide empirical verification
|
||||
4. **Circuit localization**: Identifies minimal subnetworks for behaviors
|
||||
5. **Alignment checking**: Verifies mechanisms match specifications
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Combinatorial explosion**: Testing all interventions is exponential
|
||||
2. **Approximation required**: Full causal analysis is computationally intractable
|
||||
3. **Abstraction design**: Choosing the right high-level model requires insight
|
||||
4. **Noise sensitivity**: Small variations can affect intervention outcomes
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Importance sampling**: Focus on high-impact interventions
|
||||
2. **Hierarchical search**: Use coarse-to-fine circuit discovery
|
||||
3. **Learned abstractions**: Train models to find good variable mappings
|
||||
4. **Robust statistics**: Use multiple samples and statistical tests
|
||||
|
||||
---
|
||||
|
||||
## Integration with Prime-Radiant
|
||||
|
||||
### Connection to Sheaf Cohomology
|
||||
|
||||
Causal structure forms a sheaf:
|
||||
- Open sets: Subnetworks
|
||||
- Sections: Causal mechanisms
|
||||
- Restriction maps: Marginalization
|
||||
- Cohomology: Obstruction to global causal explanation
|
||||
|
||||
### Connection to Category Theory
|
||||
|
||||
Causal abstraction is a functor:
|
||||
- Objects: Causal models
|
||||
- Morphisms: Interventional maps
|
||||
- Composition: Hierarchical abstraction
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Geiger, A., et al. (2021). "Causal Abstractions of Neural Networks." NeurIPS.
|
||||
|
||||
2. Pearl, J. (2009). "Causality: Models, Reasoning, and Inference." Cambridge.
|
||||
|
||||
3. Conmy, A., et al. (2023). "Towards Automated Circuit Discovery." NeurIPS.
|
||||
|
||||
4. Wang, K., et al. (2022). "Interpretability in the Wild." ICLR.
|
||||
|
||||
5. Goldowsky-Dill, N., et al. (2023). "Localizing Model Behavior with Path Patching." arXiv.
|
||||
451
examples/prime-radiant/docs/adr/ADR-006-quantum-topology.md
Normal file
451
examples/prime-radiant/docs/adr/ADR-006-quantum-topology.md
Normal file
@@ -0,0 +1,451 @@
|
||||
# ADR-006: Quantum Topology for Representation Analysis
|
||||
|
||||
**Status**: Accepted
|
||||
**Date**: 2024-12-15
|
||||
**Authors**: RuVector Team
|
||||
**Supersedes**: None
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
High-dimensional neural network representations exhibit complex geometric and topological structure that classical methods struggle to capture. We need tools that can:
|
||||
|
||||
1. **Handle superpositions**: Representations often encode multiple concepts simultaneously
|
||||
2. **Measure entanglement**: Detect non-local correlations between features
|
||||
3. **Track topological invariants**: Identify persistent structural properties
|
||||
4. **Model uncertainty**: Represent distributional properties of activations
|
||||
|
||||
Quantum-inspired methods offer advantages because:
|
||||
- Superposition naturally models polysemy and context-dependence
|
||||
- Entanglement captures feature interactions beyond correlation
|
||||
- Density matrices provide natural uncertainty representation
|
||||
- Topological quantum invariants are robust to noise
|
||||
|
||||
---
|
||||
|
||||
## Decision
|
||||
|
||||
We implement **quantum topology** methods for advanced representation analysis, including density matrix representations, entanglement measures, and topological invariants.
|
||||
|
||||
### Core Structures
|
||||
|
||||
#### 1. Quantum State Representation
|
||||
|
||||
```rust
|
||||
use num_complex::Complex64;
|
||||
|
||||
/// A quantum state representing neural activations
|
||||
pub struct QuantumState {
|
||||
/// Amplitudes in computational basis
|
||||
amplitudes: Vec<Complex64>,
|
||||
/// Number of qubits (log2 of dimension)
|
||||
num_qubits: usize,
|
||||
}
|
||||
|
||||
impl QuantumState {
|
||||
/// Create from real activation vector (amplitude encoding)
|
||||
pub fn from_activations(activations: &[f64]) -> Self {
|
||||
let n = activations.len();
|
||||
let num_qubits = (n as f64).log2().ceil() as usize;
|
||||
let dim = 1 << num_qubits;
|
||||
|
||||
// Normalize
|
||||
let norm: f64 = activations.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
|
||||
let mut amplitudes = vec![Complex64::new(0.0, 0.0); dim];
|
||||
for (i, &a) in activations.iter().enumerate() {
|
||||
amplitudes[i] = Complex64::new(a / norm, 0.0);
|
||||
}
|
||||
|
||||
Self { amplitudes, num_qubits }
|
||||
}
|
||||
|
||||
/// Inner product (fidelity for pure states)
|
||||
pub fn fidelity(&self, other: &Self) -> f64 {
|
||||
let inner: Complex64 = self.amplitudes.iter()
|
||||
.zip(other.amplitudes.iter())
|
||||
.map(|(a, b)| a.conj() * b)
|
||||
.sum();
|
||||
inner.norm_sqr()
|
||||
}
|
||||
|
||||
/// Convert to density matrix
|
||||
pub fn to_density_matrix(&self) -> DensityMatrix {
|
||||
let dim = self.amplitudes.len();
|
||||
let mut rho = vec![vec![Complex64::new(0.0, 0.0); dim]; dim];
|
||||
|
||||
for i in 0..dim {
|
||||
for j in 0..dim {
|
||||
rho[i][j] = self.amplitudes[i] * self.amplitudes[j].conj();
|
||||
}
|
||||
}
|
||||
|
||||
DensityMatrix { matrix: rho, dim }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Density Matrix
|
||||
|
||||
```rust
|
||||
/// Density matrix for mixed state representation
|
||||
pub struct DensityMatrix {
|
||||
/// The density matrix elements
|
||||
matrix: Vec<Vec<Complex64>>,
|
||||
/// Dimension
|
||||
dim: usize,
|
||||
}
|
||||
|
||||
impl DensityMatrix {
|
||||
/// Create maximally mixed state
|
||||
pub fn maximally_mixed(dim: usize) -> Self {
|
||||
let mut matrix = vec![vec![Complex64::new(0.0, 0.0); dim]; dim];
|
||||
let val = Complex64::new(1.0 / dim as f64, 0.0);
|
||||
for i in 0..dim {
|
||||
matrix[i][i] = val;
|
||||
}
|
||||
Self { matrix, dim }
|
||||
}
|
||||
|
||||
/// From ensemble of pure states
|
||||
pub fn from_ensemble(states: &[(f64, QuantumState)]) -> Self {
|
||||
let dim = states[0].1.amplitudes.len();
|
||||
let mut matrix = vec![vec![Complex64::new(0.0, 0.0); dim]; dim];
|
||||
|
||||
for (prob, state) in states {
|
||||
let rho = state.to_density_matrix();
|
||||
for i in 0..dim {
|
||||
for j in 0..dim {
|
||||
matrix[i][j] += Complex64::new(*prob, 0.0) * rho.matrix[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { matrix, dim }
|
||||
}
|
||||
|
||||
/// Von Neumann entropy: S(rho) = -Tr(rho log rho)
|
||||
pub fn entropy(&self) -> f64 {
|
||||
let eigenvalues = self.eigenvalues();
|
||||
-eigenvalues.iter()
|
||||
.filter(|&e| *e > 1e-10)
|
||||
.map(|e| e * e.ln())
|
||||
.sum::<f64>()
|
||||
}
|
||||
|
||||
/// Purity: Tr(rho^2)
|
||||
pub fn purity(&self) -> f64 {
|
||||
let mut trace = Complex64::new(0.0, 0.0);
|
||||
for i in 0..self.dim {
|
||||
for k in 0..self.dim {
|
||||
trace += self.matrix[i][k] * self.matrix[k][i];
|
||||
}
|
||||
}
|
||||
trace.re
|
||||
}
|
||||
|
||||
/// Eigenvalues of density matrix
|
||||
pub fn eigenvalues(&self) -> Vec<f64> {
|
||||
// Convert to nalgebra matrix and compute eigenvalues
|
||||
let mut m = DMatrix::zeros(self.dim, self.dim);
|
||||
for i in 0..self.dim {
|
||||
for j in 0..self.dim {
|
||||
m[(i, j)] = self.matrix[i][j].re; // Hermitian, so real eigenvalues
|
||||
}
|
||||
}
|
||||
let eigen = m.symmetric_eigenvalues();
|
||||
eigen.iter().cloned().collect()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Entanglement Measures
|
||||
|
||||
```rust
|
||||
/// Entanglement analysis for bipartite systems
|
||||
pub struct EntanglementAnalysis {
|
||||
/// Subsystem A
|
||||
subsystem_a: Vec<usize>,
|
||||
/// Subsystem B
|
||||
subsystem_b: Vec<usize>,
|
||||
}
|
||||
|
||||
impl EntanglementAnalysis {
|
||||
/// Compute partial trace over subsystem B
|
||||
pub fn partial_trace_b(&self, rho: &DensityMatrix) -> DensityMatrix {
|
||||
let dim_a = 1 << self.subsystem_a.len();
|
||||
let dim_b = 1 << self.subsystem_b.len();
|
||||
|
||||
let mut rho_a = vec![vec![Complex64::new(0.0, 0.0); dim_a]; dim_a];
|
||||
|
||||
for i in 0..dim_a {
|
||||
for j in 0..dim_a {
|
||||
for k in 0..dim_b {
|
||||
let row = i * dim_b + k;
|
||||
let col = j * dim_b + k;
|
||||
rho_a[i][j] += rho.matrix[row][col];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DensityMatrix { matrix: rho_a, dim: dim_a }
|
||||
}
|
||||
|
||||
/// Entanglement entropy: S(rho_A)
|
||||
pub fn entanglement_entropy(&self, rho: &DensityMatrix) -> f64 {
|
||||
let rho_a = self.partial_trace_b(rho);
|
||||
rho_a.entropy()
|
||||
}
|
||||
|
||||
/// Mutual information: I(A:B) = S(A) + S(B) - S(AB)
|
||||
pub fn mutual_information(&self, rho: &DensityMatrix) -> f64 {
|
||||
let rho_a = self.partial_trace_b(rho);
|
||||
let rho_b = self.partial_trace_a(rho);
|
||||
|
||||
rho_a.entropy() + rho_b.entropy() - rho.entropy()
|
||||
}
|
||||
|
||||
/// Concurrence (for 2-qubit systems)
|
||||
pub fn concurrence(&self, rho: &DensityMatrix) -> f64 {
|
||||
if rho.dim != 4 {
|
||||
return 0.0; // Only defined for 2 qubits
|
||||
}
|
||||
|
||||
// Spin-flip matrix
|
||||
let sigma_y = [[Complex64::new(0.0, 0.0), Complex64::new(0.0, -1.0)],
|
||||
[Complex64::new(0.0, 1.0), Complex64::new(0.0, 0.0)]];
|
||||
|
||||
// rho_tilde = (sigma_y x sigma_y) rho* (sigma_y x sigma_y)
|
||||
let rho_tilde = self.spin_flip_transform(rho, &sigma_y);
|
||||
|
||||
// R = rho * rho_tilde
|
||||
let r = self.matrix_multiply(rho, &rho_tilde);
|
||||
|
||||
// Eigenvalues of R
|
||||
let eigenvalues = r.eigenvalues();
|
||||
let mut lambdas: Vec<f64> = eigenvalues.iter()
|
||||
.map(|e| e.sqrt())
|
||||
.collect();
|
||||
lambdas.sort_by(|a, b| b.partial_cmp(a).unwrap());
|
||||
|
||||
// C = max(0, lambda_1 - lambda_2 - lambda_3 - lambda_4)
|
||||
(lambdas[0] - lambdas[1] - lambdas[2] - lambdas[3]).max(0.0)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Topological Invariants
|
||||
|
||||
```rust
|
||||
/// Topological invariants for representation spaces
|
||||
pub struct TopologicalInvariant {
|
||||
/// Type of invariant
|
||||
pub kind: InvariantKind,
|
||||
/// Computed value
|
||||
pub value: f64,
|
||||
/// Confidence/precision
|
||||
pub precision: f64,
|
||||
}
|
||||
|
||||
pub enum InvariantKind {
|
||||
/// Euler characteristic
|
||||
EulerCharacteristic,
|
||||
/// Betti numbers
|
||||
BettiNumber(usize),
|
||||
/// Chern number (for complex bundles)
|
||||
ChernNumber,
|
||||
/// Berry phase
|
||||
BerryPhase,
|
||||
/// Winding number
|
||||
WindingNumber,
|
||||
}
|
||||
|
||||
impl TopologicalInvariant {
|
||||
/// Compute Berry phase around a loop in parameter space
|
||||
pub fn berry_phase(states: &[QuantumState]) -> Self {
|
||||
let n = states.len();
|
||||
let mut phase = Complex64::new(1.0, 0.0);
|
||||
|
||||
for i in 0..n {
|
||||
let next = (i + 1) % n;
|
||||
let overlap: Complex64 = states[i].amplitudes.iter()
|
||||
.zip(states[next].amplitudes.iter())
|
||||
.map(|(a, b)| a.conj() * b)
|
||||
.sum();
|
||||
phase *= overlap;
|
||||
}
|
||||
|
||||
Self {
|
||||
kind: InvariantKind::BerryPhase,
|
||||
value: phase.arg(),
|
||||
precision: 1e-10,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute winding number from phase function
|
||||
pub fn winding_number(phases: &[f64]) -> Self {
|
||||
let mut total_winding = 0.0;
|
||||
for i in 0..phases.len() {
|
||||
let next = (i + 1) % phases.len();
|
||||
let mut delta = phases[next] - phases[i];
|
||||
|
||||
// Wrap to [-pi, pi]
|
||||
while delta > std::f64::consts::PI { delta -= 2.0 * std::f64::consts::PI; }
|
||||
while delta < -std::f64::consts::PI { delta += 2.0 * std::f64::consts::PI; }
|
||||
|
||||
total_winding += delta;
|
||||
}
|
||||
|
||||
Self {
|
||||
kind: InvariantKind::WindingNumber,
|
||||
value: (total_winding / (2.0 * std::f64::consts::PI)).round(),
|
||||
precision: 1e-6,
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Simplicial Complex for TDA
|
||||
|
||||
```rust
|
||||
/// Simplicial complex for topological data analysis
|
||||
pub struct SimplicialComplex {
|
||||
/// Vertices
|
||||
vertices: Vec<usize>,
|
||||
/// Simplices by dimension
|
||||
simplices: Vec<HashSet<Vec<usize>>>,
|
||||
/// Boundary matrices
|
||||
boundary_maps: Vec<DMatrix<f64>>,
|
||||
}
|
||||
|
||||
impl SimplicialComplex {
|
||||
/// Build Vietoris-Rips complex from point cloud
|
||||
pub fn vietoris_rips(points: &[DVector<f64>], epsilon: f64, max_dim: usize) -> Self {
|
||||
let n = points.len();
|
||||
let vertices: Vec<usize> = (0..n).collect();
|
||||
|
||||
let mut simplices = vec![HashSet::new(); max_dim + 1];
|
||||
|
||||
// 0-simplices (vertices)
|
||||
for i in 0..n {
|
||||
simplices[0].insert(vec![i]);
|
||||
}
|
||||
|
||||
// 1-simplices (edges)
|
||||
for i in 0..n {
|
||||
for j in (i+1)..n {
|
||||
if (&points[i] - &points[j]).norm() <= epsilon {
|
||||
simplices[1].insert(vec![i, j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Higher simplices (clique detection)
|
||||
for dim in 2..=max_dim {
|
||||
for simplex in &simplices[dim - 1] {
|
||||
for v in 0..n {
|
||||
if simplex.contains(&v) { continue; }
|
||||
|
||||
// Check if v is connected to all vertices in simplex
|
||||
let all_connected = simplex.iter().all(|&u| {
|
||||
simplices[1].contains(&vec![u.min(v), u.max(v)])
|
||||
});
|
||||
|
||||
if all_connected {
|
||||
let mut new_simplex = simplex.clone();
|
||||
new_simplex.push(v);
|
||||
new_simplex.sort();
|
||||
simplices[dim].insert(new_simplex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { vertices, simplices, boundary_maps: vec![] }
|
||||
}
|
||||
|
||||
/// Compute Betti numbers
|
||||
pub fn betti_numbers(&self) -> Vec<usize> {
|
||||
self.compute_boundary_maps();
|
||||
|
||||
let mut betti = Vec::new();
|
||||
for k in 0..self.simplices.len() {
|
||||
let kernel_dim = if k < self.boundary_maps.len() {
|
||||
self.kernel_dimension(&self.boundary_maps[k])
|
||||
} else {
|
||||
self.simplices[k].len()
|
||||
};
|
||||
|
||||
let image_dim = if k > 0 && k <= self.boundary_maps.len() {
|
||||
self.image_dimension(&self.boundary_maps[k - 1])
|
||||
} else {
|
||||
0
|
||||
};
|
||||
|
||||
betti.push(kernel_dim.saturating_sub(image_dim));
|
||||
}
|
||||
|
||||
betti
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Consequences
|
||||
|
||||
### Positive
|
||||
|
||||
1. **Rich representation**: Density matrices capture distributional information
|
||||
2. **Entanglement detection**: Identifies non-local feature correlations
|
||||
3. **Topological robustness**: Invariants stable under continuous deformation
|
||||
4. **Quantum advantage**: Some computations exponentially faster
|
||||
5. **Uncertainty modeling**: Natural probabilistic interpretation
|
||||
|
||||
### Negative
|
||||
|
||||
1. **Computational cost**: Density matrices are O(d^2) in memory
|
||||
2. **Classical simulation**: Full quantum benefits require quantum hardware
|
||||
3. **Interpretation complexity**: Quantum concepts less intuitive
|
||||
4. **Limited applicability**: Not all problems benefit from quantum formalism
|
||||
|
||||
### Mitigations
|
||||
|
||||
1. **Low-rank approximations**: Use matrix product states for large systems
|
||||
2. **Tensor networks**: Efficient classical simulation of structured states
|
||||
3. **Hybrid classical-quantum**: Use quantum-inspired methods on classical hardware
|
||||
4. **Domain-specific applications**: Focus on problems with natural quantum structure
|
||||
|
||||
---
|
||||
|
||||
## Integration with Prime-Radiant
|
||||
|
||||
### Connection to Sheaf Cohomology
|
||||
|
||||
Quantum states form a sheaf:
|
||||
- Open sets: Subsystems
|
||||
- Sections: Quantum states
|
||||
- Restriction: Partial trace
|
||||
- Cohomology: Entanglement obstructions
|
||||
|
||||
### Connection to Category Theory
|
||||
|
||||
Quantum mechanics as a dagger category:
|
||||
- Objects: Hilbert spaces
|
||||
- Morphisms: Completely positive maps
|
||||
- Dagger: Adjoint
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
1. Nielsen, M.A., & Chuang, I.L. (2010). "Quantum Computation and Quantum Information." Cambridge.
|
||||
|
||||
2. Carlsson, G. (2009). "Topology and Data." Bulletin of the AMS.
|
||||
|
||||
3. Coecke, B., & Kissinger, A. (2017). "Picturing Quantum Processes." Cambridge.
|
||||
|
||||
4. Schuld, M., & Petruccione, F. (2021). "Machine Learning with Quantum Computers." Springer.
|
||||
|
||||
5. Edelsbrunner, H., & Harer, J. (2010). "Computational Topology." AMS.
|
||||
321
examples/prime-radiant/docs/ddd/domain-model.md
Normal file
321
examples/prime-radiant/docs/ddd/domain-model.md
Normal file
@@ -0,0 +1,321 @@
|
||||
# Prime-Radiant Domain Model
|
||||
|
||||
## Overview
|
||||
|
||||
Prime-Radiant is a mathematical framework for AI interpretability, built on rigorous foundations from algebraic topology, category theory, and quantum mechanics. This document describes the domain model using Domain-Driven Design (DDD) principles.
|
||||
|
||||
---
|
||||
|
||||
## Bounded Contexts
|
||||
|
||||
### 1. Cohomology Context
|
||||
|
||||
**Purpose**: Analyze topological structure of representations and detect coherence failures.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**Sheaf** (Aggregate Root)
|
||||
- Contains: Presheaf, Sections, RestrictionMaps
|
||||
- Invariants: Gluing axioms, locality conditions
|
||||
- Behavior: Compute cohomology, detect obstructions
|
||||
|
||||
**ChainComplex**
|
||||
- Contains: ChainGroups, BoundaryMaps
|
||||
- Invariants: d^2 = 0 (boundary of boundary is zero)
|
||||
- Behavior: Compute homology groups
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `Section`: Data over an open set
|
||||
- `RestrictionMap`: Linear map between stalks
|
||||
- `BettiNumbers`: Topological invariants
|
||||
- `PersistenceDiagram`: Multi-scale topology
|
||||
|
||||
#### Domain Events
|
||||
|
||||
- `CoherenceViolationDetected`: When H^1 is non-trivial
|
||||
- `TopologyChanged`: When underlying graph structure changes
|
||||
- `SectionUpdated`: When local data is modified
|
||||
|
||||
---
|
||||
|
||||
### 2. Category Context
|
||||
|
||||
**Purpose**: Model compositional structure and preserve mathematical properties.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**Category** (Aggregate Root)
|
||||
- Contains: Objects, Morphisms
|
||||
- Invariants: Identity, associativity
|
||||
- Behavior: Compose morphisms, verify laws
|
||||
|
||||
**Topos** (Aggregate Root)
|
||||
- Contains: Category, SubobjectClassifier, Products, Exponentials
|
||||
- Invariants: Finite limits, exponentials exist
|
||||
- Behavior: Internal logic, subobject classification
|
||||
|
||||
#### Entities
|
||||
|
||||
- `Object`: An element of the category
|
||||
- `Morphism`: A transformation between objects
|
||||
- `Functor`: Structure-preserving map between categories
|
||||
- `NaturalTransformation`: Morphism between functors
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `MorphismId`: Unique identifier
|
||||
- `ObjectId`: Unique identifier
|
||||
- `CompositionResult`: Result of morphism composition
|
||||
|
||||
#### Domain Events
|
||||
|
||||
- `MorphismAdded`: New morphism in category
|
||||
- `FunctorApplied`: Functor maps between categories
|
||||
- `CoherenceVerified`: Axioms confirmed
|
||||
|
||||
---
|
||||
|
||||
### 3. HoTT Context (Homotopy Type Theory)
|
||||
|
||||
**Purpose**: Provide type-theoretic foundations for proofs and equivalences.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**TypeUniverse** (Aggregate Root)
|
||||
- Contains: Types, Terms, Judgments
|
||||
- Invariants: Type formation rules
|
||||
- Behavior: Type checking, univalence
|
||||
|
||||
**Path** (Entity)
|
||||
- Properties: Start, End, Homotopy
|
||||
- Invariants: Endpoints match types
|
||||
- Behavior: Concatenation, inversion, transport
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `Type`: A type in the universe
|
||||
- `Term`: An element of a type
|
||||
- `Equivalence`: Bidirectional map with proofs
|
||||
- `IdentityType`: The type of paths between terms
|
||||
|
||||
#### Domain Services
|
||||
|
||||
- `PathInduction`: J-eliminator for paths
|
||||
- `Transport`: Move values along paths
|
||||
- `Univalence`: Equivalence = Identity
|
||||
|
||||
---
|
||||
|
||||
### 4. Spectral Context
|
||||
|
||||
**Purpose**: Analyze eigenvalue structure and spectral invariants.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**SpectralDecomposition** (Aggregate Root)
|
||||
- Contains: Eigenvalues, Eigenvectors
|
||||
- Invariants: Orthogonality, completeness
|
||||
- Behavior: Compute spectrum, effective dimension
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `Eigenspace`: Subspace for eigenvalue
|
||||
- `SpectralGap`: Distance between eigenvalues
|
||||
- `SpectralFingerprint`: Comparison signature
|
||||
- `ConditionNumber`: Numerical stability measure
|
||||
|
||||
#### Domain Services
|
||||
|
||||
- `LanczosIteration`: Efficient eigenvalue computation
|
||||
- `CheegerAnalysis`: Spectral gap and graph cuts
|
||||
|
||||
---
|
||||
|
||||
### 5. Causal Context
|
||||
|
||||
**Purpose**: Implement causal abstraction for mechanistic interpretability.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**CausalModel** (Aggregate Root)
|
||||
- Contains: Variables, Edges, StructuralEquations
|
||||
- Invariants: DAG structure (no cycles)
|
||||
- Behavior: Intervention, counterfactual reasoning
|
||||
|
||||
**CausalAbstraction** (Aggregate Root)
|
||||
- Contains: LowModel, HighModel, VariableMapping
|
||||
- Invariants: Interventional consistency
|
||||
- Behavior: Verify abstraction, compute IIA
|
||||
|
||||
#### Entities
|
||||
|
||||
- `Variable`: A node in the causal graph
|
||||
- `Intervention`: An action on a variable
|
||||
- `Circuit`: Minimal subnetwork for behavior
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `StructuralEquation`: Functional relationship
|
||||
- `InterventionResult`: Outcome of intervention
|
||||
- `AlignmentScore`: How well mechanisms match
|
||||
|
||||
#### Domain Events
|
||||
|
||||
- `InterventionApplied`: Variable was modified
|
||||
- `CircuitDiscovered`: Minimal mechanism found
|
||||
- `AbstractionViolation`: Models disagree under intervention
|
||||
|
||||
---
|
||||
|
||||
### 6. Quantum Context
|
||||
|
||||
**Purpose**: Apply quantum-inspired methods to representation analysis.
|
||||
|
||||
#### Aggregates
|
||||
|
||||
**QuantumState** (Aggregate Root)
|
||||
- Contains: Amplitudes
|
||||
- Invariants: Normalization
|
||||
- Behavior: Measure, evolve, entangle
|
||||
|
||||
**DensityMatrix** (Aggregate Root)
|
||||
- Contains: Matrix elements
|
||||
- Invariants: Positive semi-definite, trace 1
|
||||
- Behavior: Entropy, purity, partial trace
|
||||
|
||||
#### Value Objects
|
||||
|
||||
- `Entanglement`: Correlation measure
|
||||
- `TopologicalInvariant`: Robust property
|
||||
- `BerryPhase`: Geometric phase
|
||||
|
||||
#### Domain Services
|
||||
|
||||
- `EntanglementAnalysis`: Compute entanglement measures
|
||||
- `TDAService`: Topological data analysis
|
||||
|
||||
---
|
||||
|
||||
## Cross-Cutting Concerns
|
||||
|
||||
### Error Handling
|
||||
|
||||
All contexts use a unified error type hierarchy:
|
||||
|
||||
```rust
|
||||
pub enum PrimeRadiantError {
|
||||
Cohomology(CohomologyError),
|
||||
Category(CategoryError),
|
||||
HoTT(HoTTError),
|
||||
Spectral(SpectralError),
|
||||
Causal(CausalError),
|
||||
Quantum(QuantumError),
|
||||
}
|
||||
```
|
||||
|
||||
### Numerical Precision
|
||||
|
||||
- Default epsilon: 1e-10
|
||||
- Configurable per computation
|
||||
- Automatic condition number checking
|
||||
|
||||
### Serialization
|
||||
|
||||
All value objects and aggregates implement:
|
||||
- `serde::Serialize` and `serde::Deserialize`
|
||||
- Custom formats for mathematical objects
|
||||
|
||||
---
|
||||
|
||||
## Context Map
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ Prime-Radiant Core │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Cohomology │────▶│ Category │────▶│ HoTT │ │
|
||||
│ │ Context │ │ Context │ │ Context │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ │ │ │
|
||||
│ ▼ ▼ ▼ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Spectral │────▶│ Causal │────▶│ Quantum │ │
|
||||
│ │ Context │ │ Context │ │ Context │ │
|
||||
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
|
||||
Relationships:
|
||||
─────────────
|
||||
Cohomology ──[U]──▶ Category : Sheaves are presheaves + gluing (Upstream/Downstream)
|
||||
Category ──[U]──▶ HoTT : Categories model type theory
|
||||
Spectral ──[S]──▶ Cohomology: Laplacian eigenvalues for cohomology (Shared Kernel)
|
||||
Causal ──[C]──▶ Category : Causal abstraction as functors (Conformist)
|
||||
Quantum ──[P]──▶ Category : Quantum channels as morphisms (Partnership)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Ubiquitous Language
|
||||
|
||||
| Term | Definition |
|
||||
|------|------------|
|
||||
| **Sheaf** | Assignment of data to open sets satisfying gluing axioms |
|
||||
| **Cohomology** | Measure of obstruction to extending local sections globally |
|
||||
| **Morphism** | Structure-preserving map between objects |
|
||||
| **Functor** | Structure-preserving map between categories |
|
||||
| **Path** | Continuous map from interval, proof of equality in HoTT |
|
||||
| **Equivalence** | Bidirectional map with inverse proofs |
|
||||
| **Spectral Gap** | Difference between consecutive eigenvalues |
|
||||
| **Intervention** | Fixing a variable to a value (do-operator) |
|
||||
| **Entanglement** | Non-local correlation in quantum states |
|
||||
| **Betti Number** | Dimension of homology group |
|
||||
|
||||
---
|
||||
|
||||
## Implementation Guidelines
|
||||
|
||||
### Aggregate Design
|
||||
|
||||
1. Keep aggregates small and focused
|
||||
2. Use value objects for immutable data
|
||||
3. Enforce invariants in aggregate root
|
||||
4. Emit domain events for state changes
|
||||
|
||||
### Repository Pattern
|
||||
|
||||
Each aggregate root has a repository:
|
||||
|
||||
```rust
|
||||
pub trait SheafRepository {
|
||||
fn find_by_id(&self, id: SheafId) -> Option<Sheaf>;
|
||||
fn save(&mut self, sheaf: Sheaf) -> Result<(), Error>;
|
||||
fn find_by_topology(&self, graph: &Graph) -> Vec<Sheaf>;
|
||||
}
|
||||
```
|
||||
|
||||
### Factory Pattern
|
||||
|
||||
Complex aggregates use factories:
|
||||
|
||||
```rust
|
||||
pub struct SheafFactory {
|
||||
pub fn from_neural_network(network: &NeuralNetwork) -> Sheaf;
|
||||
pub fn from_knowledge_graph(kg: &KnowledgeGraph) -> Sheaf;
|
||||
}
|
||||
```
|
||||
|
||||
### Domain Services
|
||||
|
||||
Cross-aggregate operations use services:
|
||||
|
||||
```rust
|
||||
pub struct CoherenceService {
|
||||
pub fn check_global_consistency(sheaf: &Sheaf) -> CoherenceReport;
|
||||
pub fn optimize_sections(sheaf: &mut Sheaf) -> OptimizationResult;
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user