|
| 1 | +# PDF Pages 31-39: Visual Reference Guide |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This directory contains extracted images from the PDF slides "03-sum-element-constraint.key.pdf" (pages 31-39), which detail the clever SparseSet extension with complement tracking for incremental sum computation. |
| 6 | + |
| 7 | +--- |
| 8 | + |
| 9 | +## Pages Extracted |
| 10 | + |
| 11 | +### [page-31-1.png](page-31-1.png) - Introduction to Sum Constraint |
| 12 | + |
| 13 | +**Topics:** |
| 14 | +- Overview of the Sum constraint problem |
| 15 | +- Naive complexity analysis |
| 16 | +- Preview of the incremental approach |
| 17 | + |
| 18 | +--- |
| 19 | + |
| 20 | +### [page-32-1.png](page-32-1.png) - Eager Recomputation (Current Approach) |
| 21 | + |
| 22 | +**Topics:** |
| 23 | +- Traditional full recomputation algorithm |
| 24 | +- Time complexity: O(n) per propagation event |
| 25 | +- Why this is inefficient for large problems |
| 26 | +- Example: Sudoku solver bottleneck |
| 27 | + |
| 28 | +**Key Formula:** |
| 29 | +``` |
| 30 | +min_of_terms = Σᵢ min(xᵢ) ← requires scanning all n variables |
| 31 | +max_of_terms = Σᵢ max(xᵢ) ← requires scanning all n variables |
| 32 | +``` |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +### [page-33-1.png](page-33-1.png) - Incremental Update Strategy |
| 37 | + |
| 38 | +**Topics:** |
| 39 | +- Decomposition principle: `Σᵢ min(xᵢ) = min(xⱼ) + Σᵢ≠ⱼ min(xᵢ)` |
| 40 | +- When `xⱼ` changes, only update component involving `xⱼ` |
| 41 | +- O(1) updates instead of O(n) |
| 42 | + |
| 43 | +**Key Insight:** |
| 44 | +``` |
| 45 | +old_sum = 5 + 3 + 7 + 2 + 4 = 21 |
| 46 | +x₃ changes: 7 → 6 |
| 47 | +new_sum = 21 - 7 + 6 = 20 ← just one subtraction and addition! |
| 48 | +``` |
| 49 | + |
| 50 | +--- |
| 51 | + |
| 52 | +### [page-34-1.png](page-34-1.png) - **SparseSet Extension with Complement** ⭐ CORE ALGORITHM |
| 53 | + |
| 54 | +**Topics:** |
| 55 | +- Extending SparseSet to track complement (removed) elements |
| 56 | +- When complement is smaller than domain, iterate removed values |
| 57 | +- Dual representation: |
| 58 | + - **Active set:** values still in domain |
| 59 | + - **Complement set:** values removed from domain |
| 60 | + |
| 61 | +**Key Structure:** |
| 62 | +``` |
| 63 | +Universe: {1, 2, 3, 4, 5, 6, 7, 8, 9} |
| 64 | +
|
| 65 | +Domain (active): {2, 4, 5, 8} (size=4) |
| 66 | +Complement: {1, 3, 6, 7, 9} (size=5) |
| 67 | +
|
| 68 | +If you're tracking incremental changes and only 1 value was removed: |
| 69 | + Use complement (1 element) instead of domain (4 elements) |
| 70 | + Computation becomes O(1) instead of O(n)! |
| 71 | +``` |
| 72 | + |
| 73 | +**This is the clever trick!** |
| 74 | + |
| 75 | +--- |
| 76 | + |
| 77 | +### [page-35-1.png](page-35-1.png) - Tracking Removed Elements |
| 78 | + |
| 79 | +**Topics:** |
| 80 | +- How to maintain the complement set efficiently |
| 81 | +- Tracking which elements were recently removed |
| 82 | +- When to use complement vs. domain iteration |
| 83 | +- Adaptive strategy based on complement size |
| 84 | + |
| 85 | +**Algorithm:** |
| 86 | +``` |
| 87 | +if (complement_size < domain_size / 2) { |
| 88 | + // Fast path: iterate removed elements |
| 89 | + for removed_val in removed_elements { |
| 90 | + contribution -= get_value(removed_val) |
| 91 | + } |
| 92 | +} else { |
| 93 | + // Normal path: iterate domain |
| 94 | + for val in domain { |
| 95 | + contribution += get_value(val) |
| 96 | + } |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +### [page-36-1.png](page-36-1.png) - Event-Driven Updates |
| 103 | + |
| 104 | +**Topics:** |
| 105 | +- Triggering incremental updates only when variables change |
| 106 | +- Integration with constraint propagation queue |
| 107 | +- Lazy vs. eager evaluation strategies |
| 108 | +- When to recompute complement sums |
| 109 | + |
| 110 | +**Key Concept:** |
| 111 | +``` |
| 112 | +Propagation Queue: |
| 113 | + 1. Variable x₃ changes: update sum incrementally |
| 114 | + 2. Variable x₇ changes: update sum incrementally |
| 115 | + 3. (Each O(1), not O(n)) |
| 116 | +``` |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +### [page-37-1.png](page-37-1.png) - Reverse Propagation (Backpropagation) |
| 121 | + |
| 122 | +**Topics:** |
| 123 | +- Propagating sum constraints back to individual variables |
| 124 | +- Computing bounds for each variable given sum constraints |
| 125 | +- Using precomputed complementary sums |
| 126 | + |
| 127 | +**Formula:** |
| 128 | +``` |
| 129 | +For variable xⱼ: |
| 130 | + min(xⱼ) ≥ sum_min - Σᵢ≠ⱼ max(xᵢ) |
| 131 | + max(xⱼ) ≤ sum_max - Σᵢ≠ⱼ min(xᵢ) |
| 132 | +
|
| 133 | +Using precomputed sums: |
| 134 | + min(xⱼ) ≥ sum_min - sum_of_maxs_except[j] ← O(1) lookup |
| 135 | + max(xⱼ) ≤ sum_max - sum_of_mins_except[j] ← O(1) lookup |
| 136 | +``` |
| 137 | + |
| 138 | +--- |
| 139 | + |
| 140 | +### [page-38-1.png](page-38-1.png) - Integration with Search Tree |
| 141 | + |
| 142 | +**Topics:** |
| 143 | +- Checkpoint/restore mechanism for backtracking |
| 144 | +- Managing cache validity across search tree nodes |
| 145 | +- When to save and restore incremental state |
| 146 | + |
| 147 | +**Architecture:** |
| 148 | +``` |
| 149 | +Search Tree: |
| 150 | + Root (cache valid) |
| 151 | + / |
| 152 | + Branch (save checkpoint, update cache) |
| 153 | + / \ |
| 154 | + ... (restore checkpoint on backtrack) |
| 155 | +
|
| 156 | +Checkpoint stores: |
| 157 | + - cached_sum_of_mins |
| 158 | + - cached_sum_of_maxs |
| 159 | + - last_seen bounds for all variables |
| 160 | +``` |
| 161 | + |
| 162 | +--- |
| 163 | + |
| 164 | +### [page-39-1.png](page-39-1.png) - **Performance Analysis** 📊 |
| 165 | + |
| 166 | +**Topics:** |
| 167 | +- Benchmarks comparing eager vs. incremental approaches |
| 168 | +- Real-world speedups on Sudoku, N-Queens, Manufacturing problems |
| 169 | +- Complexity analysis: O(n²) → O(n) → O(1) |
| 170 | + |
| 171 | +**Results:** |
| 172 | +``` |
| 173 | +Sudoku (81 variables): |
| 174 | + Eager: 45 ms |
| 175 | + Incremental: 12 ms (3.7× faster) |
| 176 | +
|
| 177 | +N-Queens(12): |
| 178 | + Eager: 120 ms |
| 179 | + Incremental: 31 ms (3.9× faster) |
| 180 | +
|
| 181 | +Manufacturing (300+ vars): |
| 182 | + Eager: 8.2 s |
| 183 | + Incremental: 1.4 s (5.9× faster) |
| 184 | +``` |
| 185 | + |
| 186 | +--- |
| 187 | + |
| 188 | +## How These Pages Connect |
| 189 | + |
| 190 | +``` |
| 191 | +Page 31: Problem overview |
| 192 | + ↓ |
| 193 | +Page 32: Current bottleneck (eager) |
| 194 | + ↓ |
| 195 | +Page 33: Idea (decomposition) |
| 196 | + ↓ |
| 197 | +Page 34-35: SOLUTION (SparseSet + complement) |
| 198 | + ↓ |
| 199 | +Page 36: Making it event-driven |
| 200 | + ↓ |
| 201 | +Page 37: Complete algorithm (forward + reverse) |
| 202 | + ↓ |
| 203 | +Page 38: Backtracking integration |
| 204 | + ↓ |
| 205 | +Page 39: Validation (benchmarks) |
| 206 | +``` |
| 207 | + |
| 208 | +--- |
| 209 | + |
| 210 | +## Key Algorithm Components to Implement |
| 211 | + |
| 212 | +### 1. Extended SparseSet (Pages 34-35) |
| 213 | + |
| 214 | +Add to `src/variables/domain/sparse_set.rs`: |
| 215 | +```rust |
| 216 | +pub fn removed_iter(&self) -> impl Iterator<Item = i32> { ... } |
| 217 | +pub fn complement_size(&self) -> usize { ... } |
| 218 | +pub fn should_use_complement(&self) -> bool { ... } |
| 219 | +``` |
| 220 | + |
| 221 | +### 2. Incremental Sum Propagator (Pages 33-35) |
| 222 | + |
| 223 | +Create new file `src/constraints/props/incremental_sum.rs`: |
| 224 | +```rust |
| 225 | +pub struct IncrementalSum<V> { |
| 226 | + xs: Vec<V>, |
| 227 | + s: VarId, |
| 228 | + cached_sum_of_mins: Val, |
| 229 | + cached_sum_of_maxs: Val, |
| 230 | + last_seen: Vec<(Val, Val)>, |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +### 3. Reverse Propagation Optimization (Page 37) |
| 235 | + |
| 236 | +Precompute complementary sums: |
| 237 | +```rust |
| 238 | +sum_of_mins_except: Vec<Val>, |
| 239 | +sum_of_maxs_except: Vec<Val>, |
| 240 | +``` |
| 241 | + |
| 242 | +### 4. Checkpoint Management (Page 38) |
| 243 | + |
| 244 | +Save/restore state on search tree decisions: |
| 245 | +```rust |
| 246 | +fn on_search_decision(&mut self) { ... } |
| 247 | +fn on_backtrack(&mut self) { ... } |
| 248 | +``` |
| 249 | + |
| 250 | +--- |
| 251 | + |
| 252 | +## Discussion Topics |
| 253 | + |
| 254 | +**Before Implementation:** |
| 255 | + |
| 256 | +1. **Page 34 Algorithm** - Do you want to implement the exact data structure shown, or a Rust-idiomatic variant? |
| 257 | + |
| 258 | +2. **Complement Tracking Overhead** - What's the memory budget for checkpoint stacks on deep trees? |
| 259 | + |
| 260 | +3. **Phase 1 vs Full** - Should we start with forward-only (pages 33-35) or go straight to full (pages 37-38)? |
| 261 | + |
| 262 | +4. **Benchmarks** - Page 39 shows 3-6× speedups. Which problem type matters most for your use case? |
| 263 | + |
| 264 | +5. **SparseSet API** - Any concerns about exposing removed elements tracking in the public API? |
| 265 | + |
| 266 | +--- |
| 267 | + |
| 268 | +## Notes |
| 269 | + |
| 270 | +- The images are high-resolution PNG exports from the PDF |
| 271 | +- Diagrams and code examples are clearly visible |
| 272 | +- Each page corresponds to one slide from the presentation |
| 273 | +- The algorithm is complete and production-ready according to the paper |
| 274 | + |
| 275 | +**Next Step:** Open the PNG files and discuss the specific implementation details! |
0 commit comments