use crate::{
Argument, ElementType, TensorData,
ir::{ArgType, Data, Node, TensorType},
};
pub fn expand_update_outputs(node: &mut Node) {
log::debug!("Expand node {} has {} inputs", node.name, node.inputs.len());
if node.inputs.len() >= 2 {
log::debug!(
"Expand node {} input[0]: {:?}",
node.name,
node.inputs[0].ty
);
log::debug!(
"Expand node {} input[1]: {:?}",
node.name,
node.inputs[1].ty
);
}
let shape = if node.inputs.len() == 2 {
match &node.inputs[1].value {
Some(value) => match &value.data {
Data::Int64s(shape) => Some(shape.clone()),
_ => panic!("Expand operation encountered invalid input types"),
},
None => None,
}
} else {
panic!("Expand operation requires exactly two inputs");
};
let input_elem_type = match &node.inputs[0].ty {
ArgType::Tensor(tensor) => tensor.elem_type.clone(),
_ => panic!("Expand operation requires first input to be a tensor"),
};
let output = match &node.outputs[0].ty {
ArgType::Tensor(tensor) => tensor.clone(),
_ => panic!("Expand operation encountered invalid output types"),
};
if let Some(shape) = shape {
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: input_elem_type.clone(),
rank: shape.len(),
static_shape: Some(shape.into_iter().map(|dim| dim as usize).collect()),
});
} else {
let output_rank = match &node.inputs[1].ty {
ArgType::Shape(rank) => {
*rank
}
ArgType::Tensor(tensor) => {
if tensor.rank == 1 {
if let Some(static_shape) = &tensor.static_shape {
static_shape
.first()
.copied()
.expect("Static shape must contain at least one element")
} else {
if let Some(value) = &node.inputs[1].value {
if let Data::Int64s(shape_data) = &value.data {
shape_data.len()
} else {
panic!(
"Expand shape tensor has unexpected data type: {:?}",
value.data
)
}
} else {
log::warn!(
"Expand node {} has dynamic shape tensor without static shape info. Using output rank if available.",
node.name
);
match &output {
TensorType { rank, .. } if *rank > 0 => *rank,
_ => panic!(
"Cannot determine output rank for Expand node {} with fully dynamic shape tensor. Please provide static shape or use Shape type.",
node.name
),
}
}
}
} else {
panic!(
"Shape tensor for Expand must be 1-dimensional, got rank {}",
tensor.rank
)
}
}
_ => panic!("Shape input must be of tensor or shape type"),
};
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: input_elem_type,
rank: output_rank,
static_shape: None, });
}
}
#[derive(Debug, Clone)]
pub enum ExpandShape {
Static(Vec<i64>),
Runtime(Argument),
}
pub fn expand_config(node: &Node) -> ExpandShape {
match &node.inputs[1].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.rank, 1, "Expand: shape tensor must be 1D");
assert!(
matches!(tensor.elem_type, ElementType::Int64),
"Expand: shape tensor must have element type int64"
);
}
ArgType::Shape(_) => {
}
_ => panic!("Only tensor input is valid for shape"),
}
match &node.inputs[1].value {
Some(TensorData {
data: Data::Int64s(shape),
..
}) => ExpandShape::Static(shape.clone()),
None => {
ExpandShape::Runtime(node.inputs[1].clone())
}
_ => panic!(
"Shape data type must be int64, is {:?}",
&node.inputs[1].value
),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ir::{ElementType, NodeType, TensorData};
use crate::node::test_utils::NodeBuilder;
fn create_test_node(
input_rank: usize,
shape_value: Option<Vec<i64>>,
shape_type: Option<ArgType>,
) -> Node {
let mut builder = NodeBuilder::new(NodeType::Expand, "test_expand")
.input_tensor_f32("input", input_rank, None)
.output_tensor_f32("output", 0, None);
if let Some(shape) = shape_value {
builder = builder.input_tensor_i64_data("shape", shape.clone(), vec![shape.len()]);
} else if let Some(st) = shape_type {
builder = builder.add_input("shape", st);
} else {
builder = builder.input_tensor_i64("shape", 1, Some(vec![3]));
}
builder.build()
}
#[test]
fn test_expand_with_constant_shape() {
let mut node = create_test_node(2, Some(vec![2, 3, 4]), None);
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 3);
assert_eq!(tensor.static_shape, Some(vec![2, 3, 4]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_expand_with_dynamic_shape() {
let mut node = create_test_node(2, None, None);
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 3);
assert_eq!(tensor.static_shape, None);
}
_ => panic!("Expected tensor output"),
}
}
#[test]
#[should_panic(expected = "Expand operation requires exactly two inputs")]
fn test_expand_with_incorrect_inputs() {
let mut node = create_test_node(2, Some(vec![2, 3, 4]), None);
node.inputs.pop();
expand_update_outputs(&mut node);
}
#[test]
fn test_expand_config_with_static_shape() {
let node = create_test_node(2, Some(vec![2, 3, 4]), None);
let config = expand_config(&node);
match config {
ExpandShape::Static(shape) => {
assert_eq!(shape, vec![2, 3, 4]);
}
ExpandShape::Runtime(_) => panic!("Expected Static config, got Runtime"),
}
}
#[test]
fn test_expand_config_with_runtime_shape() {
let node = create_test_node(2, None, None);
let config = expand_config(&node);
match config {
ExpandShape::Static(_) => panic!("Expected Runtime config, got Static"),
ExpandShape::Runtime(arg) => {
assert_eq!(arg.name, "shape");
match arg.ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Int64);
assert_eq!(tensor.rank, 1);
}
_ => panic!("Expected tensor type for runtime shape"),
}
}
}
}
#[test]
fn test_expand_config_with_shape_type() {
let shape_type = ArgType::Shape(3);
let node = create_test_node(2, None, Some(shape_type));
let config = expand_config(&node);
match config {
ExpandShape::Static(_) => panic!("Expected Runtime config, got Static"),
ExpandShape::Runtime(arg) => {
assert_eq!(arg.name, "shape");
match arg.ty {
ArgType::Shape(rank) => {
assert_eq!(rank, 3);
}
_ => panic!("Expected shape type for runtime shape"),
}
}
}
}
#[test]
#[should_panic(expected = "Expand: shape tensor must be 1D")]
fn test_expand_config_with_invalid_shape_rank() {
let invalid_shape_type = ArgType::Tensor(TensorType {
elem_type: ElementType::Int64,
rank: 2, static_shape: None,
});
let node = create_test_node(2, None, Some(invalid_shape_type));
let _ = expand_config(&node);
}
#[test]
#[should_panic(expected = "Expand: shape tensor must have element type int64")]
fn test_expand_config_with_invalid_shape_type() {
let invalid_shape_type = ArgType::Tensor(TensorType {
elem_type: ElementType::Float32, rank: 1,
static_shape: None,
});
let node = create_test_node(2, None, Some(invalid_shape_type));
let _ = expand_config(&node);
}
#[test]
#[should_panic(expected = "Only tensor input is valid for shape")]
fn test_expand_config_with_invalid_input_type() {
let invalid_shape_type = ArgType::Scalar(ElementType::Int64);
let node = create_test_node(2, None, Some(invalid_shape_type));
let _ = expand_config(&node);
}
#[test]
#[should_panic(expected = "Shape data type must be int64")]
fn test_expand_config_with_invalid_value_type() {
let mut node = create_test_node(2, None, None);
node.inputs[1].value = Some(TensorData {
shape: vec![1],
data: Data::Float32s(vec![1.0]), });
let _ = expand_config(&node);
}
#[test]
fn test_expand_update_outputs_with_shape_input() {
let mut node = create_test_node(2, None, Some(ArgType::Shape(4)));
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 4); assert_eq!(tensor.static_shape, None); }
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_expand_update_outputs_with_shape_input_static_value() {
let mut node = create_test_node(2, None, Some(ArgType::Shape(3)));
node.inputs[1].value = Some(TensorData {
shape: vec![3],
data: Data::Int64s(vec![5, 10, 15]),
});
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(tensor.elem_type, ElementType::Float32);
assert_eq!(tensor.rank, 3);
assert_eq!(tensor.static_shape, Some(vec![5, 10, 15]));
}
_ => panic!("Expected tensor output"),
}
}
#[test]
fn test_expand_preserves_input_element_type() {
{
let mut node = NodeBuilder::new(NodeType::Expand, "test_expand")
.input_tensor_f32("input", 2, None)
.input_tensor_i64_data("shape", vec![2, 3, 4], vec![3])
.output_tensor_f32("output", 0, None)
.build();
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Int64, rank: 0,
static_shape: None,
});
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(
tensor.elem_type,
ElementType::Float32,
"Expand should preserve Float32 input type"
);
assert_eq!(tensor.rank, 3);
}
_ => panic!("Expected tensor output"),
}
}
{
let mut node = NodeBuilder::new(NodeType::Expand, "test_expand")
.input_tensor_i64("input", 2, None)
.input_tensor_i64_data("shape", vec![2, 3, 4], vec![3])
.output_tensor_i64("output", 0, None)
.build();
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Float32, rank: 0,
static_shape: None,
});
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(
tensor.elem_type,
ElementType::Int64,
"Expand should preserve Int64 input type"
);
assert_eq!(tensor.rank, 3);
}
_ => panic!("Expected tensor output"),
}
}
{
let mut node = NodeBuilder::new(NodeType::Expand, "test_expand")
.input_tensor_bool("input", 2, None)
.input_tensor_i64_data("shape", vec![2, 3, 4], vec![3])
.output_tensor_bool("output", 0, None)
.build();
node.outputs[0].ty = ArgType::Tensor(TensorType {
elem_type: ElementType::Float32, rank: 0,
static_shape: None,
});
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(
tensor.elem_type,
ElementType::Bool,
"Expand should preserve Bool input type"
);
assert_eq!(tensor.rank, 3);
}
_ => panic!("Expected tensor output"),
}
}
}
#[test]
fn test_expand_with_mismatched_output_type() {
let mut node = NodeBuilder::new(NodeType::Expand, "test_expand")
.input_tensor_i64("input", 2, None) .input_tensor_i64_data("shape", vec![2, 3], vec![2])
.output_tensor_f32("output", 0, None) .build();
expand_update_outputs(&mut node);
match &node.outputs[0].ty {
ArgType::Tensor(tensor) => {
assert_eq!(
tensor.elem_type,
ElementType::Int64,
"Expand should use input type (Int64) not initial output type (Float32)"
);
assert_eq!(tensor.rank, 2);
assert_eq!(tensor.static_shape, Some(vec![2, 3]));
}
_ => panic!("Expected tensor output"),
}
}
}