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;