Skip to content

Quantum-Stochastic Computing Hybrid Networks

Combine quantum circuits with stochastic computing neurons. Quantum gates produce measurement probabilities that map naturally to SC bitstream probabilities — no additional encoding needed.

Prerequisites: pip install sc-neurocore pennylane

1. Why quantum + SC?

Quantum computing and stochastic computing share a fundamental representation: probabilities. A qubit measurement yields 0 or 1 with some probability p — exactly the semantics of a stochastic bitstream bit. This makes quantum circuits natural front-ends for SC neural networks.

Quantum output SC interpretation
P( 1⟩) = 0.7
Measurement sequence Bitstream directly
Multi-qubit state Multiple correlated bitstreams

2. Quantum feature encoder

Encode classical data into quantum amplitudes, measure, and feed the measurement probabilities to an SC layer:

import numpy as np

try:
    import pennylane as qml

    n_qubits = 4
    dev = qml.device("default.qubit", wires=n_qubits, shots=256)

    @qml.qnode(dev)
    def quantum_encoder(x):
        """Angle encoding: each feature → RY rotation."""
        for i in range(n_qubits):
            qml.RY(x[i] * np.pi, wires=i)
        # Entangle
        for i in range(n_qubits - 1):
            qml.CNOT(wires=[i, i + 1])
        return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

    # Encode a test vector
    x = np.array([0.3, 0.7, 0.5, 0.9])
    expectations = quantum_encoder(x)
    # Map from [-1, 1] to [0, 1] for SC
    probs = [(e + 1) / 2 for e in expectations]
    print(f"Input: {x}")
    print(f"Quantum probs: {[f'{p:.3f}' for p in probs]}")

except ImportError:
    print("PennyLane not installed — using simulated quantum output")
    probs = [0.35, 0.72, 0.48, 0.91]

3. Quantum → SC pipeline

from sc_neurocore import VectorizedSCLayer

# Quantum encoder outputs n_qubits probabilities
# SC layer processes them
sc_layer = VectorizedSCLayer(n_inputs=4, n_neurons=8, length=256)

quantum_output = np.array(probs)
quantum_output = np.clip(quantum_output, 0.01, 0.99)
sc_result = sc_layer.forward(quantum_output)
print(f"SC layer output: {sc_result}")

4. Variational quantum-SC classifier

A hybrid classifier: parameterised quantum circuit (feature extraction) → SC dense layer (classification):

try:
    n_qubits = 4
    n_layers_q = 2  # quantum circuit depth

    @qml.qnode(dev)
    def variational_circuit(x, params):
        """Parameterised quantum circuit for feature extraction."""
        # Encode data
        for i in range(n_qubits):
            qml.RY(x[i] * np.pi, wires=i)

        # Variational layers
        for l in range(n_layers_q):
            for i in range(n_qubits):
                qml.RY(params[l, i, 0], wires=i)
                qml.RZ(params[l, i, 1], wires=i)
            for i in range(n_qubits - 1):
                qml.CNOT(wires=[i, i + 1])

        return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

    # Initialise parameters
    params = np.random.uniform(-np.pi, np.pi, (n_layers_q, n_qubits, 2))

    # Forward pass
    x_test = np.array([0.2, 0.8, 0.4, 0.6])
    q_out = variational_circuit(x_test, params)
    sc_input = np.clip([(e + 1) / 2 for e in q_out], 0.01, 0.99)
    prediction = sc_layer.forward(np.array(sc_input))
    print(f"Hybrid prediction: {prediction.argmax()}")

except NameError:
    print("Skipping variational circuit (PennyLane not available)")

5. Quantum noise as SC randomness

Quantum measurement is inherently random — instead of using a PRNG to generate bitstreams, use actual quantum random bits:

try:
    @qml.qnode(dev)
    def quantum_random_bits(p, n_shots_implicit=None):
        """Generate random bits with probability p using a qubit."""
        qml.RY(2 * np.arcsin(np.sqrt(p)), wires=0)
        return qml.sample(qml.PauliZ(0))

    # Generate a bitstream with P(1) = 0.7
    samples = quantum_random_bits(0.7)
    # Map from {-1, +1} to {0, 1}
    bitstream = ((np.array(samples) + 1) / 2).astype(int)
    actual_prob = bitstream.mean()
    print(f"Target: 0.700, Actual: {actual_prob:.3f} ({len(bitstream)} bits)")

except NameError:
    print("Skipping quantum RNG (PennyLane not available)")

6. Entanglement-correlated bitstreams

Entangled qubits produce correlated measurement outcomes. SC multiplication of correlated bitstreams gives different results than independent streams — this is a feature, not a bug:

try:
    dev2 = qml.device("default.qubit", wires=2, shots=1000)

    @qml.qnode(dev2)
    def bell_pair():
        """Create maximally entangled Bell state |Φ+⟩."""
        qml.Hadamard(wires=0)
        qml.CNOT(wires=[0, 1])
        return qml.sample(qml.PauliZ(0)), qml.sample(qml.PauliZ(1))

    s0, s1 = bell_pair()
    b0 = ((np.array(s0) + 1) / 2).astype(int)
    b1 = ((np.array(s1) + 1) / 2).astype(int)

    # SC multiply (AND gate)
    product = b0 & b1
    independent_expected = b0.mean() * b1.mean()
    actual = product.mean()
    print(f"P(0): {b0.mean():.3f}, P(1): {b1.mean():.3f}")
    print(f"Independent product: {independent_expected:.3f}")
    print(f"Entangled product:   {actual:.3f}")
    print(f"Correlation boost:   {actual - independent_expected:+.3f}")

except NameError:
    print("Skipping Bell pair demo (PennyLane not available)")

7. Hybrid training loop

Train both quantum parameters and SC weights jointly:

def hybrid_forward(x, q_params, sc_layer_obj):
    """Quantum encoder → SC classifier."""
    try:
        q_out = variational_circuit(x, q_params)
        sc_in = np.clip([(e + 1) / 2 for e in q_out], 0.01, 0.99)
    except NameError:
        # Fallback: simulate quantum encoding with sigmoid
        sc_in = 1 / (1 + np.exp(-x[:4]))
        sc_in = np.clip(sc_in, 0.01, 0.99)

    return sc_layer_obj.forward(np.array(sc_in))

# Training sketch (parameter-shift for quantum, pseudo-gradient for SC)
LR_Q = 0.01   # quantum parameter learning rate
LR_SC = 0.005  # SC weight learning rate
# ... gradient computation omitted for brevity

8. When to use quantum-SC hybrids

Scenario Recommended
Small feature space (< 20 dims) Quantum encoder → SC
Large feature space (> 100 dims) Classical PCA → SC
Correlated features Entangled quantum encoding
Low-power edge deployment SC only (FPGA)
Quantum hardware available Full hybrid
Research/exploration Simulated quantum + SC

9. Resource estimates

# Quantum resources
n_qubits_needed = 4
circuit_depth = 2 * n_qubits  # approximate
shots_per_inference = 256  # matches SC bitstream length

# SC resources (from Tutorial 14)
sc_luts = 128 * 50 + 10 * 128  # rough LUT estimate
print(f"Quantum: {n_qubits_needed} qubits, depth {circuit_depth}, {shots_per_inference} shots")
print(f"SC FPGA: ~{sc_luts} LUTs")
print(f"Total inference time: {shots_per_inference * 100e-9 * 1e6:.1f} μs (quantum) + "
      f"{512 * 10e-9 * 1e6:.1f} μs (SC @ 100 MHz)")

What you learned

  • Quantum measurement probabilities map directly to SC bitstream probabilities
  • Angle encoding (RY gates) converts classical features to qubit amplitudes
  • Variational quantum circuits serve as trainable feature extractors
  • Entangled qubits produce correlated bitstreams (useful for structured computation)
  • Hybrid training: parameter-shift rule (quantum) + pseudo-gradient (SC)
  • Practical only for small feature spaces until quantum hardware scales

8. Noise-Aware Simulation (v3.11)

SC-NeuroCore includes a calibrated IBM Heron r2 noise model:

from sc_neurocore.quantum.noise_models import HeronR2NoiseModel, HeronR2NoiseParams
import numpy as np

model = HeronR2NoiseModel()

# Apply depolarizing noise to a pure state density matrix
pure_state = np.array([[1, 0], [0, 0]], dtype=complex)  # |0⟩
noisy = model.apply_single_qubit_noise(pure_state)
purity = np.real(np.trace(noisy @ noisy))
print(f"Purity after noise: {purity:.6f}")  # < 1.0

# Asymmetric readout error
results = [model.apply_readout_noise(0) for _ in range(1000)]
flip_rate = sum(results) / len(results)
print(f"0→1 flip rate: {flip_rate:.3f} (expected ~{model.params.readout_0to1})")

9. Parameter-Shift Gradient Optimization (v3.11)

Exact gradient computation for parameterized quantum circuits:

from sc_neurocore.quantum.param_shift import parameter_shift_gradient
import numpy as np

# Gradient of sin(θ) at θ=0.5
def f(params):
    return np.sin(params[0])

grad = parameter_shift_gradient(f, np.array([0.5]))
print(f"Computed gradient: {grad[0]:.6f}")
print(f"Exact cos(0.5):   {np.cos(0.5):.6f}")

10. End-to-End VQE Pipeline (v3.11)

Variational Quantum Eigensolver on a 2-qubit system:

from sc_neurocore.quantum.hybrid_pipeline import HybridQuantumClassicalPipeline

pipe = HybridQuantumClassicalPipeline(n_qubits=2, n_layers=1)
history, optimal_params = pipe.train(n_steps=50, lr=0.05)

print(f"Initial ⟨Z⊗Z⟩: {history[0]:.4f}")
print(f"Final   ⟨Z⊗Z⟩: {history[-1]:.4f}")
print(f"Optimal params: {optimal_params}")

# Evaluate with optimized parameters
energy = pipe.evaluate(optimal_params)
print(f"Ground state energy estimate: {energy:.4f}")

Next steps

  • Implement quantum kernel estimation → SC classification
  • Compare quantum-SC vs classical-SC on XOR / iris datasets
  • Use IBM Qiskit backend instead of PennyLane simulator
  • Explore quantum error mitigation for noisy SC bitstreams
  • Run VQE with Heron r2 noise model to study fidelity degradation