redo expressivity proxy
This commit is contained in:
parent
526d615d9b
commit
e774575604
9 changed files with 308 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]
|
||||
129
src/quantum_circuit/__init__.py
Normal file
129
src/quantum_circuit/__init__.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
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,
|
||||
}
|
||||
|
||||
|
||||
@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."""
|
||||
|
||||
_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
|
||||
"""
|
||||
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
|
||||
11
src/quantum_circuit/qiskit_helpers.py
Normal file
11
src/quantum_circuit/qiskit_helpers.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
from qiskit import QuantumCircuit
|
||||
from qiskit.circuit.parametervector import ParameterVector
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from quantum_circuit import ParametrizedQuantumCircuit
|
||||
|
||||
|
||||
def build_qiskit_circ(pqc: "ParametrizedQuantumCircuit") -> tuple[QuantumCircuit, ParameterVector]:
|
||||
raise NotImplementedError
|
||||
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