1use tenferro_ops::broadcast::{
7 broadcast_input_plan, broadcast_shape, broadcast_shapes, BroadcastError,
8};
9use tenferro_tensor::validate::matmul_config_for_shapes;
10use tenferro_tensor::{
11 BackendSession, CompareDir, Error, Result, Tensor, TensorRead, TensorScalar, ValidationError,
12};
13
14use crate::{TypedTensorMaskSessionOpsExt, TypedTensorSessionOpsExt};
15use tenferro_tensor::TypedTensor;
16
17impl<T: TensorScalar> TypedTensorSessionOpsExt<T> for TypedTensor<T> {
18 fn add(
19 &self,
20 rhs: &TypedTensor<T>,
21 session: &mut dyn BackendSession,
22 ) -> Result<TypedTensor<T>> {
23 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
24 let out = session.add_read(lhs.tensor_read(), rhs.tensor_read())?;
25 into_typed_result("add", out)
26 }
27
28 fn mul(
29 &self,
30 rhs: &TypedTensor<T>,
31 session: &mut dyn BackendSession,
32 ) -> Result<TypedTensor<T>> {
33 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
34 let out = session.mul_read(lhs.tensor_read(), rhs.tensor_read())?;
35 into_typed_result("mul", out)
36 }
37
38 fn exp(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
39 let out = session.exp_read(T::tensor_read(self))?;
40 into_typed_result("exp", out)
41 }
42
43 fn reduce_sum(
44 &self,
45 axes: &[usize],
46 session: &mut dyn BackendSession,
47 ) -> Result<TypedTensor<T>> {
48 let out = session.reduce_sum_read(T::tensor_read(self), axes)?;
49 into_typed_result("reduce_sum", out)
50 }
51
52 fn sub(
53 &self,
54 rhs: &TypedTensor<T>,
55 session: &mut dyn BackendSession,
56 ) -> Result<TypedTensor<T>> {
57 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
58 let out = session.sub_read(lhs.tensor_read(), rhs.tensor_read())?;
59 into_typed_result("sub", out)
60 }
61
62 fn div(
63 &self,
64 rhs: &TypedTensor<T>,
65 session: &mut dyn BackendSession,
66 ) -> Result<TypedTensor<T>> {
67 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
68 let out = session.div_read(lhs.tensor_read(), rhs.tensor_read())?;
69 into_typed_result("div", out)
70 }
71
72 fn rem(
73 &self,
74 rhs: &TypedTensor<T>,
75 session: &mut dyn BackendSession,
76 ) -> Result<TypedTensor<T>> {
77 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
78 let out = session.rem_read(lhs.tensor_read(), rhs.tensor_read())?;
79 into_typed_result("rem", out)
80 }
81
82 fn pow(
83 &self,
84 rhs: &TypedTensor<T>,
85 session: &mut dyn BackendSession,
86 ) -> Result<TypedTensor<T>> {
87 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
88 let out = session.pow_read(lhs.tensor_read(), rhs.tensor_read())?;
89 into_typed_result("pow", out)
90 }
91
92 fn maximum(
93 &self,
94 rhs: &TypedTensor<T>,
95 session: &mut dyn BackendSession,
96 ) -> Result<TypedTensor<T>> {
97 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
98 let out = session.maximum_read(lhs.tensor_read(), rhs.tensor_read())?;
99 into_typed_result("maximum", out)
100 }
101
102 fn minimum(
103 &self,
104 rhs: &TypedTensor<T>,
105 session: &mut dyn BackendSession,
106 ) -> Result<TypedTensor<T>> {
107 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
108 let out = session.minimum_read(lhs.tensor_read(), rhs.tensor_read())?;
109 into_typed_result("minimum", out)
110 }
111
112 fn neg(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
113 let out = session.neg_read(T::tensor_read(self))?;
114 into_typed_result("neg", out)
115 }
116
117 fn abs(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
118 let out = session.abs_read(T::tensor_read(self))?;
119 into_typed_result("abs", out)
120 }
121
122 fn sign(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
123 let out = session.sign_read(T::tensor_read(self))?;
124 into_typed_result("sign", out)
125 }
126
127 fn conj(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
128 let out = session.conj_read(T::tensor_read(self))?;
129 into_typed_result("conj", out)
130 }
131
132 fn log(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
133 let out = session.log_read(T::tensor_read(self))?;
134 into_typed_result("log", out)
135 }
136
137 fn expm1(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
138 let out = session.expm1_read(T::tensor_read(self))?;
139 into_typed_result("expm1", out)
140 }
141
142 fn log1p(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
143 let out = session.log1p_read(T::tensor_read(self))?;
144 into_typed_result("log1p", out)
145 }
146
147 fn sin(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
148 let out = session.sin_read(T::tensor_read(self))?;
149 into_typed_result("sin", out)
150 }
151
152 fn cos(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
153 let out = session.cos_read(T::tensor_read(self))?;
154 into_typed_result("cos", out)
155 }
156
157 fn tanh(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
158 let out = session.tanh_read(T::tensor_read(self))?;
159 into_typed_result("tanh", out)
160 }
161
162 fn sqrt(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
163 let out = session.sqrt_read(T::tensor_read(self))?;
164 into_typed_result("sqrt", out)
165 }
166
167 fn rsqrt(&self, session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
168 let out = session.rsqrt_read(T::tensor_read(self))?;
169 into_typed_result("rsqrt", out)
170 }
171
172 fn compare(
173 &self,
174 rhs: &TypedTensor<T>,
175 dir: CompareDir,
176 session: &mut dyn BackendSession,
177 ) -> Result<TypedTensor<bool>> {
178 let (lhs, rhs) = broadcast_binary_in_read(self, rhs, session)?;
179 let out = session.compare_read(lhs.tensor_read(), rhs.tensor_read(), &dir)?;
180 into_typed_result("compare", out)
181 }
182
183 fn clamp(
184 &self,
185 lower: &TypedTensor<T>,
186 upper: &TypedTensor<T>,
187 session: &mut dyn BackendSession,
188 ) -> Result<TypedTensor<T>> {
189 let (input, lower, upper) = broadcast_ternary_in_read(self, lower, upper, session)?;
190 let out = session.clamp_read(
191 input.tensor_read(),
192 lower.tensor_read(),
193 upper.tensor_read(),
194 )?;
195 into_typed_result("clamp", out)
196 }
197
198 fn matmul(
199 &self,
200 rhs: &TypedTensor<T>,
201 session: &mut dyn BackendSession,
202 ) -> Result<TypedTensor<T>> {
203 let config = matmul_config_for_shapes("matmul", self.shape(), rhs.shape())?;
204 let out = session.dot_general_read(T::tensor_read(self), T::tensor_read(rhs), &config)?;
205 into_typed_result("matmul", out)
206 }
207
208 fn reshape(&self, shape: &[usize], session: &mut dyn BackendSession) -> Result<TypedTensor<T>> {
209 let out = session.reshape_read(T::tensor_read(self), shape)?;
210 into_typed_result("reshape", out)
211 }
212
213 fn transpose(
214 &self,
215 perm: &[usize],
216 session: &mut dyn BackendSession,
217 ) -> Result<TypedTensor<T>> {
218 let out = session.transpose_read(T::tensor_read(self), perm)?;
219 into_typed_result("transpose", out)
220 }
221
222 fn broadcast_in_dim(
223 &self,
224 shape: &[usize],
225 dims: &[usize],
226 session: &mut dyn BackendSession,
227 ) -> Result<TypedTensor<T>> {
228 let out = session.broadcast_in_dim_read(T::tensor_read(self), shape, dims)?;
229 into_typed_result("broadcast_in_dim", out)
230 }
231}
232
233impl TypedTensorMaskSessionOpsExt for TypedTensor<bool> {
234 fn where_select<U: TensorScalar>(
235 &self,
236 on_true: &TypedTensor<U>,
237 on_false: &TypedTensor<U>,
238 session: &mut dyn BackendSession,
239 ) -> Result<TypedTensor<U>> {
240 let (condition, on_true, on_false) =
241 broadcast_ternary_in_read(self, on_true, on_false, session)?;
242 let out = session.select_read(
243 condition.tensor_read(),
244 on_true.tensor_read(),
245 on_false.tensor_read(),
246 )?;
247 into_typed_result("where_select", out)
248 }
249}
250
251#[allow(clippy::large_enum_variant)]
254pub(crate) enum ReadInput<'a> {
255 Borrowed(TensorRead<'a>),
256 Owned(Tensor),
257}
258
259impl ReadInput<'_> {
260 pub(crate) fn tensor_read(&self) -> TensorRead<'_> {
261 match self {
262 Self::Borrowed(read) => read.clone(),
263 Self::Owned(tensor) => TensorRead::from_tensor(tensor),
264 }
265 }
266}
267
268pub(crate) fn broadcast_to_in_read<'a>(
269 input: TensorRead<'a>,
270 target_shape: &[usize],
271 session: &mut dyn BackendSession,
272) -> Result<ReadInput<'a>> {
273 if input.shape() == target_shape {
274 return Ok(ReadInput::Borrowed(input));
275 }
276
277 let plan = broadcast_input_plan(input.shape(), target_shape).map_err(broadcast_error)?;
278 let source = if plan.source_shape == input.shape() {
279 ReadInput::Borrowed(input)
280 } else {
281 let reshaped = session.reshape_read(input, &plan.source_shape)?;
282 ReadInput::Owned(reshaped)
283 };
284 let out = session.broadcast_in_dim_read(source.tensor_read(), target_shape, &plan.dims)?;
285 Ok(ReadInput::Owned(out))
286}
287
288fn broadcast_binary_in_read<'a, T: TensorScalar>(
289 lhs: &'a TypedTensor<T>,
290 rhs: &'a TypedTensor<T>,
291 session: &mut dyn BackendSession,
292) -> Result<(ReadInput<'a>, ReadInput<'a>)> {
293 let shape = broadcast_shape(lhs.shape(), rhs.shape()).map_err(broadcast_error)?;
294 Ok((
295 broadcast_to_in_read(T::tensor_read(lhs), &shape, session)?,
296 broadcast_to_in_read(T::tensor_read(rhs), &shape, session)?,
297 ))
298}
299
300fn broadcast_ternary_in_read<'a, C: TensorScalar, T: TensorScalar>(
301 first: &'a TypedTensor<C>,
302 second: &'a TypedTensor<T>,
303 third: &'a TypedTensor<T>,
304 session: &mut dyn BackendSession,
305) -> Result<(ReadInput<'a>, ReadInput<'a>, ReadInput<'a>)> {
306 let shape = broadcast_shapes([first.shape(), second.shape(), third.shape()])
307 .map_err(broadcast_error)?;
308 Ok((
309 broadcast_to_in_read(C::tensor_read(first), &shape, session)?,
310 broadcast_to_in_read(T::tensor_read(second), &shape, session)?,
311 broadcast_to_in_read(T::tensor_read(third), &shape, session)?,
312 ))
313}
314
315fn broadcast_error(err: BroadcastError) -> Error {
316 match err {
317 BroadcastError::IncompatibleBinary { lhs, rhs } => {
318 Error::shape_mismatch("broadcast", lhs, rhs)
319 }
320 BroadcastError::IncompatibleInput { input, output } => {
321 Error::shape_mismatch("broadcast", input, output)
322 }
323 BroadcastError::RankTooLarge { input, output } => {
324 Error::rank_mismatch("broadcast", output.len(), input.len())
325 }
326 }
327}
328
329fn into_typed_result<T: TensorScalar>(op: &'static str, tensor: Tensor) -> Result<TypedTensor<T>> {
330 let actual = tensor.dtype();
331 T::into_typed(tensor).map_err(|_| {
332 Error::validation(
333 op,
334 ValidationError::DTypeMismatch {
335 expected: T::dtype(),
336 actual,
337 },
338 )
339 })
340}