use crate::error::Error;
use crate::neural_network::layers::ParamCounts;
use crate::neural_network::layers::validation::start_build;
use crate::neural_network::layers::{
built_layer_shape_functions, no_trainable_parameters_layer_functions,
};
use crate::neural_network::traits::{LayerBase, UnaryLayer};
use crate::neural_network::{Ctx, Shape, Tensor};
use ndarray::{Axis, Slice};
#[derive(Debug)]
pub struct Reverse {
axis: i32,
name: String,
built: Option<Shape>,
}
impl Reverse {
pub fn new(axis: i32) -> Self {
Self {
axis,
name: format!("Reverse({axis})"),
built: None,
}
}
fn resolve(&self, rank: usize) -> Result<usize, Error> {
if rank < 3 {
return Err(Error::invalid_input(format!(
"Reverse takes an input of rank 3 or more, and it got rank {rank}. A rank-2 \
input holds 1 feature vector per sample, and it holds no axis whose order \
carries meaning. A recurrent branch that gives rank 2 returned its last state \
alone, and it needs `with_return_sequences(true)` to give a time axis"
)));
}
let resolved = if self.axis < 0 {
let back = usize::try_from(-self.axis).expect("a negative i32 negates into a usize");
match rank.checked_sub(back) {
Some(index) => index,
None => return Err(self.axis_refusal(rank)),
}
} else {
usize::try_from(self.axis).expect("a positive i32 is a usize")
};
if resolved >= rank {
return Err(self.axis_refusal(rank));
}
if resolved == 0 {
return Err(Error::invalid_input(format!(
"Reverse cannot reverse axis 0 of an input of rank {rank}, because axis 0 is \
the batch axis. Reversing it would put every sample against the target of \
another sample"
)));
}
Ok(resolved)
}
#[cold]
fn axis_refusal(&self, rank: usize) -> Error {
Error::invalid_input(format!(
"Reverse names the axis {}, and the input holds rank {rank}. The axis counts \
against the full rank, and a negative axis counts back from the end",
self.axis
))
}
fn flip(&self, input: &Tensor) -> Result<Tensor, Error> {
let axis = self.resolve(input.ndim())?;
let mut output = Tensor::zeros(input.raw_dim());
output.assign(&input.view().slice_axis(Axis(axis), Slice::new(0, None, -1)));
Ok(output)
}
}
impl LayerBase for Reverse {
fn layer_type(&self) -> &str {
&self.name
}
built_layer_shape_functions!();
no_trainable_parameters_layer_functions!();
}
impl UnaryLayer for Reverse {
fn build(&mut self, input: &Shape) -> Result<(), Error> {
self.resolve(input.rank())?;
let Some(built) = start_build(&self.built, self.layer_type(), input)? else {
return Ok(());
};
self.built = Some(built);
Ok(())
}
fn forward(&self, input: &Tensor, _ctx: &mut Ctx) -> Result<Tensor, Error> {
self.flip(input)
}
fn backward(&self, grad_output: &Tensor, _ctx: &mut Ctx) -> Result<Tensor, Error> {
self.flip(grad_output)
}
fn compute_output_shape(&self, input: &Shape) -> Result<Shape, Error> {
self.resolve(input.rank())?;
Ok(input.clone())
}
}