Compare commits

..

No commits in common. "f3e9e420a71770d4be9c3230a5a651d3026f33ba" and "19e90b3d2186925b9dc7d0b04ece25f01ab3641a" have entirely different histories.

5 changed files with 73 additions and 119 deletions

View file

@ -43,7 +43,6 @@
ppkgs.tqdm
ppkgs.qiskit
ppkgs.qiskit-aer
ppkgs.matplotlib
]))
];
};

0
src/__init__.py Normal file
View file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -3,42 +3,27 @@
# "Genetic optimization of ansatz expressibility for enhanced variational quantum algorithm performance"
import random
import matplotlib.pyplot as plt
from quantum_circuit import (
Gate,
GateType,
QuantumCircuit,
circ_from_layers,
sample_random_generator,
)
from qas_flow import Stream
from quantum_circuit import (Gate, GateType, QuantumCircuit, circ_from_layers,
sample_random_generator, single_typ)
DEPTH: int = 6
QUBITS: int = 6
GENERATIONS: int = 40
GENERATION_SIZE: int = 60
PARENT_AMOUNT: int = 10
MUTATION_RATE: float = 0.1
DEPTH = 20
QUBITS = 5
GENERATIONS = 20
GENERATION_SIZE = 20
PARENT_AMOUNT = 5
MUTATION_RATE = 0.1
gate_set: list[GateType] = [
GateType.H,
GateType.RX,
GateType.RY,
GateType.RZ,
GateType.CRX,
GateType.CX,
]
def plot_best_circuits(best_circuits: list[QuantumCircuit]) -> None:
fig, ax = plt.subplots()
ax.plot([-circ.expressibility for circ in best_circuits])
fig.savefig("best_circuits.png")
def main() -> None:
seed_rng: random.Random = random.Random(1020381)
def main():
seed_rng = random.Random(1020381)
initial_population: list[QuantumCircuit] = (
Stream(sample_random_generator(random.Random(101020), QUBITS, DEPTH, gate_set))
Stream(sample_random_generator(random.Random(101020), QUBITS, DEPTH))
.apply(lambda circ: print(circ))
.apply(
lambda circ: circ.expressibility_estimate(
@ -54,8 +39,6 @@ def main() -> None:
main_rng = random.Random(2837175)
best_circuits: list[QuantumCircuit] = []
for generation in range(GENERATIONS):
print(f"starting generation {generation}")
population.sort(key=lambda qc: qc.expressibility, reverse=True)
@ -72,38 +55,28 @@ def main() -> None:
layer = child_layers[layer_idx]
gate_idx = main_rng.randrange(len(layer))
old_gate = child_layers[layer_idx][gate_idx]
if old_gate.single():
match old_gate.type:
case GateType.H | GateType.RX | GateType.RY | GateType.RZ:
child_layers[layer_idx][gate_idx] = Gate(
main_rng.choice(
[gate for gate in gate_set if single_typ(gate)]
[GateType.H, GateType.RX, GateType.RY, GateType.RZ]
),
old_gate.qubits,
old_gate.param_idx,
)
else:
case GateType.CRX | GateType.CX:
child_layers[layer_idx][gate_idx] = Gate(
old_gate.typ,
(old_gate.qubits[1], old_gate.qubits[0]),
old_gate.type,
tuple(old_gate.qubits[::-1]),
old_gate.param_idx,
)
case _:
print(f"unhandled gate: {old_gate}")
child = circ_from_layers(child_layers, QUBITS)
child.expressibility_estimate(2000, seed_rng.randint(1000, 1000000000))
offspring.append(child)
offspring.sort(key=lambda qc: qc.expressibility, reverse=True)
if population[0].expressibility > offspring[0].expressibility:
print(f"best parent > best child")
best_circuits.append(population[0])
else:
print(f"best child > best parent")
best_circuits.append(offspring[0])
population = offspring
plot_best_circuits(best_circuits)
plt.show()
if __name__ == "__main__":
main()

View file

@ -1,12 +1,11 @@
from dataclasses import dataclass
import math
import random
from dataclasses import dataclass
from enum import IntEnum
from typing import override
import numpy as np
from qiskit import QuantumCircuit as QiskitCircuit
from qiskit import transpile
from enum import IntEnum
from qiskit import QuantumCircuit as QiskitCircuit, transpile
from qiskit.circuit import ParameterVector, ParameterVectorElement
from qiskit_aer import AerSimulator
@ -26,40 +25,21 @@ class GateType(IntEnum):
CZ = 8
CRX = 9
H = 10
I = 11
def single_typ(typ: GateType) -> bool:
match typ:
case GateType.I | GateType.H | GateType.RX | GateType.RY | GateType.RZ:
return True
case (
GateType.RXX
| GateType.RYY
| GateType.RZZ
| GateType.CX
| GateType.CZ
| GateType.CRX
):
return False
@dataclass(frozen=True)
class Gate:
typ: GateType
type: GateType
qubits: int | tuple[int, int]
param_idx: int
def to_json(self):
return {
"type": int(self.typ),
"type": int(self.type),
"qubits": self.qubits,
"param_idx": self.param_idx,
}
def single(self) -> bool:
return single_typ(self.typ)
def haar_fidelity_pdf(F: np.ndarray, d: int) -> np.ndarray:
# p(F) = (d-1) * (1-F)^(d-2), for F in [0,1]
@ -91,7 +71,7 @@ class QuantumCircuit:
path_counts = [1 for _ in range(self.qubits)]
for layer in self.gates:
for gate in layer:
if gate.typ <= 3 or isinstance(gate.qubits, int):
if gate.type <= 3 or isinstance(gate.qubits, int):
continue
(q1, q2) = gate.qubits
# same logic as your existing file
@ -119,35 +99,33 @@ class QuantumCircuit:
for layer in self.gates:
for gate in layer:
if gate.typ == GateType.RX:
if gate.type == GateType.RX:
theta = thetas[gate.param_idx]
qc.rx(theta, gate.qubits)
elif gate.typ == GateType.RY:
elif gate.type == GateType.RY:
theta = thetas[gate.param_idx]
qc.ry(theta, gate.qubits)
elif gate.typ == GateType.RZ:
elif gate.type == GateType.RZ:
theta = thetas[gate.param_idx]
qc.rz(theta, gate.qubits)
elif gate.typ == GateType.H:
elif gate.type == GateType.H:
qc.h(gate.qubits)
elif gate.typ == GateType.I:
qc.id(gate.qubits)
elif gate.typ == GateType.RXX:
elif gate.type == GateType.RXX:
theta = thetas[gate.param_idx]
qc.rxx(theta, *gate.qubits)
elif gate.typ == GateType.RYY:
elif gate.type == GateType.RYY:
theta = thetas[gate.param_idx]
qc.ryy(theta, *gate.qubits)
elif gate.typ == GateType.RZZ:
elif gate.type == GateType.RZZ:
theta = thetas[gate.param_idx]
qc.rzz(theta, *gate.qubits)
elif gate.typ == GateType.CX:
elif gate.type == GateType.CX:
qc.cx(*gate.qubits)
elif gate.typ == GateType.CRX:
elif gate.type == GateType.CRX:
theta = thetas[gate.param_idx]
qc.crx(theta, *gate.qubits)
else:
raise ValueError(f"Unknown gate type: {gate.typ}")
raise ValueError(f"Unknown gate type: {gate.type}")
return qc, thetas
def to_qiskit_for_expressibility(self) -> tuple[QiskitCircuit, ParameterVector]:
@ -227,7 +205,7 @@ class QuantumCircuit:
idx = 0
for gate in layer:
match gate.typ:
match gate.type:
case GateType.RX:
strs[gate.qubits] = strs[gate.qubits][:-2] + "RX"
case GateType.RY:
@ -333,23 +311,23 @@ def circ_from_layers(layers: list[list[Gate]], qubits: int) -> QuantumCircuit:
for layer in layers:
new_layer = []
for gate in layer:
match gate.typ:
match gate.type:
case GateType.H:
new_layer.append(Gate(gate.typ, gate.qubits, params))
new_layer.append(Gate(gate.type, gate.qubits, params))
total_single += 1
case GateType.RX | GateType.RY | GateType.RZ:
new_layer.append(Gate(gate.typ, gate.qubits, params))
new_layer.append(Gate(gate.type, gate.qubits, params))
params += 1
total_single += 1
case GateType.RXX | GateType.RYY | GateType.RZZ:
new_layer.append(Gate(gate.typ, gate.qubits, params))
new_layer.append(Gate(gate.type, gate.qubits, params))
params += 1
total_double += 1
case GateType.CX | GateType.CZ:
new_layer.append(Gate(gate.typ, gate.qubits, params))
new_layer.append(Gate(gate.type, gate.qubits, params))
total_double += 1
case GateType.CRX:
new_layer.append(Gate(gate.typ, gate.qubits, params))
new_layer.append(Gate(gate.type, gate.qubits, params))
params += 1
total_double += 1
gates.append(new_layer)
@ -357,7 +335,7 @@ def circ_from_layers(layers: list[list[Gate]], qubits: int) -> QuantumCircuit:
def sample_circuit_random(
rng: random.Random, qubits: int, depth: int, gate_types: list[GateType]
rng: random.Random, qubits: int, depth: int
) -> QuantumCircuit:
params = 0
total_single = 0
@ -370,10 +348,16 @@ def sample_circuit_random(
for loc in range(qubits):
if loc in used_qubits:
continue
if loc + 1 < qubits:
gate_type = rng.choice(gate_types)
else:
gate_type = rng.choice([typ for typ in gate_types if single_typ(typ)])
gate_type = rng.choice(
[
GateType.RX,
GateType.RY,
GateType.RZ,
GateType.CX,
GateType.CRX,
GateType.H,
]
)
match gate_type:
case GateType.H:
layer.append(Gate(gate_type, loc, params))
@ -384,13 +368,13 @@ def sample_circuit_random(
params += 1
total_single += 1
used_qubits.add(loc)
case GateType.RXX | GateType.RYY | GateType.RZZ:
case GateType.RXX | GateType.RYY | GateType.RZZ if loc + 1 < qubits:
layer.append(Gate(gate_type, (loc, loc + 1), params))
params += 1
total_double += 1
used_qubits.add(loc)
used_qubits.add(loc + 1)
case GateType.CX | GateType.CZ:
case GateType.CX | GateType.CZ if loc + 1 < qubits:
layer.append(
Gate(
gate_type,
@ -401,7 +385,7 @@ def sample_circuit_random(
total_double += 1
used_qubits.add(loc)
used_qubits.add(loc + 1)
case GateType.CRX:
case GateType.CRX if loc + 1 < qubits:
layer.append(
Gate(
gate_type,
@ -420,8 +404,6 @@ def sample_circuit_random(
return QuantumCircuit(qubits, gates, total_single, total_double, params)
def sample_random_generator(
rng: random.Random, qubits: int, depth: int, gates: list[GateType]
):
def sample_random_generator(rng: random.Random, qubits: int, depth: int):
while True:
yield sample_circuit_random(rng, qubits, depth, gates)
yield sample_circuit_random(rng, qubits, depth)