Skip to main content

qudit_expr/expressions/
unitary.rs

1use std::collections::{BTreeSet, HashMap};
2use std::ops::{Deref, DerefMut};
3
4use faer::Mat;
5use qudit_core::ComplexScalar;
6use qudit_core::QuditSystem;
7use qudit_core::Radices;
8use qudit_core::UnitaryMatrix;
9use serde::{Deserialize, Serialize};
10
11use crate::{ComplexExpression, UnitarySystemExpression};
12use crate::{
13    GenerationShape, TensorExpression,
14    expressions::JittableExpression,
15    index::{IndexDirection, TensorIndex},
16};
17
18use super::NamedExpression;
19
20#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize)]
21pub struct UnitaryExpression {
22    inner: NamedExpression,
23    radices: Radices,
24}
25
26// pub trait ExpressionContainer {
27//     pub fn elements(&self) -> &[ComplexExpression];
28//     pub fn elements_mut(&mut self) -> &mut [ComplexExpression];
29
30//     pub fn conjugate(&mut self) {
31//         todo!()
32//     }
33// }
34
35// pub trait MatrixExpression: ExpressionContainer {
36//     pub fn nrows(&self) -> usize;
37//     pub fn ncols(&self) -> usize;
38
39//     pub fn dagger(&mut self) {
40//         todo!()
41//     }
42
43//     pub fn transpose(&mut self) {
44//         todo!()
45//     }
46// }
47
48impl UnitaryExpression {
49    pub fn new<T: AsRef<str>>(input: T) -> Self {
50        TensorExpression::new(input).try_into().unwrap()
51    }
52
53    // TODO: Change ToRadices by implementing appropriate Froms
54    pub fn identity<S: Into<String>, T: Into<Radices>>(name: S, radices: T) -> Self {
55        let radices = radices.into();
56        let dim = radices.dimension();
57        let mut body = Vec::with_capacity(dim * dim);
58        for i in 0..dim {
59            for j in 0..dim {
60                if i == j {
61                    body.push(ComplexExpression::one());
62                } else {
63                    body.push(ComplexExpression::zero());
64                }
65            }
66        }
67        let inner = NamedExpression::new(name, vec![], body);
68
69        UnitaryExpression { inner, radices }
70    }
71
72    pub fn set_radices(&mut self, new_radices: Radices) {
73        assert_eq!(self.radices.dimension(), new_radices.dimension());
74        self.radices = new_radices;
75    }
76
77    pub fn transpose(&mut self) {
78        let dim = self.dimension();
79        for i in 0..dim {
80            for j in (i + 1)..dim {
81                let (head, tail) = self.split_at_mut(j * dim + i);
82                std::mem::swap(&mut head[i * dim + j], &mut tail[0]);
83            }
84        }
85    }
86
87    pub fn dagger(&mut self) {
88        self.conjugate();
89        self.transpose();
90    }
91
92    pub fn classically_multiplex(
93        expressions: &[&UnitaryExpression],
94        new_dim_radices: &[usize],
95    ) -> UnitarySystemExpression {
96        let new_dim = new_dim_radices.iter().product();
97        assert!(
98            expressions.len() == new_dim,
99            "Cannot multiplex a number of expressions not equal to the length of new dimension."
100        );
101        assert!(!expressions.is_empty());
102        for expression in expressions {
103            assert_eq!(
104                expression.radices(),
105                expressions[0].radices(),
106                "All expressions must have equal radices."
107            );
108        }
109
110        // construct larger tensor
111        let mut new_expressions =
112            Vec::with_capacity(expressions[0].dimension() * expressions[0].dimension() * new_dim);
113        let mut var_map_number = 0;
114        let mut new_variables = Vec::new();
115        for i in 0..new_dim {
116            // TODO: double clone
117            let mut renamed_expression = expressions[i].clone();
118            renamed_expression.alpha_rename(Some(var_map_number));
119            new_expressions.extend(renamed_expression.elements().iter().cloned());
120            for _ in 0..expressions[i].variables().len() {
121                new_variables.push(format!("alpha_{}", var_map_number));
122                var_map_number += 1;
123            }
124        }
125
126        let new_indices = new_dim_radices
127            .iter()
128            .map(|r| (IndexDirection::Batch, (*r)))
129            .chain(
130                expressions[0]
131                    .radices()
132                    .iter()
133                    .map(|r| (IndexDirection::Output, usize::from(*r))),
134            )
135            .chain(
136                expressions[0]
137                    .radices()
138                    .iter()
139                    .map(|r| (IndexDirection::Input, usize::from(*r))),
140            )
141            .enumerate()
142            .map(|(id, (dir, size))| TensorIndex::new(dir, id, size))
143            .collect();
144
145        let name = {
146            // if all expressions have same name: multiplex_name
147            // else: multiplex_name_name_...
148            if expressions
149                .iter()
150                .all(|e| e.name() == expressions[0].name())
151            {
152                format!("Multiplexed_{}", expressions[0].name())
153            } else {
154                "Multiplexed_".to_string()
155                    + &expressions
156                        .iter()
157                        .map(|e| e.name())
158                        .collect::<Vec<_>>()
159                        .join("_")
160            }
161        };
162
163        let inner = NamedExpression::new(name, new_variables, new_expressions);
164        TensorExpression::from_raw(new_indices, inner)
165            .try_into()
166            .unwrap()
167    }
168
169    // TODO: better API for user-facing thoughts
170    pub fn classically_control(
171        &self,
172        positions: &[usize],
173        new_dim_radices: &[usize],
174    ) -> UnitarySystemExpression {
175        let new_dim = new_dim_radices.iter().product();
176        assert!(
177            positions.len() <= new_dim,
178            "Cannot place unitary in more locations than length of new dimension."
179        );
180
181        // Ensure positions are unique
182        let mut sorted_positions = positions.to_vec();
183        sorted_positions.sort_unstable();
184        assert!(
185            sorted_positions.iter().collect::<BTreeSet<_>>().len() == sorted_positions.len(),
186            "Positions must be unique"
187        );
188
189        // Construct identity expression
190        let mut identity = Vec::with_capacity(self.dimension() * self.dimension());
191        for i in 0..self.dimension() {
192            for j in 0..self.dimension() {
193                if i == j {
194                    identity.push(ComplexExpression::one());
195                } else {
196                    identity.push(ComplexExpression::zero());
197                }
198            }
199        }
200
201        // construct larger tensor
202        let mut expressions = Vec::with_capacity(self.dimension() * self.dimension() * new_dim);
203        for i in 0..new_dim {
204            if positions.contains(&i) {
205                expressions.extend(self.elements().iter().cloned());
206            } else {
207                expressions.extend(identity.iter().cloned());
208            }
209        }
210
211        let new_indices = new_dim_radices
212            .iter()
213            .map(|r| (IndexDirection::Batch, (*r)))
214            .chain(
215                self.radices
216                    .iter()
217                    .map(|r| (IndexDirection::Output, usize::from(*r))),
218            )
219            .chain(
220                self.radices
221                    .iter()
222                    .map(|r| (IndexDirection::Input, usize::from(*r))),
223            )
224            .enumerate()
225            .map(|(id, (dir, size))| TensorIndex::new(dir, id, size))
226            .collect();
227
228        let inner = NamedExpression::new(
229            format!("Stacked_{}", self.name()),
230            self.variables().to_owned(),
231            expressions,
232        );
233        TensorExpression::from_raw(new_indices, inner)
234            .try_into()
235            .unwrap()
236    }
237
238    pub fn embed(
239        &mut self,
240        sub_matrix: UnitaryExpression,
241        top_left_row_idx: usize,
242        top_left_col_idx: usize,
243    ) {
244        let nrows = self.dimension();
245        let ncols = self.dimension();
246        let sub_nrows = sub_matrix.dimension();
247        let sub_ncols = sub_matrix.dimension();
248
249        if top_left_row_idx + sub_nrows > nrows || top_left_col_idx + sub_ncols > ncols {
250            panic!("Embedding matrix is too large");
251        }
252
253        for i in 0..sub_nrows {
254            for j in 0..sub_ncols {
255                // TODO: remove clone by building into_iter for sub_matrix
256                let sub_expr = sub_matrix[i * sub_ncols + j].clone();
257                self[(top_left_row_idx + i) * ncols + (top_left_col_idx + j)] = sub_expr;
258            }
259        }
260
261        // Update variables: collect all unique variables from self and sub_matrix
262        let mut new_variables: Vec<String> = self.variables().to_vec();
263        for var in sub_matrix.variables().iter() {
264            if !new_variables.contains(var) {
265                new_variables.push(var.clone());
266            }
267        }
268        self.set_variables(new_variables);
269    }
270
271    pub fn otimes<U: AsRef<UnitaryExpression>>(&self, other: U) -> Self {
272        let other = other.as_ref();
273        let mut variables = Vec::new();
274        let mut var_map_self = HashMap::new();
275        let mut i = 0;
276        for var in self.variables().iter() {
277            variables.push(format!("x{}", i));
278            var_map_self.insert(var.clone(), format!("x{}", i));
279            i += 1;
280        }
281        let mut var_map_other = HashMap::new();
282        for var in other.variables().iter() {
283            variables.push(format!("x{}", i));
284            var_map_other.insert(var.clone(), format!("x{}", i));
285            i += 1;
286        }
287
288        let lhs_nrows = self.dimension();
289        let lhs_ncols = self.dimension();
290        let rhs_nrows = other.dimension();
291        let rhs_ncols = other.dimension();
292
293        let mut out_body = Vec::with_capacity(lhs_nrows * lhs_ncols * rhs_ncols * rhs_nrows);
294
295        for i in 0..lhs_nrows {
296            for j in 0..rhs_nrows {
297                for k in 0..lhs_ncols {
298                    for l in 0..rhs_ncols {
299                        out_body.push(
300                            self[i * lhs_ncols + k].map_var_names(&var_map_self)
301                                * other[j * rhs_ncols + l].map_var_names(&var_map_other),
302                        );
303                    }
304                }
305            }
306        }
307
308        let inner = NamedExpression::new(
309            format!("{} ⊗ {}", self.name(), other.name()),
310            variables,
311            out_body,
312        );
313
314        UnitaryExpression {
315            inner,
316            radices: self.radices.concat(&other.radices),
317        }
318    }
319
320    pub fn dot<U: AsRef<UnitaryExpression>>(&self, other: U) -> Self {
321        let other = other.as_ref();
322        let lhs_nrows = self.dimension();
323        let lhs_ncols = self.dimension();
324        let rhs_nrows = other.dimension();
325        let rhs_ncols = other.dimension();
326
327        if lhs_ncols != rhs_nrows {
328            panic!(
329                "Matrix dimensions do not match for dot product: {} != {}",
330                lhs_ncols, rhs_nrows
331            );
332        }
333
334        let mut variables = Vec::new();
335        let mut var_map_self = HashMap::new();
336        let mut i = 0;
337        for var in self.variables().iter() {
338            variables.push(format!("x{}", i));
339            var_map_self.insert(var.clone(), format!("x{}", i));
340            i += 1;
341        }
342        let mut var_map_other = HashMap::new();
343        for var in other.variables().iter() {
344            variables.push(format!("x{}", i));
345            var_map_other.insert(var.clone(), format!("x{}", i));
346            i += 1;
347        }
348
349        let mut out_body = Vec::with_capacity(lhs_nrows * rhs_ncols);
350
351        for i in 0..lhs_nrows {
352            for j in 0..rhs_ncols {
353                let mut sum = self[i * lhs_ncols].map_var_names(&var_map_self)
354                    * other[j].map_var_names(&var_map_other);
355                for k in 1..lhs_ncols {
356                    sum += self[i * lhs_ncols + k].map_var_names(&var_map_self)
357                        * other[k * rhs_ncols + j].map_var_names(&var_map_other);
358                }
359                out_body.push(sum);
360            }
361        }
362
363        let inner = NamedExpression::new(
364            format!("{} ⋅ {}", self.name(), other.name()),
365            variables,
366            out_body,
367        );
368
369        UnitaryExpression {
370            inner,
371            radices: self.radices.clone(),
372        }
373    }
374
375    pub fn get_arg_map<C: ComplexScalar>(&self, args: &[C::R]) -> HashMap<&str, C::R> {
376        self.variables()
377            .iter()
378            .zip(args.iter())
379            .map(|(a, b)| (a.as_str(), *b))
380            .collect()
381    }
382
383    pub fn eval<C: ComplexScalar>(&self, args: &[C::R]) -> UnitaryMatrix<C> {
384        let arg_map = self.get_arg_map::<C>(args);
385        let dim = self.radices.dimension();
386        let mut mat = Mat::zeros(dim, dim);
387        for i in 0..dim {
388            for j in 0..dim {
389                *mat.get_mut(i, j) = self[i * self.dimension() + j].eval(&arg_map);
390            }
391        }
392        UnitaryMatrix::new(self.radices.clone(), mat)
393    }
394
395    /// Evaluates the matrix-by-vector derivative of this expression at `args`.
396    ///
397    /// Returns one matrix per parameter (in the same order as `variables()`),
398    /// where the `k`-th matrix is the elementwise partial derivative of the
399    /// unitary with respect to the `k`-th parameter.
400    pub fn eval_grad<C: ComplexScalar>(&self, args: &[C::R]) -> Vec<Mat<C>> {
401        let arg_map = self.get_arg_map::<C>(args);
402        let dim = self.radices.dimension();
403        self.variables()
404            .iter()
405            .map(|var| {
406                let mut mat = Mat::zeros(dim, dim);
407                for i in 0..dim {
408                    for j in 0..dim {
409                        *mat.get_mut(i, j) = self[i * dim + j].differentiate(var).eval(&arg_map);
410                    }
411                }
412                mat
413            })
414            .collect()
415    }
416
417    /// Returns the canonical OpenQASM 2.0 (`qelib1.inc`) gate name for this
418    /// expression, if one exists, based on its name, qudit count, and
419    /// parameter count. Returns `None` for expressions with no fixed QASM
420    /// 2.0 equivalent (e.g. arbitrary/custom unitaries).
421    pub fn qasm_name(&self) -> Option<&'static str> {
422        qasm_gate_name_lookup(self.name(), self.radices().len(), self.num_params())
423    }
424}
425
426pub(crate) fn qasm_gate_name_lookup(
427    name: &str,
428    n_qudits: usize,
429    n_params: usize,
430) -> Option<&'static str> {
431    let mut name = name;
432    while let Some(stripped) = name.strip_suffix("_subbed") {
433        name = stripped;
434    }
435    name = name.strip_prefix("Stacked_").unwrap_or(name);
436
437    match (name, n_qudits, n_params) {
438        // Built-in primitives
439        ("U", 1, 3) => Some("U"),
440        // qelib1.inc standard gates
441        ("U2", 1, 2) => Some("u2"),
442        ("U1", 1, 1) => Some("u1"),
443        ("I", 1, 0) => Some("id"),
444        ("X", 1, 0) => Some("x"),
445        ("Y", 1, 0) => Some("y"),
446        ("Z", 1, 0) => Some("z"),
447        ("H", 1, 0) => Some("h"),
448        ("S", 1, 0) => Some("s"),
449        ("T", 1, 0) => Some("t"),
450        ("SX", 1, 0) => Some("sx"),
451        ("P", 1, 1) => Some("p"),
452        ("RX", 1, 1) => Some("rx"),
453        ("RY", 1, 1) => Some("ry"),
454        ("RZ", 1, 1) => Some("rz"),
455        ("Swap", 2, 0) => Some("swap"),
456        ("RXX", 2, 1) => Some("rxx"),
457        ("RZZ", 2, 1) => Some("rzz"),
458        ("Controlled(X)", 2, 0) => Some("cx"),
459        ("Controlled(X)", 3, 0) => Some("ccx"),
460        ("Controlled(X)", 4, 0) => Some("c3x"),
461        ("Controlled(X)", 5, 0) => Some("c4x"),
462        ("Controlled(Z)", 2, 0) => Some("cz"),
463        ("Controlled(Y)", 2, 0) => Some("cy"),
464        ("Controlled(H)", 2, 0) => Some("ch"),
465        ("Controlled(Swap)", 3, 0) => Some("cswap"),
466        ("Controlled(SX)", 2, 0) => Some("csx"),
467        ("Controlled(RX)", 2, 1) => Some("crx"),
468        ("Controlled(RY)", 2, 1) => Some("cry"),
469        ("Controlled(RZ)", 2, 1) => Some("crz"),
470        ("Controlled(U1)", 2, 1) => Some("cu1"),
471        ("Controlled(P)", 2, 1) => Some("cp"),
472        ("Controlled(U)", 2, 3) => Some("cu3"),
473        ("Dagger(S)", 1, 0) => Some("sdg"),
474        ("Dagger(T)", 1, 0) => Some("tdg"),
475        ("Dagger(SX)", 1, 0) => Some("sxdg"),
476        _ => None,
477    }
478}
479
480impl JittableExpression for UnitaryExpression {
481    fn generation_shape(&self) -> GenerationShape {
482        GenerationShape::Matrix(self.radices.dimension(), self.radices.dimension())
483    }
484}
485
486impl AsRef<UnitaryExpression> for UnitaryExpression {
487    fn as_ref(&self) -> &UnitaryExpression {
488        self
489    }
490}
491
492impl AsRef<NamedExpression> for UnitaryExpression {
493    fn as_ref(&self) -> &NamedExpression {
494        &self.inner
495    }
496}
497
498impl From<UnitaryExpression> for NamedExpression {
499    fn from(value: UnitaryExpression) -> Self {
500        value.inner
501    }
502}
503
504impl Deref for UnitaryExpression {
505    type Target = NamedExpression;
506
507    fn deref(&self) -> &Self::Target {
508        &self.inner
509    }
510}
511
512impl DerefMut for UnitaryExpression {
513    fn deref_mut(&mut self) -> &mut Self::Target {
514        &mut self.inner
515    }
516}
517
518impl From<UnitaryExpression> for TensorExpression {
519    fn from(value: UnitaryExpression) -> Self {
520        let UnitaryExpression { inner, radices } = value;
521        let indices = radices
522            .iter()
523            .map(|r| (IndexDirection::Output, usize::from(*r)))
524            .chain(
525                radices
526                    .iter()
527                    .map(|r| (IndexDirection::Input, usize::from(*r))),
528            )
529            .enumerate()
530            .map(|(i, (d, r))| TensorIndex::new(d, i, r))
531            .collect();
532        TensorExpression::from_raw(indices, inner)
533    }
534}
535
536impl TryFrom<TensorExpression> for UnitaryExpression {
537    // TODO: Come up with proper error handling
538    type Error = String;
539
540    fn try_from(value: TensorExpression) -> Result<Self, Self::Error> {
541        let mut input_radices = vec![];
542        let mut output_radices = vec![];
543        for idx in value.indices() {
544            match idx.direction() {
545                IndexDirection::Input => {
546                    input_radices.push(idx.index_size());
547                }
548                IndexDirection::Output => {
549                    output_radices.push(idx.index_size());
550                }
551                _ => {
552                    return Err(String::from(
553                        "Cannot convert a tensor with non-input, non-output indices to an isometry.",
554                    ));
555                }
556            }
557        }
558
559        if input_radices != output_radices {
560            return Err(String::from(
561                "Non-square matrix tensor cannot be converted to a unitary.",
562            ));
563        }
564
565        Ok(UnitaryExpression {
566            inner: value.into(),
567            radices: input_radices.into(),
568        })
569    }
570}
571
572impl<C: ComplexScalar> From<UnitaryMatrix<C>> for UnitaryExpression {
573    fn from(value: UnitaryMatrix<C>) -> Self {
574        let mut body = vec![Vec::with_capacity(value.ncols()); value.nrows()];
575        for col in value.col_iter() {
576            for (row_id, elem) in col.iter().enumerate() {
577                body[row_id].push(ComplexExpression::from(*elem));
578            }
579        }
580        let mut flat_body = Vec::with_capacity(value.ncols() * value.nrows());
581        for row in body.into_iter() {
582            for elem in row.into_iter() {
583                flat_body.push(elem);
584            }
585        }
586        let inner = NamedExpression::new("Constant", vec![], flat_body);
587
588        UnitaryExpression {
589            inner,
590            radices: value.radices(),
591        }
592    }
593}
594
595impl QuditSystem for UnitaryExpression {
596    fn radices(&self) -> Radices {
597        self.radices.clone()
598    }
599}
600
601#[cfg(feature = "python")]
602mod python {
603    use super::*;
604    use crate::python::PyExpressionRegistrar;
605    use numpy::PyArray2;
606    use numpy::PyArray3;
607    use numpy::PyArrayMethods;
608    use numpy::ndarray::{ArrayViewMut2, ArrayViewMut3};
609    use pyo3::{
610        PyResult,
611        exceptions::PyTypeError,
612        prelude::*,
613        types::{PyBytes, PyTuple},
614    };
615    use pyo3_stub_gen::derive::*;
616    use pyo3_stub_gen::impl_stub_type;
617    use qudit_core::Radix;
618    use qudit_core::c64;
619
620    /// A symbolic, parameterized expression representing a unitary matrix.
621    #[gen_stub_pyclass]
622    #[pyclass(name = "UnitaryExpression", module = "openqudit.expressions")]
623    pub struct PyUnitaryExpression {
624        expr: UnitaryExpression,
625    }
626
627    #[gen_stub_pymethods]
628    #[pymethods]
629    impl PyUnitaryExpression {
630        /// Parses a unitary expression from its string representation.
631        ///
632        /// # Arguments
633        ///
634        /// * `expr` - The textual definition of the unitary expression.
635        #[new]
636        fn new(expr: String) -> Self {
637            Self {
638                expr: UnitaryExpression::new(expr),
639            }
640        }
641
642        /// Constructs an identity unitary expression over the given radices.
643        ///
644        /// # Arguments
645        ///
646        /// * `name` - The name to assign to the resulting expression.
647        /// * `radices` - The radix of each qudit in the system.
648        #[staticmethod]
649        fn identity(name: String, radices: Vec<usize>) -> Self {
650            Self {
651                expr: UnitaryExpression::identity(name, radices),
652            }
653        }
654
655        /// Evaluates this expression at the given parameter values and returns
656        /// the resulting unitary matrix as a NumPy array.
657        ///
658        /// # Arguments
659        ///
660        /// * `args` - The real-valued parameters to substitute into the expression,
661        ///   in the same order as `variables()`.
662        #[pyo3(signature = (*args))]
663        fn __call__<'py>(&self, args: &Bound<'py, PyTuple>) -> PyResult<Bound<'py, PyArray2<c64>>> {
664            let py = args.py();
665            let args: Vec<f64> = args.extract()?;
666            let unitary = self.expr.eval(&args);
667            let py_array: Bound<'py, PyArray2<c64>> =
668                PyArray2::zeros(py, (unitary.dimension(), unitary.dimension()), false);
669
670            {
671                let mut readwrite = py_array.readwrite();
672                let mut py_array_view: ArrayViewMut2<c64> = readwrite.as_array_mut();
673
674                for (j, col) in unitary.col_iter().enumerate() {
675                    for (i, val) in col.iter().enumerate() {
676                        py_array_view[[i, j]] = *val;
677                    }
678                }
679            }
680
681            Ok(py_array)
682        }
683
684        /// Evaluates the matrix-by-vector derivative of this expression at the
685        /// given parameter values.
686        ///
687        /// Returns a NumPy array of shape `(num_params, dim, dim)`, where the
688        /// `k`-th entry along the first axis is the elementwise partial
689        /// derivative of the unitary matrix with respect to the `k`-th
690        /// parameter, in the same order as `variables()`.
691        ///
692        /// # Arguments
693        ///
694        /// * `args` - The real-valued parameters to substitute into the expression,
695        ///   in the same order as `variables()`.
696        #[pyo3(signature = (*args))]
697        fn gradient<'py>(&self, args: &Bound<'py, PyTuple>) -> PyResult<Bound<'py, PyArray3<c64>>> {
698            let py = args.py();
699            let args: Vec<f64> = args.extract()?;
700            let dim = self.expr.dimension();
701            let num_params = self.expr.num_params();
702            let grad = self.expr.eval_grad(&args);
703            let py_array: Bound<'py, PyArray3<c64>> =
704                PyArray3::zeros(py, (num_params, dim, dim), false);
705
706            {
707                let mut readwrite = py_array.readwrite();
708                let mut py_array_view: ArrayViewMut3<c64> = readwrite.as_array_mut();
709
710                for (p, mat) in grad.iter().enumerate() {
711                    for (j, col) in mat.col_iter().enumerate() {
712                        for (i, val) in col.iter().enumerate() {
713                            py_array_view[[p, i, j]] = *val;
714                        }
715                    }
716                }
717            }
718
719            Ok(py_array)
720        }
721
722        /// Returns the number of free (unbound) parameters in this expression.
723        fn num_params(&self) -> usize {
724            self.expr.num_params()
725        }
726
727        /// Returns the name assigned to this expression.
728        fn name(&self) -> String {
729            self.expr.name().to_string()
730        }
731
732        /// Returns the radix of each qudit that this unitary acts on.
733        fn radices(&self) -> Vec<Radix> {
734            self.expr.radices().to_vec()
735        }
736
737        /// Returns the total Hilbert space dimension of the underlying qudit system.
738        fn dimension(&self) -> usize {
739            self.expr.dimension()
740        }
741
742        /// Returns the canonical OpenQASM 2.0 (`qelib1.inc`) gate name for this
743        /// expression (e.g. `"rz"`, `"cx"`), or `None` if it has no fixed QASM
744        /// 2.0 equivalent, such as an arbitrary or custom unitary.
745        fn qasm_name(&self) -> Option<&'static str> {
746            self.expr.qasm_name()
747        }
748
749        /// Transposes this unitary expression in place.
750        fn transpose(&mut self) {
751            self.expr.transpose();
752        }
753
754        /// Conjugate-transposes (Hermitian adjoint) this unitary expression in place.
755        fn dagger(&mut self) {
756            self.expr.dagger();
757        }
758
759        /// Computes the tensor (Kronecker) product of this expression with `other`,
760        /// returning a new expression over the combined qudit system.
761        ///
762        /// # Arguments
763        ///
764        /// * `other` - The unitary expression to tensor with this one.
765        fn otimes(&self, other: &PyUnitaryExpression) -> Self {
766            Self {
767                expr: self.expr.otimes(&other.expr),
768            }
769        }
770
771        /// Computes the matrix product of this expression with `other`,
772        /// returning a new expression.
773        ///
774        /// # Arguments
775        ///
776        /// * `other` - The unitary expression to multiply with this one.
777        fn dot(&self, other: &PyUnitaryExpression) -> Self {
778            Self {
779                expr: self.expr.dot(&other.expr),
780            }
781        }
782
783        /// Embeds `sub_matrix` into this expression's matrix in place, placing its
784        /// top-left corner at the given row and column index.
785        ///
786        /// # Arguments
787        ///
788        /// * `sub_matrix` - The smaller unitary expression to embed.
789        /// * `top_left_row_idx` - Row index at which to place the sub-matrix.
790        /// * `top_left_col_idx` - Column index at which to place the sub-matrix.
791        fn embed(
792            &mut self,
793            sub_matrix: &PyUnitaryExpression,
794            top_left_row_idx: usize,
795            top_left_col_idx: usize,
796        ) {
797            self.expr
798                .embed(sub_matrix.expr.clone(), top_left_row_idx, top_left_col_idx);
799        }
800
801        // fn classically_control(&self, positions: Vec<usize>, new_dim_radices: Vec<usize>) -> crate::python::tensor::PyTensorExpression {
802        //     let result = self.expr.classically_control(&positions, &new_dim_radices);
803        //     result.into()
804        // }
805
806        fn __repr__(&self) -> String {
807            format!(
808                "UnitaryExpression(name='{}', radices={:?}, params={})",
809                self.expr.name(),
810                self.expr.radices().to_vec(),
811                self.expr.num_params()
812            )
813        }
814
815        fn __matmul__(&self, other: UnitaryExpression) -> PyUnitaryExpression {
816            Self {
817                expr: self.expr.dot(&other),
818            }
819        }
820
821        pub fn __setstate__(&mut self, state: &Bound<'_, PyBytes>) -> PyResult<()> {
822            self.expr = postcard::from_bytes(state.as_bytes())
823                .map_err(|e| PyTypeError::new_err(format!("Failed to deserialize circuit: {e}")))?;
824            Ok(())
825        }
826
827        pub fn __getstate__<'py>(&self, py: Python<'py>) -> PyResult<Bound<'py, PyBytes>> {
828            let bytes: Vec<u8> = postcard::to_allocvec(&self.expr)
829                .map_err(|e| PyTypeError::new_err(format!("Failed to serialize circuit: {e}")))?;
830            Ok(PyBytes::new(py, &bytes))
831        }
832
833        pub fn __getnewargs__(&self) -> PyResult<(String,)> {
834            // Dummy arg: __setstate__ fully restores the expression, so we just
835            // need any valid value for the expr argument of __new__.
836            Ok(("I<2>(){[[1,0,],[0,1,],]}".to_string(),))
837        }
838    }
839
840    impl From<UnitaryExpression> for PyUnitaryExpression {
841        fn from(value: UnitaryExpression) -> Self {
842            PyUnitaryExpression { expr: value }
843        }
844    }
845
846    impl From<PyUnitaryExpression> for UnitaryExpression {
847        fn from(value: PyUnitaryExpression) -> Self {
848            value.expr
849        }
850    }
851
852    impl_stub_type!(UnitaryExpression = PyUnitaryExpression);
853
854    impl<'py> IntoPyObject<'py> for UnitaryExpression {
855        type Target = <PyUnitaryExpression as IntoPyObject<'py>>::Target;
856        type Output = Bound<'py, Self::Target>;
857        type Error = PyErr;
858
859        fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
860            let py_expr = PyUnitaryExpression::from(self);
861            Bound::new(py, py_expr)
862        }
863    }
864
865    impl<'a, 'py> FromPyObject<'a, 'py> for UnitaryExpression {
866        type Error = PyErr;
867
868        fn extract(ob: Borrowed<'a, 'py, PyAny>) -> PyResult<Self> {
869            let py_expr: PyRef<PyUnitaryExpression> = ob.extract()?;
870            Ok(py_expr.expr.clone())
871        }
872    }
873
874    /// Registers the UnitaryExpression class with the Python module.
875    fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
876        parent_module.add_class::<PyUnitaryExpression>()?;
877        Ok(())
878    }
879    inventory::submit!(PyExpressionRegistrar { func: register });
880}