use onnx_runtime_ir::{Attribute, DataType};
use crate::context::InferenceContext;
use crate::dim_expr::DimExpr;
use crate::error::ShapeInferError;
use crate::registry::InferenceRegistry;
fn same_type(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
if let Some(input) = ctx.input_type(0).cloned() {
ctx.set_output_type(0, input);
}
Ok(())
}
fn float_output(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
if let Some(shape) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) {
ctx.set_output(0, DataType::Float32, shape);
}
Ok(())
}
fn array_feature_extractor(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(input) = ctx.input_type(0).cloned() else {
return Ok(());
};
if input.shape.is_empty() {
return Err(ShapeInferError::InvalidRank {
op: "ArrayFeatureExtractor".into(),
index: 0,
rank: 0,
detail: "input must have rank at least 1".into(),
});
}
let Some(indices) = ctx.input_shape(1) else {
return Ok(());
};
if indices.len() != 1 {
return Err(ShapeInferError::InvalidRank {
op: "ArrayFeatureExtractor".into(),
index: 1,
rank: indices.len(),
detail: "indices must be rank 1".into(),
});
}
let mut shape = input.shape;
shape.pop();
shape.push(indices[0].clone());
ctx.set_output(0, input.dtype, shape);
Ok(())
}
fn label_dtype(ctx: &InferenceContext) -> Option<DataType> {
for (attr, dtype) in [
("values_strings", DataType::String),
("values_int64s", DataType::Int64),
("values_floats", DataType::Float32),
] {
if ctx.node.attr(attr).is_some() {
return Some(dtype);
}
}
if let Some(Attribute::Tensor(t)) = ctx.node.attr("values_tensor") {
return Some(t.dtype);
}
for (attr, dtype) in [
("default_string", DataType::String),
("default_int64", DataType::Int64),
("default_float", DataType::Float32),
] {
if ctx.node.attr(attr).is_some() {
return Some(dtype);
}
}
if let Some(Attribute::Tensor(t)) = ctx.node.attr("default_tensor") {
return Some(t.dtype);
}
None
}
fn label_encoder_v1(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(shape) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) else {
return Ok(());
};
let dtype = match (
ctx.node.attr("default_string").is_some(),
ctx.node.attr("default_int64").is_some(),
) {
(true, false) => DataType::String,
(false, true) => DataType::Int64,
_ => return Ok(()),
};
ctx.set_output(0, dtype, shape);
Ok(())
}
fn label_encoder(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
if let (Some(shape), Some(dtype)) = (
ctx.input_shape(0).map(<[DimExpr]>::to_vec),
label_dtype(ctx),
) {
ctx.set_output(0, dtype, shape);
}
Ok(())
}
fn category_mapper(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(shape) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) else {
return Ok(());
};
let dtype = match (
ctx.node.attr("default_string").is_some(),
ctx.node.attr("default_int64").is_some(),
) {
(true, false) => DataType::String,
(false, true) => DataType::Int64,
_ => return Ok(()),
};
ctx.set_output(0, dtype, shape);
Ok(())
}
fn tf_idf_vectorizer(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(input) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) else {
return Ok(());
};
if !(1..=2).contains(&input.len()) {
return Err(ShapeInferError::InvalidRank {
op: "TfIdfVectorizer".into(),
index: 0,
rank: input.len(),
detail: "input must be rank 1 or 2".into(),
});
}
let extent = ctx
.node
.attr("ngram_indexes")
.and_then(Attribute::as_ints)
.and_then(|indexes| indexes.iter().copied().max())
.and_then(|index| index.checked_add(1))
.filter(|&extent| extent >= 0)
.map(DimExpr::constant)
.unwrap_or_else(|| ctx.fresh_dim());
let shape = if input.len() == 1 {
vec![extent]
} else {
vec![input[0].clone(), extent]
};
ctx.set_output(0, DataType::Float32, shape);
Ok(())
}
fn string_normalizer(ctx: &mut InferenceContext) -> Result<(), ShapeInferError> {
let Some(input) = ctx.input_shape(0).map(<[DimExpr]>::to_vec) else {
return Ok(());
};
if !(1..=2).contains(&input.len()) {
return Err(ShapeInferError::InvalidRank {
op: "StringNormalizer".into(),
index: 0,
rank: input.len(),
detail: "input must be rank 1 or 2".into(),
});
}
let mut shape = input;
*shape.last_mut().expect("validated non-empty rank") = ctx.fresh_dim();
ctx.set_output(0, DataType::String, shape);
Ok(())
}
pub fn register(reg: &mut InferenceRegistry) {
const ML: &str = "ai.onnx.ml";
const DEFAULT: &str = "";
reg.register(ML, "ArrayFeatureExtractor", 1, array_feature_extractor);
reg.register(ML, "Binarizer", 1, same_type);
reg.register(ML, "CategoryMapper", 1, category_mapper);
reg.register(ML, "Imputer", 1, same_type);
reg.register(ML, "LabelEncoder", 1, label_encoder_v1);
reg.register(ML, "LabelEncoder", 2, label_encoder);
reg.register(ML, "LabelEncoder", 4, label_encoder);
reg.register(ML, "Normalizer", 1, float_output);
reg.register(ML, "Scaler", 1, float_output);
reg.register(DEFAULT, "StringNormalizer", 10, string_normalizer);
reg.register(DEFAULT, "TfIdfVectorizer", 9, tf_idf_vectorizer);
}