use super::*;
use crate::backends::smartcore::RandomForest;
use crate::frame::Dataset;
use crate::traits::{Estimator, Predictor};
fn scratch(name: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("millwright_onnx_{name}.onnx"))
}
fn two_class() -> (Dataset, Frame) {
let mut rows = Vec::new();
let mut y = Vec::new();
for i in 0..20 {
rows.push(vec![i as f64 * 0.05, i as f64 * 0.05]);
y.push(0.0);
rows.push(vec![9.0 + i as f64 * 0.05, 9.0 + i as f64 * 0.05]);
y.push(1.0);
}
let cols = vec!["a".to_string(), "b".to_string()];
let ds = Dataset::new(Frame::from_rows(rows, cols.clone()).unwrap(), y).unwrap();
let probe =
Frame::from_rows(vec![vec![0.3, 0.2], vec![9.2, 9.3], vec![0.1, 0.0]], cols).unwrap();
(ds, probe)
}
#[test]
fn random_forest_serves_natively_through_onnx() {
use crate::pipeline::Pipeline;
use crate::transform::StandardScaler;
let (ds, probe) = two_class();
let mut pipe = Pipeline::new()
.step("scale", StandardScaler::new())
.estimator("rf", RandomForest::new().n_trees(15).max_depth(4));
pipe.fit(&ds).unwrap();
let native = pipe.predict(&probe).unwrap();
let path = scratch("rf_serve");
pipe.export_onnx(&path).unwrap();
let loaded = InferenceModel::load(&path).unwrap();
let via_onnx = loaded.predict(&probe).unwrap();
assert_eq!(
native, via_onnx,
"ONNX-served forest must match the pipeline"
);
let _ = std::fs::remove_file(&path);
}
#[test]
fn random_forest_exports_valid_onnx() {
let (ds, _) = two_class();
let mut rf = RandomForest::new().n_trees(20).max_depth(4);
rf.fit(&ds).unwrap();
assert!(rf.to_onnx().is_ok());
let path = scratch("rf");
rf.export_onnx(&path).unwrap();
assert!(std::fs::metadata(&path).unwrap().len() > 0);
let _ = std::fs::remove_file(&path);
}
#[test]
fn linear_regression_round_trips_through_onnx() {
use crate::backends::smartcore::LinearRegression;
let rows: Vec<Vec<f64>> = (0..15).map(|i| vec![i as f64, (i % 4) as f64]).collect();
let y: Vec<f64> = rows.iter().map(|r| 2.0 * r[0] + 3.0 * r[1] + 1.0).collect();
let ds = Dataset::new(
Frame::from_rows(rows, vec!["x1".into(), "x2".into()]).unwrap(),
y,
)
.unwrap();
let mut lr = LinearRegression::new();
lr.fit(&ds).unwrap();
let probe = Frame::from_rows(
vec![vec![20.0, 1.0], vec![5.0, 2.0]],
vec!["x1".into(), "x2".into()],
)
.unwrap();
let native = lr.predict(&probe).unwrap();
let path = scratch("lr");
lr.export_onnx(&path).unwrap();
let loaded = InferenceModel::load(&path).unwrap();
let via_onnx = loaded.predict(&probe).unwrap();
for (a, b) in native.iter().zip(&via_onnx) {
assert!((a - b).abs() < 1e-3, "native {a} vs onnx {b}");
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn pipeline_scaler_plus_linear_round_trips() {
use crate::backends::smartcore::LinearRegression;
use crate::pipeline::Pipeline;
use crate::transform::StandardScaler;
let rows: Vec<Vec<f64>> = (0..15).map(|i| vec![i as f64, (i % 4) as f64]).collect();
let y: Vec<f64> = rows.iter().map(|r| 2.0 * r[0] + 3.0 * r[1] + 1.0).collect();
let ds = Dataset::new(
Frame::from_rows(rows, vec!["x1".into(), "x2".into()]).unwrap(),
y,
)
.unwrap();
let mut pipe = Pipeline::new()
.step("scale", StandardScaler::new())
.estimator("lr", LinearRegression::new());
pipe.fit(&ds).unwrap();
let probe = Frame::from_rows(
vec![vec![20.0, 1.0], vec![5.0, 2.0]],
vec!["x1".into(), "x2".into()],
)
.unwrap();
let native = pipe.predict(&probe).unwrap();
let path = scratch("pipe");
pipe.export_onnx(&path).unwrap();
let loaded = InferenceModel::load(&path).unwrap();
let via_onnx = loaded.predict(&probe).unwrap();
for (a, b) in native.iter().zip(&via_onnx) {
assert!((a - b).abs() < 1e-3, "native {a} vs onnx {b}");
}
let _ = std::fs::remove_file(&path);
}
#[test]
fn inference_model_serves_as_a_pipeline_estimator() {
use crate::backends::smartcore::LinearRegression;
use crate::pipeline::Pipeline;
let rows: Vec<Vec<f64>> = (0..15).map(|i| vec![i as f64, (i % 4) as f64]).collect();
let y: Vec<f64> = rows.iter().map(|r| 2.0 * r[0] + 3.0 * r[1] + 1.0).collect();
let cols = vec!["x1".to_string(), "x2".to_string()];
let ds = Dataset::new(Frame::from_rows(rows, cols.clone()).unwrap(), y).unwrap();
let mut lr = LinearRegression::new();
lr.fit(&ds).unwrap();
let path = scratch("pipe_estimator");
lr.export_onnx(&path).unwrap();
let onnx = InferenceModel::load(&path).unwrap();
let mut pipe = Pipeline::new().estimator("onnx", onnx);
pipe.fit(&ds).unwrap(); let probe = Frame::from_rows(vec![vec![20.0, 1.0]], cols).unwrap();
let via_pipe = pipe.predict(&probe).unwrap();
let direct = InferenceModel::load(&path)
.unwrap()
.predict(&probe)
.unwrap();
assert!((via_pipe[0] - direct[0]).abs() < 1e-4);
let _ = std::fs::remove_file(&path);
}