use super::*;
#[test]
fn test_tagged_capture() {
let graph = FlowBuilder::from(Identity)
.tag("features")
.through(Doubler)
.build()
.unwrap();
let x = Variable::new(from_f32(&[1.0, 2.0], &[1, 2]), false);
let _ = graph.forward(&x).unwrap();
let features = graph.tagged("features").unwrap();
let data = features.data().to_f32_vec().unwrap();
assert!((data[0] - 1.0).abs() < 1e-5);
assert!((data[1] - 2.0).abs() < 1e-5);
assert!(graph.tagged("nonexistent").is_none());
}
#[test]
fn test_tagged_updates_each_forward() {
let graph = FlowBuilder::from(Doubler)
.tag("doubled")
.build()
.unwrap();
let x1 = Variable::new(from_f32(&[1.0], &[1, 1]), false);
let _ = graph.forward(&x1).unwrap();
let v1 = graph.tagged("doubled").unwrap().item().unwrap();
assert!((v1 - 2.0).abs() < 1e-5);
let x2 = Variable::new(from_f32(&[5.0], &[1, 1]), false);
let _ = graph.forward(&x2).unwrap();
let v2 = graph.tagged("doubled").unwrap().item().unwrap();
assert!((v2 - 10.0).abs() < 1e-5);
}
#[test]
fn test_tag_names() {
let graph = FlowBuilder::from(Identity)
.tag("a")
.through(Identity)
.tag("b")
.build()
.unwrap();
let mut names = graph.tag_names();
names.sort();
assert_eq!(names, vec!["a", "b"]);
}
#[test]
fn test_collect_flush_trend() {
let graph = FlowBuilder::from(ScalarSum)
.tag("loss")
.build()
.unwrap();
for val in &[1.0f32, 2.0, 3.0] {
let x = Variable::new(from_f32(&[*val], &[1, 1]), false);
let _ = graph.forward(&x).unwrap();
graph.collect(&["loss"]).unwrap();
}
let collected = graph.collected("loss");
assert_eq!(collected.len(), 3);
graph.flush(&["loss"]);
assert_eq!(graph.flush_count(), 1);
for val in &[0.5f32, 0.3, 0.2] {
let x = Variable::new(from_f32(&[*val], &[1, 1]), false);
let _ = graph.forward(&x).unwrap();
graph.collect(&["loss"]).unwrap();
}
graph.flush(&["loss"]);
assert_eq!(graph.flush_count(), 2);
let trend = graph.trend("loss");
assert_eq!(trend.len(), 2);
assert!((trend.values()[0] - 2.0).abs() < 1e-5);
assert!((trend.values()[1] - (1.0 / 3.0)).abs() < 1e-5);
assert!(trend.improving(0));
}
#[test]
fn test_record_external_values() {
let graph = FlowBuilder::from(Identity).build().unwrap();
graph.record("external_loss", &[0.5, 0.4, 0.3]);
graph.flush(&["external_loss"]);
graph.record("external_loss", &[0.1, 0.05]);
graph.flush(&["external_loss"]);
let trend = graph.trend("external_loss");
assert_eq!(trend.len(), 2);
assert!((trend.values()[0] - 0.4).abs() < 1e-5); assert!((trend.values()[1] - 0.075).abs() < 1e-5); assert!(trend.improving(0));
}
#[test]
fn test_flush_all() {
let graph = FlowBuilder::from(Identity).build().unwrap();
graph.record("a", &[1.0, 2.0]);
graph.record("b", &[3.0, 4.0]);
graph.flush(&[]);
assert_eq!(graph.trend("a").len(), 1);
assert_eq!(graph.trend("b").len(), 1);
}
#[test]
fn test_reset_trend() {
let graph = FlowBuilder::from(Identity).build().unwrap();
graph.record("loss", &[1.0]);
graph.flush(&[]);
assert_eq!(graph.trend("loss").len(), 1);
graph.reset_trend(&["loss"]);
assert_eq!(graph.trend("loss").len(), 0);
}
#[test]
fn test_trends_group() {
let graph = FlowBuilder::from(Identity).build().unwrap();
for epoch in &[10.0, 8.0, 6.0, 4.0] {
graph.record("a", &[*epoch]);
graph.record("b", &[*epoch * 0.5]);
graph.flush(&[]);
}
let tg = graph.trends(&["a", "b"]);
assert_eq!(tg.len(), 2);
assert!(tg.all_improving(0));
}
#[test]
fn test_tag_group() {
let graph = FlowBuilder::from(Identity)
.split(vec![
Box::new(Doubler),
Box::new(Tripler),
Box::new(Identity),
])
.tag_group("branch")
.merge(MergeOp::Add)
.build()
.unwrap();
let members = graph.tag_group("branch").unwrap();
assert_eq!(members, &["branch_0", "branch_1", "branch_2"]);
assert!(graph.tag_group("nonexistent").is_none());
let x = Variable::new(from_f32(&[1.0, 2.0], &[1, 2]), false);
let _ = graph.forward(&x).unwrap();
let b0 = graph.tagged("branch_0").unwrap();
let b0_data = b0.data().to_f32_vec().unwrap();
assert!((b0_data[0] - 2.0).abs() < 1e-5, "doubler: got {}", b0_data[0]);
let b1 = graph.tagged("branch_1").unwrap();
let b1_data = b1.data().to_f32_vec().unwrap();
assert!((b1_data[0] - 3.0).abs() < 1e-5, "tripler: got {}", b1_data[0]);
}
#[test]
fn test_tag_group_observation() {
let graph = FlowBuilder::from(Identity)
.split(vec![Box::new(ScalarSum), Box::new(ScalarSum)])
.tag_group("head")
.merge(MergeOp::Add)
.build()
.unwrap();
for epoch in &[1.0f32, 2.0, 3.0] {
let x = Variable::new(from_f32(&[*epoch], &[1, 1]), false);
let _ = graph.forward(&x).unwrap();
graph.collect(&["head_0", "head_1"]).unwrap();
graph.flush(&["head_0", "head_1"]);
}
let tg = graph.trends(&["head"]);
assert_eq!(tg.len(), 2); }
#[test]
fn test_tag_group_errors() {
let result = FlowBuilder::from(Identity)
.tag_group("bad")
.build();
assert!(result.is_err());
let result = FlowBuilder::from(Identity)
.split(vec![Box::new(Doubler), Box::new(Tripler)])
.tag_group("x")
.merge(MergeOp::Add)
.split(vec![Box::new(Doubler), Box::new(Tripler)])
.tag_group("x")
.merge(MergeOp::Add)
.build();
assert!(result.is_err());
}
#[test]
fn test_collect_with_sum_reduction() {
let graph = FlowBuilder::from(Identity)
.tag("features")
.build()
.unwrap();
let x = Variable::new(from_f32(&[1.0, 2.0, 3.0], &[1, 3]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["features"], Reduce::Sum).unwrap();
let collected = graph.collected("features");
assert_eq!(collected.len(), 1);
assert!((collected[0] - 6.0).abs() < 1e-5, "sum([1,2,3]) = 6, got {}", collected[0]);
}
#[test]
fn test_collect_with_mean_reduction() {
let graph = FlowBuilder::from(Identity)
.tag("out")
.build()
.unwrap();
let x = Variable::new(from_f32(&[2.0, 4.0, 6.0], &[1, 3]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["out"], Reduce::Mean).unwrap();
let collected = graph.collected("out");
assert!((collected[0] - 4.0).abs() < 1e-5, "mean([2,4,6]) = 4, got {}", collected[0]);
}
#[test]
fn test_collect_with_max_reduction() {
let graph = FlowBuilder::from(Identity)
.tag("out")
.build()
.unwrap();
let x = Variable::new(from_f32(&[1.0, 5.0, 3.0], &[1, 3]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["out"], Reduce::Max).unwrap();
let collected = graph.collected("out");
assert!((collected[0] - 5.0).abs() < 1e-5, "max([1,5,3]) = 5, got {}", collected[0]);
}
#[test]
fn test_collect_with_min_reduction() {
let graph = FlowBuilder::from(Identity)
.tag("out")
.build()
.unwrap();
let x = Variable::new(from_f32(&[-2.0, 0.0, 3.0], &[1, 3]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["out"], Reduce::Min).unwrap();
let collected = graph.collected("out");
assert!((collected[0] - (-2.0)).abs() < 1e-5, "min([-2,0,3]) = -2, got {}", collected[0]);
}
#[test]
fn test_collect_with_norm_reduction() {
let graph = FlowBuilder::from(Identity)
.tag("out")
.build()
.unwrap();
let x = Variable::new(from_f32(&[3.0, 4.0], &[1, 2]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["out"], Reduce::Norm).unwrap();
let collected = graph.collected("out");
assert!((collected[0] - 5.0).abs() < 1e-4, "norm([3,4]) = 5, got {}", collected[0]);
}
#[test]
fn test_collect_rejects_non_scalar() {
let graph = FlowBuilder::from(Identity)
.tag("out")
.build()
.unwrap();
let x = Variable::new(from_f32(&[1.0, 2.0], &[1, 2]), false);
let _ = graph.forward(&x).unwrap();
assert!(graph.collect(&["out"]).is_err());
}
#[test]
fn test_collect_with_scalar_passthrough() {
let graph = FlowBuilder::from(ScalarSum)
.tag("loss")
.build()
.unwrap();
let x = Variable::new(from_f32(&[3.0, 7.0], &[1, 2]), false);
let _ = graph.forward(&x).unwrap();
graph.collect_with(&["loss"], Reduce::Max).unwrap();
let collected = graph.collected("loss");
assert!((collected[0] - 10.0).abs() < 1e-5);
}
#[test]
fn test_collect_with_flush_trend_pipeline() {
let graph = FlowBuilder::from(Identity)
.tag("h")
.build()
.unwrap();
let x1 = Variable::new(from_f32(&[3.0, 4.0], &[1, 2]), false);
let _ = graph.forward(&x1).unwrap();
graph.collect_with(&["h"], Reduce::Norm).unwrap();
let x2 = Variable::new(from_f32(&[1.0, 0.0], &[1, 2]), false);
let _ = graph.forward(&x2).unwrap();
graph.collect_with(&["h"], Reduce::Norm).unwrap();
graph.flush(&["h"]);
let x3 = Variable::new(from_f32(&[0.5, 0.5], &[1, 2]), false);
let _ = graph.forward(&x3).unwrap();
graph.collect_with(&["h"], Reduce::Norm).unwrap();
graph.flush(&["h"]);
let trend = graph.trend("h");
assert_eq!(trend.len(), 2);
assert!((trend.values()[0] - 3.0).abs() < 1e-4);
assert!(trend.improving(0)); }