tensor4all_core/defaults/
direct_sum.rs1use crate::defaults::DynIndex;
10use crate::defaults::IdxTensorError;
11use crate::index_like::IndexLike;
12use crate::tensor::IdxTensor;
13use anyhow::Result;
14use num_traits::Zero;
15use tensor4all_tensorbackend::TensorElement;
16
17pub fn direct_sum(
61 a: &IdxTensor,
62 b: &IdxTensor,
63 pairs: &[(DynIndex, DynIndex)],
64) -> std::result::Result<(IdxTensor, Vec<DynIndex>), IdxTensorError> {
65 if a.is_f64() && b.is_f64() {
66 direct_sum_typed::<f64>(a, b, pairs).map_err(IdxTensorError::from)
67 } else if a.is_complex() && b.is_complex() {
68 direct_sum_typed::<num_complex::Complex64>(a, b, pairs).map_err(IdxTensorError::from)
69 } else {
70 Err(IdxTensorError::Operation {
71 operation: "direct_sum",
72 source: std::sync::Arc::new(std::io::Error::other(
73 "direct_sum requires both tensors to have the same dense scalar type (f64 or Complex64)",
74 )),
75 })
76 }
77}
78
79#[allow(dead_code)]
81struct DirectSumSetup {
82 common_a_positions: Vec<usize>,
83 common_b_positions: Vec<usize>,
84 paired_a_positions: Vec<usize>,
85 paired_b_positions: Vec<usize>,
86 paired_dims_a: Vec<usize>,
87 paired_dims_b: Vec<usize>,
88 result_indices: Vec<DynIndex>,
89 result_dims: Vec<usize>,
90 result_strides: Vec<usize>,
91 result_total: usize,
92 a_strides: Vec<usize>,
93 b_strides: Vec<usize>,
94 new_indices: Vec<DynIndex>,
95 n_common: usize,
96}
97
98fn setup_direct_sum(
99 a: &IdxTensor,
100 b: &IdxTensor,
101 pairs: &[(DynIndex, DynIndex)],
102) -> Result<DirectSumSetup> {
103 use std::collections::HashMap;
104
105 if pairs.is_empty() {
106 return Err(anyhow::anyhow!(
107 "direct_sum requires at least one index pair"
108 ));
109 }
110
111 let a_idx_map: HashMap<&DynIndex, usize> = a
114 .indices
115 .iter()
116 .enumerate()
117 .map(|(i, idx)| (idx, i))
118 .collect();
119 let b_idx_map: HashMap<&DynIndex, usize> = b
120 .indices
121 .iter()
122 .enumerate()
123 .map(|(i, idx)| (idx, i))
124 .collect();
125
126 let mut a_paired_indices: std::collections::HashSet<&DynIndex> =
128 std::collections::HashSet::new();
129 let mut b_paired_indices: std::collections::HashSet<&DynIndex> =
130 std::collections::HashSet::new();
131
132 for (a_idx, b_idx) in pairs {
133 if !a_idx_map.contains_key(a_idx) {
134 return Err(anyhow::anyhow!(
135 "Index not found in first tensor (direct_sum)"
136 ));
137 }
138 if !b_idx_map.contains_key(b_idx) {
139 return Err(anyhow::anyhow!(
140 "Index not found in second tensor (direct_sum)"
141 ));
142 }
143 a_paired_indices.insert(a_idx);
144 b_paired_indices.insert(b_idx);
145 }
146
147 let mut common_a_positions: Vec<usize> = Vec::new();
151 let mut common_b_positions: Vec<usize> = Vec::new();
152 for (a_pos, a_idx) in a.indices.iter().enumerate() {
153 if a_paired_indices.contains(a_idx) {
154 continue;
155 }
156 if let Some(&b_pos) = b_idx_map.get(a_idx) {
157 if b_paired_indices.contains(a_idx) {
158 continue;
159 }
160 let a_dims = a.dims();
161 let b_dims = b.dims();
162 if a_dims[a_pos] != b_dims[b_pos] {
163 return Err(anyhow::anyhow!(
164 "Dimension mismatch for common index: {} vs {}",
165 a_dims[a_pos],
166 b_dims[b_pos]
167 ));
168 }
169 common_a_positions.push(a_pos);
170 common_b_positions.push(b_pos);
171 }
172 }
173
174 let paired_a_positions: Vec<usize> = pairs.iter().map(|(a_idx, _)| a_idx_map[a_idx]).collect();
176 let paired_b_positions: Vec<usize> = pairs.iter().map(|(_, b_idx)| b_idx_map[b_idx]).collect();
177 let a_dims = a.dims();
178 let b_dims = b.dims();
179 let paired_dims_a: Vec<usize> = paired_a_positions.iter().map(|&p| a_dims[p]).collect();
180 let paired_dims_b: Vec<usize> = paired_b_positions.iter().map(|&p| b_dims[p]).collect();
181
182 let mut new_indices: Vec<DynIndex> = Vec::new();
184 for (&dim_a, &dim_b) in paired_dims_a.iter().zip(&paired_dims_b) {
185 let new_dim = dim_a
186 .checked_add(dim_b)
187 .ok_or_else(|| anyhow::anyhow!("direct_sum index dimension overflowed usize"))?;
188 let new_index = DynIndex::new_link(new_dim)
189 .map_err(|e| anyhow::anyhow!("Failed to create index: {:?}", e))?;
190 new_indices.push(new_index);
191 }
192
193 let mut result_indices: Vec<DynIndex> = Vec::new();
195 let mut result_dims: Vec<usize> = Vec::new();
196
197 let a_dims = a.dims();
199 for &a_pos in &common_a_positions {
200 result_indices.push(a.indices[a_pos].clone());
201 result_dims.push(a_dims[a_pos]);
202 }
203
204 for new_idx in &new_indices {
206 result_indices.push(new_idx.clone());
207 result_dims.push(new_idx.dim());
208 }
209
210 let result_total = checked_product(&result_dims, "direct_sum result")?;
212 let result_strides = checked_strides(&result_dims, "direct_sum result")?;
213
214 let a_dims = a.dims();
215 let b_dims = b.dims();
216 let a_strides = checked_strides(&a_dims, "direct_sum lhs")?;
217 let b_strides = checked_strides(&b_dims, "direct_sum rhs")?;
218
219 let n_common = common_a_positions.len();
220
221 Ok(DirectSumSetup {
222 common_a_positions,
223 common_b_positions,
224 paired_a_positions,
225 paired_b_positions,
226 paired_dims_a,
227 paired_dims_b,
228 result_indices,
229 result_dims,
230 result_strides,
231 result_total,
232 a_strides,
233 b_strides,
234 new_indices,
235 n_common,
236 })
237}
238
239fn checked_product(dims: &[usize], name: &str) -> Result<usize> {
240 dims.iter().try_fold(1usize, |acc, &dim| {
241 acc.checked_mul(dim)
242 .ok_or_else(|| anyhow::anyhow!("{name} shape product overflowed usize"))
243 })
244}
245
246fn checked_strides(dims: &[usize], name: &str) -> Result<Vec<usize>> {
247 let mut strides = vec![1usize; dims.len()];
248 for i in 1..dims.len() {
249 strides[i] = strides[i - 1]
250 .checked_mul(dims[i - 1])
251 .ok_or_else(|| anyhow::anyhow!("{name} stride overflowed usize"))?;
252 }
253 Ok(strides)
254}
255
256fn linear_to_multi(linear: usize, dims: &[usize]) -> Vec<usize> {
257 let mut multi = vec![0; dims.len()];
258 let mut remaining = linear;
259 for i in 0..dims.len() {
260 multi[i] = remaining % dims[i];
261 remaining /= dims[i];
262 }
263 multi
264}
265
266fn multi_to_linear(multi: &[usize], strides: &[usize]) -> Result<usize> {
267 multi.iter().zip(strides).try_fold(0usize, |acc, (&m, &s)| {
268 let offset = m
269 .checked_mul(s)
270 .ok_or_else(|| anyhow::anyhow!("direct_sum flat offset overflowed usize"))?;
271 acc.checked_add(offset)
272 .ok_or_else(|| anyhow::anyhow!("direct_sum flat offset overflowed usize"))
273 })
274}
275
276fn direct_sum_typed<T: TensorElement + Zero>(
277 a: &IdxTensor,
278 b: &IdxTensor,
279 pairs: &[(DynIndex, DynIndex)],
280) -> Result<(IdxTensor, Vec<DynIndex>)> {
281 if a.tracks_grad() || b.tracks_grad() {
282 return Err(anyhow::anyhow!(
283 "direct_sum does not support tracked tensors until an AD-preserving block path is available"
284 ));
285 }
286 let setup = setup_direct_sum(a, b, pairs)?;
287 let a_data = a.to_vec::<T>()?;
288 let b_data = b.to_vec::<T>()?;
289
290 let mut result_data: Vec<T> = vec![T::zero(); setup.result_total];
291
292 #[allow(clippy::needless_range_loop)]
293 for result_linear in 0..setup.result_total {
294 let result_multi = linear_to_multi(result_linear, &setup.result_dims);
295 let common_multi: Vec<usize> = result_multi[..setup.n_common].to_vec();
296 let paired_multi: Vec<usize> = result_multi[setup.n_common..].to_vec();
297
298 let all_from_a = paired_multi
299 .iter()
300 .enumerate()
301 .all(|(i, &pm)| pm < setup.paired_dims_a[i]);
302 let all_from_b = paired_multi
303 .iter()
304 .enumerate()
305 .all(|(i, &pm)| pm >= setup.paired_dims_a[i]);
306
307 if all_from_a {
308 let a_dims = a.dims();
309 let mut a_multi = vec![0usize; a_dims.len()];
310 for (i, &cp) in setup.common_a_positions.iter().enumerate() {
311 a_multi[cp] = common_multi[i];
312 }
313 for (i, &pp) in setup.paired_a_positions.iter().enumerate() {
314 a_multi[pp] = paired_multi[i];
315 }
316 let a_linear = multi_to_linear(&a_multi, &setup.a_strides)?;
317 result_data[result_linear] = a_data[a_linear];
318 } else if all_from_b {
319 let b_dims = b.dims();
320 let mut b_multi = vec![0usize; b_dims.len()];
321 for (i, &cp) in setup.common_b_positions.iter().enumerate() {
322 b_multi[cp] = common_multi[i];
323 }
324 for (i, &pp) in setup.paired_b_positions.iter().enumerate() {
325 b_multi[pp] = paired_multi[i] - setup.paired_dims_a[i];
326 }
327 let b_linear = multi_to_linear(&b_multi, &setup.b_strides)?;
328 result_data[result_linear] = b_data[b_linear];
329 }
330 }
332
333 let result = IdxTensor::from_dense(setup.result_indices, result_data)?;
334 Ok((result, setup.new_indices))
335}
336
337#[cfg(test)]
338mod tests;