The Core Distinction: Structural vs Dynamic
Structural Loops (✅ Work in Symbolic Mode)
Loop bounds are known at graph-building time (int, const):
for (int i = 0; i < 10; i++) {
result += symbolic_value * i;
}
Dynamic Loops (⚠️ Numeric Mode ONLY)
Loop continuation depends on runtime values:
while (error > tolerance) {
error = update(error);
}
Common Patterns
1. Simple For Loop
template <typename Scalar>
Scalar sum_of_squares(int n) {
Scalar sum = 0.0;
for (int i = 1; i <= n; ++i) {
Scalar value = static_cast<Scalar>(i);
sum += value * value;
}
return sum;
}
2. Nested Loops (Matrix Operations)
template <typename Scalar>
Matrix<Scalar> multiply(const Matrix<Scalar>& A, const Matrix<Scalar>& B) {
int m = A.rows(), n = A.cols(), p = B.cols();
Matrix<Scalar> C(m, p);
for (int i = 0; i < m; ++i) {
for (int j = 0; j < p; ++j) {
for (int k = 0; k < n; ++k) {
C(i,j) += A(i,k) * B(k,j);
}
}
}
return C;
}
3. While Loop → Fixed Iterations
while (error > tol) { ... }
for (int i = 0; i < max_iterations; ++i) {
x = update(x);
}
4. Break/Continue → Conditional Accumulation
for (int i = 0; i < n; ++i) {
if (values(i) > threshold) break;
}
Scalar result = default_value;
for (int i = 0; i < n; ++i) {
values(i),
result);
}
auto where(const Cond &cond, const T1 &if_true, const T2 &if_false)
Select values based on condition (ternary operator) Returns: cond ? if_true : if_false Supports mixed...
Definition Logic.hpp:43
When You Need Dynamic Loops
Use Case: Time-stepping simulation until convergence
Option 1: Numeric-Only Simulation (RECOMMENDED for pure simulation)
double simulate_until_steady_state(double x0) {
double x = x0;
double error = 1000.0;
while (error > 1e-6) {
double x_new = physics_update(x);
error = std::abs(x_new - x);
x = x_new;
}
return x;
}
Option 2: Fixed Iterations (for symbolic compatibility)
template <typename Scalar>
Scalar simulate_fixed_steps(const Scalar& x0, int n_steps) {
Scalar x = x0;
for (int i = 0; i < n_steps; ++i) {
x = physics_update(x);
}
return x;
}
Option 3: Hybrid Approach (best of both worlds)
int n_steps = determine_steps_numerically();
template <typename Scalar>
Scalar simulate_hybrid(const Scalar& x0) {
return simulate_fixed_steps(x0, n_steps);
}
Advanced Patterns
Conditional Logic Inside Loops
template <typename Scalar>
Scalar selective_sum(const Vector<Scalar>& values,
const Vector<Scalar>& thresholds) {
Scalar sum = 0.0;
for (int i = 0; i < values.size(); ++i) {
values(i),
Scalar(0.0));
}
return sum;
}
Element-wise Transformations
template <typename Scalar>
Vector<Scalar> apply_function(const Vector<Scalar>& input) {
int n = input.size();
Vector<Scalar> output(n);
for (int i = 0; i < n; ++i) {
output(i) = complex_function(input(i));
}
return output;
}
Key Takeaways
| Feature | Numeric Mode | Symbolic Mode |
| for (int i=0; i<N; i++) | ✅ Yes | ✅ Yes |
| while (symbolic > val) | ✅ Yes | ❌ No |
| if (symbolic) break | ✅ Yes | ❌ No |
| Loop values | Any | Symbolic OK |
| Loop bounds | Any | Must be structural |
When to use which:
- Pure simulation/analysis: Use dynamic loops freely!
- Optimization/AutoDiff: Use fixed iterations
- Both: Hybrid approach (numeric determines N, symbolic uses fixed N)
See Also