Metadata-Version: 2.4
Name: convergio
Version: 0.1.5
Summary: Automatic convergence detection for iterative numerical methods. Stop wasting compute.
Author-email: Maximilian Jurak <max.jurak@3kaiserberge.com>
License-Expression: MIT
Project-URL: Homepage, https://3kaiserberge.com
Project-URL: Repository, https://github.com/3Kaiserberge/convergio
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy
Dynamic: license-file

# Convergio

**Automatic convergence detection for iterative numerical methods.**

Stop wasting compute. One function call tells you if your solver has converged, is stalling, is oscillating, or is diverging.

*"Your solver finished 3000 iterations ago."*

## Install

```bash
pip install convergio
```

Only dependency: NumPy.

## Quick Start

### Analyze a completed run

```python
from convergio import detect

result = detect(my_residual_history)

print(result.state)        # "converged" | "stalling" | "oscillating" | "diverging" | ...
print(result.quality)      # 0.0 — 1.0
print(result.converged_at) # step where convergence was detected
```

### Detect stalling (new in v0.1.3)

A solver can look "converged" (low variance) but actually be stuck above the target residual. Convergio distinguishes between true convergence and stalling:

```python
result = detect(residuals, residual_target=1e-8)

if result.state == "stalling":
    print("Solver plateaued — try different parameters")
elif result.state == "converged":
    print(f"Solved at step {result.converged_at}")
```

### Monitor a running simulation

```python
from convergio import watch

mon = watch(var_threshold=1e-6, residual_target=1e-8)

for step in range(10000):
    residual = solver.step()
    stop, info = mon.step(residual)
    if stop:
        print(f"Done at step {step} — saved {10000 - step} iterations")
        break
```

## What It Detects

| State | Meaning | Recommendation |
|-------|---------|----------------|
| `converged` | Signal has stabilized below target | Stop |
| `stalling` | Signal is flat but *above* target | Adjust parameters |
| `oscillating` | Signal oscillates without settling | Adjust parameters |
| `bistable` | Signal jumps between 2+ distinct states | Investigate |
| `diverging` | Variance is growing | Restart |
| `warming_up` | Not enough data yet | Continue |

## Benchmark Results

Tested on synthetic signals across all convergence states (v0.1.4):

### Detection Accuracy

| Signal Type | N=200 | N=500 | N=1000 |
|-------------|-------|-------|--------|
| Converging (exp. decay) | converged | converged | converged |
| Stalling (flat, above target) | stalling | stalling | stalling |
| Oscillating (sine + noise) | — | oscillating | oscillating |
| Diverging (random walk) | — | diverging | diverging |

**Overall: 87% correct** across all signal types and lengths. Hardest case: bistable signals (multi-modal jumping), which can resemble oscillation.

### Compute Savings with `watch()`

| Scenario | Budget | Stopped at | Saved |
|----------|--------|-----------|-------|
| Fast convergence | 2,000 | 1,100 | **45%** |
| Medium convergence | 5,000 | 4,200 | **16%** |
| Diverging (early abort) | 5,000 | 350 | **93%** |

`watch()` detects convergence *and* divergence. Diverging runs are killed early instead of burning through the full iteration budget.

Stalling signals are flagged but **not** auto-stopped — you decide what to do.

### Speed

| Signal Length | Time | Throughput |
|---------------|------|------------|
| 1,000 | 5 ms | 200K pts/s |
| 10,000 | 52 ms | 190K pts/s |
| 100,000 | 517 ms | 193K pts/s |
| 1,000,000 | 5.1 s | 197K pts/s |

`detect()` runs at ~190,000 points/second. For a typical FEM solver with 5,000 iterations, analysis takes **~25 ms** — negligible compared to solve time.

## API Reference

### `detect(signal, window=0, var_threshold=1e-6, osc_threshold=0.3, residual_target=0.0)`

Analyze a complete time series.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `signal` | array-like | — | 1D array of scalar values |
| `window` | int | 0 (auto) | Analysis window size |
| `var_threshold` | float | 1e-6 | Variance below this = stable |
| `osc_threshold` | float | 0.3 | Zero-crossing freq above this = oscillating |
| `residual_target` | float | 0.0 | If > 0: stable signal above this = stalling |

Returns `ConvergenceResult`.

### `watch(var_threshold=1e-6, check_every=50, min_steps=100, window=0, residual_target=0.0)`

Create a live monitor. Returns `Watch` object. Call `.step(value)` each iteration.

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `var_threshold` | float | 1e-6 | Variance below this = stable |
| `check_every` | int | 50 | Check convergence every N steps |
| `min_steps` | int | 100 | Minimum steps before first check |
| `window` | int | 0 (auto) | Analysis window size |
| `residual_target` | float | 0.0 | If > 0: stable signal above this = stalling |

### `ConvergenceResult`

| Field | Type | Description |
|-------|------|-------------|
| `state` | str | converged, stalling, oscillating, bistable, diverging, warming_up |
| `quality` | float | 0.0 — 1.0 convergence quality |
| `converged_at` | int or None | Step where convergence detected |
| `final_variance` | float | Variance of last window |
| `oscillation_freq` | float | Detected oscillation frequency |
| `n_modes` | int | Number of distinct stable states |
| `recommendation` | str | stop, continue, restart, adjust_params |
| `saved_steps` | int | Steps saved by early stopping |
| `total_steps` | int | Total steps in analyzed signal |

## What's New

### v0.1.4
- Trend-check: smooth decline is no longer misclassified as stalling

### v0.1.3
- `residual_target` parameter: distinguishes true convergence from stalling
- `stalling` state: flat signal above target is now correctly identified
- `watch()` reports stalling without auto-stopping (user decides)

### v0.1.0
- Initial release: converged, oscillating, bistable, diverging, warming_up

## Works With

- FEM / CFD solvers (residual monitoring)
- ML training (loss convergence)
- Monte Carlo simulations (running averages)
- Optimization loops (objective function)
- Molecular dynamics (energy equilibration)
- Any iterative numerical process that produces a scalar time series

## License

MIT

## Author

Maximilian Jurak — [3 Kaiserberge Engineering & Research](https://3kaiserberge.com)

max.jurak@3kaiserberge.com
