diff --git a/src/display.rs b/src/display.rs
index 8932894..de1bc02 100644
--- a/src/display.rs
+++ b/src/display.rs
@@ -3,7 +3,7 @@
//! render the same way, by walking a shape.
use std::fmt;
-use std::fmt::Write as _;
+use std::fmt::{Display, Write as _};
use crate::domain::HRect;
use crate::mapping::{BoxedMapping, BoxedPowersetMapping};
@@ -13,41 +13,43 @@ use crate::tensor::{
/// A tensor whose domain is a hrect. Only these can be printed, since formatting
/// walks a shape and visits every index of it.
-pub trait BoxedTensor: Tensor {
+pub trait BoxedTensor: Tensor {
fn hrect(&self) -> &HRect;
}
/// Renders a boxed tensor. Every `Display` implementation below defers to this,
/// and each is written for the boxed instantiation of its tensor only.
-pub fn format_boxed(tensor: &dyn BoxedTensor) -> String {
+pub fn format_boxed(tensor: &dyn BoxedTensor) -> String {
format_tensor(tensor.hrect().shape(), &|index| tensor.get(index))
}
-impl fmt::Display for Physical {
+impl Display for Physical {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_boxed(self))
}
}
-impl fmt::Display for RightComposition {
+impl Display for RightComposition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_boxed(self))
}
}
-impl fmt::Display for LeftComposition {
+impl + ?Sized, A, B: Display> Display for LeftComposition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_boxed(self))
}
}
-impl fmt::Display for ReductionTensor {
+impl, M: BoxedPowersetMapping + ?Sized, A: Display> Display
+ for ReductionTensor
+{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&format_boxed(self))
}
}
-fn format_tensor(shape: &[usize], accessor: &dyn Fn(&[usize]) -> f32) -> String {
+fn format_tensor(shape: &[usize], accessor: &dyn Fn(&[usize]) -> A) -> String {
if shape.is_empty() {
return format!("tensor({})", accessor(&[]));
}
@@ -70,11 +72,11 @@ fn format_tensor(shape: &[usize], accessor: &dyn Fn(&[usize]) -> f32) -> String
out
}
-fn max_width(
+fn max_width(
shape: &[usize],
index: &mut [usize],
dim: usize,
- accessor: &dyn Fn(&[usize]) -> f32,
+ accessor: &dyn Fn(&[usize]) -> A,
) -> usize {
let mut width = 0;
for i in 0..shape[dim] {
@@ -88,14 +90,14 @@ fn max_width(
width
}
-fn format_nested(
+fn format_nested(
out: &mut String,
shape: &[usize],
index: &mut [usize],
dim: usize,
indent: &str,
width: usize,
- accessor: &dyn Fn(&[usize]) -> f32,
+ accessor: &dyn Fn(&[usize]) -> A,
) {
let innermost = dim == shape.len() - 1;
out.push('[');
diff --git a/src/functions.rs b/src/functions.rs
index 93307fb..5e4569a 100644
--- a/src/functions.rs
+++ b/src/functions.rs
@@ -3,6 +3,8 @@
//! machinery of its own.
use std::assert_eq;
+use std::fmt::Display;
+use std::ops::{Mul, Neg};
use std::rc::Rc;
use crate::display::BoxedTensor;
@@ -12,40 +14,55 @@ use crate::mapping::{
Squeeze, Unsqueeze, Where,
};
use crate::tensor::{
- JaggedTensor, LeftComposition, Physical, Product, Reduction, ReductionTensor, RightComposition,
- Sum, Tensor,
+ JaggedTensor, LeftComposition, Physical, Product, ProductReduction, Reduction, ReductionTensor,
+ RightComposition, Sum, SumReduction, Tensor,
};
-pub fn square(u: Rc) -> LeftComposition {
+pub fn square + Copy + 'static, U: Tensor + ?Sized>(
+ u: Rc,
+) -> LeftComposition {
LeftComposition::new(u, |x| x * x)
}
-pub fn negate(u: Rc) -> LeftComposition {
+pub fn negate + 'static, U: Tensor + ?Sized>(
+ u: Rc,
+) -> LeftComposition {
LeftComposition::new(u, |x| -x)
}
-pub fn scale(u: Rc, factor: f32) -> LeftComposition {
+pub fn scale + Copy + 'static, U: Tensor + ?Sized>(
+ u: Rc,
+ factor: A,
+) -> LeftComposition {
LeftComposition::new(u, move |x| x * factor)
}
-pub fn pow(u: Rc, exponent: f32) -> LeftComposition {
- LeftComposition::new(u, move |x| x.powf(exponent))
-}
-
-/// Checking the bounds here rather than leaving it to `f32::clamp` reports the
-/// mistake where it is made, not at the first read, and rules out the NaN
-/// bounds that would panic there too.
-pub fn clamp(u: Rc, low: f32, high: f32) -> LeftComposition {
+/// Holds values between `low` and `high`. Checking the bounds here reports the
+/// mistake where it is made, not at the first read, and rules out NaN bounds,
+/// which nothing would be below or above.
+pub fn clamp + ?Sized>(
+ u: Rc,
+ low: A,
+ high: A,
+) -> LeftComposition {
assert!(low <= high, "clamp needs low <= high, got {low} and {high}");
- LeftComposition::new(u, move |x| x.clamp(low, high))
+ LeftComposition::new(u, move |x| {
+ if x < low {
+ low
+ } else if x > high {
+ high
+ } else {
+ x
+ }
+ })
}
/// Reorders the axes of a tensor: `axes[i]` is the axis of `u` that becomes
/// axis `i` of the result.
-pub fn permute(
- u: Rc,
+pub fn permute + 'static>(
+ u: Rc,
axes: Vec,
-) -> RightComposition {
+) -> RightComposition {
let codomain = u.hrect().clone();
RightComposition::new(u, Rc::new(Permutation::new(codomain, axes)))
}
@@ -53,7 +70,9 @@ pub fn permute(
/// Swaps the last two axes and leaves any leading ones alone, so a stack of
/// matrices transposes matrix by matrix. At rank two this is the plain
/// transpose.
-pub fn transpose(u: Rc) -> RightComposition {
+pub fn transpose + 'static>(
+ u: Rc,
+) -> RightComposition {
let ndim = u.domain().ndim();
assert!(ndim >= 2, "transpose needs at least two dimensions");
let mut axes: Vec = (0..ndim).collect();
@@ -63,11 +82,11 @@ pub fn transpose(u: Rc) -> RightComposition(
- u: Rc,
+pub fn movedim + 'static>(
+ u: Rc,
source: usize,
destination: usize,
-) -> RightComposition {
+) -> RightComposition {
let ndim = u.domain().ndim();
assert!(source < ndim, "source axis {source} is out of range");
assert!(
@@ -82,7 +101,10 @@ pub fn movedim(
/// Inserts an axis of extent 1 at `dim`, which can be anywhere from before the
/// first axis to after the last.
-pub fn unsqueeze(u: Rc, dim: usize) -> RightComposition {
+pub fn unsqueeze + 'static>(
+ u: Rc,
+ dim: usize,
+) -> RightComposition {
let codomain = u.hrect().clone();
RightComposition::new(u, Rc::new(Unsqueeze::new(codomain, dim)))
}
@@ -127,7 +149,10 @@ impl Dims for &[usize] {
/// Drops axes of extent 1, as torch's `squeeze` does: `squeeze(u, ())` drops all
/// of them, `squeeze(u, 1)` only axis 1, and `squeeze(u, [0, 2])` axes 0 and 2.
/// A named axis whose extent isn't 1 is left alone.
-pub fn squeeze(u: Rc, dims: impl Dims) -> RightComposition {
+pub fn squeeze + 'static>(
+ u: Rc,
+ dims: impl Dims,
+) -> RightComposition {
let codomain = u.hrect().clone();
let ndim = codomain.shape().len();
let named = dims.axes(ndim);
@@ -146,7 +171,10 @@ pub fn squeeze(u: Rc, dims: impl Dims) -> RightComp
}
/// Joins tensors end to end along `dim`; their other extents must agree.
-pub fn concatenate(tensors: Vec>, dim: usize) -> RightComposition {
+pub fn concatenate(
+ tensors: Vec>>,
+ dim: usize,
+) -> RightComposition {
assert!(!tensors.is_empty(), "concatenate needs at least one tensor");
let underlying = JaggedTensor::new(tensors);
let codomain = underlying.jagged_domain().clone();
@@ -155,7 +183,10 @@ pub fn concatenate(tensors: Vec>, dim: usize) -> RightCompos
/// Joins tensors of one shape along a new axis at `dim`, by concatenating them
/// once each has an axis of extent 1 there.
-pub fn stack(tensors: Vec>, dim: usize) -> RightComposition {
+pub fn stack(
+ tensors: Vec>>,
+ dim: usize,
+) -> RightComposition {
assert!(!tensors.is_empty(), "stack needs at least one tensor");
let shape = tensors[0].hrect().clone();
assert!(
@@ -166,20 +197,19 @@ pub fn stack(tensors: Vec>, dim: usize) -> RightComposition<
.into_iter()
.map(|tensor| {
let unsqueeze = Unsqueeze::new(tensor.hrect().clone(), dim);
- Rc::new(RightComposition::new(tensor, Rc::new(unsqueeze))) as Rc
+ Rc::new(RightComposition::new(tensor, Rc::new(unsqueeze))) as Rc>
})
.collect();
concatenate(unsqueezed, dim)
}
-/// Reads `input` where `condition` is not zero and `other` where it is. torch's
-/// `where` takes a boolean condition; cten's tensors hold floats, so zero stands
-/// for false. `where` is a Rust keyword, hence the underscore.
-pub fn where_(
- condition: Rc,
- input: Rc,
- other: Rc,
-) -> RightComposition {
+/// Reads `input` where `condition` holds and `other` where it does not, as
+/// torch's `where` does. `where` is a Rust keyword, hence the underscore.
+pub fn where_(
+ condition: Rc>,
+ input: Rc>,
+ other: Rc>,
+) -> RightComposition {
let underlying = JaggedTensor::new(vec![input, other]);
let codomain = underlying.jagged_domain().clone();
RightComposition::new(
@@ -191,7 +221,13 @@ pub fn where_(
/// Spreads a vector over the diagonal of a tensor of the given rank: rank two
/// gives a diagonal matrix, rank three a tensor whose only non-zero indices
/// are of the form `[i, i, i]`.
-pub fn diagonalize(u: Rc, rank: usize) -> Sum {
+pub fn diagonalize + 'static>(
+ u: Rc,
+ rank: usize,
+) -> Sum
+where
+ SumReduction: Reduction,
+{
assert_eq!(u.domain().ndim(), 1, "diagonalize takes a vector");
assert!(rank > 0, "a diagonal tensor has at least one dimension");
let extent = u.hrect().shape()[0];
@@ -200,7 +236,7 @@ pub fn diagonalize(u: Rc, rank: usize) -> Sum(shape: Vec) -> ReductionTensor {
+fn neutral>(shape: Vec) -> ReductionTensor {
let mapping = EmptyMapping::new(HRect::new(shape), HRect::new(vec![0]));
ReductionTensor::new(Rc::new(Physical::new(vec![])), Rc::new(mapping))
}
@@ -231,11 +267,14 @@ pub fn eye(n: usize) -> Sum {
/// `indices`, and zero everywhere else. As in torch, `indices` has one row per
/// dimension, so column `k` is the coordinate of `values[k]`, and values at a
/// repeated coordinate add up.
-pub fn sparse_coo_tensor(
+pub fn sparse_coo_tensor(
indices: Vec>,
- values: Rc,
+ values: Rc>,
size: Vec,
-) -> Sum {
+) -> Sum
+where
+ SumReduction: Reduction,
+{
assert_eq!(
indices.len(),
size.len(),
@@ -273,30 +312,49 @@ pub fn sparse_coo_tensor(
/// `physical` sits at `basisĀ·x + offset`, and the tensor is zero off that
/// lattice. `basis` has a row per dimension of `shape` and a column per
/// dimension of `physical`. Values the lattice sends to one index add up.
-pub fn sparse_latticed_tensor(
- physical: Rc,
+pub fn sparse_latticed_tensor + 'static>(
+ physical: Rc,
basis: Vec>,
offset: Vec,
shape: Vec,
-) -> Sum {
+) -> Sum
+where
+ SumReduction: Reduction,
+{
let affine = Affine::new(physical.hrect().clone(), basis, offset, HRect::new(shape));
Sum::new(physical, Rc::new(InvAffine::new(affine)))
}
-pub fn sum(input: Rc, dim: usize) -> Sum {
+pub fn sum + 'static>(
+ input: Rc,
+ dim: usize,
+) -> Sum
+where
+ SumReduction: Reduction,
+{
let codomain = input.hrect().clone();
Sum::new(input, Rc::new(InvProjection::new(codomain, dim)))
}
-pub fn prod(input: Rc, dim: usize) -> Product {
+pub fn prod + 'static>(
+ input: Rc,
+ dim: usize,
+) -> Product
+where
+ ProductReduction: Reduction,
+{
let codomain = input.hrect().clone();
Product::new(input, Rc::new(InvProjection::new(codomain, dim)))
}
-pub fn mm(
- mat1: Rc,
- mat2: Rc,
-) -> Sum {
+pub fn mm + 'static, V: BoxedTensor + 'static>(
+ mat1: Rc,
+ mat2: Rc,
+) -> Sum
+where
+ SumReduction: Reduction,
+ ProductReduction: Reduction,
+{
assert_eq!(mat1.hrect().shape().len(), 2, "mat1 must be a matrix");
assert_eq!(mat2.hrect().shape().len(), 2, "mat2 must be a matrix");
assert_eq!(
diff --git a/src/lib.rs b/src/lib.rs
index d1e8b96..638af52 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -12,8 +12,8 @@ pub mod tensor;
pub use display::{BoxedTensor, format_boxed};
pub use domain::{Domain, HRect, JaggedDomain};
pub use functions::{
- Dims, clamp, concatenate, diagonalize, eye, eye_rect, mm, movedim, negate, ones, permute, pow,
- prod, scale, sparse_coo_tensor, sparse_latticed_tensor, square, squeeze, stack, sum, transpose,
+ Dims, clamp, concatenate, diagonalize, eye, eye_rect, mm, movedim, negate, ones, permute, prod,
+ scale, sparse_coo_tensor, sparse_latticed_tensor, square, squeeze, stack, sum, transpose,
unsqueeze, where_, zeros,
};
pub use mapping::{
diff --git a/src/mapping.rs b/src/mapping.rs
index b2642d0..61c0bcf 100644
--- a/src/mapping.rs
+++ b/src/mapping.rs
@@ -348,18 +348,18 @@ impl HRectMapping for Concat {
}
}
-/// Reads the first of two tensors where `condition` is not zero and the second
-/// where it is. Unlike the mappings above, where an index is sent depends on a
+/// Reads the first of two tensors where `condition` holds and the second where
+/// it does not. Unlike the mappings above, where an index is sent depends on a
/// tensor's values and not only on the index itself.
pub struct Where {
signature: Signature,
- condition: Rc,
+ condition: Rc>,
}
impl Where {
/// Takes the codomain, the two tensors picked from. The domain is their
/// common shape, which `condition` must share.
- pub fn new(codomain: JaggedDomain, condition: Rc) -> Self {
+ pub fn new(codomain: JaggedDomain, condition: Rc>) -> Self {
let parts = codomain.parts();
assert_eq!(parts.len(), 2, "where picks from exactly two tensors");
let shape = parts[0].clone();
@@ -381,12 +381,7 @@ impl HRectMapping for Where {
type Codomain = JaggedDomain;
fn map(&self, index: &[usize]) -> Vec {
- // Zero reads as false and anything else, NaN included, as true.
- let part = if self.condition.get(index) != 0.0 {
- 0
- } else {
- 1
- };
+ let part = if self.condition.get(index) { 0 } else { 1 };
let mut result = Vec::with_capacity(index.len() + 1);
result.push(part);
result.extend_from_slice(index);
diff --git a/src/tensor.rs b/src/tensor.rs
index d9bf73d..72b63a0 100644
--- a/src/tensor.rs
+++ b/src/tensor.rs
@@ -6,9 +6,10 @@ use crate::domain::{Domain, HRect, JaggedDomain};
use crate::mapping::{BoxedMapping, BoxedPowersetMapping, IndexMapping, PowersetMapping};
/// A mapping from an index set to a value set. The index set is the tensor's
-/// domain; the value set is fixed to the float numbers for now, for simplicity.
-pub trait Tensor {
- fn get(&self, index: &[usize]) -> f32;
+/// domain, and the value set is its value type `A`, `f32` unless said
+/// otherwise.
+pub trait Tensor {
+ fn get(&self, index: &[usize]) -> A;
fn domain(&self) -> &dyn Domain;
}
@@ -17,7 +18,7 @@ pub trait Tensor {
/// which is all that can be checked without enumerating a domain. In
/// particular, a mapping into flat positions needs a one-dimensional tensor
/// under it.
-fn check_composable(codomain: &dyn Domain, u: &dyn Tensor) {
+fn check_composable(codomain: &dyn Domain, u: &dyn Tensor) {
assert_eq!(
codomain.ndim(),
u.domain().ndim(),
@@ -27,26 +28,26 @@ fn check_composable(codomain: &dyn Domain, u: &dyn Tensor) {
);
}
-pub struct Physical {
- values: Vec,
+pub struct Physical {
+ values: Vec,
domain: HRect,
}
-impl Physical {
- pub fn new(values: Vec) -> Self {
+impl Physical {
+ pub fn new(values: Vec) -> Self {
let domain = HRect::new(vec![values.len()]);
Self { values, domain }
}
}
-impl Tensor for Physical {
- fn get(&self, index: &[usize]) -> f32 {
+impl Tensor for Physical {
+ fn get(&self, index: &[usize]) -> A {
assert_eq!(
index.len(),
1,
"a physical tensor is indexed by a single coordinate"
);
- self.values[index[0]]
+ self.values[index[0]].clone()
}
fn domain(&self) -> &dyn Domain {
@@ -54,7 +55,7 @@ impl Tensor for Physical {
}
}
-impl BoxedTensor for Physical {
+impl BoxedTensor for Physical {
fn hrect(&self) -> &HRect {
&self.domain
}
@@ -63,13 +64,13 @@ impl BoxedTensor for Physical {
/// Several tensors read as one: `[t, rest..]` reads part `t` at `rest`. This is
/// the underlying that concatenating, stacking and `where` remap into. Its domain
/// is jagged rather than a hrect, so it is not printable.
-pub struct JaggedTensor {
- parts: Vec>,
+pub struct JaggedTensor {
+ parts: Vec>>,
domain: JaggedDomain,
}
-impl JaggedTensor {
- pub fn new(parts: Vec>) -> Self {
+impl JaggedTensor {
+ pub fn new(parts: Vec>>) -> Self {
let domain = JaggedDomain::new(parts.iter().map(|part| part.hrect().clone()).collect());
Self { parts, domain }
}
@@ -79,8 +80,8 @@ impl JaggedTensor {
}
}
-impl Tensor for JaggedTensor {
- fn get(&self, index: &[usize]) -> f32 {
+impl Tensor for JaggedTensor {
+ fn get(&self, index: &[usize]) -> A {
self.parts[index[0]].get(&index[1..])
}
@@ -92,21 +93,22 @@ impl Tensor for JaggedTensor {
// The domain is the mapping's own domain, so it is never passed in. The mapping
// type is a parameter rather than always `dyn IndexMapping` so that composing
// over a boxed mapping is known to give a boxed, printable tensor. Pass
-// `dyn BoxedMapping` or `dyn IndexMapping` for it to erase the mapping.
-pub struct RightComposition {
- u: Rc,
+// `dyn BoxedMapping` or `dyn IndexMapping` for it to erase the mapping. The
+// values are the underlying tensor's, read elsewhere.
+pub struct RightComposition {
+ u: Rc>,
mapping: Rc,
}
-impl RightComposition {
- pub fn new(u: Rc, mapping: Rc) -> Self {
+impl RightComposition {
+ pub fn new(u: Rc>, mapping: Rc) -> Self {
check_composable(mapping.codomain(), &*u);
Self { u, mapping }
}
}
-impl Tensor for RightComposition {
- fn get(&self, virtual_index: &[usize]) -> f32 {
+impl Tensor for RightComposition {
+ fn get(&self, virtual_index: &[usize]) -> A {
let underlying_index = self.mapping.map(virtual_index);
self.u.get(&underlying_index)
}
@@ -116,7 +118,7 @@ impl Tensor for RightComposition {
}
}
-impl BoxedTensor for RightComposition {
+impl BoxedTensor for RightComposition {
fn hrect(&self) -> &HRect {
self.mapping.hrect()
}
@@ -124,22 +126,23 @@ impl BoxedTensor for RightComposition {
// Transforming the values leaves the index set alone, so the domain is the
// underlying tensor's. Its type is a parameter so that a transform of a boxed
-// tensor is known to be boxed, and printable, in turn.
-pub struct LeftComposition {
- u: Rc,
+// tensor is known to be boxed, and printable, in turn. The transform may change
+// the value type, from `A` to `B`, as a comparison turns numbers into booleans.
+pub struct LeftComposition {
+ u: Rc,
// Held as a closure rather than a plain `fn` pointer so that a transform can
// carry values of its own, as `scale` and `clamp` do.
- f: Rc f32>,
+ f: Rc B>,
}
-impl LeftComposition {
- pub fn new(u: Rc, f: impl Fn(f32) -> f32 + 'static) -> Self {
+impl + ?Sized, A, B> LeftComposition {
+ pub fn new(u: Rc, f: impl Fn(A) -> B + 'static) -> Self {
Self { u, f: Rc::new(f) }
}
}
-impl Tensor for LeftComposition {
- fn get(&self, index: &[usize]) -> f32 {
+impl + ?Sized, A, B> Tensor for LeftComposition {
+ fn get(&self, index: &[usize]) -> B {
(self.f)(self.u.get(index))
}
@@ -148,64 +151,74 @@ impl Tensor for LeftComposition {
}
}
-impl BoxedTensor for LeftComposition {
+impl + ?Sized, A, B> BoxedTensor for LeftComposition {
fn hrect(&self) -> &HRect {
self.u.hrect()
}
}
-pub trait Reduction {
- const NEUTRAL: f32;
- fn combine(a: f32, b: f32) -> f32;
+/// A way of combining the values of a tensor of value type `A` into one.
+pub trait Reduction {
+ const NEUTRAL: A;
+ fn combine(a: A, b: A) -> A;
}
pub struct SumReduction;
-
-impl Reduction for SumReduction {
- const NEUTRAL: f32 = 0.0;
- fn combine(a: f32, b: f32) -> f32 {
- a + b
- }
-}
-
pub struct ProductReduction;
+pub struct SupReduction;
+pub struct InfReduction;
-impl Reduction for ProductReduction {
- const NEUTRAL: f32 = 1.0;
- fn combine(a: f32, b: f32) -> f32 {
- a * b
- }
-}
+// The four reductions for each value type they apply to: a sum starts from 0, a
+// product from 1, and a sup and an inf from the bottom and top of the type.
+macro_rules! reductions {
+ ($($value:ty: $zero:expr, $one:expr, $bottom:expr, $top:expr;)*) => {$(
+ impl Reduction<$value> for SumReduction {
+ const NEUTRAL: $value = $zero;
+ fn combine(a: $value, b: $value) -> $value {
+ a + b
+ }
+ }
-pub struct SupReduction;
+ impl Reduction<$value> for ProductReduction {
+ const NEUTRAL: $value = $one;
+ fn combine(a: $value, b: $value) -> $value {
+ a * b
+ }
+ }
-impl Reduction for SupReduction {
- const NEUTRAL: f32 = f32::NEG_INFINITY;
- fn combine(a: f32, b: f32) -> f32 {
- a.max(b)
- }
-}
+ impl Reduction<$value> for SupReduction {
+ const NEUTRAL: $value = $bottom;
+ fn combine(a: $value, b: $value) -> $value {
+ a.max(b)
+ }
+ }
-pub struct InfReduction;
+ impl Reduction<$value> for InfReduction {
+ const NEUTRAL: $value = $top;
+ fn combine(a: $value, b: $value) -> $value {
+ a.min(b)
+ }
+ }
+ )*};
+}
-impl Reduction for InfReduction {
- const NEUTRAL: f32 = f32::INFINITY;
- fn combine(a: f32, b: f32) -> f32 {
- a.min(b)
- }
+reductions! {
+ f32: 0.0, 1.0, f32::NEG_INFINITY, f32::INFINITY;
+ f64: 0.0, 1.0, f64::NEG_INFINITY, f64::INFINITY;
+ i64: 0, 1, i64::MIN, i64::MAX;
}
// As in a right composition, the domain is the mapping's own domain, and the
// mapping type is a parameter so that reducing over a boxed mapping is known to
// give a boxed, printable tensor.
-pub struct ReductionTensor {
- u: Rc,
+pub struct ReductionTensor {
+ u: Rc>,
mapping: Rc,
_marker: PhantomData R>,
}
-impl ReductionTensor {
- pub fn new(u: Rc, mapping: Rc) -> Self {
+impl, M: PowersetMapping + ?Sized, A> ReductionTensor