app
AI Flow: Routing
Public Made by Adomby adom
The routing stage of AI Flow: routes a placed KiCad board connection by connection and reports what it made, what it could not and why. A guide, never a gate.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
//! The router's clock counts work done, not seconds passed.
//!
//! Every budget in the router (the run's limit, a connection's turn, a repair's share, a closing
//! pass's grant) used to be wall-clock time, so the same board routed differently depending on how
//! busy the machine was: on benchmark C one binary gave 301 joins in one run and 294 in another
//! (2026-11-06 and 11-09), and no change smaller than that could be measured. Here a "second" is a
//! fixed amount of search work, so the same input always gives the same board, on any machine and
//! under any load. `Instant` has the few methods of `std::time::Instant` the router uses, so the
//! budgets read as they did.
//!
//! What counts as work: one unit per search node expanded, a fixed charge per search started, and
//! one unit each time the clock is read, so a loop that only watches the clock still ends.
use std::cell::Cell;
use std::time::Duration;
/// Work units to one clock second. Measured 2026-11-09 on the development machine, each board
/// routed alone: benchmark C 164,090 units per wall second, benchmark B 239,069, piprobe_v13
/// 344,868 (a denser board spends longer on each search step). Set to benchmark C's, the board whose
/// run is bound by its limit, so its 790 s means the work it meant before; the smaller boards
/// finish before their limits. A fixed number on purpose: changing it changes every board's result.
pub const UNITS_PER_SECOND: f64 = 164_000.0;
/// What starting one search costs, in units: building its lines and tables.
pub const SEARCH_START: u64 = 200;
thread_local! {
static WORK: Cell<u64> = const { Cell::new(0) };
}
pub fn tick(n: u64) {
WORK.with(|w| w.set(w.get() + n));
}
pub fn units() -> u64 {
WORK.with(|w| w.get())
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
pub struct Instant(f64);
impl Instant {
pub fn now() -> Instant {
tick(1);
Instant(units() as f64 / UNITS_PER_SECOND)
}
pub fn elapsed(&self) -> Duration {
Instant::now().saturating_duration_since(*self)
}
/// The earlier of two instants.
pub fn min(self, other: Instant) -> Instant {
if other.0 < self.0 { other } else { self }
}
pub fn saturating_duration_since(&self, earlier: Instant) -> Duration {
Duration::from_secs_f64((self.0 - earlier.0).max(0.0))
}
}
impl std::ops::Add<Duration> for Instant {
type Output = Instant;
fn add(self, d: Duration) -> Instant {
Instant(self.0 + d.as_secs_f64())
}
}