DEV Community

Alain Airom (Ayrom)
Alain Airom (Ayrom)

Posted on

Can Bob help in Quantum Application Programming?

Using Bob and Qiskit to build an end-to-end demonstration.

TL;DR - Quantum Software Ecosystem

The quantum software landscape is anchored by a few dominant open-source frameworks backed by major industry players, with IBM's Qiskit commanding the largest share of developer mindshare and ecosystem adoption. Supported by millions of cumulative downloads (approaching nearly 30 million total PyPI installations) and hundreds of thousands of active users worldwide, Qiskit leads the market due to its deep integration with IBM’s cloud-accessible superconducting hardware, robust transpiler services, and extensive educational resources. Competing frameworks like Google's Cirq—focused heavily on Noisy Intermediate-Scale Quantum (NISQ) processor architectures—and Xanadu's PennyLane—which has gained substantial traction for quantum machine learning and differentiable quantum programming—hold strong academic and specialized niches. Meanwhile, enterprise-focused platforms like Microsoft’s Azure Quantum (Q#) and Amazon’s Braket act as hardware-agnostic multi-tenant middleware layers, collectively driving a rapidly expanding quantum software market that parallels the broader hardware industry's robust annual growth.

What is Qskit

Qiskit is an open-source software development kit (SDK) designed for working with quantum computers at the level of circuits, operators, and primitives. Developed primarily by IBM, it serves as the industry’s most widely adopted framework for quantum computing, bridging the gap between theoretical quantum physics and practical software engineering. By offering a Python-based programming environment backed by high-performance compilation tools written in Rust, Qiskit enables researchers, developers, and enterprise professionals to design quantum algorithms, optimize circuits for specific hardware topologies, and execute workloads seamlessly across local simulators or cloud-accessible utility-scale quantum processors.


Qiskit

Excerpt from Qiskit GitHub.

License Current Release Downloads Coverage Status PyPI - Python Version Minimum rustc 1.89 Downloads DOI

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.

This library is the core component of Qiskit, which contains the building blocks for creating and working with quantum circuits, quantum operators, and primitive functions (Sampler and Estimator). It also contains a transpiler that supports optimizing quantum circuits, and a quantum information toolbox for creating advanced operators.

Qiskit provides two public APIs: a Python API and a C API. The Python API is the primary interface and prior to Qiskit 2.0 was the only public API available for Qiskit. The C API is designed to provide direct access to Qiskit's internal data model (which is written in Rust). The C API can be consumed as either a shared library (libqiskit.so) for standalone use, or from its embedding in the qiskit Python package for writing Python extension modules.


Using IBM Bob to Write Qiskit Enabled Applications

Disclaimer: *Although I am far from a quantum computing expert—with only a foundational grasp of its underlying mechanics, current use cases, and future possibilities—I was eager to explore a domain where I am essentially a novice. I wanted to see how Bob could help me design and construct an engaging, user-friendly, and educational application to demystify these complex concepts. So, I put Bob to the test and asked him to help me build it from the ground up!*


Implementation Architecture & Code

Python Algorithmes

Deep Dive: The Python Algorithm Layer & Architectural Design

At the core of the application lies a meticulously engineered Python algorithm layer that brings foundational quantum principles to life through clean, modular dataclasses. Designed as stateless, self-contained units, the application's algorithms encapsulate circuit construction, local simulation via AerSimulator, and semantic result interpretation into robust, reusable components.

As mapped out in the system's high-level architecture, user interactions originating from the Streamlit GUI (app.py) are routed dynamically through sidebar controls into individual algorithm modules under src/algorithms/. Each algorithm implementation conforms to a uniform interface, making it trivial to extend the suite or inspect its inner workings programmatically.

  • Entanglement and Foundational Circuits: Bell States: the simplest yet most profound demonstration of quantum mechanics in the suite is the Bell State (EPR Pair) implementation (bell_state.py). It models maximal quantum entanglement between two qubits:
@dataclass
class BellStateDemo:
    shots: int = 1024
    circuit: QuantumCircuit | None = field(default=None, repr=False)
    counts: dict[str, int] = field(default_factory=dict)
    circuit_diagram: str = ""

    def build_circuit(self) -> QuantumCircuit:
        qc = QuantumCircuit(2, 2)
        qc.h(0)        # Hadamard: |0⟩ → |+⟩ = (|0⟩+|1⟩)/√2
        qc.cx(0, 1)    # CNOT: entangles qubits 0 and 1
        qc.measure([0, 1], [0, 1])
        return qc
Enter fullscreen mode Exit fullscreen mode

When simulated, the results consistently yield a roughly 50/50 split between 00 and 11 outcomes, proving that measuring one qubit instantaneously determines the state of the other regardless of classical separation.

  • Query Complexity Advantage: Bernstein-Vazirani & Deutsch-Jozsa: to illustrate quantum computational speedups, the suite features algorithmic oracles that demonstrate exponential query advantages over classical computing. In the Bernstein-Vazirani Algorithm (bernstein_vazirani.py), a hidden n-bit string s*s* is uncovered using a single quantum query compared to n*n* classical queries:
"""
Bernstein-Vazirani Algorithm
=============================
Finds a hidden bit-string s ∈ {0,1}ⁿ encoded in an oracle f(x) = s·x (mod 2)
with a SINGLE quantum query, compared to n queries classically.

The algorithm demonstrates the power of quantum parallelism:
  • Classical: requires n queries (one per bit)
  • Quantum:   requires 1 query

This is one of the clearest examples of an exponential query advantage.

Circuit:
    |0⟩ ─H─●─H─  (qubit carrying bit 0 of s)
    |0⟩ ─H─●─H─  (qubit carrying bit 1 of s)
    |1⟩ ─H─⊕──   (ancilla in |−⟩)

References:
    • Bernstein, E. & Vazirani, U. (1997). Quantum Complexity Theory.
    • Qiskit documentation: https://github.com/Qiskit/documentation
"""

# Ancilla preparation and Hadamard superposition across query qubits
qc.x(n)
qc.h(n)
qc.barrier()
qc.h(range(n))

# Oracle: CNOT on ancilla for every bit=1 in the secret string
for i, bit in enumerate(reversed(self.secret_string)):
    if bit == "1":
        qc.cx(i, n)

qc.h(range(n))  # Inverse Hadamard to interfere and expose the secret
Enter fullscreen mode Exit fullscreen mode
  • Similarly, the Deutsch-Jozsa Algorithm (deutsch_jozsa.py) determines whether a Boolean function is constant or balanced with a single query, leveraging quantum parallelism and interference to yield an all-zero measurement for constant functions.
  """
  Deutsch-Jozsa Algorithm
  ========================
  Determines whether a Boolean function f: {0,1}ⁿ → {0,1} is CONSTANT
  (same output for all inputs) or BALANCED (0 for half, 1 for half) using
  a single quantum query.

  Classical algorithm: needs up to 2^(n-1)+1 queries in the worst case.
  Quantum algorithm:   needs exactly 1 query — exponential speedup.

  This was the first demonstration of a quantum algorithm outperforming
  any deterministic classical algorithm.

  References:
      • Deutsch, D. & Jozsa, R. (1992). Rapid Solution of Problems by Quantum Computation.
      • Qiskit documentation: https://github.com/Qiskit/documentation
  """
  def _interpret(self) -> str:
          """Generate a human-readable result interpretation.

          Rule: all-zero measurement → CONSTANT; any non-zero bit → BALANCED.
          """
          total = sum(self.counts.values())
          zero_state = "0" * self.num_qubits
          zero_count = self.counts.get(zero_state, 0)
          measured_constant = zero_count / total > 0.9

          expected_constant = self.oracle_type.startswith("constant")
          verdict = "CONSTANT" if measured_constant else "BALANCED"
          correct = "✓ Correct" if measured_constant == expected_constant else "✗ Unexpected"

          return (
              f"Oracle type: {self.oracle_type}\n"
              f"Measured all-zero ({zero_state}): {zero_count}/{total} times "
              f"({zero_count/total*100:.1f}%)\n"
              f"Verdict: Function is {verdict}  [{correct}]\n\n"
              "Rule: all-zero measurement → CONSTANT; any non-zero → BALANCED\n"
              f"Classical worst case: {2**(self.num_qubits-1)+1} queries needed.\n"
              "Quantum: always 1 query — the first-ever proven quantum advantage."
          )
Enter fullscreen mode Exit fullscreen mode
  • Quadratic Speedups & Amplitude Amplification: Grover's Search: for unstructured database search, Grover's Search Algorithm (grover.py) provides a quadratic speedup (O(√N) vs. classical O(N)) by utilizing iterative amplitude amplification. The algorithm computes the optimal number of iterations based on the search space size (iterations = ⌊(π/4) × √(2ⁿ)⌋) and alternates between a phase-flip oracle and the Grover diffuser:
def __post_init__(self) -> None:
    n = 2 ** self.num_qubits
    self.iterations = max(1, int(math.pi / 4 * math.sqrt(n)))

def build_circuit(self) -> QuantumCircuit:
    qc = QuantumCircuit(self.num_qubits, self.num_qubits)
    qc.h(range(self.num_qubits))   # Equal superposition across all states
    for _ in range(self.iterations):
        qc.compose(self._oracle(), inplace=True)
        qc.compose(self._diffuser(), inplace=True)
    qc.measure(range(self.num_qubits), range(self.num_qubits))
    return qc
Enter fullscreen mode Exit fullscreen mode
  • Advanced Protocols: Quantum Teleportation and Feed-Forward Control: advanced protocols such as Quantum Teleportation (teleportation.py) move an arbitrary quantum state between parties using pre-shared entanglement and classical communication channels. Crucially, Qiskit 1.0’s modern control-flow builder interface is utilized to handle post-measurement classical feed-forward operations conditionally:
qc.measure(0, 0)
qc.measure(1, 1)

# Conditional corrections using Qiskit 1.0 if_test context managers
with qc.if_test((qc.clbits[1], 1)):
    qc.x(2)  # Bob applies X if Alice's bit 1 is 1
with qc.if_test((qc.clbits[0], 1)):
    qc.z(2)  # Bob applies Z if Alice's bit 0 is 1

qc.measure(2, 2)
Enter fullscreen mode Exit fullscreen mode

Summary of Python Execution Flow

Every algorithm adheres to a decoupled execution lifecycle:

  1. Instantiation: Parameters (such as shots, target, or secret_string) are injected via standard Python dataclass fields.
  2. Circuit Compilation: .build_circuit() constructs the structural QuantumCircuit using primitive gates.
  3. Simulation: .run() transpiles the circuit and dispatches it to the local qiskit_aer.AerSimulator.
  4. Interpretation: Raw counts are processed into human-readable semantic metrics and visualized via utility modules (such as plot_histogram and automated text/PNG diagram renderers).

Deep Dive: The C Language Demo Layer & Compilation Architecture

To demonstrate the full performance and low-level capabilities of the Qiskit ecosystem, the application includes a dedicated C language demo suite (src/c_demos/) built around the high-performance Qiskit C API (qiskit.h). Rather than limiting quantum programming exclusively to high-level Python wrappers, this layer exposes direct memory management, native circuit allocation, sparse observable construction, and optimized runtime compilation.

The C demonstration layer consists of three main components:

  1. The Source Registry (c_source_registry.py): Stores metadata, build instructions, and expected outputs for standalone C programs like the Bell state (bell_state_c.c), multi-qubit GHZ state generators (ghz_state_c.c), and sparse Pauli-string observable builders (observable_demo_c.c).
  2. The C Demo Manager (CDemoManager): A robust compilation and execution controller that automatically detects available Qiskit C runtime pathways, compiles source files on the fly, and securely executes them.
  3. The C API Integration: Leverages primitives like qk_circuit_new, qk_circuit_gate, and qk_obs_zero to manipulate quantum structures with near-metal performance.

Dual Runtime Compilation Pathways (CDemoManager)

Because executing compiled native code within a web framework UI requires careful environment handling, CDemoManager implements a sophisticated runtime detection strategy supporting two distinct pathways alongside a graceful fallback:

  • PATH A — Standalone Shared Library (libqiskit.{so,dylib}): Triggered when users build Qiskit from source via make c or provide an explicit QISKIT_C_LIB environment variable. The manager compiles standalone C source code via gcc linking directly against -lqiskit and setting runtime path variables (DYLD_LIBRARY_PATH / LD_LIBRARY_PATH).

  • PATH B — Bundled Python Extension (qiskit.capi): Designed for standard pip installations (pip install qiskit >= 1.0), which ship qiskit.h alongside qiskit._accelerate (the Rust-compiled shared library).

The Thread-Safety Challenge & Subprocess Workaround

A critical engineering hurdle solved by CDemoManager involves macOS dynamic linker (dyld) restrictions. Streamlit executes user code within background worker threads. On macOS, calling dlopen() on a compiled shared extension from a non-main thread triggers a fatal SIGABRT crash because Mach-O segment initializers require the main thread.

To bypass this safely, CDemoManager spawns an isolated child Python process via subprocess.run(). The child compilation workflow performs the following steps securely:

  1. Pre-flight Checks: Verifies that the C compiler (gcc/cc), qiskit.h, Python.h, and -undefined dynamic_lookup linker flags are fully operational.
  2. Dynamic Extension Patching: Wraps the C source with custom Python C-API module bindings (PyMethodDef, PyModuleDef), renaming main() to an internal handler _qk_demo_run() and calling qk_import() inside PyInit_*().
  3. Child Execution: Compiles the extension inside an isolated temporary directory, loads it via importlib.util.exec_module() on the child process's main thread, captures stdout streams via fmemopen, and returns structured JSON telemetry back to the main application without risking server instability.

Core C API Code Patterns

The underlying C implementations mirror high-level Qiskit logic with explicit memory control. For example, constructing a Bell State in bell_state_c.c allocates circuit structures and maps quantum gates directly:

/* ============================================================================
 * bell_state_c.c — Bell State using the Qiskit C API
 * ============================================================================
 * Demonstrates quantum entanglement by creating a Bell state |Φ+⟩:
 *   |Φ+⟩ = (|00⟩ + |11⟩) / √2
 *
 * Build instructions (UNIX/macOS):
 *   1. Build Qiskit C library from source:
 *      git clone https://github.com/Qiskit/qiskit.git
 *      cd qiskit && make c
 *
 *   2. Compile this file:
 *      gcc bell_state_c.c -o bell_state \
 *          -I /path/to/qiskit/dist/c/include \
 *          -L /path/to/qiskit/dist/c/lib \
 *          -lqiskit \
 *          -Wl,-rpath,/path/to/qiskit/dist/c/lib
 *
 *   3. Run:
 *      ./bell_state
 *
 * References:
 *   - Qiskit C API: https://quantum.cloud.ibm.com/docs/en/guides/install-c-api
 *   - Qiskit GitHub: https://github.com/Qiskit/qiskit
 * ============================================================================
 */

#include <stdio.h>
#include <stdint.h>
#include <qiskit.h>

int main(void) {
    printf("=== Bell State Demo (Qiskit C API) ===\\n\\n");

    /* ── 1. Create a 2-qubit circuit with 2 classical bits ──────────── */
    QkCircuit *qc = qk_circuit_new(2, 2);
    if (!qc) {
        fprintf(stderr, "ERROR: Failed to allocate circuit\\n");
        return 1;
    }

    /* ── 2. Apply Hadamard gate to qubit 0 ──────────────────────────── */
    /* H gate: |0⟩ → (|0⟩ + |1⟩) / √2   (creates superposition)       */
    uint32_t q0[1] = {0};
    qk_circuit_gate(qc, QkGate_H, q0, NULL);

    /* ── 3. Apply CNOT (CX) gate: control=0, target=1 ───────────────── */
    /* CX gate: entangles the two qubits                                 */
    uint32_t cx_qubits[2] = {0, 1};
    qk_circuit_gate(qc, QkGate_CX, cx_qubits, NULL);

    /* ── 4. Measure both qubits ─────────────────────────────────────── */
    /* qk_circuit_measure(circuit, qubit_index, clbit_index) — one call per qubit */
    qk_circuit_measure(qc, 0, 0);
    qk_circuit_measure(qc, 1, 1);

    /* ── 5. Print circuit information ──────────────────────────────────*/
    printf("Num instructions: %lu\\n", qk_circuit_num_instructions(qc));
    printf("Num qubits:       %u\\n",  qk_circuit_num_qubits(qc));
    printf("Num clbits:       %u\\n",  qk_circuit_num_clbits(qc));
    char *circ_str = qk_circuit_draw(qc, NULL);
    printf("Circuit string:\\n%s\\n\\n", circ_str);
    qk_str_free(circ_str);

    printf("Quantum state created: |Φ+⟩ = (|00⟩ + |11⟩) / √2\\n");
    printf("Expected measurement:  50%% |00⟩,  50%% |11⟩\\n");
    printf("Entanglement verified: qubits always collapse to the same value.\\n");

    /* ── 6. Free circuit memory ─────────────────────────────────────── */
    qk_circuit_free(qc);

    printf("\\n=== Demo complete ===\\n");
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Similarly, advanced components like Sparse Observables (observable_demo_c.c) construct complex Pauli-string measurements (O=2⋅(X0⊗Y1⊗Z2)+0.5⋅(Z0⊗Z1)O=2⋅(*X*0⊗*Y*1⊗*Z*2)+0.5⋅(*Z*0⊗*Z*1)) for use with Estimator primitives:

/* ============================================================================
 * observable_demo_c.c — Sparse Observable Construction (Qiskit C API)
 * ============================================================================
 * Demonstrates quantum observable construction using the Qiskit C API.
 * Observables are fundamental to quantum computing: they represent measurable
 * physical quantities (e.g., energy, magnetisation).
 *
 * This example builds the observable:
 *   O = 2·(X₀ ⊗ Y₁ ⊗ Z₂) + 0.5·(Z₀ ⊗ Z₁)
 *
 * Build instructions (UNIX/macOS):
 *   1. Build Qiskit C library:
 *      git clone https://github.com/Qiskit/qiskit.git
 *      cd qiskit && make c
 *
 *   2. Compile:
 *      gcc observable_demo_c.c -o observable_demo \
 *          -I /path/to/qiskit/dist/c/include \
 *          -L /path/to/qiskit/dist/c/lib \
 *          -lqiskit \
 *          -Wl,-rpath,/path/to/qiskit/dist/c/lib
 *
 * References:
 *   - Qiskit C API docs: https://quantum.cloud.ibm.com/docs/en/guides/install-c-api
 *   - Qiskit GitHub:     https://github.com/Qiskit/qiskit
 * ============================================================================
 */

#include <stdio.h>
#include <stdint.h>
#include <qiskit.h>

int main(void) {
    printf("=== Sparse Observable Demo (Qiskit C API) ===\\n\\n");

    uint32_t num_qubits = 10;

    /* ── 1. Allocate a zero observable on 10 qubits ──────────────────── */
    QkObs *obs = qk_obs_zero(num_qubits);
    if (!obs) {
        fprintf(stderr, "ERROR: Failed to allocate observable\\n");
        return 1;
    }

    /* ── 2. Add term:  2·(X₀ Y₁ Z₂) ──────────────────────────────────── */
    {
        QkComplex64 coeff    = {2.0, 0.0};            /* coefficient 2+0i  */
        QkBitTerm   bits[3]  = {QkBitTerm_X,
                                 QkBitTerm_Y,
                                 QkBitTerm_Z};
        uint32_t    idxs[3]  = {0, 1, 2};             /* qubit indices     */
        QkObsTerm   term     = {coeff, 3, bits, idxs, num_qubits};
        qk_obs_add_term(obs, &term);
    }

    /* ── 3. Add term:  0.5·(Z₀ Z₁) ───────────────────────────────────── */
    {
        QkComplex64 coeff   = {0.5, 0.0};
        QkBitTerm   bits[2] = {QkBitTerm_Z, QkBitTerm_Z};
        uint32_t    idxs[2] = {0, 1};
        QkObsTerm   term    = {coeff, 2, bits, idxs, num_qubits};
        qk_obs_add_term(obs, &term);
    }

    /* ── 4. Query properties ─────────────────────────────────────────── */
    printf("Num qubits:  %u\\n",  qk_obs_num_qubits(obs));
    printf("Num terms:   %lu\\n", qk_obs_num_terms(obs));
    char *obs_str = qk_obs_str(obs);
    printf("Observable:\\n  %s\\n\\n", obs_str);
    qk_str_free(obs_str);

    printf("Observable represents: O = 2·(X₀⊗Y₁⊗Z₂) + 0.5·(Z₀⊗Z₁)\\n");
    printf("Use with EstimatorV2 primitive to compute ⟨ψ|O|ψ⟩.\\n");

    /* ── 5. Free memory ──────────────────────────────────────────────── */
    qk_obs_free(obs);

    printf("\\n=== Demo complete ===\\n");
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Summary: The C Language Demo Layer & Compilation Architecture

The C language demo layer (src/c_demos/) provides high-performance, low-level quantum circuit manipulation and observable construction utilizing the native Qiskit C API (qiskit.h). Designed to bridge high-level Python software with near-metal execution, this layer is organized into three primary modules:

  • The C Source Registry (c_source_registry.py): Stores metadata, expected outputs, and source code paths for standalone demonstrations including Bell states (bell_state_c.c), multi-qubit GHZ entanglement chains (ghz_state_c.c), and sparse Pauli-string observable builders (observable_demo_c.c).
  • The C Demo Manager (CDemoManager): A dynamic compilation controller that automatically detects available Qiskit C runtime environments, handles compiler handshakes, and manages execution pathways.
  • Direct C API Primitives: Employs low-level functions such as qk_circuit_new, qk_circuit_gate, qk_obs_zero, and qk_obs_add_term to construct circuits and observables with precise memory control.

Dual Runtime Compilation Pathways

CDemoManager supports two distinct execution paths alongside a graceful fallback when no C library is present:

  1. PATH A (Standalone Shared Library): Compiles standalone C files directly against a custom build of libqiskit.{so,dylib} generated via git clone and make c, utilizing QISKIT_C_LIB environment variables and linker search paths.
  2. PATH B (Bundled Python Extension): Automatically detects headers from standard pip installations (pip install qiskit >= 1.0) via qiskit.capi and links against the Rust-compiled shared library (qiskit._accelerate). To prevent fatal SIGABRT crashes caused by macOS dynamic linker (dyld) restrictions on background worker threads, compilation and execution are offloaded to an isolated child subprocess where dlopen() safely runs on the main thread.

Conclusion

In conclusion, Bob has successfully delivered a comprehensive, production-ready quantum computing demonstration suite that seamlessly bridges high-level Python application logic with low-level C performance. Through the Streamlit graphical interface and Python algorithm layer, users can interactively explore foundational quantum mechanics—ranging from entangled Bell states and exponential query advantages in the Bernstein-Vazirani and Deutsch-Jozsa algorithms to quadratic speedups in Grover's search, Quantum Fourier Transforms, and quantum teleportation. Furthermore, by engineering the C demo layer and CDemoManager, Bob has provided robust dual-compilation pathways that leverage either standalone shared libraries or bundled pip installations (qiskit.capi). Complete with thread-safe child subprocess execution for macOS stability, automated pre-flight checks, sparse observable construction, and extensive test coverage, Bob’s implementation offers an elegant, highly versatile blueprint for exploring utility-scale quantum software development.

Thanks for reading ⚛️

Links & References (stuff to read :))

Top comments (0)