Repository: HQSquantumsimulations/qoqo (language: python)

Issue:
qoqo Circuit from_ methods are not classmethods
from_json and from_bincode in qoqo.Circuit are not implemeted as classmethods.

We need to change that so we can call `Circuit.from_json(...)` directly


Discussion / hints:

Fix patch:
diff --git a/qoqo/src/circuit.rs b/qoqo/src/circuit.rs
index 3acefa98..d811a83f 100644
--- a/qoqo/src/circuit.rs
+++ b/qoqo/src/circuit.rs
@@ -15,6 +15,7 @@ use bincode::{deserialize, serialize};
 use pyo3::exceptions::{PyIndexError, PyRuntimeError, PyTypeError, PyValueError};
 use pyo3::prelude::*;
 use pyo3::types::PyByteArray;
+use pyo3::types::PyType;
 use pyo3::{PyIterProtocol, PyMappingProtocol, PyNumberProtocol, PyObjectProtocol};
 use roqoqo::prelude::*;
 use roqoqo::{Circuit, OperationIterator, ROQOQO_VERSION};
@@ -216,6 +217,8 @@ impl CircuitWrapper {
         Ok(b)
     }
 
+    #[allow(unused_variables)]
+    #[classmethod]
     /// Convert the bincode representation of the Circuit to a Circuit using the [bincode] crate.
     ///
     /// Args:
@@ -227,12 +230,12 @@ impl CircuitWrapper {
     /// Raises:
     ///     TypeError: Input cannot be converted to byte array.
     ///     ValueError: Input cannot be deserialized to Circuit.
-    pub fn from_bincode(&self, input: &PyAny) -> PyResult<CircuitWrapper> {
+    pub fn from_bincode(cls: &PyType, input: &PyAny) -> PyResult<Self> {
         let bytes = input
             .extract::<Vec<u8>>()
             .map_err(|_| PyTypeError::new_err("Input cannot be converted to byte array"))?;
 
-        Ok(CircuitWrapper {
+        Ok(Self {
             internal: deserialize(&bytes[..])
                 .map_err(|_| PyValueError::new_err("Input cannot be deserialized to Circuit"))?,
         })
@@ -251,6 +254,8 @@ impl CircuitWrapper {
         Ok(serialized)
     }
 
+    #[allow(unused_variables)]
+    #[classmethod]
     /// Convert the json representation of a Circuit to a Circuit.
     ///
     /// Args:
@@ -261,9 +266,9 @@ impl CircuitWrapper {
     ///
     /// Raises:
     ///     ValueError: Input cannot be deserialized to Circuit.
-    fn from_json(&self, input: &str) -> PyResult<CircuitWrapper> {
-        Ok(CircuitWrapper {
-            internal: serde_json::from_str(input)
+    pub fn from_json(cls: &PyType, json_string: &str) -> PyResult<Self> {
+        Ok(Self {
+            internal: serde_json::from_str(json_string)
                 .map_err(|_| PyValueError::new_err("Input cannot be deserialized to Circuit"))?,
         })
     }


Test patch:
diff --git a/qoqo/tests/integration/circuit.rs b/qoqo/tests/integration/circuit.rs
index df701d26..55bbd49f 100644
--- a/qoqo/tests/integration/circuit.rs
+++ b/qoqo/tests/integration/circuit.rs
@@ -282,9 +282,13 @@ fn test_to_from_bincode() {
         let circuit = new_circuit(py);
         populate_circuit_rotatex(py, circuit, 0, 3);
 
+        // testing 'to_bincode' and 'from_bincode' functions
         let serialised = circuit.call_method0("to_bincode").unwrap();
         let new = new_circuit(py);
         let deserialised = new.call_method1("from_bincode", (serialised,)).unwrap();
+        let comparison =
+            bool::extract(deserialised.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
+        assert!(comparison);
 
         let deserialised_error =
             new.call_method1("from_bincode", (bincode::serialize("fails").unwrap(),));
@@ -300,9 +304,15 @@ fn test_to_from_bincode() {
         let serialised_error = serialised.call_method0("to_bincode");
         assert!(serialised_error.is_err());
 
+        // testing that 'from_bincode' can be called directly on a circuit (python classmethod)
+        let circuit_type = py.get_type::<CircuitWrapper>();
+        let deserialised_py = circuit_type
+            .call_method1("from_bincode", (serialised,))
+            .unwrap();
+
         let comparison =
-            bool::extract(deserialised.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
-        assert!(comparison)
+            bool::extract(deserialised_py.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
+        assert!(comparison);
     })
 }
 
@@ -353,10 +363,15 @@ fn test_to_from_json() {
         let circuit = new_circuit(py);
         populate_circuit_rotatex(py, circuit, 0, 3);
 
+        // testing 'from_json' and 'to_json' functions
         let serialised = circuit.call_method0("to_json").unwrap();
         let new = new_circuit(py);
         let deserialised = new.call_method1("from_json", (serialised,)).unwrap();
 
+        let comparison =
+            bool::extract(deserialised.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
+        assert!(comparison);
+
         let deserialised_error =
             new.call_method1("from_json", (serde_json::to_string("fails").unwrap(),));
         assert!(deserialised_error.is_err());
@@ -371,9 +386,15 @@ fn test_to_from_json() {
         let deserialised_error = deserialised.call_method0("from_json");
         assert!(deserialised_error.is_err());
 
+        // testing that 'from_json' can be called directly on a circuit (python classmethod)
+        let circuit_type = py.get_type::<CircuitWrapper>();
+        let deserialised_py = circuit_type
+            .call_method1("from_json", (serialised,))
+            .unwrap();
+
         let comparison =
-            bool::extract(deserialised.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
-        assert!(comparison)
+            bool::extract(deserialised_py.call_method1("__eq__", (circuit,)).unwrap()).unwrap();
+        assert!(comparison);
     })
 }