1use std::iter::Sum;
2use std::ops::AddAssign;
3
4use mdarray::{Array, Dim, DynRank, Layout, Shape, Slice};
5use num_complex::ComplexFloat;
6use num_traits::{MulAdd, One, Zero};
7
8use super::simple::naive_matmul;
9use crate::{
10 Naive,
11 contract::{
12 _contract, _empty_einsum_shape, _hypercontract, Axes, Contract, ContractBuilder,
13 MatmulBuilder, einsum_to_contract_axes,
14 },
15};
16
17struct NaiveMatmulBuilder<'a, T, D0, D1, D2, La, Lb>
18where
19 La: Layout,
20 Lb: Layout,
21 D0: Dim,
22 D1: Dim,
23 D2: Dim,
24{
25 alpha: T,
26 a: &'a Slice<T, (D0, D1), La>,
27 b: &'a Slice<T, (D1, D2), Lb>,
28}
29
30struct NaiveContractBuilder<'a, T, Sa, Sb, La, Lb>
31where
32 La: Layout,
33 Lb: Layout,
34 Sa: Shape,
35 Sb: Shape,
36{
37 alpha: T,
38 a: &'a Slice<T, Sa, La>,
39 b: &'a Slice<T, Sb, Lb>,
40 axes: Axes<'a>,
41 einsum: bool,
42 empty_output_shape: Option<Vec<usize>>,
43 einsum_axes_a: Option<Vec<usize>>,
44 einsum_axes_b: Option<Vec<usize>>,
45 current_output_labels: Option<Vec<u8>>,
46 requested_output_labels: Option<Vec<u8>>,
47}
48
49impl<'a, T, D0, D1, D2, La, Lb> MatmulBuilder<'a, T, D0, D1, D2, La, Lb>
50 for NaiveMatmulBuilder<'a, T, D0, D1, D2, La, Lb>
51where
52 La: Layout,
53 Lb: Layout,
54 T: ComplexFloat + Zero + One + MulAdd<Output = T>,
55 D0: Dim,
56 D1: Dim,
57 D2: Dim,
58{
59 fn scale(mut self, factor: T) -> Self {
60 self.alpha = self.alpha * factor;
61 self
62 }
63
64 fn eval(self) -> Array<T, (D0, D2)> {
65 let (m, _) = *self.a.shape();
66 let (_, n) = *self.b.shape();
67 let mut c = Array::from_elem((m, n), T::zero());
68 naive_matmul(self.alpha, self.a, self.b, T::zero(), &mut c);
69 c
70 }
71
72 fn write<Lc: Layout>(self, c: &mut Slice<T, (D0, D2), Lc>) {
73 naive_matmul(self.alpha, self.a, self.b, T::zero(), c);
74 }
75
76 fn add_to<Lc: Layout>(self, c: &mut Slice<T, (D0, D2), Lc>) {
77 naive_matmul(self.alpha, self.a, self.b, T::one(), c);
78 }
79
80 fn add_to_scaled<Lc: Layout>(self, c: &mut Slice<T, (D0, D2), Lc>, beta: T) {
81 naive_matmul(self.alpha, self.a, self.b, beta, c);
82 }
83}
84
85impl<'a, T, Sa, Sb, La, Lb> ContractBuilder<'a, T, Sa, Sb, La, Lb>
86 for NaiveContractBuilder<'a, T, Sa, Sb, La, Lb>
87where
88 La: Layout,
89 Lb: Layout,
90 T: ComplexFloat + Zero + One + MulAdd<Output = T> + AddAssign + Sum,
91 Sa: Shape,
92 Sb: Shape,
93{
94 fn scale(mut self, factor: T) -> Self {
95 self.alpha = self.alpha * factor;
96 self
97 }
98
99 fn eval(self) -> Array<T, DynRank> {
100 if let Some(shape) = self.empty_output_shape {
101 return Array::from_elem(shape, T::zero());
102 }
103 if self.einsum {
104 let a = self.a.to_array().into_dyn();
105 let b = self.b.to_array().into_dyn();
106
107 let axes_a = self
114 .einsum_axes_a
115 .as_deref()
116 .expect("missing einsum axis labels for A");
117 let axes_b = self
118 .einsum_axes_b
119 .as_deref()
120 .expect("missing einsum axis labels for B");
121
122 let mut result = _hypercontract(Naive, a.expr(), b.expr(), axes_a, axes_b);
123
124 if let (Some(current), Some(requested)) = (
125 self.current_output_labels.as_deref(),
126 self.requested_output_labels.as_deref(),
127 )
128 && current != requested
129 {
130 let perm: Vec<usize> = requested
131 .iter()
132 .map(|label| {
133 current
134 .iter()
135 .position(|cur| cur == label)
136 .expect("output label not present in contraction result")
137 })
138 .collect();
139
140 result = result.permute(perm).to_tensor().into_dyn();
141 }
142
143 if self.alpha != T::one() {
144 result = result.map(|x| x * self.alpha).into_dyn();
145 }
146
147 result
148 } else {
149 _contract(Naive, self.a, self.b, self.axes, self.alpha)
150 }
151 }
152
153 fn write<Sc: Shape, Lc: Layout>(self, c: &mut Slice<T, Sc, Lc>) {
154 let result = self.eval();
155 assert_eq!(c.rank(), result.rank(), "output rank mismatch");
156 for i in 0..c.rank() {
157 assert_eq!(
158 c.dim(i),
159 result.dim(i),
160 "output shape mismatch on axis {i}: expected {}, got {}",
161 result.dim(i),
162 c.dim(i)
163 );
164 }
165 if c.is_empty() {
166 return;
167 }
168 for (dst, src) in c.iter_mut().zip(result.iter()) {
169 *dst = *src;
170 }
171 }
172
173 fn add_to<Sc: Shape, Lc: Layout>(self, c: &mut Slice<T, Sc, Lc>) {
174 self.add_to_scaled(c, T::one())
175 }
176
177 fn add_to_scaled<Sc: Shape, Lc: Layout>(self, c: &mut Slice<T, Sc, Lc>, beta: T) {
178 let result = self.eval();
179 assert_eq!(c.rank(), result.rank(), "output rank mismatch");
180 for i in 0..c.rank() {
181 assert_eq!(
182 c.dim(i),
183 result.dim(i),
184 "output shape mismatch on axis {i}: expected {}, got {}",
185 result.dim(i),
186 c.dim(i)
187 );
188 }
189 if c.is_empty() {
190 return;
191 }
192 for (dst, src) in c.iter_mut().zip(result.iter()) {
193 *dst = beta * *dst + *src;
194 }
195 }
196}
197
198impl<T> Contract<T> for Naive
199where
200 T: ComplexFloat + Zero + One + MulAdd<Output = T> + AddAssign + Sum,
201{
202 fn matmul<'a, D0, D1, D2, La, Lb>(
203 &self,
204 a: &'a Slice<T, (D0, D1), La>,
205 b: &'a Slice<T, (D1, D2), Lb>,
206 ) -> impl MatmulBuilder<'a, T, D0, D1, D2, La, Lb>
207 where
208 La: Layout,
209 Lb: Layout,
210 D0: Dim,
211 D1: Dim,
212 D2: Dim,
213 {
214 NaiveMatmulBuilder {
215 alpha: T::one(),
216 a,
217 b,
218 }
219 }
220
221 fn contract_all<'a, Sa, Sb, La, Lb>(
222 &self,
223 a: &'a Slice<T, Sa, La>,
224 b: &'a Slice<T, Sb, Lb>,
225 ) -> T
226 where
227 T: 'a,
228 Sa: Shape,
229 Sb: Shape,
230 La: Layout,
231 Lb: Layout,
232 {
233 _contract(Naive, a, b, Axes::All, T::one()).into_scalar()
234 }
235
236 fn contract_n<'a, Sa, Sb, La, Lb>(
237 &self,
238 a: &'a Slice<T, Sa, La>,
239 b: &'a Slice<T, Sb, Lb>,
240 n: usize,
241 ) -> impl ContractBuilder<'a, T, Sa, Sb, La, Lb>
242 where
243 T: 'a,
244 Sa: Shape,
245 Sb: Shape,
246 La: Layout,
247 Lb: Layout,
248 {
249 NaiveContractBuilder {
250 alpha: T::one(),
251 a,
252 b,
253 axes: Axes::LastFirst { k: n },
254 einsum: false,
255 empty_output_shape: None,
256 einsum_axes_a: None,
257 einsum_axes_b: None,
258 current_output_labels: None,
259 requested_output_labels: None,
260 }
261 }
262
263 fn contract_pairs<'a, Sa, Sb, La, Lb>(
264 &self,
265 a: &'a Slice<T, Sa, La>,
266 b: &'a Slice<T, Sb, Lb>,
267 axes_a: &'a [usize],
268 axes_b: &'a [usize],
269 ) -> impl ContractBuilder<'a, T, Sa, Sb, La, Lb>
270 where
271 T: 'a,
272 Sa: Shape,
273 Sb: Shape,
274 La: Layout,
275 Lb: Layout,
276 {
277 NaiveContractBuilder {
278 alpha: T::one(),
279 a,
280 b,
281 axes: Axes::Specific(axes_a, axes_b),
282 einsum: false,
283 empty_output_shape: None,
284 einsum_axes_a: None,
285 einsum_axes_b: None,
286 current_output_labels: None,
287 requested_output_labels: None,
288 }
289 }
290
291 fn contract<'a, Sa, Sb, La, Lb>(
292 &self,
293 a: &'a Slice<T, Sa, La>,
294 b: &'a Slice<T, Sb, Lb>,
295 indices_a: &'a [u8],
296 indices_b: &'a [u8],
297 indices_c: &'a [u8],
298 ) -> impl ContractBuilder<'a, T, Sa, Sb, La, Lb>
299 where
300 T: 'a,
301 Sa: Shape,
302 Sb: Shape,
303 La: Layout,
304 Lb: Layout,
305 {
306 assert_eq!(
307 indices_a.len(),
308 a.rank(),
309 "einsum indices_a length ({}) must match A rank ({})",
310 indices_a.len(),
311 a.rank()
312 );
313 assert_eq!(
314 indices_b.len(),
315 b.rank(),
316 "einsum indices_b length ({}) must match B rank ({})",
317 indices_b.len(),
318 b.rank()
319 );
320
321 let free: std::collections::HashSet<u8> = indices_c.iter().copied().collect();
322 let current_output_labels: Vec<u8> = indices_a
323 .iter()
324 .chain(indices_b.iter())
325 .copied()
326 .filter(|label| free.contains(label))
327 .collect();
328 let (einsum_axes_a, einsum_axes_b) =
329 einsum_to_contract_axes(indices_a, indices_b, indices_c);
330
331 NaiveContractBuilder {
332 alpha: T::one(),
333 a,
334 b,
335 axes: Axes::SpecificOwned(Vec::new(), Vec::new()),
336 einsum: true,
337 empty_output_shape: _empty_einsum_shape(a, b, indices_a, indices_b, indices_c),
338 einsum_axes_a: Some(einsum_axes_a),
339 einsum_axes_b: Some(einsum_axes_b),
340 current_output_labels: Some(current_output_labels),
341 requested_output_labels: Some(indices_c.to_vec()),
342 }
343 }
344}