Changes for PyAETNA/.kilo/eliminate_safeguards_plan.md: 61 added lines, 0 removed lines.
Original line number
Diff line number
Diff line
# Strategy: Eliminating Model Safeguards and Fixing Boundary Initialization
## Problem Description
PyAETNA currently employs several numerical safeguards (e.g., `jnp.clip`, `jnp.where`, and `jnp.maximum`) within its physical models (specifically in `body_models.py`) to prevent `NaN` values during the forward pass. While these prevent immediate crashes, they mask a systemic issue: **Boundary Initialization**.
As analyzed in `boundary_init_analysis.md`, when using the `auto` sampling pipeline with `init_to_uniform`, bounded priors (such as `TruncatedNormal`) are frequently initialized **exactly** at their lower or upper boundaries.
### The Failure Mechanism:
1.**Unconstrained Space**: Samplers operate in unconstrained space $(-\infty, \infty)$. A value exactly at the boundary of a constrained prior maps to $\pm\infty$ in unconstrained space.
2.**Gradient Collapse**: At $\pm\infty$, the gradient of the transformation becomes zero or `NaN`.
3.**The "Invisible Wall"**: Model safeguards (like clipping) allow the forward pass to succeed, but the sampler remains stranded on a flat plateau with no gradient to guide it back into the high-probability region. This leads to numerical instability, extremely slow warmup, or total failure of the ensemble phase.
## Goal
Transition from a "safeguarded" model that hides errors to an "honest" model that prevents them by design. This involves moving safety from the **Model (Forward Pass)** to the **Prior/Initialization (Input Stage)**.
---
## Detailed Implementation Plan
### Step 1: Boundary Detection & Validation (The "Honesty" Check)
Before proceeding with the purge of safeguards, we must implement a strict validation step to detect boundary hits.
**Proposed Function: `validate_initial_walkers`**
This function should be called after the sampler has initialized the walkers but **before** the JIT-compiled sampling loop begins.
**Logic:**
1. Iterate through all initialized parameters.
2. For each parameter with a bounded prior $[low, high]$:
- Check if any walker value $\theta_i$ satisfies $\theta_i = low$ or $\theta_i = high$ (within the numerical precision ised by the pipeline).
3. If boundary hits are detected:
- Log the specific parameter name.
- Log the index of the walker(s) hitting the boundary and the unconstrained `z`.
- Log the exact value and the prior bounds.
-**Raise a `RuntimeError` and exit.** The error must be logged to the same logfile used by the rest of the pipeline.
*This ensures that we cannot start a run that is mathematically doomed to fail due to $z = \pm\infty$.*
### Step 2: Hardening the Priors
To make the model safe without internal clipping, we shift the boundaries of the priors themselves.
1.**$\epsilon$-Shrinking**: Update the prior parsing logic to automatically shrink bounded priors by a small margin $\epsilon$ (e.g., $10^{-6}$).
- $[low, high] \rightarrow [low + \epsilon, high - \epsilon]$.
2.**Result**: This ensures that even if a walker is at the very edge of the prior, it is still strictly within the valid domain of functions like `arccos` and `sqrt`.
### Step 3: Fixing the Initialization Root Cause
Address the bug in the interaction between `init_to_uniform` and bounded distributions.
1.**Custom Init Wrapper**: If `init_to_uniform` continues to produce boundary values, implement a custom initialization wrapper that draws samples from the interior of the support.
2.**Verification**: Use the `validate_initial_walkers` function from Step 1 to verify that the new initialization strategy produces zero boundary hits.
### Step 4: Purging the Safeguards
Once the input stage is guaranteed to be safe, remove the "crutches" from the physics engine.
1.**Remove `jnp.clip`**: Replace clipped calls (e.g., in `compute_inclination`) with direct calls.
2.**Remove `jnp.where` for safety**: Remove the `b_safe` logic in `compute_transit_duration` and `compute_eclipse_duration`.
3.**Remove `jnp.maximum(..., 0.0)`**: Remove the flooring of square root arguments.
## Expected Outcome
-**Numerical Honesty**: Any `NaN` encountered will be a legitimate signal of a model or prior failure, not a masked symptom.
-**Faster Convergence**: Walkers will start in the interior of the unconstrained space, providing healthy gradients for the sampler.
-**Cleaner Code**: The physics models in `body_models.py` will be stripped of boilerplate safety logic, making them easier to maintain and read.
<divclass="help">Initialisation radius for ensemble walkers in unconstrained space. Default: <code>2.0</code>. Set to < 2 (e.g. <code>0.5</code>) to avoid walkers being initialized exactly at prior boundaries (<code>lo</code>), which can stall walkers in bounded priors.</div>