Compare commits
2 commits
526d615d9b
...
93deb42c27
| Author | SHA1 | Date | |
|---|---|---|---|
| 93deb42c27 | |||
| e774575604 |
9 changed files with 379 additions and 2 deletions
|
|
@ -1,7 +1,13 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
from ga_qas import GeneticAlgorithmSettings
|
||||||
|
from qd_qas import QualityDiversitySettings
|
||||||
|
from settings import QuantumArchitectureSearchSettings
|
||||||
|
from tf_qas import TrainingFreeSettings
|
||||||
|
|
||||||
|
|
||||||
class SearchStrategy(Enum):
|
class SearchStrategy(Enum):
|
||||||
TFQAS = 1
|
TFQAS = 1
|
||||||
|
|
@ -9,8 +15,7 @@ class SearchStrategy(Enum):
|
||||||
QDQAS = 3
|
QDQAS = 3
|
||||||
|
|
||||||
|
|
||||||
def main(search_strategy: SearchStrategy):
|
def main(search_settings: QualityDiversitySettings | TrainingFreeSettings | GeneticAlgorithmSettings):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
8
src/ga_qas/__init__.py
Normal file
8
src/ga_qas/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from settings import QuantumArchitectureSearchSettings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class QualityDiversitySettings(QuantumArchitectureSearchSettings):
|
||||||
|
initial_population_size: int
|
||||||
28
src/qd_qas/__init__.py
Normal file
28
src/qd_qas/__init__.py
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable
|
||||||
|
|
||||||
|
from settings import QuantumArchitectureSearchSettings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class QualityDiversitySettings(QuantumArchitectureSearchSettings):
|
||||||
|
"""
|
||||||
|
Hyperparameters for Quality Diversity based Quantum Architecture Search
|
||||||
|
"""
|
||||||
|
|
||||||
|
initial_population_size: int
|
||||||
|
"""How many (random) circuits should be generated at the beginning"""
|
||||||
|
|
||||||
|
offspring_size: int
|
||||||
|
"""How many offspring should be generated from the previous generation"""
|
||||||
|
|
||||||
|
generation_size: int
|
||||||
|
"""How many of the previous generation offspring become a parent for the next generation"""
|
||||||
|
|
||||||
|
generation_count: int
|
||||||
|
"""How many generations to optimize for"""
|
||||||
|
|
||||||
|
mutation_rate: float
|
||||||
|
"""for each gate in a circuit from the offspring perform a mutation if random [0, 1) < mutation_rate"""
|
||||||
|
|
||||||
|
cost_function: Callable[[ParametrizedQuantumCircuit], float]
|
||||||
146
src/quantum_circuit/__init__.py
Normal file
146
src/quantum_circuit/__init__.py
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
import enum
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from qiskit import QuantumCircuit
|
||||||
|
from qiskit.circuit.parametervector import ParameterVector
|
||||||
|
|
||||||
|
from quantum_circuit.proxy_config import ProxyConfig
|
||||||
|
from quantum_circuit.qiskit_helpers import build_qiskit_circ
|
||||||
|
|
||||||
|
from .proxies import (calculate_entanglement, calculate_expressivity,
|
||||||
|
calculate_fidelity)
|
||||||
|
|
||||||
|
|
||||||
|
class QuantumType(enum.Enum):
|
||||||
|
Identity = enum.auto()
|
||||||
|
Hadamard = enum.auto()
|
||||||
|
X = enum.auto()
|
||||||
|
RX = enum.auto()
|
||||||
|
RXX = enum.auto()
|
||||||
|
Y = enum.auto()
|
||||||
|
RY = enum.auto()
|
||||||
|
RYY = enum.auto()
|
||||||
|
Z = enum.auto()
|
||||||
|
RZ = enum.auto()
|
||||||
|
RZZ = enum.auto()
|
||||||
|
CX = enum.auto()
|
||||||
|
CRX = enum.auto()
|
||||||
|
CZ = enum.auto()
|
||||||
|
|
||||||
|
def is_single_qubit(self):
|
||||||
|
return self in {
|
||||||
|
QuantumType.Identity,
|
||||||
|
QuantumType.Hadamard,
|
||||||
|
QuantumType.X,
|
||||||
|
QuantumType.RX,
|
||||||
|
QuantumType.Y,
|
||||||
|
QuantumType.RY,
|
||||||
|
QuantumType.Z,
|
||||||
|
QuantumType.RZ,
|
||||||
|
}
|
||||||
|
|
||||||
|
def is_parameterized(self):
|
||||||
|
return self in {
|
||||||
|
QuantumType.RX,
|
||||||
|
QuantumType.RXX,
|
||||||
|
QuantumType.RY,
|
||||||
|
QuantumType.RYY,
|
||||||
|
QuantumType.RZ,
|
||||||
|
QuantumType.RZZ,
|
||||||
|
QuantumType.CRX,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class QuantumGate:
|
||||||
|
type: QuantumType
|
||||||
|
qubits: tuple[int] | tuple[int, int]
|
||||||
|
|
||||||
|
|
||||||
|
class ParametrizedQuantumCircuit:
|
||||||
|
"""
|
||||||
|
A Quantum circuit representation to use in the Quantum Architecture Searches.
|
||||||
|
It has methods for various proxies and stores the results as well, to have everything close.
|
||||||
|
Allows easy access to the gates inside so it can be used by functions that use it.
|
||||||
|
"""
|
||||||
|
|
||||||
|
qubits: int
|
||||||
|
"""How many qubits does the circuit contain"""
|
||||||
|
|
||||||
|
gates: list[list[QuantumGate]]
|
||||||
|
"""Gates in the quantum circuit, organised as a list of layers of gates."""
|
||||||
|
|
||||||
|
parameters: int
|
||||||
|
"""How many parameters are used the circuit"""
|
||||||
|
|
||||||
|
_expressivity: float | None = None
|
||||||
|
"""
|
||||||
|
Expressivity of the circuit, following the definition from {THE PAPER}
|
||||||
|
Use `expressivity` to get the value
|
||||||
|
""" # TODO: link paper
|
||||||
|
|
||||||
|
_entanglement: float | None = None
|
||||||
|
"""
|
||||||
|
Entanglement of the circuit, following the definition from {THE PAPER}
|
||||||
|
Use `entanglement` to get the value
|
||||||
|
""" # TODO: link paper
|
||||||
|
|
||||||
|
_fidelity: float | None = None
|
||||||
|
"""
|
||||||
|
Approximated fidelity of the circuit
|
||||||
|
Use `fidelity` to get the value
|
||||||
|
"""
|
||||||
|
|
||||||
|
_qiskit_circ: tuple[QuantumCircuit, ParameterVector] | None = None
|
||||||
|
|
||||||
|
def __init__(self, qubits: int):
|
||||||
|
"""
|
||||||
|
Initialise an empty circuit for the passed amount of qubits
|
||||||
|
"""
|
||||||
|
self.qubits = qubits
|
||||||
|
self.gates = []
|
||||||
|
|
||||||
|
def append_layer(self, layer: list[QuantumGate]):
|
||||||
|
"""
|
||||||
|
Add a layer to the end of the existing circuit in-place
|
||||||
|
"""
|
||||||
|
for gate in layer:
|
||||||
|
if gate.type.is_parameterized():
|
||||||
|
self.parameters += 1
|
||||||
|
self.gates.append(layer)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def circ(self) -> tuple[QuantumCircuit, ParameterVector]:
|
||||||
|
"""
|
||||||
|
Qiskit circuit version to be used for simulations etc.
|
||||||
|
"""
|
||||||
|
if self._qiskit_circ is None:
|
||||||
|
self._qiskit_circ = build_qiskit_circ(self)
|
||||||
|
|
||||||
|
return self._qiskit_circ
|
||||||
|
|
||||||
|
def expressivity(self, config: ProxyConfig) -> float:
|
||||||
|
"""
|
||||||
|
Expressivity of the circuit, following the definition from {THE PAPER}
|
||||||
|
""" # TODO: Link Paper
|
||||||
|
if self._expressivity is None:
|
||||||
|
self._expressivity = float(calculate_expressivity(self, config)[0])
|
||||||
|
return self._expressivity
|
||||||
|
|
||||||
|
def entanglement(self, config: ProxyConfig) -> float:
|
||||||
|
"""
|
||||||
|
Entanglement of the circuit, following the definition from {THE PAPER}
|
||||||
|
""" # TODO: Link Paper
|
||||||
|
if self._entanglement is None:
|
||||||
|
self._entanglement = float(calculate_entanglement(self, config)[0])
|
||||||
|
|
||||||
|
return self._entanglement
|
||||||
|
|
||||||
|
def fidelity(self, config: ProxyConfig) -> float:
|
||||||
|
"""
|
||||||
|
Approximated fidelity of the circuit
|
||||||
|
"""
|
||||||
|
if self._fidelity is None:
|
||||||
|
self._fidelity = float(calculate_fidelity(self, config)[0])
|
||||||
|
|
||||||
|
return self._fidelity
|
||||||
94
src/quantum_circuit/proxies.py
Normal file
94
src/quantum_circuit/proxies.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
from itertools import pairwise
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
from numpy.typing import NDArray
|
||||||
|
from qiskit import transpile
|
||||||
|
from qiskit_aer.backends.aer_simulator import AerSimulator
|
||||||
|
from qiskit_aer.backends.aerbackend import AerBackend
|
||||||
|
from scipy import stats
|
||||||
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from quantum_circuit import ParametrizedQuantumCircuit
|
||||||
|
from quantum_circuit.proxy_config import ProxyConfig
|
||||||
|
|
||||||
|
|
||||||
|
def single_circuit_param_fidelity(circ: ParametrizedQuantumCircuit, samples: int, backend: AerBackend) -> float:
|
||||||
|
qc, thetas = circ.circ
|
||||||
|
qc.save_statevector()
|
||||||
|
|
||||||
|
tqc = transpile(qc, backend)
|
||||||
|
|
||||||
|
number_of_initial_circuits = 2 * samples
|
||||||
|
|
||||||
|
params: NDArray[np.float64] = np.random.uniform(-np.pi, np.pi, (len(thetas), number_of_initial_circuits))
|
||||||
|
|
||||||
|
binds = [{param: params[idx] for idx, param in enumerate(thetas.params)}]
|
||||||
|
|
||||||
|
job = backend.run([tqc], parameter_binds=binds)
|
||||||
|
result = job.result()
|
||||||
|
|
||||||
|
sv = np.array(
|
||||||
|
[np.asarray(result.get_statevector(i), dtype=np.complex128) for i in range(number_of_initial_circuits)]
|
||||||
|
)
|
||||||
|
left = sv[:samples]
|
||||||
|
right = sv[samples:]
|
||||||
|
|
||||||
|
return np.power(np.absolute((left * right.conjugate()).sum(-1)), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_fidelity(
|
||||||
|
circs: list[ParametrizedQuantumCircuit] | ParametrizedQuantumCircuit, config: ProxyConfig
|
||||||
|
) -> NDArray[np.float64]:
|
||||||
|
if isinstance(circs, ParametrizedQuantumCircuit):
|
||||||
|
circs = [circs]
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_expressivity(
|
||||||
|
circs: list[ParametrizedQuantumCircuit] | ParametrizedQuantumCircuit, config: ProxyConfig
|
||||||
|
) -> NDArray[np.float64]:
|
||||||
|
tqdm_depth = 0
|
||||||
|
if isinstance(circs, ParametrizedQuantumCircuit):
|
||||||
|
circs = [circs]
|
||||||
|
|
||||||
|
qubits = config.qubits
|
||||||
|
samples = config.expressivity_samples
|
||||||
|
bin_count = config.expressivity_bins
|
||||||
|
force_recalculate = config.force_recalculate
|
||||||
|
bins: np.ndarray[tuple[int], np.dtype[np.float64]] = np.linspace(0.0, 1.0, bin_count + 1)
|
||||||
|
|
||||||
|
haar_power = (1 << qubits) - 1
|
||||||
|
lower_edges: np.ndarray[tuple[int], np.dtype[np.float64]] = -1.0 * np.power(1.0 - bins[:-1], haar_power)
|
||||||
|
higher_edges: np.ndarray[tuple[int], np.dtype[np.float64]] = -1.0 * np.power(1.0 - bins[1:], haar_power)
|
||||||
|
haar_values: np.ndarray[tuple[int], np.dtype[np.float64]] = higher_edges - lower_edges
|
||||||
|
|
||||||
|
backend = AerSimulator(method="statevector")
|
||||||
|
|
||||||
|
fidelities = np.zeros((len(circs)))
|
||||||
|
for idx, circuit in tqdm(enumerate(circs), position=tqdm_depth, desc="Computing Fidelities", leave=False):
|
||||||
|
if not force_recalculate and circs[idx]._expressivity is not None:
|
||||||
|
continue
|
||||||
|
fidelities[idx] = single_circuit_param_fidelity(circuit, samples, backend)
|
||||||
|
|
||||||
|
expressivity = np.zeros((len(circs)))
|
||||||
|
for idx, fid in tqdm(enumerate(fidelities), position=tqdm_depth, desc="Computing Expressibility"):
|
||||||
|
if not force_recalculate and circs[idx]._expressivity is not None:
|
||||||
|
continue
|
||||||
|
bin_idx = np.floor(fid * bin_count).astype(int)
|
||||||
|
num = np.array([len(bin_idx[bin_idx == i]) for i in range(bin_count)])
|
||||||
|
|
||||||
|
expressivity[idx] = -stats.entropy(num, haar_values)
|
||||||
|
# NOTE: I'm setting the expressivity here directly, for caching
|
||||||
|
circs[idx]._expressivity = float(expressivity[idx])
|
||||||
|
|
||||||
|
return expressivity
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_entanglement(
|
||||||
|
circs: list[ParametrizedQuantumCircuit] | ParametrizedQuantumCircuit, config: ProxyConfig
|
||||||
|
) -> NDArray[np.float64]:
|
||||||
|
if isinstance(circs, ParametrizedQuantumCircuit):
|
||||||
|
circs = [circs]
|
||||||
|
raise NotImplementedError
|
||||||
17
src/quantum_circuit/proxy_config.py
Normal file
17
src/quantum_circuit/proxy_config.py
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class ProxyConfig:
|
||||||
|
"""
|
||||||
|
Configuration for various proxies, defaults are included for every setting so only the
|
||||||
|
non-default values need to be changed when creating one
|
||||||
|
"""
|
||||||
|
|
||||||
|
qubits: int
|
||||||
|
force_recalculate: bool = False
|
||||||
|
|
||||||
|
entanglement_samples: int = 1000
|
||||||
|
|
||||||
|
expressivity_samples: int = 1000
|
||||||
|
expressivity_bins: int = 100
|
||||||
65
src/quantum_circuit/qiskit_helpers.py
Normal file
65
src/quantum_circuit/qiskit_helpers.py
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from qiskit import QuantumCircuit
|
||||||
|
from qiskit.circuit.parameter import Parameter
|
||||||
|
from qiskit.circuit.parametervector import ParameterVector
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from quantum_circuit import (ParametrizedQuantumCircuit, QuantumGate,
|
||||||
|
QuantumType)
|
||||||
|
|
||||||
|
|
||||||
|
def add_qubit_gate(circ: QuantumCircuit, gate: QuantumGate, theta: Parameter) -> int:
|
||||||
|
match gate.type:
|
||||||
|
case QuantumType.Identity:
|
||||||
|
return 0
|
||||||
|
case QuantumType.Hadamard:
|
||||||
|
circ.h(gate.qubits[0])
|
||||||
|
return 0
|
||||||
|
case QuantumType.X:
|
||||||
|
circ.x(gate.qubits[0])
|
||||||
|
return 0
|
||||||
|
case QuantumType.RX:
|
||||||
|
circ.rx(theta, gate.qubits[0])
|
||||||
|
return 1
|
||||||
|
case QuantumType.RXX:
|
||||||
|
circ.rxx(theta, gate.qubits[0], gate.qubits[1]) # ty:ignore[index-out-of-bounds]
|
||||||
|
return 1
|
||||||
|
case QuantumType.Y:
|
||||||
|
circ.y(gate.qubits[0])
|
||||||
|
return 0
|
||||||
|
case QuantumType.RY:
|
||||||
|
circ.ry(theta, gate.qubits[0])
|
||||||
|
return 1
|
||||||
|
case QuantumType.RYY:
|
||||||
|
circ.ryy(theta, gate.qubits[0], gate.qubits[1]) # ty:ignore[index-out-of-bounds]
|
||||||
|
return 1
|
||||||
|
case QuantumType.Z:
|
||||||
|
circ.z(gate.qubits[0])
|
||||||
|
return 0
|
||||||
|
case QuantumType.RZ:
|
||||||
|
circ.rz(theta, gate.qubits[0])
|
||||||
|
return 1
|
||||||
|
case QuantumType.RZZ:
|
||||||
|
circ.rzz(theta, gate.qubits[0], gate.qubits[1]) # ty:ignore[index-out-of-bounds]
|
||||||
|
return 1
|
||||||
|
case QuantumType.CRX:
|
||||||
|
circ.crx(theta, gate.qubits[0], gate.qubits[1]) # ty:ignore[index-out-of-bounds]
|
||||||
|
return 1
|
||||||
|
case QuantumType.CX:
|
||||||
|
circ.cx(gate.qubits[0], gate.qubits[1]) # ty:ignore[index-out-of-bounds]
|
||||||
|
return 0
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def build_qiskit_circ(pqc: "ParametrizedQuantumCircuit") -> tuple[QuantumCircuit, ParameterVector]:
|
||||||
|
|
||||||
|
circ = QuantumCircuit(pqc.qubits)
|
||||||
|
thetas = ParameterVector("thetas", circ.parameters)
|
||||||
|
|
||||||
|
current_theta_index = 0
|
||||||
|
for layer in pqc.gates:
|
||||||
|
for gate in layer:
|
||||||
|
current_theta_index += add_qubit_gate(circ, gate, thetas[current_theta_index])
|
||||||
|
|
||||||
|
return circ, thetas
|
||||||
6
src/settings.py
Normal file
6
src/settings.py
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class QuantumArchitectureSearchSettings:
|
||||||
|
qubits: int
|
||||||
8
src/tf_qas/__init__.py
Normal file
8
src/tf_qas/__init__.py
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from settings import QuantumArchitectureSearchSettings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TrainingFreeSettings(QuantumArchitectureSearchSettings):
|
||||||
|
sample_size: int
|
||||||
Loading…
Add table
Add a link
Reference in a new issue