Compare commits
No commits in common. "f3e9e420a71770d4be9c3230a5a651d3026f33ba" and "19e90b3d2186925b9dc7d0b04ece25f01ab3641a" have entirely different histories.
f3e9e420a7
...
19e90b3d21
5 changed files with 73 additions and 119 deletions
|
|
@ -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
0
src/__init__.py
Normal file
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue