rival3 0.1.0

Real Computation via Interval Arithmetic
Documentation
//! Adaptive precision tuning and path reduction

pub(crate) mod path_reduction;
mod precision;

use crate::eval::{
    machine::{Discretization, Hint, Machine},
    profile::Execution,
    tricks::slack_bits,
};
use path_reduction::{path_reduction, schedule_child};
use precision::{precision_tuning, update_repeats};

impl<D: Discretization> Machine<D> {
    /// Adjust precision and repeats using the backward tuning pass
    pub(crate) fn adjust(&mut self, hints: &[Hint]) -> bool {
        assert_eq!(hints.len(), self.instructions.len(), "hint length mismatch");
        if self.iteration == 0 {
            return false;
        }
        backward_pass(self, hints)
    }

    /// Compute hints indicating which instructions should be executed, skipped, or aliased
    pub(crate) fn make_hint(&self, old_hint: &[Hint]) -> (Vec<Hint>, bool) {
        let len = self.instructions.len();
        let mut hints = vec![Hint::Skip; len];
        let mut converged = old_hint.len() == len;

        // Roots should always be executed.
        for &root in &self.outputs {
            if let Some(idx) = self.register_to_instruction(root) {
                hints[idx] = Hint::Execute;
            }
        }

        for idx in (0..len).rev() {
            if matches!(hints[idx], Hint::Skip) {
                continue;
            }

            if let Some(previous) = old_hint.get(idx) {
                match previous {
                    Hint::KnownBool(val) => {
                        hints[idx] = Hint::KnownBool(*val);
                        continue;
                    }
                    Hint::Alias(pos) => {
                        if let Some(reg) = self.instructions[idx].data.input_at(*pos as usize) {
                            schedule_child(&mut hints, self, reg);
                        }
                        hints[idx] = Hint::Alias(*pos);
                        continue;
                    }
                    Hint::Execute | Hint::Skip => {}
                }
            } else {
                converged = false;
            }

            let mut schedule = |reg: usize| schedule_child(&mut hints, self, reg);
            let outcome = path_reduction(self, idx, &mut schedule);

            converged = converged && outcome.converged;
            hints[idx] = outcome.hint;
        }

        (hints, converged)
    }
}

/// Compute required precision for each instruction by propagating from outputs to inputs
fn backward_pass<D: Discretization>(machine: &mut Machine<D>, hints: &[Hint]) -> bool {
    let instruction_count = machine.instructions.len();
    let profiling = machine.profiling_enabled;
    let start_time = if profiling {
        Some(std::time::Instant::now())
    } else {
        None
    };
    let first_tuning_pass = machine.iteration == 1;

    let mut vprecs_max = vec![0u32; instruction_count];
    let mut work_repeats = vec![true; instruction_count];

    // Step 1: Add slack bits to outputs that hit discretization boundaries.
    // Slack grows exponentially with iteration to push intervals away from boundaries.
    let slack = slack_bits(machine.iteration, machine.slack_unit);
    for (&root, &boundary_issue) in machine.outputs.iter().zip(machine.output_distance.iter()) {
        if boundary_issue && let Some(idx) = machine.register_to_instruction(root) {
            vprecs_max[idx] = vprecs_max[idx].max(slack);
        }
    }

    // Step 1b: Check if reevaluation is needed.

    // Mark all outputs for reevaluation.
    for &root in &machine.outputs {
        if let Some(idx) = machine.register_to_instruction(root) {
            work_repeats[idx] = false;
        }
    }

    // Traverse instructions from outputs to inputs to mark necessary reevaluations.
    for idx in (0..instruction_count).rev() {
        if work_repeats[idx] {
            continue;
        }
        let reg = &machine.registers[machine.instruction_register(idx)];
        if reg.lo.immovable && reg.hi.immovable {
            work_repeats[idx] = true;
            continue;
        }

        let var_count = machine.arguments.len();
        let mut mark = |reg: usize| {
            if reg >= var_count {
                work_repeats[reg - var_count] = false;
            }
        };
        path_reduction(machine, idx, &mut mark);
    }

    // Step 2: Precision tuning.
    let mut vprecs_min = vec![0u32; instruction_count];
    if precision_tuning(
        machine,
        hints,
        &work_repeats,
        &mut vprecs_max,
        &mut vprecs_min,
    ) {
        return true;
    }

    // Step 3: Update repeats based on new precisions.
    let mut any_reevaluation =
        update_repeats(machine, &mut work_repeats, &vprecs_max, first_tuning_pass);

    // Step 4: If no precision increase, try logspan bumps.
    if !any_reevaluation {
        // Bumps mode adds precision based on interval width (logspan) rather than slack.
        machine.bumps = machine.bumps.saturating_add(1);
        machine.bumps_activated = true;
        // Reset and recalculate precisions for bumps mode.
        vprecs_max.fill(0);
        work_repeats.fill(false);
        if precision_tuning(
            machine,
            hints,
            &work_repeats,
            &mut vprecs_max,
            &mut vprecs_min,
        ) {
            return true;
        }
        any_reevaluation =
            update_repeats(machine, &mut work_repeats, &vprecs_max, first_tuning_pass);
        if !any_reevaluation {
            work_repeats.fill(true);
        }
    }

    // Step 5: Update machine state.
    machine.repeats.copy_from_slice(&work_repeats);
    machine.precisions.copy_from_slice(&vprecs_max);

    if profiling && let Some(t0) = start_time {
        let dt_ms = t0.elapsed().as_secs_f64() * 1000.0;
        machine.profiler.record(Execution {
            name: "adjust",
            number: -1,
            precision: (machine.iteration as u32) * 1000,
            time_ms: dt_ms,
            iteration: machine.iteration,
        });
    }
    false
}