I was always some way skeptical with using AI (LLMs in this case) to create code. But don’t get me wrong, I love AI and always have the urge to use it more frequently, I just can’t afford for better models such as claude or newer versions of GPT, neither enough hardware to run GLM or Kimi.
Recently, I saw the last LiveOverflow video and it hit me in a different way. The way Fabian showed his urge to explore LLMs made me want too. I recommend deeply you guys to watch this video yourselves.

Anyway, I’m not very much into security, even though it’s another topic I find really intesresting, I’m still a courious guy in the field of research, mostly in AI and quantum computing.
Nowdays, I’m facing big problems with my QCOP project. At first, I used this code to generate my dataset:
"""Generate random circuits by hand"""
import random
from math import pi
from abc import ABC, abstractmethod
from qiskit import QuantumCircuit
from qiskit.circuit import Gate as QiskitGate
from qiskit.circuit.library import (
ZGate,
XGate,
HGate,
RYGate,
CXGate,
CZGate,
IGate
)
class Gate(ABC):
"""interface for gates"""
@classmethod
@abstractmethod
def get_random_gate(cls) -> QiskitGate:
pass
class SingleQubitGate(Gate):
"""Handle single qubit gates"""
rotation_gates = [RYGate]
simple_gates = [ZGate, XGate, HGate, IGate]
all_gates = [*rotation_gates, *simple_gates]
@classmethod
def get_random_gate(cls) -> QiskitGate:
"""Return an instance of a single qubit gate ready to use"""
gate = random.choice(cls.all_gates)
if gate in cls.rotation_gates:
param = random.uniform(0, 2 * pi)
return gate(param)
return gate()
class MultiQubitGate(Gate):
"""Handle multi qubit gates"""
gates = [CXGate, CZGate]
@classmethod
def get_random_gate(cls) -> QiskitGate:
"""Return an instance of a multi-qubit gate ready to use"""
gate = random.choice(cls.gates)
return gate()
def generate_circuit(n_qubits:int, total_gates:int) -> QuantumCircuit:
"""Generate a circuit based on the amount of gates"""
qc = QuantumCircuit(n_qubits)
for _ in range(total_gates):
if random.random() < 0.5:
qubit = [random.randint(0, n_qubits - 1)]
gate = SingleQubitGate.get_random_gate()
qc.append(gate, qubit)
else:
qubits = random.sample(range(n_qubits), 2)
gate = MultiQubitGate.get_random_gate()
qc.append(gate, qubits)
if random.random() < 0.1:
qc.barrier()
return qc
def get_random_circuit(n_qubits: int, total_gates: int) -> QuantumCircuit:
"""Generate a random circuit based on the amount of qubits and gates."""
total_gates = random.randint(0, total_gates)
return generate_circuit(n_qubits,total_gates)
Which generated pretty different circuits, But I had some issues with the circuit results. The circuit images were different, but the quantum states they generate was almost identical to each other. This way, the model was converging pretty fast, but overfitting since he would understand that every circuit has pretty much the same quantum state.
I was really pissed off by this code. I tried changing some factors, but it was not enough.
At the end, I tried using the built-in random circuit functions from qiskit. But the code was not what I needed. Currently, it has not enough tweaks that would allow me to generate the circuits I wanted to.
So, I got the inspiration from the source code, and started modifying it as I wished, adding barriers, the correct gates, changing probability and the general code structure. The final code was something like:
"""Generate random circuits by hand"""
from typing import List
from qiskit import QuantumCircuit
from qiskit.circuit.library import RYGate, XGate, ZGate, HGate, IGate, CXGate, CZGate, SwapGate
import numpy as np
from utils.constants import DEFAULT_RANDOM_SEED
class RandomCircuit:
"""Code based on https://github.com/Qiskit/qiskit/blob/stable/2.5/qiskit/circuit/random/utils.py#L685-L753"""
def __init__(self, seed:int=37):
self._all_gates = {
'x': lambda : XGate(),
'z': lambda : ZGate(),
'h': lambda : HGate(),
'id': lambda : IGate(),
'cx': lambda : CXGate(),
'cz': lambda : CZGate(),
'swap': lambda : SwapGate(),
'ry':lambda theta: RYGate(theta)
}
self._two_qubit = ['cx', 'cz', 'swap']
self._with_parameters = ['ry']
self._low_param = 0
self._high_param = 2*np.pi
self._rng = np.random.default_rng(seed)
def _get_angle(self) -> float:
return self._rng.uniform(low=self._low_param,high=self._high_param,size=None)
def _gate_num_qubits(self, gate:str) -> int:
return int(gate in self._two_qubit)+1
def _get_random_gates(self,num_gates:int)-> list[str]:
return self._rng.choice(list(self._all_gates.keys()), num_gates)
def _add_to_circuit(self, gates:List[str], qc:QuantumCircuit) -> None:
num_qubits = qc.num_qubits
for gate in gates:
pos = self._rng.choice(range(num_qubits), self._gate_num_qubits(gate), replace=False).tolist()
qc.append(
self._all_gates[gate]()
if gate not in self._with_parameters
else self._all_gates[gate](self._get_angle())
, pos, copy=False)
def _add_barrier_at_the_end(self,qc:QuantumCircuit) -> None:
if self._rng.random() <= 0.3:
qc.barrier()
def get_random_circuit(self, num_gates:int, num_qubits:int, add_barrier:bool=True, max_layers:int=5, min_layers:int=0) -> QuantumCircuit:
qc = QuantumCircuit(num_qubits)
gates = self._get_random_gates(num_gates)
num_layers = self._rng.integers(min_layers,max_layers,1)[0]
if not num_layers:
self._add_to_circuit(gates, qc)
self._add_barrier_at_the_end(qc)
else:
c = num_gates
for _ in range(num_layers):
layer_gates = self._rng.integers(0,c,1)[0]
if not layer_gates:
continue
selected_gates = self._rng.choice(gates,layer_gates,replace=False).tolist()
self._add_to_circuit(selected_gates, qc)
c -= len(selected_gates)
if c <= 0:
self._add_barrier_at_the_end(qc)
break
qc.barrier()
if c > 0:
selected_gates = self._rng.choice(gates,c,replace=False).tolist()
self._add_to_circuit(selected_gates, qc)
self._add_barrier_at_the_end(qc)
return qc
def get_random_circuit(n_qubits: int, total_gates: int) -> QuantumCircuit:
"""Generate a random circuit based on the amount of qubits and gates."""
rc = RandomCircuit(seed=DEFAULT_RANDOM_SEED)
total_gates = np.random.randint(0, total_gates)
return rc.get_random_circuit(total_gates, n_qubits, max_layers=total_gates)
However, another problem appeard. The generated circuits were not sparse enough. Most part of them would lead to the same results. I did a small experiment to identify how bad it was, and:

Well, from $60140$ images, only $620$ were unique. That’s pretty much $1\text{\%}$ of all images. Which is really bad.
The light
After watch the LiveOverflow video, I decided to use more AI to help me in my daily life.
I gave a prompt to gemini, and right in the first show It generated the code:
from typing import List, Optional
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit.library import RYGate, XGate, ZGate, HGate, IGate, CXGate, CZGate, SwapGate
class EnhancedRandomCircuit:
def __init__(self, seed: Optional[int] = None):
self._all_gates = {
'x': lambda: XGate(),
'z': lambda: ZGate(),
'h': lambda: HGate(),
'id': lambda: IGate(),
'cx': lambda: CXGate(),
'cz': lambda: CZGate(),
'swap': lambda: SwapGate(),
'ry': lambda theta: RYGate(theta)
}
self._single_qubit = ['x', 'z', 'h', 'id', 'ry']
self._two_qubit = ['cx', 'cz', 'swap']
self._with_parameters = ['ry']
self._rng = np.random.default_rng(seed)
def _get_angle(self) -> float:
return float(self._rng.uniform(0, 2 * np.pi))
def _select_target_qubits(self, num_qubits: int, num_targets: int, topology: str) -> List[int]:
"""Selects qubit targets based on sampled connectivity topology."""
if num_targets == 1 or topology == "global" or num_qubits <= 2:
return self._rng.choice(num_qubits, num_targets, replace=False).tolist()
# Local / Nearest-Neighbor topology (creates sparse entanglement graphs)
if topology == "local":
q1 = int(self._rng.choice(num_qubits))
# Choose adjacent qubit in a ring topology
q2 = (q1 + int(self._rng.choice([-1, 1]))) % num_qubits
return [q1, q2]
return self._rng.choice(num_qubits, num_targets, replace=False).tolist()
def get_random_circuit(
self,
num_gates: int,
num_qubits: int,
max_layers: int = 5
) -> QuantumCircuit:
qc = QuantumCircuit(num_qubits)
# 1. Sample macro-style for this specific circuit instance
p_two_qubit = self._rng.beta(0.5, 1.5) # Biased toward single-qubit or two-qubit
p_identity = self._rng.choice([0.0, 0.1, 0.3, 0.5]) # Forces active vs highly sparse qubits
topology = self._rng.choice(["global", "local", "local"]) # Biased toward sparse spatial connectivity
# 2. Build conditional gate probability distribution
single_weight = (1.0 - p_two_qubit) * (1.0 - p_identity) / (len(self._single_qubit) - 1)
weights = {}
for g in self._single_qubit:
weights[g] = p_identity if g == 'id' else single_weight
for g in self._two_qubit:
weights[g] = p_two_qubit / len(self._two_qubit)
gate_names = list(weights.keys())
gate_probs = np.array(list(weights.values()), dtype=np.float64)
gate_probs /= np.sum(gate_probs) # Normalize
# 3. Distribute gates non-uniformly across layers
num_layers = int(self._rng.integers(1, max_layers + 1))
# Dirichlet distribution for non-uniform gate density per layer
layer_splits = self._rng.dirichlet(np.ones(num_layers) * 0.5)
gates_per_layer = self._rng.multinomial(num_gates, layer_splits)
for l_idx, count in enumerate(gates_per_layer):
if count == 0:
continue
selected_gates = self._rng.choice(gate_names, size=count, p=gate_probs)
for g_name in selected_gates:
n_q = 2 if g_name in self._two_qubit else 1
pos = self._select_target_qubits(num_qubits, n_q, topology)
gate_obj = (
self._all_gates[g_name](self._get_angle())
if g_name in self._with_parameters
else self._all_gates[g_name]()
)
qc.append(gate_obj, pos, copy=False)
if l_idx < num_layers - 1 and self._rng.random() < 0.4:
qc.barrier()
return qc
def get_random_circuit(n_qubits: int, total_gates: int) -> QuantumCircuit:
"""Thread-safe dataset generation function."""
# Seed locally per invocation to avoid thread lock overhead while preserving thread-safety
local_seed = np.random.randint(0, 2**31 - 1)
rc = EnhancedRandomCircuit(seed=local_seed)
# Vary max gates dynamically (from lightweight to full depth)
gates = int(np.random.randint(1, max(2, total_gates + 1)))
layers = int(np.random.randint(1, 8))
return rc.get_random_circuit(num_gates=gates, num_qubits=n_qubits, max_layers=layers)
I used a jupyter notebook to examine the overall distribution, and look at this:

For $10000$ circuit approximatelly $98\text{\%}$ were unique, and for $30000$ $\approx 97\text{\%}$. THAT’S AMAZING!!!!!

Now we can generate way better circuits.
I let it run sometime and at the end it generated: $60233$ unique circuits. That’s approximatelly a $97\text{\%}$ improvement overall.
I also ensured that the outcomes were sparse as possible:

I also cleaned a bit the equal superposition, just as a way to avoid overfitting.

Conclusion
Well, so far so good! Now it’s time to train the model and check the results.
I’ll be right back soon!
For interested people in the dataset, it’s available in its raw version at: https://huggingface.co/datasets/Dpbm/quantum-circuits and https://www.kaggle.com/datasets/dpbmanalysis/quantum-circuit-images.
