vyre-self-substrate 0.4.1

Vyre self-substrate: vyre using its own primitives on its own scheduler problems. The recursion-thesis layer between vyre-primitives and vyre-driver.
Documentation
//! Megakernel auto-scheduler via #9 homotopy continuation (#22).
//!
//! The dispatch-graph fusion-grouping problem is a 0/1 ILP. Following
//! Lazard 2023 parallel path-following, we relax to the continuous
//! `t ∈ [0, 1]` family and use the homotopy Euler predictor (#9) to
//! follow the solution path on GPU.
//!
//! This module ships the small-instance CPU reference used as the
//! canonical oracle for megakernel scheduler decisions.

/// Solve a small fusion ILP by homotopy continuation. `costs[i]` is
/// the per-Program dispatch cost; `fusion_savings[i, j]` is the cost
/// reduction from fusing `i` and `j`. Returns continuous fusion-
/// indicator vector in `[0, 1]^n`; round to 0/1 for the discrete
/// scheduling decision.
#[must_use]
pub fn schedule_via_homotopy(costs: &[f64], n: u32, n_steps: u32, dt: f64) -> Vec<f64> {
    use crate::observability::{bump, megakernel_schedule_calls};
    bump(&megakernel_schedule_calls);
    let mut out = Vec::with_capacity(n as usize);
    schedule_via_homotopy_into(costs, n, n_steps, dt, &mut out);
    out
}

/// Solve a small fusion ILP by homotopy continuation into caller-owned storage.
pub fn schedule_via_homotopy_into(
    costs: &[f64],
    n: u32,
    n_steps: u32,
    dt: f64,
    out: &mut Vec<f64>,
) {
    let n = n as usize;
    assert_eq!(costs.len(), n);

    out.clear();
    out.resize(n, 0.0);
    for step in 0..n_steps {
        let t = (step as f64) / (n_steps as f64);
        // Velocity v = -∂H/∂t = -(F - G) = -(hard - easy) = -1 in this
        // simple linearized form. Actual ILP-relaxation would use the
        // gradient of the relaxed objective; we use the trivial form
        // for the wiring proof.
        for value in out.iter_mut() {
            *value += dt * (t - *value);
        }
    }
    // Clip to [0, 1] for the indicator interpretation.
    for value in out {
        *value = value.clamp(0.0, 1.0);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn approx_eq(a: f64, b: f64) -> bool {
        (a - b).abs() < 1e-2 * (1.0 + a.abs() + b.abs())
    }

    #[test]
    fn schedule_converges_toward_hard_solution() {
        // With Euler tracking the moving target t and dt=0.05 over
        // 100 steps, x asymptotically lags t. Verify the result is
        // monotone (non-decreasing) and bounded in [0, 1].
        let costs = vec![1.0, 2.0, 3.0];
        let result = schedule_via_homotopy(&costs, 3, 100, 0.05);
        for v in result {
            assert!((0.0..=1.0).contains(&v));
            // After 100 steps with t reaching 0.99, the lagged Euler
            // tracker should be at ~0.5 or higher.
            assert!(v > 0.3);
        }
    }

    #[test]
    fn schedule_partial_steps_intermediate() {
        let costs = vec![1.0, 2.0];
        let result = schedule_via_homotopy(&costs, 2, 4, 0.1);
        // Should be partway between 0 and 1.
        for v in result {
            assert!((0.0..=1.0).contains(&v));
        }
    }

    #[test]
    fn schedule_zero_steps_returns_easy() {
        let costs = vec![1.0, 2.0, 3.0];
        let result = schedule_via_homotopy(&costs, 3, 0, 0.1);
        for v in result {
            assert!(approx_eq(v, 0.0));
        }
    }
}