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
26impl UnitaryExpression {
49 pub fn new<T: AsRef<str>>(input: T) -> Self {
50 TensorExpression::new(input).try_into().unwrap()
51 }
52
53 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 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 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 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 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 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 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 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 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 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 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 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 ("U", 1, 3) => Some("U"),
440 ("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 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 #[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 #[new]
636 fn new(expr: String) -> Self {
637 Self {
638 expr: UnitaryExpression::new(expr),
639 }
640 }
641
642 #[staticmethod]
649 fn identity(name: String, radices: Vec<usize>) -> Self {
650 Self {
651 expr: UnitaryExpression::identity(name, radices),
652 }
653 }
654
655 #[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 #[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 fn num_params(&self) -> usize {
724 self.expr.num_params()
725 }
726
727 fn name(&self) -> String {
729 self.expr.name().to_string()
730 }
731
732 fn radices(&self) -> Vec<Radix> {
734 self.expr.radices().to_vec()
735 }
736
737 fn dimension(&self) -> usize {
739 self.expr.dimension()
740 }
741
742 fn qasm_name(&self) -> Option<&'static str> {
746 self.expr.qasm_name()
747 }
748
749 fn transpose(&mut self) {
751 self.expr.transpose();
752 }
753
754 fn dagger(&mut self) {
756 self.expr.dagger();
757 }
758
759 fn otimes(&self, other: &PyUnitaryExpression) -> Self {
766 Self {
767 expr: self.expr.otimes(&other.expr),
768 }
769 }
770
771 fn dot(&self, other: &PyUnitaryExpression) -> Self {
778 Self {
779 expr: self.expr.dot(&other.expr),
780 }
781 }
782
783 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 __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 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 fn register(parent_module: &Bound<'_, PyModule>) -> PyResult<()> {
876 parent_module.add_class::<PyUnitaryExpression>()?;
877 Ok(())
878 }
879 inventory::submit!(PyExpressionRegistrar { func: register });
880}