qoqo/
lib.rs

1// Copyright © 2021-2024 HQS Quantum Simulations GmbH. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the
9// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
10// express or implied. See the License for the specific language governing permissions and
11// limitations under the License.
12
13#![deny(missing_docs)]
14#![deny(rustdoc::missing_crate_level_docs)]
15#![deny(missing_debug_implementations)]
16
17//! Qoqo quantum computing toolkit
18//!
19//! Quantum Operation Quantum Operation
20//! Yes we use [reduplication](https://en.wikipedia.org/wiki/Reduplication)
21
22use pyo3::exceptions::PyValueError;
23use pyo3::prelude::*;
24
25use pyo3::types::PyDict;
26
27use pyo3::{wrap_pyfunction, wrap_pymodule};
28
29pub mod operations;
30
31pub mod measurements;
32
33pub mod devices;
34
35mod circuit;
36pub use circuit::{convert_into_circuit, CircuitWrapper, OperationIteratorWrapper};
37
38mod quantum_program;
39pub use quantum_program::{convert_into_quantum_program, QuantumProgramWrapper};
40
41pub mod noise_models;
42
43#[cfg(feature = "circuitdag")]
44mod circuitdag;
45#[cfg(feature = "circuitdag")]
46pub use circuitdag::{convert_into_circuitdag, CircuitDagWrapper};
47
48/// qoqo version information, used for qoqo import/export checks
49pub const QOQO_VERSION: &str = env!("CARGO_PKG_VERSION");
50
51use roqoqo::{operations::AVAILABLE_GATES_HQSLANG, RoqoqoBackendError, RoqoqoError};
52use struqture::spins::PlusMinusLindbladNoiseOperator;
53use struqture_py::spins::PlusMinusLindbladNoiseOperatorWrapper;
54use thiserror::Error;
55
56/// Errors that can occur in qoqo.
57#[derive(Error, Debug, PartialEq)]
58pub enum QoqoError {
59    /// Error an Operation cannot be extracted from PyAny object passed from python.
60    #[error("Converting PyAny to Operation not possible")]
61    ConversionError,
62    /// Error a Circuit cannot be extracted from PyAny object passed from python.
63    #[error("Cannot extract roqoqo object from python object")]
64    CannotExtractObject,
65    /// Error for version mismatch between separately compiled packages.
66    ///
67    /// Error when trying to extract a roqoqo object from a PyAny python object that has been created
68    /// from a python package that has been compiled separately.
69    /// To avoid unexpected behaviour this is only allowed when qoqo and roqoqo in both packages are the same version.
70    #[error("Package versions of qoqo and roqoqo do not match versions of qoqo object passed from python")]
71    VersionMismatch,
72    /// Transparent forwarding of roqoqo errors.
73    #[error(transparent)]
74    RoqoqoError(#[from] RoqoqoError),
75}
76
77/// Errors that can occur in qoqo backends.
78#[derive(Error, Debug, PartialEq)]
79pub enum QoqoBackendError {
80    /// Error a Circuit cannot be extracted from PyAny object passed from python.
81    #[error("Cannot extract rust object from python object")]
82    CannotExtractObject,
83    /// Error for version mismatch between separately compiled packages.
84    ///
85    /// Error when trying to extract a Backend or Device from a PyAny python object that has been created
86    /// from a python package that has been compiled separately.
87    /// To avoid unexpected behaviour this is only allowed when qoqo and roqoqo in both packages are the same version.
88    #[error("Package versions of qoqo backend and roqoqo backend do not match versions of qoqo object passed from python")]
89    VersionMismatch,
90    /// Transparent forwarding of roqoqo errors.
91    #[error(transparent)]
92    RoqoqoBackendError(#[from] RoqoqoBackendError),
93}
94
95/// List of hqslang of all available gates
96#[pyfunction]
97pub fn available_gates_hqslang() -> Vec<String> {
98    AVAILABLE_GATES_HQSLANG
99        .iter()
100        .map(|&s| String::from(s))
101        .collect::<Vec<String>>()
102}
103
104use std::sync::OnceLock;
105/// Struqture version installed locally in user's environment
106pub static STRUQTURE_VERSION: OnceLock<String> = OnceLock::new();
107/// Struqture PlusMinusLindbladNoiseOperator object from local struqture package
108pub static STRUQTURE_OPERATOR: OnceLock<Py<PyAny>> = OnceLock::new();
109
110pub(crate) fn get_operator<'py>(
111    py: Python<'py>,
112    noise: &PlusMinusLindbladNoiseOperator,
113) -> PyResult<Bound<'py, PyAny>> {
114    let version: &String = STRUQTURE_VERSION
115        .get()
116        .expect("Could not get STRUQTURE_VERSION");
117    if version.starts_with('1') {
118        let class: &Py<PyAny> = STRUQTURE_OPERATOR
119            .get()
120            .expect("No struqture operator found");
121        let json_string = serde_json::to_string(
122            &noise
123                .to_struqture_1()
124                .expect("Could not convert struqture 2 object to struqture 1"),
125        )
126        .expect("Could not serialize to JSON");
127        Ok(class
128            .bind(py)
129            .call_method1("from_json", (json_string.as_str(),))
130            .expect("Could not create struqture 1.x PlusMinusLindbladNoiseOperator from JSON")
131            .to_owned())
132    } else {
133        let pmlno = PlusMinusLindbladNoiseOperatorWrapper {
134            internal: noise.clone(),
135        };
136        pmlno
137            .into_pyobject(py)
138            .map_err(|_| {
139                PyValueError::new_err(
140                    "Could not convert PlusMinusLindbladNoiseOperator into a python object.",
141                )
142            })
143            .map(|bound| bound.as_any().to_owned())
144    }
145}
146
147/// Quantum Operation Quantum Operation (qoqo)
148///
149/// Yes, we use reduplication.
150///
151/// qoqo is the HQS python package to represent quantum circuits.
152///
153/// .. autosummary::
154///     :toctree: generated/
155///
156///     Circuit
157///     CircuitDag
158///     QuantumProgram
159///     operations
160///     measurements
161///     devices
162///     noise_models
163///     available_gates_hqslang
164///
165#[pymodule]
166fn qoqo(_py: Python, module: &Bound<PyModule>) -> PyResult<()> {
167    let binding = PyModule::import(_py, "importlib.metadata")
168        .expect("Could not import importlib.metadata module for function")
169        .getattr("version")
170        .expect("Could not get version function of importlib.metadata")
171        .call1(("struqture_py",))
172        .expect("Could not get version attribute of struqture_py")
173        .unbind();
174    let version: String = binding
175        .extract(_py)
176        .expect("Could not extract version string");
177    STRUQTURE_VERSION.get_or_init(|| version);
178    let operator: Py<PyAny> = _py
179        .import("struqture_py.spins")
180        .unwrap_or_else(|_| {
181            panic!("Could not import struqture_py.spins module for get_noise_operator")
182        })
183        .getattr("PlusMinusLindbladNoiseOperator")
184        .expect("Could not get PlusMinusLindbladOperator class")
185        .unbind();
186    STRUQTURE_OPERATOR.get_or_init(|| operator);
187
188    module.add_class::<CircuitWrapper>()?;
189    module.add_class::<QuantumProgramWrapper>()?;
190    #[cfg(feature = "circuitdag")]
191    module.add_class::<CircuitDagWrapper>()?;
192    module.add_function(wrap_pyfunction!(available_gates_hqslang, module)?)?;
193    let wrapper = wrap_pymodule!(operations::operations);
194    module.add_wrapped(wrapper)?;
195    let wrapper2 = wrap_pymodule!(measurements::measurements);
196    module.add_wrapped(wrapper2)?;
197    let wrapper3 = wrap_pymodule!(devices::devices);
198    module.add_wrapped(wrapper3)?;
199    let wrapper4 = wrap_pymodule!(noise_models::noise_models);
200    module.add_wrapped(wrapper4)?;
201    // Adding nice imports corresponding to maturin example
202    let system = PyModule::import(_py, "sys")?;
203    let binding = system.getattr("modules")?;
204    let system_modules: &Bound<PyDict> = binding.downcast()?;
205    system_modules.set_item("qoqo.operations", module.getattr("operations")?)?;
206    system_modules.set_item("qoqo.measurements", module.getattr("measurements")?)?;
207    system_modules.set_item("qoqo.devices", module.getattr("devices")?)?;
208    system_modules.set_item("qoqo.noise_models", module.getattr("noise_models")?)?;
209
210    Ok(())
211}