Skip to main content

diffsol_la/context/
mod.rs

1use crate::{error::LaError, DefaultDenseMatrix, Matrix, Vector};
2
3#[cfg(feature = "cuda")]
4#[allow(deprecated)]
5pub mod cuda;
6
7#[cfg(feature = "cuda-oxide")]
8pub mod cuda_oxide;
9
10#[cfg(feature = "nalgebra")]
11pub mod nalgebra;
12
13#[cfg(feature = "faer")]
14pub mod faer;
15
16/// The panic raised when the broadcast-into rule is violated.  Kept out of line so that
17/// [`Context::assert_broadcastable_into`] inlines as the comparisons and the division and
18/// nothing else -- in particular not the panic's formatting.
19#[cold]
20#[inline(never)]
21fn panic_incompatible_nbatch(src_nbatch: usize, dest_nbatch: usize, op: &str) -> ! {
22    panic!(
23        "incompatible nbatch in {}: lhs={}, rhs={}",
24        op, dest_nbatch, src_nbatch
25    );
26}
27
28/// Source batch feeding destination batch `dest` of `dest_nbatch`, for an operand holding
29/// `src_nbatch` batches. Broadcast is grouped: each source batch is repeated across `dest_nbatch / src_nbatch`
30/// *contiguous* destination batches
31#[inline]
32pub(crate) fn broadcast_batch(dest: usize, src_nbatch: usize, dest_nbatch: usize) -> usize {
33    // matching batch counts are by far the common case, and returning `dest` unchanged keeps
34    // the caller's index provably in range -- without this branch the division loses that and
35    // the elementwise loops downstream deoptimise (~6% on lin_alg_ops axpy/faer/100)
36    if src_nbatch == dest_nbatch {
37        return dest;
38    }
39    dest * src_nbatch / dest_nbatch
40}
41
42/// defines the current execution and allocation context of an operator / vector / matrix
43/// for example:
44/// - threading model (e.g. single-threaded, multi-threaded, GPU)
45/// - custom allocators, host/device memory
46/// - etc.
47///
48/// It will generally be the case that all the operators / vectors / matrices for the current ode problem
49/// share the same context
50pub trait Context: Clone + Default {
51    /// Returns the batch count for this context.
52    ///
53    /// When `nbatch > 1`, vectors and matrices store data for `nbatch`
54    /// independent ODE systems simultaneously.  Operations between operands
55    /// with different batch counts use broadcast semantics: an operand with
56    /// `nbatch == B` is applied to `B * P` batches of the other operand, repeating each
57    /// of its batches over `P` contiguous batches (`nbatch == 1` is the `B == 1` case,
58    /// applied to every batch of the other operand).
59    #[inline]
60    fn nbatch(&self) -> usize {
61        1
62    }
63    /// Waits for any queued work to finish.
64    ///
65    /// A no-op on the CPU backends, where every operation has already run by the time it
66    /// returns. On a device backend the operations only enqueue, so anything timing them has to
67    /// call this before reading the clock.
68    #[inline]
69    fn synchronize(&self) {}
70    /// Creates a new context with the given batch count.
71    ///
72    /// Other properties of the context (e.g. CUDA stream, faer parallelism)
73    /// are preserved.
74    ///
75    /// Returns an error when a backend cannot create a requested batch context.
76    fn clone_with_nbatch(&self, nbatch: usize) -> Result<Self, LaError>;
77    /// Panics unless an operand holding `src_nbatch` batches broadcasts *into* `self`'s
78    /// batches, i.e. unless `self.nbatch()` is an exact multiple of `src_nbatch`.
79    ///
80    /// Every batched operation picks one operand whose batch count governs the result, and
81    /// `self` is always that operand:
82    ///
83    /// - an **owned** operand has priority, because the result can be written into it --
84    ///   `A + &B` and `A + B` are governed by `A`, `&A + B` by `B`;
85    /// - otherwise the **left-hand side** governs, and the result is a fresh allocation at its
86    ///   batch count -- `&A + &B` is governed by `A`;
87    /// - operations with a fixed destination are governed by it: assignments, `copy_from`,
88    ///   `axpy`, `gemv` into `y`, a linear solve in place.
89    ///
90    /// The other operand is then broadcast into the governing count: each of its batches is
91    /// repeated across `dest_nbatch / src_nbatch` *contiguous* destination batches.
92    #[inline]
93    fn assert_broadcastable_into(&self, src_nbatch: usize, op: &str) {
94        let nbatch = self.nbatch();
95        if src_nbatch == nbatch || src_nbatch == 1 {
96            return;
97        }
98        // grouped broadcast: the division stays inline, only the panic is out of line
99        if src_nbatch == 0 || !nbatch.is_multiple_of(src_nbatch) {
100            panic_incompatible_nbatch(src_nbatch, nbatch, op);
101        }
102    }
103    fn vector_from_element<V: Vector<C = Self>>(&self, len: usize, value: V::T) -> V {
104        V::from_element(len, value, self.clone())
105    }
106    fn vector_from_vec<V: Vector<C = Self>>(&self, vec: Vec<V::T>) -> V {
107        V::from_vec(vec, self.clone())
108    }
109    fn vector_zeros<V: Vector<C = Self>>(&self, len: usize) -> V {
110        V::zeros(len, self.clone())
111    }
112    fn dense_mat_zeros<V: Vector<C = Self> + DefaultDenseMatrix>(
113        &self,
114        rows: usize,
115        cols: usize,
116    ) -> <V as DefaultDenseMatrix>::M {
117        <<V as DefaultDenseMatrix>::M as Matrix>::zeros(rows, cols, self.clone())
118    }
119}