Compare commits
No commits in common. "93deb42c2745b902b3190a5014f964e045638322" and "526d615d9ba4926e1e3f54f41c4a132cd5edaa32" have entirely different histories.
93deb42c27
...
526d615d9b
9 changed files with 2 additions and 379 deletions
|
|
@ -1,13 +1,7 @@
|
||||||
#!/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
|
||||||
|
|
@ -15,7 +9,8 @@ class SearchStrategy(Enum):
|
||||||
QDQAS = 3
|
QDQAS = 3
|
||||||
|
|
||||||
|
|
||||||
def main(search_settings: QualityDiversitySettings | TrainingFreeSettings | GeneticAlgorithmSettings):
|
def main(search_strategy: SearchStrategy):
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from settings import QuantumArchitectureSearchSettings
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class QualityDiversitySettings(QuantumArchitectureSearchSettings):
|
|
||||||
initial_population_size: int
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
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]
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
||||||
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
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class QuantumArchitectureSearchSettings:
|
|
||||||
qubits: int
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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