Skip to main content

qudit_expr/
cache.rs

1use std::collections::BTreeMap;
2use std::sync::{Arc, Mutex};
3
4use qudit_core::RealScalar;
5use serde::{Deserialize, Serialize};
6
7use crate::TensorExpression;
8use crate::analysis::simplify_expressions_iter;
9use crate::codegen::CompilableUnit;
10use crate::expressions::{ExpressionBody, NamedExpression};
11use crate::index::{IndexSize, TensorIndex};
12use crate::{
13    ComplexExpression, DifferentiationLevel, Expression, GRADIENT, GenerationShape, HESSIAN,
14    Module, ModuleBuilder, WriteFunc,
15};
16
17pub type ExpressionId = u64;
18
19#[derive(Serialize, Deserialize)]
20pub struct CachedExpressionBody {
21    original: ExpressionBody,
22    func: Option<Vec<Expression>>,
23    grad: Option<Vec<Expression>>,
24    hess: Option<Vec<Expression>>,
25}
26
27impl CachedExpressionBody {
28    pub fn new(original: impl Into<ExpressionBody>) -> CachedExpressionBody {
29        let original = original.into();
30        CachedExpressionBody {
31            original,
32            func: None,
33            grad: None,
34            hess: None,
35        }
36    }
37
38    pub fn num_elements(&self) -> usize {
39        self.original.num_elements()
40    }
41
42    // Simplify and differentiate to prepare expression to evaluate up to diff_level.
43    pub fn prepare(&mut self, variables: &[String], diff_level: DifferentiationLevel) -> bool {
44        let mut has_changed = false;
45
46        if self.func.is_none() {
47            self.func = Some(simplify_expressions_iter(
48                self.original.iter().flat_map(|c| [&c.real, &c.imag]),
49            ));
50
51            has_changed = true;
52        }
53
54        if diff_level >= GRADIENT && self.grad.is_none() {
55            let mut grad_exprs = vec![];
56            for variable in variables {
57                for expr in self.original.iter() {
58                    let grad_expr = expr.differentiate(variable);
59                    grad_exprs.push(grad_expr);
60                }
61            }
62
63            self.grad = Some(simplify_expressions_iter(
64                self.original
65                    .iter()
66                    .chain(grad_exprs.iter())
67                    .flat_map(|c| [&c.real, &c.imag]),
68            ));
69
70            has_changed = true;
71        }
72
73        if diff_level >= HESSIAN && self.hess.is_some() {
74            let mut grad_exprs = vec![];
75            for variable in variables {
76                for expr in self.original.iter() {
77                    let grad_expr = expr.differentiate(variable);
78                    grad_exprs.push(grad_expr);
79                }
80            }
81
82            let mut hess_exprs = vec![];
83            for variable in variables {
84                for expr in grad_exprs.iter() {
85                    let hess_expr = expr.differentiate(variable);
86                    hess_exprs.push(hess_expr);
87                }
88            }
89
90            self.hess = Some(simplify_expressions_iter(
91                self.original
92                    .iter()
93                    .chain(grad_exprs.iter())
94                    .chain(hess_exprs.iter())
95                    .flat_map(|c| [&c.real, &c.imag]),
96            ));
97
98            has_changed = true;
99        }
100
101        has_changed
102    }
103}
104
105impl<B: Into<ExpressionBody>> From<B> for CachedExpressionBody {
106    fn from(value: B) -> Self {
107        CachedExpressionBody::new(value)
108    }
109}
110
111#[derive(Serialize, Deserialize)]
112pub struct CachedTensorExpression {
113    name: String,
114    variables: Vec<String>,
115    indices: Vec<TensorIndex>,
116    expressions: CachedExpressionBody,
117    id_lookup: BTreeMap<ExpressionId, (Vec<usize>, GenerationShape)>,
118    base_id: ExpressionId,
119}
120
121impl CachedTensorExpression {
122    pub fn new<E: Into<TensorExpression>>(expr: E, base_id: ExpressionId) -> Self {
123        let expr = expr.into();
124        let mut id_lookup = BTreeMap::new();
125        id_lookup.insert(base_id, ((0..expr.rank()).collect(), expr.indices().into()));
126        let (name, variables, body, indices) = expr.destruct();
127        CachedTensorExpression {
128            name,
129            variables,
130            indices,
131            expressions: body.into(),
132            id_lookup,
133            base_id,
134        }
135    }
136
137    // TODO: deduplicate code with tensorexpression
138    pub fn indices(&self) -> &[TensorIndex] {
139        &self.indices
140    }
141
142    // TODO: deduplicate code with tensorexpression
143    pub fn rank(&self) -> usize {
144        self.indices.len()
145    }
146
147    pub fn num_params(&self) -> usize {
148        self.variables.len()
149    }
150
151    // TODO: deduplicate code with tensorexpression
152    pub fn dimensions(&self) -> Vec<IndexSize> {
153        self.indices.iter().map(|idx| idx.index_size()).collect()
154    }
155
156    // TODO: deduplicate code with tensorexpression
157    pub fn tensor_strides(&self) -> Vec<usize> {
158        let mut strides = Vec::with_capacity(self.indices.len());
159        let mut current_stride = 1;
160        for &index in self.indices.iter().rev() {
161            strides.push(current_stride);
162            current_stride *= index.index_size();
163        }
164        strides.reverse();
165        strides
166    }
167
168    pub fn elements(&self) -> &[ComplexExpression] {
169        &self.expressions.original
170    }
171
172    // Simplify and differentiate to prepare expression to evaluate up to diff_level.
173    pub fn prepare(&mut self, diff_level: DifferentiationLevel) -> bool {
174        self.expressions.prepare(&self.variables, diff_level)
175    }
176
177    fn add_to_builder<'a, R: RealScalar>(
178        &'a self,
179        mut builder: ModuleBuilder<'a, R>,
180    ) -> ModuleBuilder<'a, R> {
181        if let Some(func) = &self.expressions.func {
182            // println!("Adding {} function to module", self.name.clone() + "_" + "1");
183            let unit = CompilableUnit::new(
184                &(format!("expr_{}_{}", self.base_id, "1")),
185                func.as_ref(),
186                self.variables.clone(),
187                self.expressions.original.len() * 2,
188            );
189
190            builder = builder.add_unit(unit);
191        }
192
193        if let Some(grad) = &self.expressions.grad {
194            // println!("Adding {} function to module", self.name.clone() + "_" + "2");
195            let unit = CompilableUnit::new(
196                &(format!("expr_{}_{}", self.base_id, "2")),
197                grad.as_ref(),
198                self.variables.clone(),
199                self.expressions.original.len() * 2,
200            );
201
202            builder = builder.add_unit(unit);
203        }
204
205        if self.expressions.hess.is_some() {
206            // println!("Adding {} function to module", self.name.clone() + "_" + "3");
207            let unit = CompilableUnit::new(
208                &(format!("expr_{}_{}", self.base_id, "3")),
209                self.expressions.grad.as_ref().unwrap(),
210                self.variables.clone(),
211                self.expressions.original.len() * 2,
212            );
213
214            builder = builder.add_unit(unit);
215        }
216
217        builder
218    }
219
220    pub fn num_elements(&self) -> usize {
221        self.expressions.num_elements()
222    }
223
224    pub fn form_expression(&self) -> TensorExpression {
225        let named = NamedExpression::new(
226            self.name.clone(),
227            self.variables.clone(),
228            self.expressions.original.clone(),
229        );
230        TensorExpression::from_raw(self.indices.clone(), named)
231    }
232
233    pub fn form_modified_indices(
234        &self,
235        modifiers: &(Vec<usize>, GenerationShape),
236    ) -> Vec<TensorIndex> {
237        let perm_index_sizes: Vec<usize> = modifiers
238            .0
239            .iter()
240            .map(|p| self.indices[*p].index_size())
241            .collect();
242        let redirection = modifiers.1.calculate_directions(&perm_index_sizes);
243        perm_index_sizes
244            .iter()
245            .zip(redirection.iter())
246            .enumerate()
247            .map(|(id, (s, d))| TensorIndex::new(*d, id, *s))
248            .collect()
249    }
250
251    pub fn form_modified_expression(
252        &self,
253        modifiers: &(Vec<usize>, GenerationShape),
254    ) -> TensorExpression {
255        let perm_index_sizes: Vec<usize> = modifiers
256            .0
257            .iter()
258            .map(|p| self.indices[*p].index_size())
259            .collect();
260        let redirection = modifiers.1.calculate_directions(&perm_index_sizes);
261        let mut expression = self.form_expression();
262        let new_indices = perm_index_sizes
263            .iter()
264            .zip(redirection.iter())
265            .enumerate()
266            .map(|(id, (s, d))| TensorIndex::new(*d, id, *s))
267            .collect();
268        expression.permute(&modifiers.0, redirection);
269        expression.reindex(new_indices);
270        expression
271    }
272}
273
274#[derive(Default, Serialize, Deserialize)]
275pub struct ExpressionCache {
276    expressions: BTreeMap<ExpressionId, CachedTensorExpression>,
277    id_lookup: BTreeMap<ExpressionId, ExpressionId>, // Maps derived expressions to their base id
278    name_lookup: BTreeMap<String, Vec<ExpressionId>>, // Name to base expression ids
279    #[serde(skip)]
280    module32: Option<Module<f32>>,
281    #[serde(skip)]
282    module64: Option<Module<f64>>,
283    id_counter: ExpressionId,
284}
285
286impl ExpressionCache {
287    pub fn new() -> Self {
288        Self {
289            expressions: BTreeMap::new(),
290            id_lookup: BTreeMap::new(),
291            name_lookup: BTreeMap::new(),
292            module32: None,
293            module64: None,
294            id_counter: 0,
295        }
296    }
297
298    pub fn new_shared() -> Arc<Mutex<Self>> {
299        Arc::new(Self::new().into())
300    }
301
302    pub fn get(&self, expr_id: ExpressionId) -> Option<TensorExpression> {
303        // TODO: do better.
304        let base_id = self
305            .id_lookup
306            .get(&expr_id)
307            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
308        let cexpr = self
309            .expressions
310            .get(base_id)
311            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
312        let modifiers = cexpr
313            .id_lookup
314            .get(&expr_id)
315            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
316        Some(cexpr.form_modified_expression(modifiers))
317    }
318
319    pub fn lookup(&self, expr: impl AsRef<NamedExpression>) -> Option<ExpressionId> {
320        let expr = expr.as_ref();
321        if let Some(ids) = self.name_lookup.get(expr.name()) {
322            for id in ids {
323                let cexpr = self
324                    .expressions
325                    .get(id)
326                    .expect("Expected since just looked up id.");
327                if expr == &cexpr.expressions.original {
328                    return Some(*id);
329                }
330            }
331        }
332        None
333    }
334
335    pub fn contains(&self, expr: impl AsRef<NamedExpression>) -> bool {
336        self.lookup(expr).is_some()
337    }
338
339    fn get_new_id(&mut self) -> ExpressionId {
340        let new_id = self.id_counter;
341        self.id_counter += 1;
342        new_id
343    }
344
345    fn uncompile(&mut self) {
346        self.module32 = None;
347        self.module64 = None;
348    }
349
350    pub fn remove(&mut self, expr_id: ExpressionId) {
351        let mut modified_flag = false;
352        if let Some(base_id) = self.id_lookup.get(&expr_id) {
353            // Only actually remove anything if its a base expression
354            // TODO: This might be incorrect as derived expressions might
355            // still exist. Maybe, just mark this one as removed, and remove
356            // it when it's id_lookup is empty?
357            if *base_id == expr_id {
358                modified_flag = true;
359                let cexpr = self.expressions.remove(base_id).unwrap();
360                self.id_lookup.remove(&expr_id);
361                let name_vec = self.name_lookup.get_mut(&cexpr.name).unwrap();
362                name_vec.swap_remove(name_vec.iter().position(|x| *x == expr_id).unwrap());
363            }
364        }
365
366        if modified_flag {
367            self.uncompile();
368        }
369    }
370
371    pub fn insert(&mut self, expr: impl Into<TensorExpression>) -> ExpressionId {
372        let expr: TensorExpression = expr.into();
373        if let Some(id) = self.lookup(&expr) {
374            return id;
375        }
376
377        self.uncompile();
378
379        let id = self.get_new_id();
380        self.name_lookup.insert(expr.name().to_owned(), vec![id]);
381        self.expressions
382            .insert(id, CachedTensorExpression::new(expr, id));
383        self.id_lookup.insert(id, id);
384        id
385    }
386
387    pub fn indices(&self, expr_id: ExpressionId) -> Vec<TensorIndex> {
388        let base_id = self
389            .id_lookup
390            .get(&expr_id)
391            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
392        let cexpr = self
393            .expressions
394            .get(base_id)
395            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
396        let modifiers = cexpr
397            .id_lookup
398            .get(&expr_id)
399            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
400        cexpr.form_modified_indices(modifiers)
401    }
402
403    pub fn num_elements(&self, expr_id: ExpressionId) -> usize {
404        let base_id = self
405            .id_lookup
406            .get(&expr_id)
407            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
408        let cexpr = self
409            .expressions
410            .get(base_id)
411            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
412        cexpr.num_elements()
413    }
414
415    pub fn generation_shape(&self, expr_id: ExpressionId) -> GenerationShape {
416        let base_id = self
417            .id_lookup
418            .get(&expr_id)
419            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
420        let cexpr = self
421            .expressions
422            .get(base_id)
423            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
424        let modifiers = cexpr
425            .id_lookup
426            .get(&expr_id)
427            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
428        modifiers.1
429    }
430
431    pub fn base_name(&self, expr_id: ExpressionId) -> String {
432        let base_id = self
433            .id_lookup
434            .get(&expr_id)
435            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
436        let cexpr = self
437            .expressions
438            .get(base_id)
439            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
440
441        cexpr.name.clone()
442    }
443
444    pub fn name(&self, expr_id: ExpressionId) -> String {
445        let base_id = self
446            .id_lookup
447            .get(&expr_id)
448            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
449        let cexpr = self
450            .expressions
451            .get(base_id)
452            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
453        let modifiers = cexpr
454            .id_lookup
455            .get(&expr_id)
456            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
457
458        let base_name = cexpr.name.clone();
459        let permutation = modifiers
460            .0
461            .iter()
462            .map(|&i| i.to_string())
463            .collect::<Vec<String>>()
464            .join("_");
465        let shape = modifiers
466            .1
467            .to_vec()
468            .iter()
469            .map(|&i| i.to_string())
470            .collect::<Vec<String>>()
471            .join("_");
472        format!("{base_name}_perm{permutation}_shape{shape}")
473    }
474
475    pub fn num_params(&self, expr_id: ExpressionId) -> usize {
476        let base_id = self
477            .id_lookup
478            .get(&expr_id)
479            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
480        let cexpr = self
481            .expressions
482            .get(base_id)
483            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
484        cexpr.num_params()
485    }
486
487    pub fn trace(&mut self, expr_id: ExpressionId, pairs: Vec<(usize, usize)>) -> ExpressionId {
488        // expr_id -> base_id -> base_expr -> derived_name -> base_name -> check if base_name_tracedXYZ exists
489        let old_name = self.name(expr_id);
490        let traced = pairs
491            .iter()
492            .map(|(x, y)| format!("{x}_{y}"))
493            .collect::<Vec<String>>()
494            .join("_");
495        let traced_name = format!("traced{traced}_{old_name}");
496
497        let base_id = self
498            .id_lookup
499            .get(&expr_id)
500            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
501        let cexpr = self
502            .expressions
503            .get(base_id)
504            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
505        let modifiers = cexpr
506            .id_lookup
507            .get(&expr_id)
508            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
509
510        let old_expr: TensorExpression = cexpr.form_modified_expression(modifiers);
511
512        let mut traced_expr = old_expr.partial_trace(&pairs);
513        traced_expr.set_name(traced_name.clone());
514
515        if let Some(ids) = self.name_lookup.get(&traced_name) {
516            for id in ids {
517                let cexpr_other = self
518                    .expressions
519                    .get(id)
520                    .unwrap_or_else(|| panic!("Failed to {id} in cache."));
521
522                if traced_expr.elements() == cexpr_other.elements() {
523                    return *id;
524                }
525            }
526        }
527
528        self.uncompile();
529        self.insert(traced_expr)
530    }
531
532    pub fn permute_reshape(
533        &mut self,
534        expr_id: ExpressionId,
535        perm: Vec<usize>,
536        reshape: GenerationShape,
537    ) -> ExpressionId {
538        let base_id = *self
539            .id_lookup
540            .get(&expr_id)
541            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
542        let cexpr = self
543            .expressions
544            .get(&base_id)
545            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
546        let modifiers = cexpr
547            .id_lookup
548            .get(&expr_id)
549            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
550
551        // assert reshape num elements is correct
552        let composed_perm: Vec<usize> = perm.iter().map(|&idx| modifiers.0[idx]).collect(); // TODO: check if correct ordering
553
554        let new_val = (composed_perm, reshape);
555        for (id, val) in cexpr.id_lookup.iter() {
556            if *val == new_val {
557                return *id;
558            }
559        }
560
561        let new_id = self.get_new_id();
562        let cexpr = self
563            .expressions
564            .get_mut(&base_id)
565            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
566        cexpr.id_lookup.insert(new_id, new_val);
567        self.id_lookup.insert(new_id, base_id);
568        new_id
569    }
570
571    fn _get_module_mut<R: RealScalar>(&mut self) -> &mut Option<Module<R>> {
572        if std::any::TypeId::of::<R>() == std::any::TypeId::of::<f32>() {
573            // Safety: Just checked exact type id
574            unsafe { std::mem::transmute(&mut self.module32) }
575        } else if std::any::TypeId::of::<R>() == std::any::TypeId::of::<f64>() {
576            // Safety: Just checked exact type id
577            unsafe { std::mem::transmute(&mut self.module64) }
578        } else {
579            unreachable!()
580        }
581    }
582
583    fn _get_module<R: RealScalar>(&self) -> &Option<Module<R>> {
584        if std::any::TypeId::of::<R>() == std::any::TypeId::of::<f32>() {
585            // Safety: Just checked exact type id
586            unsafe { std::mem::transmute(&self.module32) }
587        } else if std::any::TypeId::of::<R>() == std::any::TypeId::of::<f64>() {
588            // Safety: Just checked exact type id
589            unsafe { std::mem::transmute(&self.module64) }
590        } else {
591            unreachable!()
592        }
593    }
594
595    pub fn is_compiled<R: RealScalar>(&self) -> bool {
596        self._get_module::<R>().is_some()
597    }
598
599    // Simplify and differentiate to prepare expression to evaluate up to diff_level.
600    pub fn prepare(&mut self, diff_level: DifferentiationLevel) {
601        let mut should_uncompile = false;
602        for cexpr in self.expressions.values_mut() {
603            if cexpr.prepare(diff_level) {
604                should_uncompile = true;
605            }
606        }
607
608        if should_uncompile {
609            self.uncompile();
610        }
611    }
612
613    pub fn compile<R: RealScalar>(&mut self) {
614        let mut module_builder: ModuleBuilder<R> = ModuleBuilder::new("cache");
615        // For each base expression
616        for cexpr in self.expressions.values_mut() {
617            module_builder = cexpr.add_to_builder(module_builder);
618        }
619        *self._get_module_mut() = Some(module_builder.build());
620    }
621
622    // TODO: explore a lock feature: compile and lock -> returns something that needs to be passed
623    // into get_fn; that provides guarantees that when the lock goes out of scope all the write
624    // functions are also out of scope.
625
626    pub fn get_fn<R: RealScalar>(
627        &mut self,
628        expr_id: ExpressionId,
629        diff_level: DifferentiationLevel,
630    ) -> WriteFunc<R> {
631        if !self.is_compiled::<R>() {
632            self.compile::<R>();
633        }
634
635        let module = self
636            ._get_module()
637            .as_ref()
638            .expect("Module should exist due to previous compilation.");
639
640        let base_id = self
641            .id_lookup
642            .get(&expr_id)
643            .expect("Unexpected expression id.");
644
645        // TODO: ensure diff_level function exists
646        module
647            .get_function(&format!("expr_{}_{}", base_id, diff_level))
648            .expect("Error retrieving compiled expression.")
649    }
650
651    pub fn get_output_map<R: RealScalar>(
652        &self,
653        expr_id: ExpressionId,
654        row_stride: u64,
655        col_stride: u64,
656        mat_stride: u64,
657    ) -> Vec<u64> {
658        let base_id = self
659            .id_lookup
660            .get(&expr_id)
661            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
662        let cexpr = self
663            .expressions
664            .get(base_id)
665            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
666        let modifiers = cexpr
667            .id_lookup
668            .get(&expr_id)
669            .unwrap_or_else(|| panic!("Failed to {expr_id} in cache."));
670
671        let (perm, shape) = modifiers;
672
673        // TODO: deduplicate code with tensorexpression.permute; extract calc_elem_perm ->
674        // Vec<TensorIndex>
675        let original_strides = cexpr.tensor_strides();
676        let original_dimensions = cexpr.dimensions();
677
678        let new_strides = {
679            let mut strides = Vec::with_capacity(cexpr.rank());
680            let mut current_stride = 1;
681            for perm_index in perm.iter().map(|p| cexpr.indices()[*p]).rev() {
682                strides.push(current_stride);
683                current_stride *= perm_index.index_size();
684            }
685            strides.reverse();
686            strides
687        };
688
689        let mut complex_elem_perm = Vec::with_capacity(cexpr.num_elements());
690        for i in 0..cexpr.num_elements() {
691            let mut original_coordinate: Vec<usize> = Vec::with_capacity(cexpr.rank());
692            let mut temp_i = i;
693            for d_idx in 0..cexpr.rank() {
694                original_coordinate
695                    .push((temp_i / original_strides[d_idx]) % original_dimensions[d_idx]);
696                temp_i %= original_strides[d_idx]; // Update temp_i for next dimension
697            }
698
699            // Map original coordinate components to their new positions according to `perm`.
700            // If `perm[j]` is `k`, it means the `j`-th dimension in the new order
701            // corresponds to the `k`-th dimension in the original order.
702            let mut permuted_coordinate: Vec<usize> = vec![0; cexpr.rank()];
703            for j in 0..cexpr.rank() {
704                permuted_coordinate[j] = original_coordinate[perm[j]];
705            }
706
707            // Calculate new linear index using the permuted coordinate and new strides
708            let mut new_linear_idx = 0;
709            for d_idx in 0..cexpr.rank() {
710                new_linear_idx += permuted_coordinate[d_idx] * new_strides[d_idx];
711            }
712            complex_elem_perm.push(new_linear_idx);
713        }
714
715        let ncols = shape.ncols() as u64;
716        let nrows = shape.nrows() as u64;
717
718        let num_real_elements = cexpr.num_elements() * 2;
719        let mut map = Vec::with_capacity(num_real_elements);
720
721        for real_idx in 0..(num_real_elements as u64) {
722            let complex_idx = real_idx / 2;
723            let complex_perm_idx = complex_elem_perm[complex_idx as usize] as u64;
724            let imag_offset = real_idx % 2;
725            let mat_idx = complex_perm_idx / (nrows * ncols);
726            let row_idx = (complex_perm_idx % (nrows * ncols)) / ncols;
727            let col_idx = complex_perm_idx % ncols;
728            map.push(
729                2 * (mat_idx * mat_stride + row_idx * row_stride + col_idx * col_stride)
730                    + imag_offset,
731            )
732        }
733
734        map
735    }
736}