Define frozen routing corpus and isolated timing protocol
This commit is contained in:
@@ -384,6 +384,10 @@ fn metrics(project: &Project, diagram: Id) -> Value {
|
||||
"drawing_area":routed.bounds.size.width as f64*routed.bounds.size.height as f64,
|
||||
"route_bends":bends,"route_length":total_length,
|
||||
"projected_nodes":view.nodes,"projected_ports":view.ports,
|
||||
"projected_edges":view.edges,
|
||||
"routing_constraints":edges.iter().map(|e|json!({"id":e.id,"effective_pins":e.pins,
|
||||
"pin_fractions":e.pin_fractions,"custom_suspended":e.custom_suspended,
|
||||
"actual_endpoint_owners":projection::edge_endpoint_placements(project,&project.diagrams[&diagram],e.id)})).collect::<Vec<_>>(),
|
||||
"route_endpoints":edges.iter().map(|e|json!({"id":e.id,
|
||||
"source_owner":definitions[&e.id].source,"target_owner":definitions[&e.id].target,
|
||||
"source":e.points.first(),"target":e.points.last()})).collect::<Vec<_>>(),
|
||||
@@ -735,6 +739,50 @@ fn observe(name: &str, f: Builder) -> Value {
|
||||
"second_layout_exact":second.as_ref().is_ok_and(|s|s.project==after),"second_layout_error":second.err().map(|e|e.to_string()),
|
||||
"before_roundtrip":roundtrip(&before),"after_roundtrip":roundtrip(&after)}})
|
||||
}
|
||||
fn route_corpus(path: &std::path::Path) {
|
||||
let text = std::fs::read_to_string(path).expect("Read frozen routing corpus manifest");
|
||||
let manifest: Value = serde_json::from_str(&text).expect("Parse corpus manifest");
|
||||
assert_eq!(manifest["schema"], 1);
|
||||
let directory = path.parent().expect("Corpus manifest directory");
|
||||
let mut names = BTreeSet::new();
|
||||
let mut prepared = Vec::new();
|
||||
for case in manifest["cases"].as_array().expect("Corpus cases") {
|
||||
let name = case["name"].as_str().expect("Case name");
|
||||
assert!(names.insert(name.to_owned()), "Duplicate case name");
|
||||
let file = std::path::Path::new(case["project_file"].as_str().expect("Project file"));
|
||||
assert!(
|
||||
file.components()
|
||||
.all(|part| matches!(part, std::path::Component::Normal(_))),
|
||||
"Corpus project files must use relative normal components"
|
||||
);
|
||||
let project: Project = serde_json::from_str(
|
||||
&std::fs::read_to_string(directory.join(file)).expect("Read frozen Project"),
|
||||
)
|
||||
.expect("Decode frozen Project");
|
||||
validate_candidate(&project).expect("Validate frozen Project");
|
||||
let diagram: Id = serde_json::from_value(case["diagram"].clone()).expect("Diagram UUID");
|
||||
assert!(
|
||||
project.diagrams.contains_key(&diagram),
|
||||
"Missing corpus diagram"
|
||||
);
|
||||
prepared.push((name.to_owned(), project, diagram));
|
||||
}
|
||||
assert!(!prepared.is_empty(), "Empty corpus");
|
||||
println!(
|
||||
"{}",
|
||||
json!({"probe":"routing_corpus_geometry_v1","metric_selfchecks":"passed",
|
||||
"mode":"Route-only observation of frozen authored Projects; no Layout, reset or timing.",
|
||||
"manifest":manifest,"quality_thresholds":null,
|
||||
"limitations":"Existing raw geometry metrics; estimated main-label rectangles, no glyph or port-label acceptance. Folded promotion is observed, not passed to the unsupported layered prototype. Automatic non-port attachment coordinates may legally change across router variants."})
|
||||
);
|
||||
for (name, project, diagram) in prepared {
|
||||
println!(
|
||||
"{}",
|
||||
json!({"fixture":name,"diagram":diagram,"before_project":project,
|
||||
"before":metrics(&project,diagram),"roundtrip":roundtrip(&project)})
|
||||
);
|
||||
}
|
||||
}
|
||||
fn main() {
|
||||
// Analytic checks validate the observer, not any layout's quality.
|
||||
let horizontal = [(0., 0.), (10., 0.)];
|
||||
@@ -773,6 +821,16 @@ fn main() {
|
||||
assert_eq!(overlap((0., 0., 10., 10.), (10., 0., 20., 10.)), 0.);
|
||||
assert!((inside_length((0., 5.), (10., 5.), (2., 2., 8., 8.)) - 6.).abs() < 3. * EPS);
|
||||
assert_eq!(inside_length((0., 2.), (10., 2.), (2., 2., 8., 8.)), 0.);
|
||||
let args: Vec<_> = std::env::args_os().collect();
|
||||
if args.len() == 3 && args[1] == "--route-corpus" {
|
||||
route_corpus(std::path::Path::new(&args[2]));
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
args.len(),
|
||||
1,
|
||||
"Usage: auto_layout_probe [--route-corpus MANIFEST]"
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
json!({"probe":"auto_layout_probe_v2","reference_source":"715036e132677dd2bc2fb37ed316d93dc9987aec","metric_selfchecks":"passed", "quality_thresholds":null,
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
//! Frozen-corpus route-only timing. Protocol: harness/experiments/routing-independent-20260925/timing-protocol.md.
|
||||
use oa_core::{Id, Project, projection, routing, validate_candidate};
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
use std::hint::black_box;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const ROUNDS: usize = 7;
|
||||
const SLOW_CALL: Duration = Duration::from_secs(5);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Manifest {
|
||||
schema: u32,
|
||||
cases: Vec<Case>,
|
||||
#[serde(flatten)]
|
||||
metadata: std::collections::BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Case {
|
||||
name: String,
|
||||
project_file: PathBuf,
|
||||
diagram: Id,
|
||||
description: String,
|
||||
#[serde(flatten)]
|
||||
metadata: std::collections::BTreeMap<String, serde_json::Value>,
|
||||
}
|
||||
|
||||
fn timed_route(
|
||||
project: &Project,
|
||||
diagram: &oa_core::Diagram,
|
||||
view: &projection::ProjectedView,
|
||||
) -> (routing::RoutedView, Duration) {
|
||||
// Input black_box is outside the clock. Result consumption is inside it;
|
||||
// allocation is part of routing, result destruction is outside the clock.
|
||||
let project = black_box(project);
|
||||
let diagram = black_box(diagram);
|
||||
let view = black_box(view);
|
||||
let started = Instant::now();
|
||||
let result = black_box(routing::route_diagram(project, diagram, view));
|
||||
let elapsed = started.elapsed();
|
||||
(result, elapsed)
|
||||
}
|
||||
|
||||
fn point_digest(route: &routing::RoutedView) -> String {
|
||||
// Stable FNV-1a64 over ID bytes, point count and IEEE754 coordinate bits,
|
||||
// little endian. Diagnostic only, not a cryptographic integrity guarantee.
|
||||
let mut hash = 0xcbf29ce484222325_u64;
|
||||
let mut feed = |bytes: &[u8]| {
|
||||
for byte in bytes {
|
||||
hash ^= u64::from(*byte);
|
||||
hash = hash.wrapping_mul(0x100000001b3);
|
||||
}
|
||||
};
|
||||
for (id, edge) in &route.edges {
|
||||
feed(id.as_bytes());
|
||||
feed(&(edge.points.len() as u64).to_le_bytes());
|
||||
for point in &edge.points {
|
||||
feed(&point.x.to_bits().to_le_bytes());
|
||||
feed(&point.y.to_bits().to_le_bytes());
|
||||
}
|
||||
}
|
||||
format!("{hash:016x}")
|
||||
}
|
||||
|
||||
fn relative_project(root: &Path, file: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
if file.as_os_str().is_empty()
|
||||
|| !file
|
||||
.components()
|
||||
.all(|part| matches!(part, Component::Normal(_)))
|
||||
{
|
||||
return Err(
|
||||
"project_file must be a nonempty relative path without parent traversal".into(),
|
||||
);
|
||||
}
|
||||
let path = root.join(file).canonicalize()?;
|
||||
if !path.starts_with(root) {
|
||||
return Err("project_file resolves outside the manifest directory".into());
|
||||
}
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let manifest_path = PathBuf::from(
|
||||
args.next()
|
||||
.ok_or("usage: routing_cost_probe MANIFEST.json")?,
|
||||
)
|
||||
.canonicalize()?;
|
||||
if args.next().is_some() {
|
||||
return Err(
|
||||
"protocol is fixed: one warmup and seven measured calls; no extra arguments".into(),
|
||||
);
|
||||
}
|
||||
let manifest: Manifest = serde_json::from_slice(&std::fs::read(&manifest_path)?)?;
|
||||
if manifest.schema != 1 || manifest.cases.is_empty() {
|
||||
return Err("expected schema1 with at least one case".into());
|
||||
}
|
||||
let root = manifest_path
|
||||
.parent()
|
||||
.ok_or("manifest has no parent directory")?;
|
||||
println!(
|
||||
"{}",
|
||||
json!({
|
||||
"protocol": "routing_cost_probe_v1", "manifest_schema": manifest.schema,
|
||||
"warmup_calls": 1, "measured_calls": ROUNDS,
|
||||
"slow_call_limit_ns": SLOW_CALL.as_nanos(),
|
||||
"debug_assertions": cfg!(debug_assertions),
|
||||
"os": std::env::consts::OS, "arch": std::env::consts::ARCH,
|
||||
"case_count": manifest.cases.len(),
|
||||
"corpus_metadata": manifest.metadata,
|
||||
"clock_scope": "route_diagram plus result black_box; excludes projection, JSON, equality, digest, output and result destruction",
|
||||
"digest": "FNV-1a64 IDs/counts/f32 point bits; diagnostic, not cryptographic",
|
||||
"acceptance": "none; external receipts must bind source, optimized build, binary and corpus"
|
||||
})
|
||||
);
|
||||
let mut names = std::collections::BTreeSet::new();
|
||||
for case in manifest.cases {
|
||||
if !names.insert(case.name.clone()) {
|
||||
return Err("duplicate case name".into());
|
||||
}
|
||||
let bytes = std::fs::read(relative_project(root, &case.project_file)?)?;
|
||||
let project: Project = serde_json::from_slice(&bytes)?;
|
||||
validate_candidate(&project)?;
|
||||
let original = project.clone();
|
||||
let diagram = project
|
||||
.diagrams
|
||||
.get(&case.diagram)
|
||||
.ok_or("case diagram is absent")?;
|
||||
let view = projection::project_diagram(&project, diagram);
|
||||
let (first, warmup) = timed_route(&project, diagram, &view);
|
||||
let mut samples = Vec::with_capacity(ROUNDS);
|
||||
let mut equality = Vec::with_capacity(ROUNDS);
|
||||
let mut endpoints = Vec::with_capacity(ROUNDS);
|
||||
let mut censored = warmup > SLOW_CALL;
|
||||
if !censored {
|
||||
for _ in 0..ROUNDS {
|
||||
let (route, elapsed) = timed_route(&project, diagram, &view);
|
||||
samples.push(elapsed.as_nanos());
|
||||
equality.push(route == first);
|
||||
endpoints.push(
|
||||
route.edges.len() == first.edges.len()
|
||||
&& route.edges.iter().all(|(id, edge)| {
|
||||
first.edges.get(id).is_some_and(|other| {
|
||||
edge.points.first() == other.points.first()
|
||||
&& edge.points.last() == other.points.last()
|
||||
})
|
||||
}),
|
||||
);
|
||||
if elapsed > SLOW_CALL {
|
||||
censored = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut ordered = samples.clone();
|
||||
ordered.sort_unstable();
|
||||
let complete = !censored && samples.len() == ROUNDS;
|
||||
let median = complete.then(|| ordered[ROUNDS / 2]);
|
||||
let warnings: usize = first.edges.values().map(|edge| edge.warnings.len()).sum();
|
||||
let project_unchanged = project == original;
|
||||
let stable = equality.iter().all(|same| *same);
|
||||
println!(
|
||||
"{}",
|
||||
json!({
|
||||
"case": case.name, "description": case.description,
|
||||
"corpus_case_metadata": case.metadata,
|
||||
"project_file": case.project_file, "diagram": case.diagram,
|
||||
"project_bytes": bytes.len(), "nodes": view.nodes.len(),
|
||||
"edges": view.edges.len(), "ports": view.ports.len(),
|
||||
"routed_edges": first.edges.len(), "warnings": warnings,
|
||||
"route_points": first.edges.values().map(|edge| edge.points.len()).sum::<usize>(),
|
||||
"route_point_digest": point_digest(&first),
|
||||
"warmup_ns": warmup.as_nanos(), "samples_ns": samples,
|
||||
"median_ns": median, "max_observed_measured_ns": ordered.last(),
|
||||
"complete": complete, "censored": censored,
|
||||
"censor_reason": if censored { Some("single call exceeded five seconds; remaining repeats skipped") } else { None },
|
||||
"repeat_full_routed_view_equal_warmup": equality,
|
||||
"repeat_endpoints_equal_warmup": endpoints,
|
||||
"stability_observed": !samples.is_empty(),
|
||||
"project_unchanged": project_unchanged
|
||||
})
|
||||
);
|
||||
if !stable || !project_unchanged {
|
||||
return Err(
|
||||
"routing stability or Project preservation failed; receipt above retained".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,547 @@
|
||||
//! Source-first inputs for independent routing experiments. Never invokes layout/routing.
|
||||
use oa_core::*;
|
||||
use serde_json::{Value, json};
|
||||
use std::{collections::BTreeMap, error::Error, fs, path::PathBuf};
|
||||
|
||||
struct Case {
|
||||
name: &'static str,
|
||||
description: &'static str,
|
||||
project: Project,
|
||||
diagram: Id,
|
||||
trace_edges: Vec<Id>,
|
||||
metadata: Value,
|
||||
}
|
||||
|
||||
struct Builder {
|
||||
project: Project,
|
||||
diagram: Id,
|
||||
base: u128,
|
||||
next: u128,
|
||||
edges: Vec<Id>,
|
||||
}
|
||||
|
||||
impl Builder {
|
||||
fn new(name: &str, base: u128) -> Self {
|
||||
let mut project = Project::empty();
|
||||
project.id = Id::from_u128(base);
|
||||
project.alias = name.into();
|
||||
project.name = name.into();
|
||||
let old = project.ensure_diagram(Layer::Sa);
|
||||
let diagram = Id::from_u128(base + 1);
|
||||
reidentify(&mut project, old, diagram).unwrap();
|
||||
let d = project.diagrams.get_mut(&diagram).unwrap();
|
||||
d.alias = format!("{name}_main");
|
||||
d.name = format!("{name} main view");
|
||||
d.projection = ProjectionKind::FunctionalDataflow;
|
||||
Self {
|
||||
project,
|
||||
diagram,
|
||||
base,
|
||||
next: base + 100,
|
||||
edges: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn stable(&mut self, old: Id) -> Id {
|
||||
let id = Id::from_u128(self.next);
|
||||
self.next += 1;
|
||||
reidentify(&mut self.project, old, id).unwrap();
|
||||
id
|
||||
}
|
||||
|
||||
fn function(&mut self, name: &str, x: f32, y: f32) -> Id {
|
||||
let old = self
|
||||
.project
|
||||
.add_element(Kind::Function, Layer::Sa, name, None)
|
||||
.unwrap();
|
||||
let id = self.stable(old);
|
||||
let old = self.project.place(self.diagram, id, Position { x, y });
|
||||
self.stable(old);
|
||||
id
|
||||
}
|
||||
|
||||
fn exchange(
|
||||
&mut self,
|
||||
name: &str,
|
||||
source: Id,
|
||||
target: Id,
|
||||
source_port: PortChoice,
|
||||
target_port: PortChoice,
|
||||
) -> Id {
|
||||
let id = Id::from_u128(self.base + 10_000 + self.edges.len() as u128);
|
||||
let request = FunctionalExchangeRequest {
|
||||
id,
|
||||
alias: format!("flow_{:02}", self.edges.len()),
|
||||
name: name.into(),
|
||||
description: String::new(),
|
||||
source,
|
||||
target,
|
||||
source_port,
|
||||
target_port,
|
||||
items: vec![],
|
||||
};
|
||||
let commands = ports::plan_functional_exchange(&self.project, &request).unwrap();
|
||||
self.project = execute_batch(&self.project, &commands).unwrap();
|
||||
self.edges.push(id);
|
||||
id
|
||||
}
|
||||
|
||||
fn port_pair(&self, edge: Id) -> (Id, Id) {
|
||||
let RelationBody::FunctionalExchange {
|
||||
source_port,
|
||||
target_port,
|
||||
..
|
||||
} = self.project.relations[&edge].body
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
(source_port, target_port)
|
||||
}
|
||||
|
||||
fn anchor(&mut self, owner: Id, port: Id, side: PortAnchorSide, offset: f32) {
|
||||
let node = self.project.diagrams[&self.diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.find(|n| n.element == owner)
|
||||
.unwrap()
|
||||
.id;
|
||||
self.project = prepare_command(
|
||||
&self.project,
|
||||
&Command::SetPortAnchor {
|
||||
diagram: self.diagram,
|
||||
node,
|
||||
anchor: PortAnchor { port, side, offset },
|
||||
},
|
||||
)
|
||||
.unwrap()
|
||||
.project;
|
||||
}
|
||||
}
|
||||
|
||||
fn sentinel(edge: Id, x: f32, y: f32) -> EdgeRoute {
|
||||
EdgeRoute {
|
||||
edge,
|
||||
source_attachment: None,
|
||||
target_attachment: None,
|
||||
waypoints: vec![Position { x, y }],
|
||||
label: Some(EdgeLabel {
|
||||
fraction: 0.4,
|
||||
offset: Position { x: -30., y: -70. },
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn workshop() -> Vec<Case> {
|
||||
let original =
|
||||
persistence::decode(include_bytes!("../../../examples/routing-workshop.json")).unwrap();
|
||||
let diagram = original
|
||||
.diagrams
|
||||
.values()
|
||||
.find(|d| d.alias == "sa_system_boundary_and_functions")
|
||||
.unwrap()
|
||||
.id;
|
||||
assert_eq!(
|
||||
original.diagrams[&diagram].projection,
|
||||
ProjectionKind::Allocation
|
||||
);
|
||||
assert_eq!(original.diagrams[&diagram].nodes.len(), 12);
|
||||
let container = original
|
||||
.elements
|
||||
.values()
|
||||
.find(|e| e.alias == "rescue_coordination_system")
|
||||
.unwrap()
|
||||
.id;
|
||||
let placement = original.diagrams[&diagram]
|
||||
.nodes
|
||||
.values()
|
||||
.find(|n| n.element == container)
|
||||
.unwrap()
|
||||
.id;
|
||||
let traces: Vec<_> = [
|
||||
"sa_report_to_relay",
|
||||
"sa_direct_advice",
|
||||
"sa_incident_decision",
|
||||
]
|
||||
.iter()
|
||||
.map(|alias| {
|
||||
original
|
||||
.relations
|
||||
.values()
|
||||
.find(|r| r.alias == *alias)
|
||||
.unwrap()
|
||||
.id
|
||||
})
|
||||
.collect();
|
||||
let mut folded = original.clone();
|
||||
assert!(!folded.diagrams[&diagram].nodes[&placement].collapsed);
|
||||
folded
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&placement)
|
||||
.unwrap()
|
||||
.collapsed = true;
|
||||
let mut restored = folded.clone();
|
||||
restored
|
||||
.diagrams
|
||||
.get_mut(&diagram)
|
||||
.unwrap()
|
||||
.nodes
|
||||
.get_mut(&placement)
|
||||
.unwrap()
|
||||
.collapsed = false;
|
||||
assert_eq!(restored, original);
|
||||
vec![
|
||||
Case {
|
||||
name: "workshop_sa_original",
|
||||
description: "Saved coastal rescue Allocation view, all source records retained.",
|
||||
project: original,
|
||||
diagram,
|
||||
trace_edges: traces.clone(),
|
||||
metadata: json!({"source":"examples/routing-workshop.json","view_alias":"sa_system_boundary_and_functions"}),
|
||||
},
|
||||
Case {
|
||||
name: "workshop_sa_folded",
|
||||
description: "Same saved Project with only rescue_coordination_system placement folded.",
|
||||
project: folded,
|
||||
diagram,
|
||||
trace_edges: traces,
|
||||
metadata: json!({"source":"examples/routing-workshop.json","folded_placement":placement,
|
||||
"folded_element":container,"unfold_restores_original_exact":true}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn mixed() -> Vec<Case> {
|
||||
let mut b = Builder::new("mixed_dispatch", 100_000);
|
||||
let nodes: Vec<_> = [
|
||||
("Sensor A", 40., 90.),
|
||||
("Sensor B", 40., 510.),
|
||||
("Ingest", 380., 250.),
|
||||
("Validate", 720., 80.),
|
||||
("Archive", 720., 510.),
|
||||
("Correlate", 1060., 270.),
|
||||
("Plan", 1400., 80.),
|
||||
("Dispatch", 1740., 270.),
|
||||
("Actuator", 1400., 680.),
|
||||
("Operator", 1740., 680.),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, x, y)| b.function(name, x, y))
|
||||
.collect();
|
||||
let e0 = b.exchange(
|
||||
"Sensor A observations",
|
||||
nodes[0],
|
||||
nodes[2],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let ingest_input = b.port_pair(e0).1;
|
||||
let e1 = b.exchange(
|
||||
"Sensor B observations",
|
||||
nodes[1],
|
||||
nodes[2],
|
||||
PortChoice::Create,
|
||||
PortChoice::Reuse(ingest_input),
|
||||
);
|
||||
let e2 = b.exchange(
|
||||
"Validate incoming observations",
|
||||
nodes[2],
|
||||
nodes[3],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let ingest_output = b.port_pair(e2).0;
|
||||
let e3 = b.exchange(
|
||||
"Archive incoming observations",
|
||||
nodes[2],
|
||||
nodes[4],
|
||||
PortChoice::Reuse(ingest_output),
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Validated observations",
|
||||
nodes[3],
|
||||
nodes[5],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Archived context",
|
||||
nodes[4],
|
||||
nodes[5],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Correlated situation",
|
||||
nodes[5],
|
||||
nodes[6],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Planned actions",
|
||||
nodes[6],
|
||||
nodes[7],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let e8 = b.exchange(
|
||||
"Actuator dispatch",
|
||||
nodes[7],
|
||||
nodes[8],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let dispatch_output = b.port_pair(e8).0;
|
||||
let e9 = b.exchange(
|
||||
"Operator dispatch copy",
|
||||
nodes[7],
|
||||
nodes[9],
|
||||
PortChoice::Reuse(dispatch_output),
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Operator acknowledgement",
|
||||
nodes[9],
|
||||
nodes[8],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Archived actuator policy",
|
||||
nodes[4],
|
||||
nodes[8],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
b.exchange(
|
||||
"Sensor B archive stream",
|
||||
nodes[1],
|
||||
nodes[4],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let feedback = b.exchange(
|
||||
"Sensor A sampling feedback",
|
||||
nodes[6],
|
||||
nodes[0],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
let (feedback_output, feedback_input) = b.port_pair(feedback);
|
||||
b.anchor(nodes[0], b.port_pair(e0).0, PortAnchorSide::Right, 0.25);
|
||||
b.anchor(nodes[2], ingest_input, PortAnchorSide::Left, 0.3);
|
||||
b.anchor(nodes[2], ingest_output, PortAnchorSide::Bottom, 0.6);
|
||||
b.anchor(nodes[6], feedback_output, PortAnchorSide::Top, 0.5);
|
||||
b.anchor(nodes[0], feedback_input, PortAnchorSide::Left, 0.6);
|
||||
b.anchor(nodes[7], dispatch_output, PortAnchorSide::Right, 0.5);
|
||||
b.project
|
||||
.diagrams
|
||||
.get_mut(&b.diagram)
|
||||
.unwrap()
|
||||
.routes
|
||||
.push(sentinel(feedback, 300., -100.));
|
||||
let secondary = Id::from_u128(b.base + 2);
|
||||
let mut other = b.project.diagrams[&b.diagram].clone();
|
||||
other.id = secondary;
|
||||
other.alias = "mixed_dispatch_secondary".into();
|
||||
other.name = "Feedback review sentinel".into();
|
||||
other.description = "Unaffected other-view preservation sentinel".into();
|
||||
other.nodes.clear();
|
||||
other.routes = vec![sentinel(feedback, 460., 480.)];
|
||||
b.project.diagrams.insert(secondary, other);
|
||||
for (i, element) in [nodes[0], nodes[2], nodes[6]].into_iter().enumerate() {
|
||||
let old = b.project.place(
|
||||
secondary,
|
||||
element,
|
||||
Position {
|
||||
x: 100. + i as f32 * 340.,
|
||||
y: 100.,
|
||||
},
|
||||
);
|
||||
b.stable(old);
|
||||
}
|
||||
assert_eq!(b.edges.len(), 14);
|
||||
assert_eq!(b.project.diagrams[&b.diagram].nodes.len(), 10);
|
||||
let originals = b.edges.clone();
|
||||
let replacements: Vec<_> = originals.iter().rev().copied().collect();
|
||||
let mut permuted = b.project.clone();
|
||||
for (i, id) in originals.iter().enumerate() {
|
||||
reidentify(&mut permuted, *id, Id::from_u128(1_000_000 + i as u128)).unwrap();
|
||||
}
|
||||
for (i, id) in replacements.iter().enumerate() {
|
||||
reidentify(&mut permuted, Id::from_u128(1_000_000 + i as u128), *id).unwrap();
|
||||
}
|
||||
let mut restored = permuted.clone();
|
||||
for (i, id) in replacements.iter().enumerate() {
|
||||
reidentify(&mut restored, *id, Id::from_u128(1_000_000 + i as u128)).unwrap();
|
||||
}
|
||||
for (i, id) in originals.iter().enumerate() {
|
||||
reidentify(&mut restored, Id::from_u128(1_000_000 + i as u128), *id).unwrap();
|
||||
}
|
||||
assert_eq!(restored, b.project);
|
||||
let correspondence: BTreeMap<_, _> = originals.iter().copied().zip(replacements).collect();
|
||||
let traces = vec![e1, e3, e9, feedback];
|
||||
let permuted_traces = traces.iter().map(|id| correspondence[id]).collect();
|
||||
let metadata = json!({"shared_input":ingest_input,"shared_outputs":[ingest_output,dispatch_output],
|
||||
"sentinel_edge":feedback,"secondary_diagram":secondary,"fixed_anchor_records":6,
|
||||
"authored_waypoint_count_main":1,"authored_label_count_main":1,
|
||||
"declared_feedback_cycle":[nodes[0],nodes[2],nodes[3],nodes[5],nodes[6],nodes[0]]});
|
||||
vec![
|
||||
Case {
|
||||
name: "mixed_dispatch",
|
||||
description: "Ten-function sensing/decision/dispatch network, explicit port sharing and fixed anchors.",
|
||||
project: b.project,
|
||||
diagram: b.diagram,
|
||||
trace_edges: traces,
|
||||
metadata,
|
||||
},
|
||||
Case {
|
||||
name: "mixed_dispatch_edge_ids_reversed",
|
||||
description: "Same mixed Project with fixed reversed relation-ID permutation, remapped references.",
|
||||
project: permuted,
|
||||
diagram: b.diagram,
|
||||
trace_edges: permuted_traces,
|
||||
metadata: json!({"paired_case":"mixed_dispatch","edge_id_correspondence":correspondence,
|
||||
"inverse_recovers_original_exact":true,"secondary_diagram":secondary}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn sparse() -> Case {
|
||||
let mut b = Builder::new("subsystems_40_sparse", 200_000);
|
||||
let names = [
|
||||
"Sensing",
|
||||
"Communications",
|
||||
"Assessment",
|
||||
"Planning",
|
||||
"Delivery",
|
||||
];
|
||||
let stages = [
|
||||
"Acquire",
|
||||
"Decode",
|
||||
"Validate",
|
||||
"Enrich",
|
||||
"Aggregate",
|
||||
"Decide",
|
||||
"Package",
|
||||
"Publish",
|
||||
];
|
||||
let mut nodes = Vec::new();
|
||||
for (group, name) in names.iter().enumerate() {
|
||||
let row: Vec<_> = stages
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(stage, label)| {
|
||||
b.function(
|
||||
&format!("{name} {label}"),
|
||||
80. + stage as f32 * 300.,
|
||||
80. + group as f32 * 240.,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
nodes.push(row);
|
||||
}
|
||||
for group in 0..5 {
|
||||
for stage in 0..7 {
|
||||
b.exchange(
|
||||
&format!(
|
||||
"{} {} to {}",
|
||||
names[group],
|
||||
stages[stage],
|
||||
stages[stage + 1]
|
||||
),
|
||||
nodes[group][stage],
|
||||
nodes[group][stage + 1],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut trace_edges = Vec::new();
|
||||
for group in 0..5 {
|
||||
for stage in [1, 3, 5, 7] {
|
||||
let next = (group + 1) % 5;
|
||||
let id = b.exchange(
|
||||
&format!(
|
||||
"{} {} handoff to {} {}",
|
||||
names[group],
|
||||
stages[stage],
|
||||
names[next],
|
||||
stages[stage - 1]
|
||||
),
|
||||
nodes[group][stage],
|
||||
nodes[next][stage - 1],
|
||||
PortChoice::Create,
|
||||
PortChoice::Create,
|
||||
);
|
||||
if (group == 0 && stage == 1) || (group == 4 && stage == 7) {
|
||||
trace_edges.push(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(b.edges.len(), 55);
|
||||
assert_eq!(b.project.diagrams[&b.diagram].nodes.len(), 40);
|
||||
Case {
|
||||
name: "subsystems_40_sparse",
|
||||
description: "Five eight-stage subsystem chains with twenty directed cross-subsystem handoffs; all ports distinct.",
|
||||
project: b.project,
|
||||
diagram: b.diagram,
|
||||
trace_edges,
|
||||
metadata: json!({"groups":names,"stages":stages,"chain_edges":35,"cross_edges":20,
|
||||
"all_exchange_ports_distinct":true,"grid_columns":8,"grid_rows":5}),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let mut args = std::env::args_os().skip(1);
|
||||
let output = PathBuf::from(
|
||||
args.next()
|
||||
.ok_or("Usage: routing_eval_corpus NEW_DIRECTORY")?,
|
||||
);
|
||||
if args.next().is_some() {
|
||||
return Err("Expected exactly one new output directory".into());
|
||||
}
|
||||
if output.exists() {
|
||||
return Err("Output directory already exists; refusing overwrite".into());
|
||||
}
|
||||
let mut cases = workshop();
|
||||
cases.extend(mixed());
|
||||
cases.push(sparse());
|
||||
let mut files = Vec::new();
|
||||
let mut entries = Vec::new();
|
||||
for case in cases {
|
||||
validate_candidate(&case.project)?;
|
||||
let file = format!("{}.json", case.name);
|
||||
let traces: Vec<_> = case
|
||||
.trace_edges
|
||||
.iter()
|
||||
.map(|id| {
|
||||
let relation = &case.project.relations[id];
|
||||
json!({"edge":id,"alias":relation.alias,"name":relation.name,
|
||||
"source":relation.source(),"target":relation.target()})
|
||||
})
|
||||
.collect();
|
||||
entries.push(json!({"name":case.name,"project_file":file,"diagram":case.diagram,
|
||||
"description":case.description,"metadata":case.metadata,"trace_tasks":traces,
|
||||
"source_counts":{"elements":case.project.elements.len(),"relations":case.project.relations.len(),
|
||||
"views":case.project.diagrams.len(),"selected_view_placements":case.project.diagrams[&case.diagram].nodes.len(),
|
||||
"selected_view_authored_routes":case.project.diagrams[&case.diagram].routes.len()}}));
|
||||
files.push((file, serde_json::to_vec_pretty(&case.project)?));
|
||||
}
|
||||
let manifest = json!({"schema":1,"cases":entries,"definition_baseline":"f72aa2ca",
|
||||
"construction":"Source-authored positions/topology, no Layout/projection/routing calls; validate all before writing.",
|
||||
"invariants":"Routing is read-only. Preserve all semantic records, port identities, fixed anchors, authored routes/labels, fold flags, and other views. ID permutation correspondence is explicit. No quality or automatic endpoint-side expectation.",
|
||||
"generation_in_routing_timing":false});
|
||||
let manifest_bytes = serde_json::to_vec_pretty(&manifest)?;
|
||||
fs::create_dir(&output)?;
|
||||
for (name, bytes) in files {
|
||||
fs::write(output.join(name), bytes)?;
|
||||
}
|
||||
fs::write(output.join("manifest.json"), manifest_bytes)?;
|
||||
println!("{}", output.join("manifest.json").display());
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user