Skip to content

Compose a subcircuit and undo it

Build a reusable rotation, follow it with its inverse and repeat the pair. Inspect the operation order and verify that a qubit returns to its initial state. Complete parameter binding first; this one-qubit local experiment uses probabilities without finite sampling.

Suppose the source applies RX first and RZ second. Its matrix is U = RZ(θ/2) RX(θ), since matrices act on a state from right to left. The inverse is U† = RX(−θ) RZ(−θ/2): operationally, undo RZ first, then RX. Negating angles without reversing order generally does not invert a sequence of noncommuting gates.

Build and inspect the round trip

"""Compose reusable subcircuits and verify an inverse round trip.

``compose`` maps reusable declarations into a destination circuit. ``inverse``
and ``repeat`` produce new circuits with explicit gate order, so transformations
can be inspected and tested before execution.
"""

from __future__ import annotations

import json

from cascaqit import Circuit


def main() -> None:
    """Build a reusable rotation and cancel it with its inverse."""
    rotation = Circuit(("data",), program_id="lesson.digital.subcircuit")
    theta = rotation.parameter("theta", lower_bound=-1.0, upper_bound=1.0)
    rotation.rx(theta, "data").rz(theta / 2, "data")

    declaration = Circuit(("data",), program_id="lesson.digital.compose")
    declaration.compose(rotation).compose(rotation.inverse())
    bound = declaration.repeat(2).bind({"theta": 0.4})
    result = bound.run(shots=0, return_probabilities=True)

    payload = {
        "track": "digital_developer",
        "level": "applied",
        "lesson": "circuit_composition",
        "facts": {
            "gate_count": len(bound.operations),
            "gate_names": [
                operation.definition.name for operation in bound.operations
            ],
            "parameter_names": [item.name for item in bound.parameters],
            "round_trip_probability_0": round((result.probabilities or {})["0"], 12),
            "source_unchanged": not rotation.is_bound,
        },
        "boundaries": {
            "hardware_execution": False,
            "cloud_execution": False,
            "network_accessed": False,
            "credentials_loaded": False,
        },
    }
    print(json.dumps(payload, sort_keys=True))


if __name__ == "__main__":
    main()

Download the full script

python3 examples/user/tracks/digital_developer/03_applied_circuit_composition_en.py

The receiver declaration is extended by each compose() call. inverse(), repeat() and bind() return separate circuits. Both composed copies refer to the compatible parameter theta, so one binding supplies their shared angle. Matching names with incompatible declarations would be an error.

{
  "boundaries": {
    "cloud_execution": false,
    "credentials_loaded": false,
    "hardware_execution": false,
    "network_accessed": false
  },
  "facts": {
    "gate_count": 8,
    "gate_names": [
      "rx",
      "rz",
      "rz",
      "rx",
      "rx",
      "rz",
      "rz",
      "rx"
    ],
    "parameter_names": [
      "theta"
    ],
    "round_trip_probability_0": 1.0,
    "source_unchanged": true
  },
  "lesson": "circuit_composition",
  "level": "applied",
  "track": "digital_developer"
}

Each round trip contains four operations; repeating twice produces eight. Read gate_names as the stored base names, not the full operation semantics: inversion is also represented by operation modifiers. The sequence of names alone cannot tell you which operations are inverses.

The ideal result has P(0) = 1 because U†U is the identity. source_unchanged confirms that the reusable source still has an unbound parameter. This is a check on reuse; inspect the source operations as well when changing the example.

Choose the right construction method

Operation Use it for
append() Add an individual catalog operation with operands and arguments
compose() Add a whole subcircuit, optionally with a complete qubit mapping
inverse() Make the reversed inverse of a supported unitary circuit before measurement
repeat(count) Create repeated copies; the supported range is 1 through 1,024

To use the named source qubit in a new two-qubit destination, write Circuit(2).compose(rotation, qubit_map={"data": "q1"}). Bind before executing. A mapping must cover the source qubits; a name is not automatically interpreted as a physical position.

Test the reasoning

  1. Replace .repeat(2) with .repeat(3). Predict the operation count and P(0).
  2. Run rotation.bind({"theta": 0.4}) directly, with no inverse or repetition, and compute P(1). Explain why the following RZ does not change this Z-basis probability, even though it changes relative phase.
  3. Add measurement to rotation before calling inverse(). Inspect the error and move measurement to the end of the completed circuit.
Check your reasoning

Three identity pairs contain 12 operations and keep P(0) = 1. A single RX(0.4) gives P(1) = sin²(0.2) ≈ 0.039470; the subsequent RZ multiplies amplitudes by phases without changing their magnitudes. Measured circuits cannot be inverted. Build unitary transformations first and measure once at the end. Returning |0⟩ in this example is one test input; a proof that two arbitrary circuits are equivalent needs more than one input state's counts.

Continue with several observables from one state to inspect phase-sensitive information that a single measurement basis can miss.

中文版

SDK 1.0.8a · `8b227bff`