Skip to main content

strided_traits/
element_op.rs

1//! Element-wise operations applied lazily to strided views.
2//!
3//! Julia's StridedViews.jl supports four element operations that form a group:
4//! - `identity`: No transformation
5//! - `conj`: Complex conjugate
6//! - `transpose`: Element-wise transpose (for matrix elements)
7//! - `adjoint`: Element-wise adjoint (conj + transpose)
8//!
9//! These operations are composed at the type level to avoid runtime dispatch.
10//!
11//! # Key Design: `ElementOp<T>` is Generic Over T
12//!
13//! `Identity` implements `ElementOp<T>` for any `T: Copy`, requiring no
14//! additional bounds. `Conj`, `Transpose`, and `Adjoint` require
15//! `T: ElementOpApply`. This allows custom scalar types (e.g., tropical
16//! semiring types) to use `Identity` views without implementing
17//! `ElementOpApply`.
18//!
19//! Composition associated types (Inverse, ComposeConj, etc.) are separated
20//! into `ComposableElementOp<T>`, only available when `T: ElementOpApply`.
21
22use num_complex::Complex;
23use num_traits::Num;
24
25// ---------------------------------------------------------------------------
26// ElementOpApply: trait for types that support conj/transpose/adjoint
27// ---------------------------------------------------------------------------
28
29/// Trait for types that support element operations (conj, transpose, adjoint).
30///
31/// Default implementations return `self` unchanged, so real-valued types
32/// (and custom types that don't need complex operations) can simply write:
33///
34/// ```
35/// use strided_traits::ElementOpApply;
36///
37/// #[derive(Clone, Copy, Debug, PartialEq)]
38/// struct MyType(f64);
39///
40/// impl ElementOpApply for MyType {}
41///
42/// // The provided methods leave the element unchanged.
43/// assert_eq!(MyType(2.0).conj(), MyType(2.0));
44/// assert_eq!(MyType(2.0).adjoint(), MyType(2.0));
45/// ```
46pub trait ElementOpApply: Copy {
47    #[inline(always)]
48    fn conj(self) -> Self {
49        self
50    }
51    #[inline(always)]
52    fn transpose(self) -> Self {
53        self
54    }
55    #[inline(always)]
56    fn adjoint(self) -> Self {
57        self
58    }
59}
60
61// Real types: use default identity implementations
62macro_rules! impl_element_op_apply_real {
63    ($($t:ty),*) => {
64        $(impl ElementOpApply for $t {})*
65    };
66}
67
68impl_element_op_apply_real!(
69    f32, f64, i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize
70);
71
72// Complex types: override with actual conjugation
73impl<T: Num + Copy + Clone + std::ops::Neg<Output = T>> ElementOpApply for Complex<T> {
74    #[inline(always)]
75    fn conj(self) -> Self {
76        Complex::conj(&self)
77    }
78
79    #[inline(always)]
80    fn transpose(self) -> Self {
81        self
82    }
83
84    #[inline(always)]
85    fn adjoint(self) -> Self {
86        Complex::conj(&self)
87    }
88}
89
90// ---------------------------------------------------------------------------
91// Marker types
92// ---------------------------------------------------------------------------
93
94/// Identity operation: f(x) = x
95#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
96pub struct Identity;
97
98/// Complex conjugate operation: f(x) = conj(x)
99#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
100pub struct Conj;
101
102/// Transpose operation: f(x) = transpose(x)
103/// For scalar numbers, this is identity.
104/// For matrix elements, this would transpose each element.
105#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
106pub struct Transpose;
107
108/// Adjoint operation: f(x) = adjoint(x) = conj(transpose(x))
109/// For scalar numbers, this is conj.
110#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
111pub struct Adjoint;
112
113// ---------------------------------------------------------------------------
114// ElementOp<T>: generic over element type
115// ---------------------------------------------------------------------------
116
117/// Trait for element-wise operations applied to strided views.
118///
119/// Generic over the element type `T`. `Identity` implements this for any
120/// `T: Copy`, while `Conj`, `Transpose`, and `Adjoint` require
121/// `T: ElementOpApply`.
122///
123/// Operations form a group under composition:
124/// ```text
125///   compose | Id   | Conj | Trans | Adj
126/// ---------|------|------|-------|------
127///   Id     | Id   | Conj | Trans | Adj
128///   Conj   | Conj | Id   | Adj   | Trans
129///   Trans  | Trans| Adj  | Id    | Conj
130///   Adj    | Adj  | Trans| Conj  | Id
131/// ```
132pub trait ElementOp<T>: Copy + Default + 'static {
133    /// Whether this operation is the identity (no-op).
134    const IS_IDENTITY: bool = false;
135
136    /// Apply the operation to a value.
137    fn apply(value: T) -> T;
138}
139
140// Identity: works with ANY Copy type (no ElementOpApply needed)
141impl<T: Copy> ElementOp<T> for Identity {
142    const IS_IDENTITY: bool = true;
143
144    #[inline(always)]
145    fn apply(value: T) -> T {
146        value
147    }
148}
149
150// Conj, Transpose, Adjoint: only work with ElementOpApply types
151impl<T: ElementOpApply> ElementOp<T> for Conj {
152    #[inline(always)]
153    fn apply(value: T) -> T {
154        value.conj()
155    }
156}
157
158impl<T: ElementOpApply> ElementOp<T> for Transpose {
159    #[inline(always)]
160    fn apply(value: T) -> T {
161        value.transpose()
162    }
163}
164
165impl<T: ElementOpApply> ElementOp<T> for Adjoint {
166    #[inline(always)]
167    fn apply(value: T) -> T {
168        value.adjoint()
169    }
170}
171
172// ---------------------------------------------------------------------------
173// ComposableElementOp<T>: composition associated types
174// ---------------------------------------------------------------------------
175
176/// Trait for element operations that support type-level composition.
177///
178/// Only available when `T: ElementOpApply`, since composition with
179/// `Conj`/`Transpose`/`Adjoint` requires the element type to support
180/// those operations.
181pub trait ComposableElementOp<T: ElementOpApply>: ElementOp<T> {
182    /// The inverse operation (for this group, each element is its own inverse).
183    type Inverse: ComposableElementOp<T>;
184
185    /// Compose with Conj: Self then Conj
186    type ComposeConj: ComposableElementOp<T>;
187
188    /// Compose with Transpose: Self then Transpose
189    type ComposeTranspose: ComposableElementOp<T>;
190
191    /// Compose with Adjoint: Self then Adjoint
192    type ComposeAdjoint: ComposableElementOp<T>;
193}
194
195impl<T: ElementOpApply> ComposableElementOp<T> for Identity {
196    type Inverse = Identity;
197    type ComposeConj = Conj;
198    type ComposeTranspose = Transpose;
199    type ComposeAdjoint = Adjoint;
200}
201
202impl<T: ElementOpApply> ComposableElementOp<T> for Conj {
203    type Inverse = Conj;
204    type ComposeConj = Identity;
205    type ComposeTranspose = Adjoint;
206    type ComposeAdjoint = Transpose;
207}
208
209impl<T: ElementOpApply> ComposableElementOp<T> for Transpose {
210    type Inverse = Transpose;
211    type ComposeConj = Adjoint;
212    type ComposeTranspose = Identity;
213    type ComposeAdjoint = Conj;
214}
215
216impl<T: ElementOpApply> ComposableElementOp<T> for Adjoint {
217    type Inverse = Adjoint;
218    type ComposeConj = Transpose;
219    type ComposeTranspose = Conj;
220    type ComposeAdjoint = Identity;
221}
222
223// ---------------------------------------------------------------------------
224// Compose<Other>: helper trait for composing two ElementOp types
225// ---------------------------------------------------------------------------
226
227/// Helper trait for composing two ElementOp types.
228///
229/// Only available when `T: ElementOpApply`.
230pub trait Compose<T: ElementOpApply, Other: ComposableElementOp<T>>:
231    ComposableElementOp<T>
232{
233    type Result: ComposableElementOp<T>;
234}
235
236impl<T: ElementOpApply, Op: ComposableElementOp<T>> Compose<T, Identity> for Op {
237    type Result = Op;
238}
239
240impl<T: ElementOpApply> Compose<T, Conj> for Identity {
241    type Result = Conj;
242}
243
244impl<T: ElementOpApply> Compose<T, Conj> for Conj {
245    type Result = Identity;
246}
247
248impl<T: ElementOpApply> Compose<T, Conj> for Transpose {
249    type Result = Adjoint;
250}
251
252impl<T: ElementOpApply> Compose<T, Conj> for Adjoint {
253    type Result = Transpose;
254}
255
256impl<T: ElementOpApply> Compose<T, Transpose> for Identity {
257    type Result = Transpose;
258}
259
260impl<T: ElementOpApply> Compose<T, Transpose> for Conj {
261    type Result = Adjoint;
262}
263
264impl<T: ElementOpApply> Compose<T, Transpose> for Transpose {
265    type Result = Identity;
266}
267
268impl<T: ElementOpApply> Compose<T, Transpose> for Adjoint {
269    type Result = Conj;
270}
271
272impl<T: ElementOpApply> Compose<T, Adjoint> for Identity {
273    type Result = Adjoint;
274}
275
276impl<T: ElementOpApply> Compose<T, Adjoint> for Conj {
277    type Result = Transpose;
278}
279
280impl<T: ElementOpApply> Compose<T, Adjoint> for Transpose {
281    type Result = Conj;
282}
283
284impl<T: ElementOpApply> Compose<T, Adjoint> for Adjoint {
285    type Result = Identity;
286}
287
288#[cfg(test)]
289mod tests;