use crate::{Element, Shape, Tensor};
use super::Value;
impl<'tape, E: Element> Value<'tape, E> {
pub fn abs(self) -> Self {
self.maximum(-self)
}
pub fn relu(self) -> Self {
self.maximum(self.literal(Tensor::counted(self.shape(), 0)))
}
}
impl<'tape, E: Element> Value<'tape, E> {
pub fn softplus(self) -> Self {
self.relu() + (-self.abs()).exp().log1p()
}
pub fn gelu(self) -> Self {
let one = self.literal(Tensor::counted(self.shape(), 1));
let two = self.literal(Tensor::counted(self.shape(), 2));
self * (one + (self / two.sqrt()).erf()) / two
}
pub fn softmax(self, axis: usize) -> Self {
self.log_softmax(axis).exp()
}
pub fn mean_along(self, axis: usize) -> Self {
let shape = self.shape();
assert!(axis < shape.rank(), "mean_along axis {axis} is out of rank");
let extent = shape.axes()[axis];
self.sum_along(axis) / Tensor::counted(shape.without_axis(axis), extent)
}
pub fn broadcast_like(self, reference: Self) -> Self {
self.broadcast(reference.shape())
}
pub fn broadcast_along_like(self, axis: usize, reference: Self) -> Self {
let reference_shape = reference.shape();
assert!(
axis < reference_shape.rank(),
"axis {axis} is out of rank for {reference_shape}"
);
assert_eq!(
self.shape(),
reference_shape.without_axis(axis),
"broadcast along axis {axis} of {reference_shape} requires the remaining shape"
);
self.broadcast_along(axis, reference_shape.axes()[axis])
}
pub fn transpose(self) -> Self {
let rank = self.shape().rank();
assert!(rank <= 2, "transpose supports rank 2 at most");
self.permute((0..rank).rev())
}
pub fn unsqueeze(self, axis: usize) -> Self {
let mut axes: Vec<usize> = self.shape().axes().to_vec();
assert!(axis <= axes.len(), "unsqueeze axis {axis} is out of rank");
axes.insert(axis, 1);
self.reshape(axes)
}
pub fn squeeze(self, axis: usize) -> Self {
let mut axes: Vec<usize> = self.shape().axes().to_vec();
assert!(axis < axes.len(), "squeeze axis {axis} is out of rank");
assert_eq!(axes[axis], 1, "squeeze requires an extent-1 axis");
axes.remove(axis);
self.reshape(axes)
}
pub fn broadcast_to(self, shape: impl Into<Shape>) -> Self {
let target = shape.into();
let source = self.shape();
if source == target {
return self;
}
assert!(
target.rank() >= source.rank(),
"broadcast to {target} from {source} lowers the rank"
);
let offset = target.rank() - source.rank();
for (axis, &extent) in source.axes().iter().enumerate() {
let aligned = target.axes()[offset + axis];
assert!(
extent == aligned || extent == 1,
"broadcast to {target} from {source} cannot align source axis \
{axis} of extent {extent} to extent {aligned}"
);
}
if source.volume() == 1 {
return self.broadcast(target);
}
let mut current = if offset == 0 {
self
} else {
let mut axes = vec![1; offset];
axes.extend_from_slice(source.axes());
self.reshape(axes)
};
for axis in 0..target.rank() {
let aligned = target.axes()[axis];
if current.shape().axes()[axis] == aligned {
continue;
}
current = current.squeeze(axis).broadcast_along(axis, aligned);
}
current
}
}
pub fn concat<'tape, E: Element>(values: &[Value<'tape, E>], axis: usize) -> Value<'tape, E> {
let first = values.first().expect("concat requires at least one value");
let reference = first.shape();
assert!(
axis < reference.rank(),
"concat axis {axis} is out of rank for {reference}"
);
for value in &values[1..] {
let shape = value.shape();
assert_eq!(
shape.without_axis(axis),
reference.without_axis(axis),
"concat along axis {axis} requires equal shapes off the axis, \
got {shape} against {reference}"
);
}
if values.len() == 1 {
return *first;
}
let combined: usize = values.iter().map(|value| value.shape().axes()[axis]).sum();
let mut offset = 0;
let mut total: Option<Value<'tape, E>> = None;
for &value in values {
let padded = value.pad(axis, offset, combined);
offset += value.shape().axes()[axis];
total = Some(match total {
Some(sum) => sum + padded,
None => padded,
});
}
total.expect("concat combines at least one value")
}
pub fn stack<'tape, E: Element>(values: &[Value<'tape, E>], axis: usize) -> Value<'tape, E> {
let lifted: Vec<Value<'tape, E>> = values.iter().map(|&value| value.unsqueeze(axis)).collect();
concat(&lifted, axis)
}
#[cfg(test)]
#[path = "tests/composite_tests.rs"]
mod tests;