Skip to main content

rill_core/math/vector/
ops.rs

1//! # Arithmetic operations for vectors
2//!
3//! Implementation of basic arithmetic operations for vector types.
4//!
5//! ## Types
6//!
7//! | Type | Purpose |
8//! |---|---|
9//! | `add_slices` / `sub_slices` / … | Low-level element-wise ops (write to pre-allocated buffer) |
10//! | [`SliceMut`] | Mutable slice wrapper — supports `+=`, `-=`, `*=`, `/=` operator syntax |
11//! | [`SlicePair`] | Two-slice pair — supports `.add_into()` / `.sub_into()` / `.mul_into()` / `.div_into()` |
12//!
13//! ## Usage
14//!
15//! ```rust,no_run
16//! use rill_core::math::vector::ops::{SliceMut, SlicePair};
17//! use rill_core::prelude::ScalarVector4;
18//!
19//! let a = [1.0f32, 2.0, 3.0, 4.0];
20//! let b = [5.0f32, 6.0, 7.0, 8.0];
21//! let mut out = [0.0f32; 4];
22//!
23//! // Pair ops: compute a + b → out
24//! SlicePair::new(&a, &b).add_into::<4, ScalarVector4<f32>>(&mut out);
25//!
26//! // Accumulation: out += a (element-wise via += operator)
27//! let mut out_mut = SliceMut::new(&mut out);
28//! out_mut += &a as &[f32];
29//!
30//! // Scalar broadcast: out *= 2.0
31//! out_mut *= 2.0;
32//! ```
33
34use super::traits::*;
35use crate::Transcendental;
36use std::ops::{AddAssign, DivAssign, MulAssign, SubAssign};
37
38// -----------------------------------------------------------------------------
39// Helper functions
40
41/// Element-wise addition of two slices, storing the result in a third
42pub fn add_slices<T: Transcendental, const N: usize, V>(a: &[T], b: &[T], out: &mut [T])
43where
44    V: Vector<T, N>,
45{
46    assert_eq!(a.len(), b.len());
47    assert_eq!(a.len(), out.len());
48
49    let chunks = a.len() / N;
50    let remainder = a.len() % N;
51
52    for i in 0..chunks {
53        let start = i * N;
54        let a_vec = V::load(&a[start..start + N]);
55        let b_vec = V::load(&b[start..start + N]);
56        let result = a_vec + b_vec;
57        result.store(&mut out[start..start + N]);
58    }
59
60    // Handle remainder
61    if remainder > 0 {
62        let start = chunks * N;
63        for i in 0..remainder {
64            out[start + i] = a[start + i] + b[start + i];
65        }
66    }
67}
68
69/// Element-wise subtraction of two slices
70pub fn sub_slices<T: Transcendental, const N: usize, V>(a: &[T], b: &[T], out: &mut [T])
71where
72    V: Vector<T, N>,
73{
74    assert_eq!(a.len(), b.len());
75    assert_eq!(a.len(), out.len());
76
77    let chunks = a.len() / N;
78    let remainder = a.len() % N;
79
80    for i in 0..chunks {
81        let start = i * N;
82        let a_vec = V::load(&a[start..start + N]);
83        let b_vec = V::load(&b[start..start + N]);
84        let result = a_vec - b_vec;
85        result.store(&mut out[start..start + N]);
86    }
87
88    if remainder > 0 {
89        let start = chunks * N;
90        for i in 0..remainder {
91            out[start + i] = a[start + i] - b[start + i];
92        }
93    }
94}
95
96/// Element-wise multiplication of two slices
97pub fn mul_slices<T: Transcendental, const N: usize, V>(a: &[T], b: &[T], out: &mut [T])
98where
99    V: Vector<T, N>,
100{
101    assert_eq!(a.len(), b.len());
102    assert_eq!(a.len(), out.len());
103
104    let chunks = a.len() / N;
105    let remainder = a.len() % N;
106
107    for i in 0..chunks {
108        let start = i * N;
109        let a_vec = V::load(&a[start..start + N]);
110        let b_vec = V::load(&b[start..start + N]);
111        let result = a_vec * b_vec;
112        result.store(&mut out[start..start + N]);
113    }
114
115    if remainder > 0 {
116        let start = chunks * N;
117        for i in 0..remainder {
118            out[start + i] = a[start + i] * b[start + i];
119        }
120    }
121}
122
123/// Element-wise division of two slices
124pub fn div_slices<T: Transcendental, const N: usize, V>(a: &[T], b: &[T], out: &mut [T])
125where
126    V: Vector<T, N>,
127{
128    assert_eq!(a.len(), b.len());
129    assert_eq!(a.len(), out.len());
130
131    let chunks = a.len() / N;
132    let remainder = a.len() % N;
133
134    for i in 0..chunks {
135        let start = i * N;
136        let a_vec = V::load(&a[start..start + N]);
137        let b_vec = V::load(&b[start..start + N]);
138        let result = a_vec / b_vec;
139        result.store(&mut out[start..start + N]);
140    }
141
142    if remainder > 0 {
143        let start = chunks * N;
144        for i in 0..remainder {
145            out[start + i] = a[start + i] / b[start + i];
146        }
147    }
148}
149
150/// Multiply a slice by a scalar
151pub fn mul_scalar_slice<T: Transcendental, const N: usize, V>(a: &[T], scalar: T, out: &mut [T])
152where
153    V: Vector<T, N>,
154{
155    assert_eq!(a.len(), out.len());
156
157    let scalar_vec = V::splat(scalar);
158    let chunks = a.len() / N;
159    let remainder = a.len() % N;
160
161    for i in 0..chunks {
162        let start = i * N;
163        let a_vec = V::load(&a[start..start + N]);
164        let result = a_vec * scalar_vec;
165        result.store(&mut out[start..start + N]);
166    }
167
168    if remainder > 0 {
169        let start = chunks * N;
170        for i in 0..remainder {
171            out[start + i] = a[start + i] * scalar;
172        }
173    }
174}
175
176/// Add a scalar to a slice
177pub fn add_scalar_slice<T: Transcendental, const N: usize, V>(a: &[T], scalar: T, out: &mut [T])
178where
179    V: Vector<T, N>,
180{
181    assert_eq!(a.len(), out.len());
182
183    let scalar_vec = V::splat(scalar);
184    let chunks = a.len() / N;
185    let remainder = a.len() % N;
186
187    for i in 0..chunks {
188        let start = i * N;
189        let a_vec = V::load(&a[start..start + N]);
190        let result = a_vec + scalar_vec;
191        result.store(&mut out[start..start + N]);
192    }
193
194    if remainder > 0 {
195        let start = chunks * N;
196        for i in 0..remainder {
197            out[start + i] = a[start + i] + scalar;
198        }
199    }
200}
201
202// ============================================================================
203// SliceMut — mutable slice with operator-assign syntax (out += a, out *= s)
204// ============================================================================
205
206/// A mutable slice that supports element-wise `+=`, `-=`, `*=`, `/=` with
207/// another slice or scalar broadcast.
208///
209/// Operators use the scalar path (no SIMD). For SIMD-accelerated ops, use
210/// the `add_slices` / `sub_slices` / … helpers or [`SlicePair`].
211#[derive(Debug)]
212pub struct SliceMut<'a, T>(pub &'a mut [T]);
213
214impl<'a, T: Transcendental> SliceMut<'a, T> {
215    /// Wrap a mutable slice.
216    #[inline(always)]
217    pub fn new(slice: &'a mut [T]) -> Self {
218        Self(slice)
219    }
220
221    /// Unwrap, returning the underlying mutable slice.
222    #[inline(always)]
223    pub fn into_inner(self) -> &'a mut [T] {
224        self.0
225    }
226}
227
228impl<'a, T: Transcendental> AddAssign<&[T]> for SliceMut<'a, T> {
229    fn add_assign(&mut self, rhs: &[T]) {
230        for (a, &b) in self.0.iter_mut().zip(rhs.iter()) {
231            *a += b;
232        }
233    }
234}
235
236impl<'a, T: Transcendental> SubAssign<&[T]> for SliceMut<'a, T> {
237    fn sub_assign(&mut self, rhs: &[T]) {
238        for (a, &b) in self.0.iter_mut().zip(rhs.iter()) {
239            *a -= b;
240        }
241    }
242}
243
244impl<'a, T: Transcendental> MulAssign<&[T]> for SliceMut<'a, T> {
245    fn mul_assign(&mut self, rhs: &[T]) {
246        for (a, &b) in self.0.iter_mut().zip(rhs.iter()) {
247            *a *= b;
248        }
249    }
250}
251
252impl<'a, T: Transcendental> DivAssign<&[T]> for SliceMut<'a, T> {
253    fn div_assign(&mut self, rhs: &[T]) {
254        for (a, &b) in self.0.iter_mut().zip(rhs.iter()) {
255            *a /= b;
256        }
257    }
258}
259
260impl<'a, T: Transcendental> AddAssign<T> for SliceMut<'a, T> {
261    fn add_assign(&mut self, rhs: T) {
262        for v in self.0.iter_mut() {
263            *v = *v + rhs;
264        }
265    }
266}
267
268impl<'a, T: Transcendental> SubAssign<T> for SliceMut<'a, T> {
269    fn sub_assign(&mut self, rhs: T) {
270        for v in self.0.iter_mut() {
271            *v = *v - rhs;
272        }
273    }
274}
275
276impl<'a, T: Transcendental> MulAssign<T> for SliceMut<'a, T> {
277    fn mul_assign(&mut self, rhs: T) {
278        for v in self.0.iter_mut() {
279            *v = *v * rhs;
280        }
281    }
282}
283
284impl<'a, T: Transcendental> DivAssign<T> for SliceMut<'a, T> {
285    fn div_assign(&mut self, rhs: T) {
286        for v in self.0.iter_mut() {
287            *v = *v / rhs;
288        }
289    }
290}
291
292// ============================================================================
293// SlicePair — two-slice pair with SIMD-accelerated element-wise ops
294// ============================================================================
295
296/// A pair of immutable slices, providing SIMD-accelerated element-wise
297/// binary operations that write the result into a pre-allocated output buffer.
298///
299/// # Example
300///
301/// ```rust,no_run
302/// use rill_core::math::vector::ops::SlicePair;
303/// use rill_core::prelude::ScalarVector4;
304///
305/// let a = [1.0f32, 2.0, 3.0, 4.0];
306/// let b = [5.0f32, 6.0, 7.0, 8.0];
307/// let mut out = [0.0f32; 4];
308///
309/// SlicePair::new(&a, &b).add_into::<4, ScalarVector4<f32>>(&mut out);
310/// assert_eq!(out, [6.0, 8.0, 10.0, 12.0]);
311/// ```
312pub struct SlicePair<'a, T>(pub &'a [T], pub &'a [T]);
313
314impl<'a, T: Transcendental> SlicePair<'a, T> {
315    /// Create a new pair from two slices.
316    #[inline(always)]
317    pub fn new(a: &'a [T], b: &'a [T]) -> Self {
318        Self(a, b)
319    }
320
321    /// Element-wise addition: `out[i] = a[i] + b[i]`.
322    #[inline(always)]
323    pub fn add_into<const N: usize, V: Vector<T, N>>(self, out: &mut [T]) {
324        add_slices::<T, N, V>(self.0, self.1, out)
325    }
326
327    /// Element-wise subtraction: `out[i] = a[i] - b[i]`.
328    #[inline(always)]
329    pub fn sub_into<const N: usize, V: Vector<T, N>>(self, out: &mut [T]) {
330        sub_slices::<T, N, V>(self.0, self.1, out)
331    }
332
333    /// Element-wise multiplication: `out[i] = a[i] * b[i]`.
334    #[inline(always)]
335    pub fn mul_into<const N: usize, V: Vector<T, N>>(self, out: &mut [T]) {
336        mul_slices::<T, N, V>(self.0, self.1, out)
337    }
338
339    /// Element-wise division: `out[i] = a[i] / b[i]`.
340    #[inline(always)]
341    pub fn div_into<const N: usize, V: Vector<T, N>>(self, out: &mut [T]) {
342        div_slices::<T, N, V>(self.0, self.1, out)
343    }
344
345    /// Element-wise remainder: `out[i] = a[i] % b[i]`.
346    pub fn rem_into<const N: usize, V: Vector<T, N>>(self, out: &mut [T]) {
347        for (o, (&a, &b)) in out.iter_mut().zip(self.0.iter().zip(self.1.iter())) {
348            *o = a % b;
349        }
350    }
351}
352
353// -----------------------------------------------------------------------------
354// Tests
355// -----------------------------------------------------------------------------
356
357#[cfg(test)]
358mod tests {
359    use super::*;
360
361    // Test implementations for scalar vectors will be added later
362    // #[test]
363    // fn test_add_slices() {
364    // }
365}