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

View file

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