use anyhow::{bail, Result};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionNode {
pub id: String,
pub index: usize,
pub path: String,
pub size_bytes: u64,
pub compressed_bytes: u64,
pub signature_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CavsRoutes {
pub store_bytes: u64,
pub build_ms: u64,
pub cold: Vec<Vec<u64>>,
pub warm: Vec<Vec<u64>>,
pub install: Vec<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EdgeMeasure {
pub engine: String,
pub raw_patch_bytes: u64,
pub compressed_patch_bytes: u64,
pub diff_ms: u64,
pub apply_ms: u64,
pub verify_ms: u64,
pub peak_rss_mib: Option<f64>,
pub verified: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchEdge {
pub from: usize,
pub to: usize,
pub policies: Vec<String>,
pub measures: Vec<EdgeMeasure>,
}
impl PatchEdge {
pub fn bytes(&self, engine: &str) -> Option<u64> {
self.measures
.iter()
.find(|m| m.engine == engine && m.verified)
.map(|m| m.compressed_patch_bytes)
}
pub fn measure(&self, engine: &str) -> Option<&EdgeMeasure> {
self.measures.iter().find(|m| m.engine == engine)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PolicySpec {
pub name: String,
pub label: String,
pub edge_idxs: Vec<usize>,
pub notes: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PatchGraph {
pub versions: Vec<VersionNode>,
pub edges: Vec<PatchEdge>,
pub policies: Vec<PolicySpec>,
pub cavs_routes: Option<CavsRoutes>,
pub structure_only: bool,
pub tool_versions: BTreeMap<String, String>,
}
impl PatchGraph {
pub fn policy(&self, name: &str) -> Option<&PolicySpec> {
self.policies.iter().find(|p| p.name == name)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LadderMode {
Aligned,
Dense,
}
impl LadderMode {
pub fn parse(s: &str) -> Result<Self> {
match s {
"aligned" => Ok(LadderMode::Aligned),
"dense" => Ok(LadderMode::Dense),
other => bail!("unknown --ladder-mode {other:?} (aligned, dense)"),
}
}
pub fn label(self) -> &'static str {
match self {
LadderMode::Aligned => "aligned",
LadderMode::Dense => "dense",
}
}
}
pub fn adjacent_pairs(n: usize) -> Vec<(usize, usize)> {
(0..n.saturating_sub(1)).map(|i| (i, i + 1)).collect()
}
pub fn ladder_pairs(n: usize, mode: LadderMode) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let mut d = 1usize;
while n > 0 && d < n {
match mode {
LadderMode::Aligned => {
let mut start = 0;
while start + d < n {
out.push((start, start + d));
start += d;
}
}
LadderMode::Dense => {
for start in 0..n - d {
out.push((start, start + d));
}
}
}
d *= 2;
}
out.sort();
out.dedup();
out
}
pub fn base_pairs(n: usize, base: usize, bidirectional: bool) -> Vec<(usize, usize)> {
let mut out = Vec::new();
for i in 0..n {
if i == base {
continue;
}
out.push((base, i));
if bidirectional {
out.push((i, base));
}
}
out
}
pub fn hot_pairs_latest_k(n: usize, k: usize) -> Vec<(usize, usize)> {
let mut out = adjacent_pairs(n);
if n >= 2 {
let latest = n - 1;
for back in 2..=k {
if back >= n {
break;
}
out.push((latest - back, latest));
}
}
out.sort();
out.dedup();
out
}
pub fn all_pairs(n: usize) -> Vec<(usize, usize)> {
let mut out = Vec::new();
for i in 0..n {
for j in i + 1..n {
out.push((i, j));
}
}
out
}
pub fn cheapest_path(
edges: &[PatchEdge],
edge_idxs: &[usize],
n: usize,
from: usize,
to: usize,
bytes_of: &dyn Fn(&PatchEdge) -> Option<u64>,
) -> Option<Vec<usize>> {
if from == to {
return Some(Vec::new());
}
let mut adj: Vec<Vec<(usize, usize, u64)>> = vec![Vec::new(); n];
for &e in edge_idxs {
let edge = &edges[e];
let bytes = bytes_of(edge).unwrap_or(1); adj[edge.from].push((edge.to, e, bytes));
}
let mut best: Vec<(u64, u64)> = vec![(u64::MAX, u64::MAX); n];
let mut prev: Vec<Option<(usize, usize)>> = vec![None; n];
let mut heap = std::collections::BinaryHeap::new();
best[from] = (0, 0);
heap.push(std::cmp::Reverse((0u64, 0u64, from)));
while let Some(std::cmp::Reverse((bytes, steps, node))) = heap.pop() {
if (bytes, steps) > best[node] {
continue;
}
if node == to {
break;
}
for &(next, e, w) in &adj[node] {
let cand = (bytes.saturating_add(w), steps + 1);
if cand < best[next] {
best[next] = cand;
prev[next] = Some((node, e));
heap.push(std::cmp::Reverse((cand.0, cand.1, next)));
}
}
}
if best[to].0 == u64::MAX {
return None;
}
let mut path = Vec::new();
let mut cursor = to;
while cursor != from {
let (p, e) = prev[cursor]?;
path.push(e);
cursor = p;
}
path.reverse();
Some(path)
}
#[cfg(test)]
mod tests {
use super::*;
fn edge(from: usize, to: usize, bytes: u64) -> PatchEdge {
PatchEdge {
from,
to,
policies: vec!["test".into()],
measures: vec![EdgeMeasure {
engine: "test".into(),
raw_patch_bytes: bytes,
compressed_patch_bytes: bytes,
diff_ms: 0,
apply_ms: 0,
verify_ms: 0,
peak_rss_mib: None,
verified: true,
}],
}
}
#[test]
fn adjacent_is_linear() {
assert_eq!(adjacent_pairs(5), vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
assert!(adjacent_pairs(1).is_empty());
}
#[test]
fn aligned_ladder_stays_under_2n() {
for n in [2usize, 5, 9, 16, 33, 100] {
let edges = ladder_pairs(n, LadderMode::Aligned);
assert!(edges.len() < 2 * n, "n={n} produced {} edges", edges.len());
for i in 0..n - 1 {
assert!(edges.contains(&(i, i + 1)));
}
}
let e = ladder_pairs(9, LadderMode::Aligned);
for pair in [(0, 2), (2, 4), (4, 6), (6, 8), (0, 4), (4, 8), (0, 8)] {
assert!(e.contains(&pair), "missing {pair:?}");
}
}
#[test]
fn dense_ladder_covers_unaligned_starts() {
let e = ladder_pairs(6, LadderMode::Dense);
assert!(e.contains(&(1, 3)));
assert!(e.contains(&(3, 5)));
assert!(!ladder_pairs(6, LadderMode::Aligned).contains(&(1, 3)));
}
#[test]
fn ladder_path_is_logarithmic() {
let n = 33;
let pairs = ladder_pairs(n, LadderMode::Aligned);
let edges: Vec<PatchEdge> = pairs.iter().map(|&(f, t)| edge(f, t, 1)).collect();
let idxs: Vec<usize> = (0..edges.len()).collect();
let path = cheapest_path(&edges, &idxs, n, 0, n - 1, &|e| e.bytes("test")).unwrap();
assert!(path.len() <= 2 * (n as f64).log2().ceil() as usize);
let path = cheapest_path(&edges, &idxs, n, 1, 31, &|e| e.bytes("test")).unwrap();
assert!(!path.is_empty() && path.len() <= 10);
}
#[test]
fn base_hub_routes_need_bidirectional() {
let n = 6;
let one_way = base_pairs(n, 0, false);
let edges: Vec<PatchEdge> = one_way.iter().map(|&(f, t)| edge(f, t, 1)).collect();
let idxs: Vec<usize> = (0..edges.len()).collect();
assert!(cheapest_path(&edges, &idxs, n, 2, 5, &|e| e.bytes("test")).is_none());
let both = base_pairs(n, 0, true);
let edges: Vec<PatchEdge> = both.iter().map(|&(f, t)| edge(f, t, 1)).collect();
let idxs: Vec<usize> = (0..edges.len()).collect();
let path = cheapest_path(&edges, &idxs, n, 2, 5, &|e| e.bytes("test")).unwrap();
assert_eq!(path.len(), 2);
assert_eq!(both.len(), 2 * (n - 1));
}
#[test]
fn all_pairs_is_quadratic_and_one_hop() {
let n = 10;
let pairs = all_pairs(n);
assert_eq!(pairs.len(), n * (n - 1) / 2);
let edges: Vec<PatchEdge> = pairs.iter().map(|&(f, t)| edge(f, t, 1)).collect();
let idxs: Vec<usize> = (0..edges.len()).collect();
let path = cheapest_path(&edges, &idxs, n, 3, 9, &|e| e.bytes("test")).unwrap();
assert_eq!(path.len(), 1);
}
#[test]
fn hot_pairs_add_direct_edges_to_latest() {
let pairs = hot_pairs_latest_k(10, 3);
assert!(pairs.contains(&(7, 9)) && pairs.contains(&(6, 9)));
assert!(pairs.contains(&(8, 9))); assert_eq!(pairs.len(), 9 + 2);
}
#[test]
fn cheapest_path_prefers_fewer_bytes_over_fewer_steps() {
let edges = vec![edge(0, 2, 100), edge(0, 1, 10), edge(1, 2, 10)];
let idxs = vec![0, 1, 2];
let path = cheapest_path(&edges, &idxs, 3, 0, 2, &|e| e.bytes("test")).unwrap();
assert_eq!(path, vec![1, 2]);
}
}