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
|
||||
from argparse import ArgumentParser
|
||||
from dataclasses import dataclass
|
||||
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):
|
||||
TFQAS = 1
|
||||
|
|
@ -9,8 +15,7 @@ class SearchStrategy(Enum):
|
|||
QDQAS = 3
|
||||
|
||||
|
||||
def main(search_strategy: SearchStrategy):
|
||||
|
||||
def main(search_settings: QualityDiversitySettings | TrainingFreeSettings | GeneticAlgorithmSettings):
|
||||
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