Repository: rigetti/qcs-sdk-rust (language: python)

Issue:
Implement `conjugate_pauli_by_clifford` and `generate_randomized_benchmarking_sequence`
To support https://github.com/rigetti/pyquil/issues/1497

From https://github.com/rigetti/pyquil/blob/master/pyquil/api/_compiler_client.py#L207-L240 these are relatively straightforward calls to `quilc`.


Discussion / hints:

Fix patch:
diff --git a/crates/lib/src/compiler/quilc.rs b/crates/lib/src/compiler/quilc.rs
index 6f62a4c7c..71ca1822f 100644
--- a/crates/lib/src/compiler/quilc.rs
+++ b/crates/lib/src/compiler/quilc.rs
@@ -126,6 +126,142 @@ pub fn get_version_info(client: &Qcs) -> Result<String, Error> {
     }
 }
 
+/// Pauli Term
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd)]
+#[serde(tag = "_type")]
+pub struct PauliTerm {
+    /// Qubit indices onto which the factors of the Pauli term are applied.
+    pub indices: Vec<u64>,
+
+    /// Ordered factors of the Pauli term.
+    pub symbols: Vec<String>,
+}
+
+/// Request to conjugate a Pauli Term by a Clifford element.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd)]
+#[serde(tag = "_type")]
+pub struct ConjugateByCliffordRequest {
+    /// Pauli Term to conjugate.
+    pub pauli: PauliTerm,
+
+    /// Clifford element.
+    pub clifford: String,
+}
+
+/// The "outer" request shape for a `conjugate_pauli_by_clifford` request.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd)]
+struct ConjugatePauliByCliffordRequest {
+    #[serde(rename = "*args")]
+    args: [ConjugateByCliffordRequest; 1],
+}
+
+impl From<ConjugateByCliffordRequest> for ConjugatePauliByCliffordRequest {
+    fn from(value: ConjugateByCliffordRequest) -> Self {
+        Self { args: [value] }
+    }
+}
+
+/// Conjugate Pauli by Clifford response.
+#[derive(Clone, Deserialize, Debug, PartialEq, PartialOrd)]
+pub struct ConjugatePauliByCliffordResponse {
+    /// Encoded global phase factor on the emitted Pauli.
+    pub phase: i64,
+
+    /// Description of the encoded Pauli.
+    pub pauli: String,
+}
+
+/// Given a circuit that consists only of elements of the Clifford group,
+/// return its action on a `PauliTerm`.
+/// In particular, for Clifford ``C``, and Pauli ``P``, this returns the Pauli Term
+/// representing ``CPC^{\dagger}``.
+pub fn conjugate_pauli_by_clifford(
+    client: &Qcs,
+    request: ConjugateByCliffordRequest,
+) -> Result<ConjugatePauliByCliffordResponse, Error> {
+    #[cfg(feature = "tracing")]
+    tracing::debug!("requesting quilc conjugate_pauli_by_clifford");
+
+    let config = client.get_config();
+    let endpoint = config.quilc_url();
+    let request: ConjugatePauliByCliffordRequest = request.into();
+    let request = rpcq::RPCRequest::new("conjugate_pauli_by_clifford", &request);
+    let rpcq_client = rpcq::Client::new(endpoint)
+        .map_err(|source| Error::from_quilc_error(endpoint.into(), source))?;
+    match rpcq_client.run_request::<_, ConjugatePauliByCliffordResponse>(&request) {
+        Ok(response) => Ok(response),
+        Err(source) => Err(Error::from_quilc_error(endpoint.into(), source)),
+    }
+}
+
+/// Request to generate a randomized benchmarking sequence.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd)]
+#[serde(tag = "_type")]
+pub struct RandomizedBenchmarkingRequest {
+    /// Depth of the benchmarking sequence.
+    pub depth: u64,
+
+    /// Number of qubits involved in the benchmarking sequence.
+    pub qubits: u64,
+
+    /// List of Quil programs, each describing a Clifford.
+    pub gateset: Vec<String>,
+
+    /// PRNG seed. Set this to guarantee repeatable results.
+    pub seed: Option<u64>,
+
+    /// Fixed Clifford, specified as a Quil string, to interleave through an RB sequence.
+    pub interleaver: Option<String>,
+}
+
+/// The "outer" request shape for a `generate_rb_sequence` request.
+#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, PartialOrd)]
+struct GenerateRandomizedBenchmarkingSequenceRequest {
+    #[serde(rename = "*args")]
+    args: [RandomizedBenchmarkingRequest; 1],
+}
+
+impl From<RandomizedBenchmarkingRequest> for GenerateRandomizedBenchmarkingSequenceRequest {
+    fn from(value: RandomizedBenchmarkingRequest) -> Self {
+        Self { args: [value] }
+    }
+}
+
+/// Randomly generated benchmarking sequence response.
+#[derive(Clone, Deserialize, Debug, PartialEq, PartialOrd)]
+pub struct GenerateRandomizedBenchmarkingSequenceResponse {
+    /// List of Cliffords, each expressed as a list of generator indices.
+    pub sequence: Vec<Vec<i64>>,
+}
+
+/// Construct a randomized benchmarking experiment on the given qubits, decomposing into
+/// gateset. If interleaver is not provided, the returned sequence will have the form
+/// ```C_1 C_2 ... C_(depth-1) C_inv ,```
+///
+/// where each C is a Clifford element drawn from gateset, ``C_{< depth}`` are randomly selected,
+/// and ``C_inv`` is selected so that the entire sequence composes to the identity.  If an
+/// interleaver ``G`` (which must be a Clifford, and which will be decomposed into the native
+/// gateset) is provided, then the sequence instead takes the form
+/// ```C_1 G C_2 G ... C_(depth-1) G C_inv .```
+pub fn generate_randomized_benchmarking_sequence(
+    client: &Qcs,
+    request: RandomizedBenchmarkingRequest,
+) -> Result<GenerateRandomizedBenchmarkingSequenceResponse, Error> {
+    #[cfg(feature = "tracing")]
+    tracing::debug!("requesting quilc generate_randomized_benchmarking_sequence");
+
+    let config = client.get_config();
+    let endpoint = config.quilc_url();
+    let request: GenerateRandomizedBenchmarkingSequenceRequest = request.into();
+    let request = rpcq::RPCRequest::new("generate_rb_sequence", &request);
+    let rpcq_client = rpcq::Client::new(endpoint)
+        .map_err(|source| Error::from_quilc_error(endpoint.into(), source))?;
+    match rpcq_client.run_request::<_, GenerateRandomizedBenchmarkingSequenceResponse>(&request) {
+        Ok(response) => Ok(response),
+        Err(source) => Err(Error::from_quilc_error(endpoint.into(), source)),
+    }
+}
+
 /// All of the errors that can occur within this module.
 #[derive(Debug, thiserror::Error)]
 pub enum Error {
@@ -296,4 +432,47 @@ MEASURE 1 ro[1]
         let semver_re = Regex::new(r"^([0-9]+)\.([0-9]+)\.([0-9]+)$").unwrap();
         assert!(semver_re.is_match(&version));
     }
+
+    #[tokio::test]
+    async fn test_conjugate_pauli_by_clifford() {
+        let client = Qcs::load().await.unwrap_or_default();
+        let request = ConjugateByCliffordRequest {
+            pauli: PauliTerm {
+                indices: vec![0],
+                symbols: vec!["X".into()],
+            },
+            clifford: "H 0".into(),
+        };
+        let response = conjugate_pauli_by_clifford(&client, request)
+            .expect("Should conjugate pauli by clifford");
+
+        assert_eq!(
+            response,
+            ConjugatePauliByCliffordResponse {
+                phase: 0,
+                pauli: "Z".into(),
+            }
+        );
+    }
+
+    #[tokio::test]
+    async fn test_generate_randomized_benchmark_sequence() {
+        let client = Qcs::load().await.unwrap_or_default();
+        let request = RandomizedBenchmarkingRequest {
+            depth: 2,
+            qubits: 1,
+            gateset: vec!["X 0", "H 0"].into_iter().map(String::from).collect(),
+            seed: Some(314),
+            interleaver: Some("Y 0".into()),
+        };
+        let response = generate_randomized_benchmarking_sequence(&client, request)
+            .expect("Should generate randomized benchmark sequence");
+
+        assert_eq!(
+            response,
+            GenerateRandomizedBenchmarkingSequenceResponse {
+                sequence: vec![vec![1, 0], vec![0, 1, 0, 1], vec![1, 0]],
+            }
+        )
+    }
 }
diff --git a/crates/python/qcs_sdk/compiler/quilc.pyi b/crates/python/qcs_sdk/compiler/quilc.pyi
index 581f186f4..fb9ed4fff 100644
--- a/crates/python/qcs_sdk/compiler/quilc.pyi
+++ b/crates/python/qcs_sdk/compiler/quilc.pyi
@@ -1,4 +1,4 @@
-from typing import Optional, final
+from typing import List, Optional, final
 
 from ..qpu.isa import InstructionSetArchitecture
 from ..qpu.client import QCSClient
@@ -67,6 +67,134 @@ class TargetDevice:
         ...
 
 
+@final
+class PauliTerm:
+    """Pauli Term used for ConjugatePauliByCliffordRequest."""
+
+    def __new__(
+        cls,
+        /,
+        indices: List[int],
+        symbols: List[str],
+    ) -> "PauliTerm": ...
+
+    @property
+    def indices(self) -> List[int]:
+        """Qubit indices onto which the factors of the Pauli Term are applied."""
+        ...
+    @indices.setter
+    def indices(self, value: List[int]): ...
+
+    @property
+    def symbols(self) -> List[str]:
+        """Ordered factors of the Pauli Term."""
+        ...
+    @symbols.setter
+    def symbols(self, value: List[str]): ...
+
+
+@final
+class ConjugateByCliffordRequest:
+    """Request to conjugate a Pauli Term by a Clifford element."""
+
+    def __new__(
+        cls,
+        /,
+        pauli: PauliTerm,
+        clifford: str,
+    ) -> "ConjugateByCliffordRequest": ...
+
+    @property
+    def pauli(self) -> PauliTerm:
+        """Pauli Term to conjugate."""
+        ...
+    @pauli.setter
+    def pauli(self, value: PauliTerm): ...
+
+    @property
+    def clifford(self) -> str:
+        """Clifford element."""
+        ...
+    @clifford.setter
+    def clifford(self, value: str): ...
+
+
+@final
+class ConjugatePauliByCliffordResponse:
+    """Pauli Term conjugated by a Clifford element."""
+
+    @property
+    def phase(self) -> int:
+        """Encoded global phase factor on the emitted Pauli."""
+        ...
+
+    @property
+    def pauli(self) -> str:
+        """Description of the encoded Pauli."""
+        ...
+
+
+@final
+class RandomizedBenchmarkingRequest:
+    """Request to generate a randomized benchmarking sequence."""
+
+    def __new__(
+        cls,
+        /,
+        depth: int,
+        qubits: int,
+        gateset: List[str],
+        seed: Optional[int] = None,
+        interleaver: Optional[str] = None,
+    ) -> "RandomizedBenchmarkingRequest": ...
+
+    @property
+    def depth(self) -> int:
+        """Depth of the benchmarking sequence."""
+        ...
+    @depth.setter
+    def depth(self, value: int): ...
+
+    @property
+    def qubits(self) -> int:
+        """Number of qubits involved in the benchmarking sequence. Limit 2."""
+        ...
+    @qubits.setter
+    def qubits(self, value: int): ...
+
+    @property
+    def gateset(self) -> List[str]:
+        """List of Quil programs, each describing a Clifford."""
+        ...
+    @gateset.setter
+    def gateset(self, value: List[str]): ...
+
+    @property
+    def seed(self) -> Optional[int]:
+        """PRNG seed. Set this to guarantee repeatable results."""
+        ...
+    @seed.setter
+    def seed(self, value: Optional[int]): ...
+
+    @property
+    def interleaver(self) -> Optional[str]:
+        """Fixed Clifford, specified as a Quil string, to interleave through an RB sequence."""
+        ...
+    @interleaver.setter
+    def interleaver(self, value: Optional[str]): ...
+
+
+@final
+class GenerateRandomizedBenchmarkingSequenceResponse:
+    """Randomly generated benchmarking sequence response."""
+
+    @property
+    def sequence(self) -> List[List[int]]:
+        """List of Cliffords, each expressed as a list of generator indices."""
+        ...
+
+
+
 def compile_program(
     quil: str,
     target: TargetDevice,
@@ -131,3 +259,86 @@ async def get_version_info_async(
     :raises QuilcError: If the is a failure connecting to Quilc.
     """
     ...
+
+
+def conjugate_pauli_by_clifford(
+    request: ConjugateByCliffordRequest,
+    client: Optional[QCSClient] = ...,
+) -> ConjugatePauliByCliffordResponse:
+    """
+    Given a circuit that consists only of elements of the Clifford group, return its action on a PauliTerm.
+    In particular, for Clifford C, and Pauli P, this returns the PauliTerm representing CPC^{\dagger}.
+    
+    :param request: Pauli Term conjugation request.
+    :param client: Optional client configuration. If ``None``, a default one is created.
+
+    :raises QuilcError: If the is a failure connecting to Quilc or if the request is malformed.
+    """
+    ...
+
+
+def conjugate_pauli_by_clifford_async(
+    request: ConjugateByCliffordRequest,
+    client: Optional[QCSClient] = ...,
+) -> ConjugatePauliByCliffordResponse:
+    """
+    Given a circuit that consists only of elements of the Clifford group, return its action on a PauliTerm.
+    In particular, for Clifford C, and Pauli P, this returns the PauliTerm representing CPC^{\dagger}.
+    (async analog of ``conjugate_pauli_by_clifford``)
+    
+    :param request: Pauli Term conjugation request.
+    :param client: Optional client configuration. If ``None``, a default one is created.
+
+    :raises QuilcError: If the is a failure connecting to Quilc or if the request is malformed.
+    """
+    ...
+
+
+def generate_randomized_benchmarking_sequence(
+    request: RandomizedBenchmarkingRequest,
+    client: Optional[QCSClient] = ...,
+) -> GenerateRandomizedBenchmarkingSequenceResponse:
+    """
+    Construct a randomized benchmarking experiment on the given qubits, decomposing into
+    gateset. If interleaver is not provided, the returned sequence will have the form
+
+        C_1 C_2 ... C_(depth-1) C_inv ,
+
+    where each C is a Clifford element drawn from gateset, C_{< depth} are randomly selected,
+    and C_inv is selected so that the entire sequence composes to the identity.  If an
+    interleaver G (which must be a Clifford, and which will be decomposed into the native
+    gateset) is provided, then the sequence instead takes the form
+
+        C_1 G C_2 G ... C_(depth-1) G C_inv .
+    
+    :param request: Pauli Term conjugation request.
+    :param client: Optional client configuration. If ``None``, a default one is created.
+
+    :raises QuilcError: If the is a failure connecting to Quilc or if the request is malformed.
+    """
+    ...
+
+
+def generate_randomized_benchmarking_sequence_async(
+    request: RandomizedBenchmarkingRequest,
+    client: Optional[QCSClient] = ...,
+) -> GenerateRandomizedBenchmarkingSequenceResponse:
+    """
+    Construct a randomized benchmarking experiment on the given qubits, decomposing into
+    gateset. If interleaver is not provided, the returned sequence will have the form
+
+        C_1 C_2 ... C_(depth-1) C_inv ,
+
+    where each C is a Clifford element drawn from gateset, C_{< depth} are randomly selected,
+    and C_inv is selected so that the entire sequence composes to the identity.  If an
+    interleaver G (which must be a Clifford, and which will be decomposed into the native
+    gateset) is provided, then the sequence instead takes the form
+
+        C_1 G C_2 G ... C_(depth-1) G C_inv .
+    (async analog of ``generate_randomized_benchmarking_sequence``)
+    
+    :param request: Pauli Term conjugation request.
+    :param client: Optional client configuration. If ``None``, a default one is created.
+
+    :raises QuilcError: If the is a failure connecting to Quilc or if the request is malformed.
+    """
\ No newline at end of file
diff --git a/crates/python/src/compiler/quilc.rs b/crates/python/src/compiler/quilc.rs
index 16096371a..158166311 100644
--- a/crates/python/src/compiler/quilc.rs
+++ b/crates/python/src/compiler/quilc.rs
@@ -1,9 +1,18 @@
-use pyo3::exceptions::PyValueError;
-use pyo3::{exceptions::PyRuntimeError, pyfunction, pymethods, PyResult};
-use qcs::compiler::quilc::{CompilerOpts, TargetDevice, DEFAULT_COMPILER_TIMEOUT};
+use pyo3::{
+    exceptions::{PyRuntimeError, PyValueError},
+    pyfunction, pymethods,
+    types::{PyInt, PyString},
+    Py, PyResult,
+};
+use qcs::compiler::quilc::{
+    CompilerOpts, ConjugateByCliffordRequest, ConjugatePauliByCliffordResponse,
+    GenerateRandomizedBenchmarkingSequenceResponse, PauliTerm, RandomizedBenchmarkingRequest,
+    TargetDevice, DEFAULT_COMPILER_TIMEOUT,
+};
 use qcs_api_client_openapi::models::InstructionSetArchitecture;
 use rigetti_pyo3::{
-    create_init_submodule, py_wrap_error, py_wrap_struct, py_wrap_type, wrap_error, ToPythonError,
+    create_init_submodule, py_wrap_data_struct, py_wrap_error, py_wrap_struct, py_wrap_type,
+    wrap_error, ToPythonError,
 };
 
 use crate::py_sync::py_function_sync_async;
@@ -13,7 +22,12 @@ use crate::qpu::isa::PyInstructionSetArchitecture;
 create_init_submodule! {
     classes: [
         PyCompilerOpts,
-        PyTargetDevice
+        PyTargetDevice,
+        PyPauliTerm,
+        PyConjugateByCliffordRequest,
+        PyConjugatePauliByCliffordResponse,
+        PyRandomizedBenchmarkingRequest,
+        PyGenerateRandomizedBenchmarkingSequenceResponse
     ],
     consts: [DEFAULT_COMPILER_TIMEOUT],
     errors: [QuilcError],
@@ -21,7 +35,11 @@ create_init_submodule! {
         py_compile_program,
         py_compile_program_async,
         py_get_version_info,
-        py_get_version_info_async
+        py_get_version_info_async,
+        py_conjugate_pauli_by_clifford,
+        py_conjugate_pauli_by_clifford_async,
+        py_generate_randomized_benchmarking_sequence,
+        py_generate_randomized_benchmarking_sequence_async
     ],
 }
 
@@ -106,3 +124,107 @@ py_function_sync_async! {
             .map_err(RustQuilcError::to_py_err)
     }
 }
+
+py_wrap_data_struct! {
+    PyPauliTerm(PauliTerm) as "PauliTerm" {
+        indices: Vec<u64> => Vec<Py<PyInt>>,
+        symbols: Vec<String> => Vec<Py<PyString>>
+    }
+}
+
+#[pymethods]
+impl PyPauliTerm {
+    #[new]
+    fn __new__(indices: Vec<u64>, symbols: Vec<String>) -> PyResult<Self> {
+        Ok(Self(PauliTerm { indices, symbols }))
+    }
+}
+
+py_wrap_data_struct! {
+    PyConjugateByCliffordRequest(ConjugateByCliffordRequest) as "ConjugateByCliffordRequest" {
+        pauli: PauliTerm => PyPauliTerm,
+        clifford: String => Py<PyString>
+    }
+}
+
+#[pymethods]
+impl PyConjugateByCliffordRequest {
+    #[new]
+    fn __new__(pauli: PyPauliTerm, clifford: String) -> PyResult<Self> {
+        Ok(Self(ConjugateByCliffordRequest {
+            pauli: pauli.into(),
+            clifford,
+        }))
+    }
+}
+
+py_wrap_data_struct! {
+    PyConjugatePauliByCliffordResponse(ConjugatePauliByCliffordResponse) as "ConjugatePauliByCliffordResponse" {
+        phase: i64 => Py<PyInt>,
+        pauli: String => Py<PyString>
+    }
+}
+
+py_function_sync_async! {
+    #[pyfunction(client = "None")]
+    async fn conjugate_pauli_by_clifford(
+        request: PyConjugateByCliffordRequest,
+        client: Option<PyQcsClient>,
+    ) -> PyResult<PyConjugatePauliByCliffordResponse> {
+        let client = PyQcsClient::get_or_create_client(client).await?;
+        qcs::compiler::quilc::conjugate_pauli_by_clifford(&client, request.into())
+            .map(PyConjugatePauliByCliffordResponse::from)
+            .map_err(RustQuilcError::from)
+            .map_err(RustQuilcError::to_py_err)
+    }
+}
+
+py_wrap_data_struct! {
+    PyRandomizedBenchmarkingRequest(RandomizedBenchmarkingRequest) as "RandomizedBenchmarkingRequest" {
+        depth: u64 => Py<PyInt>,
+        qubits: u64 => Py<PyInt>,
+        gateset: Vec<String> => Vec<Py<PyString>>,
+        seed: Option<u64> => Option<Py<PyInt>>,
+        interleaver: Option<String> => Option<Py<PyString>>
+    }
+}
+
+#[pymethods]
+impl PyRandomizedBenchmarkingRequest {
+    #[new]
+    fn __new__(
+        depth: u64,
+        qubits: u64,
+        gateset: Vec<String>,
+        seed: Option<u64>,
+        interleaver: Option<String>,
+    ) -> PyResult<Self> {
+        Ok(Self(RandomizedBenchmarkingRequest {
+            depth,
+            qubits,
+            gateset,
+            seed,
+            interleaver,
+        }))
+    }
+}
+
+py_wrap_data_struct! {
+    PyGenerateRandomizedBenchmarkingSequenceResponse(GenerateRandomizedBenchmarkingSequenceResponse) as "GenerateRandomizedBenchmarkingSequenceResponse" {
+        sequence: Vec<Vec<i64>> => Vec<Vec<Py<PyInt>>>
+    }
+}
+
+py_function_sync_async! {
+    #[pyfunction(client = "None")]
+    async fn generate_randomized_benchmarking_sequence(
+        request: PyRandomizedBenchmarkingRequest,
+        client: Option<PyQcsClient>,
+    ) -> PyResult<PyGenerateRandomizedBenchmarkingSequenceResponse> {
+        let client = PyQcsClient::get_or_create_client(client).await?;
+        qcs::compiler::quilc::generate_randomized_benchmarking_sequence(&client, request.into())
+            .map(PyGenerateRandomizedBenchmarkingSequenceResponse::from)
+            .map_err(RustQuilcError::from)
+            .map_err(RustQuilcError::to_py_err)
+    }
+}


Test patch:
diff --git a/crates/python/tests/compiler/test_quilc.py b/crates/python/tests/compiler/test_quilc.py
index c77f313e3..dba23ada3 100644
--- a/crates/python/tests/compiler/test_quilc.py
+++ b/crates/python/tests/compiler/test_quilc.py
@@ -5,11 +5,20 @@
     DEFAULT_COMPILER_TIMEOUT,
     CompilerOpts,
     TargetDevice,
+    PauliTerm,
+    GenerateRandomizedBenchmarkingSequenceResponse,
+    RandomizedBenchmarkingRequest,
+    ConjugateByCliffordRequest,
+    ConjugatePauliByCliffordResponse,
     QuilcError,
     compile_program,
     compile_program_async,
     get_version_info,
     get_version_info_async,
+    conjugate_pauli_by_clifford,
+    conjugate_pauli_by_clifford_async,
+    generate_randomized_benchmarking_sequence,
+    generate_randomized_benchmarking_sequence_async,
 )
 
 @pytest.fixture
@@ -68,3 +77,57 @@ async def test_get_version_info_async():
     """A valid version should be returned."""
     version = await get_version_info_async()
     assert version
+
+
+def test_conjugate_pauli_by_clifford():
+    """Pauli should be conjugated by clifford."""
+    request = ConjugateByCliffordRequest(
+        pauli=PauliTerm(indices=[0], symbols=["X"]),
+        clifford="H 0"
+    )
+    response = conjugate_pauli_by_clifford(request)
+    assert type(response) == ConjugatePauliByCliffordResponse
+    assert response.pauli == "Z"
+    assert response.phase == 0
+
+
+@pytest.mark.asyncio
+async def test_conjugate_pauli_by_clifford_async():
+    """Pauli should be conjugated by clifford."""
+    request = ConjugateByCliffordRequest(
+        pauli=PauliTerm(indices=[0], symbols=["X"]),
+        clifford="H 0"
+    )
+    response = await conjugate_pauli_by_clifford_async(request)
+    assert type(response) == ConjugatePauliByCliffordResponse
+    assert response.pauli == "Z"
+    assert response.phase == 0
+
+
+def test_generate_randomized_benchmark_sequence():
+    """Random benchmark should run predictably."""
+    request = RandomizedBenchmarkingRequest(
+        depth=2,
+        qubits=1,
+        gateset=["X 0", "H 0"],
+        seed=314,
+        interleaver="Y 0",
+    )
+    response = generate_randomized_benchmarking_sequence(request)
+    assert type(response) == GenerateRandomizedBenchmarkingSequenceResponse
+    assert response.sequence == [[1, 0], [0, 1, 0, 1], [1, 0]]
+
+
+@pytest.mark.asyncio
+async def test_generate_randomized_benchmark_sequence_async():
+    """Random benchmark should run predictably."""
+    request = RandomizedBenchmarkingRequest(
+        depth=2,
+        qubits=1,
+        gateset=["X 0", "H 0"],
+        seed=314,
+        interleaver="Y 0",
+    )
+    response = await generate_randomized_benchmarking_sequence_async(request)
+    assert type(response) == GenerateRandomizedBenchmarkingSequenceResponse
+    assert response.sequence == [[1, 0], [0, 1, 0, 1], [1, 0]]
\ No newline at end of file