-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* state preparation * logging * register * execution helpers * qubit support * serialization * typing * wavefunction overlaps Co-authored-by: Aleksander Wennersteen <[email protected]> Co-authored-by: Mario Dagrada <[email protected]> Co-authored-by: Vincent Elfving <[email protected]> Co-authored-by: Dominik Seitz <[email protected]> Co-authored-by: Joao Moutinho <[email protected]> Co-authored-by: Vytautas Abramavicius <[email protected]> Co-authored-by: Niklas Heim <[email protected]> Co-authored-by: Roland Guichard <[email protected]>
- Loading branch information
1 parent
d73b9a1
commit 6d785c7
Showing
14 changed files
with
2,908 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from __future__ import annotations | ||
|
||
from importlib import import_module | ||
|
||
from torch import cdouble, set_default_dtype | ||
from torch import float64 as torchfloat64 | ||
|
||
from .backend import * | ||
from .backends import * | ||
from .blocks import * | ||
from .circuit import * | ||
from .constructors import * | ||
from .errors import * | ||
from .execution import * | ||
from .measurements import * | ||
from .ml_tools import * | ||
from .models import * | ||
from .operations import * | ||
from .overlap import * | ||
from .parameters import * | ||
from .register import * | ||
from .serialization import * | ||
from .states import * | ||
from .transpile import * | ||
from .types import * | ||
from .utils import * | ||
|
||
DEFAULT_FLOAT_DTYPE = torchfloat64 | ||
DEFAULT_COMPLEX_DTYPE = cdouble | ||
set_default_dtype(DEFAULT_FLOAT_DTYPE) | ||
""" | ||
The imports above fetch the functions defined in the __all__ of each sub-module | ||
to the qadence name space. Make sure each added submodule has the respective definition: | ||
- `__all__ = ["function0", "function1", ...]` | ||
Furthermore, add the submodule to the list below to automatically build | ||
the __all__ of the qadence namespace. Make sure to keep alphabetical ordering. | ||
""" | ||
|
||
list_of_submodules = [ | ||
".backends", | ||
".blocks", | ||
".circuit", | ||
".constructors", | ||
".errors", | ||
".execution", | ||
".measurements", | ||
".ml_tools", | ||
".models", | ||
".operations", | ||
".overlap", | ||
".parameters", | ||
".register", | ||
".serialization", | ||
".states", | ||
".transpile", | ||
".types", | ||
".utils", | ||
] | ||
|
||
__all__ = [] | ||
for submodule in list_of_submodules: | ||
__all_submodule__ = getattr(import_module(submodule, package="qadence"), "__all__") | ||
__all__ += __all_submodule__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
from __future__ import annotations | ||
|
||
from dataclasses import dataclass | ||
from itertools import chain as flatten | ||
from pathlib import Path | ||
from typing import Iterable | ||
|
||
from sympy import Array, Basic | ||
|
||
from qadence.blocks import AbstractBlock, AnalogBlock, CompositeBlock, chain | ||
from qadence.blocks.utils import parameters, primitive_blocks | ||
from qadence.parameters import Parameter | ||
from qadence.register import Register | ||
|
||
# Modules to be automatically added to the qadence namespace | ||
__all__ = ["QuantumCircuit"] | ||
|
||
|
||
@dataclass(eq=False) # Avoid unhashability errors due to mutable attributes. | ||
class QuantumCircuit: | ||
"""A QuantumCircuit instance is completely abstract and it needs to be passed to a quantum | ||
backend in order to be executed. | ||
""" | ||
|
||
block: AbstractBlock | ||
register: Register | ||
|
||
def __init__(self, support: int | Register, *blocks: AbstractBlock): | ||
""" | ||
Arguments: | ||
support: `Register` or number of qubits. If an integer is provided, a register is | ||
constructed with `Register.all_to_all(x)` | ||
*blocks: (Possibly multiple) blocks to construct the circuit from. | ||
""" | ||
self.block = chain(*blocks) if len(blocks) != 1 else blocks[0] | ||
self.register = Register(support) if isinstance(support, int) else support | ||
|
||
global_block = isinstance(self.block, AnalogBlock) and self.block.qubit_support.is_global | ||
if not global_block and len(self.block) and self.block.n_qubits > self.register.n_qubits: | ||
raise ValueError( | ||
f"Register with {self.register.n_qubits} qubits is too small for the " | ||
f"given block with {self.block.n_qubits} qubits" | ||
) | ||
|
||
@property | ||
def n_qubits(self) -> int: | ||
return self.register.n_qubits | ||
|
||
def __eq__(self, other: object) -> bool: | ||
if not isinstance(other, QuantumCircuit): | ||
raise TypeError(f"Cannot compare {type(self)} to {type(other)}.") | ||
if self.block != other.block: # type: ignore[call-overload] | ||
return False | ||
if self.register != other.register: | ||
return False | ||
return True | ||
|
||
def __hash__(self) -> int: | ||
return hash(self._to_json()) | ||
|
||
def __iter__(self) -> Iterable: | ||
if isinstance(self.block, CompositeBlock): | ||
yield from self.block | ||
else: | ||
yield self.block | ||
|
||
def __contains__(self, other: object) -> bool: | ||
if isinstance(other, AbstractBlock): | ||
if isinstance(self.block, CompositeBlock): | ||
return other in self.block | ||
else: | ||
return other == self.block | ||
elif isinstance(other, Parameter): | ||
return other in self.unique_parameters | ||
else: | ||
raise TypeError(f"Cant compare {type(self)} to {type(other)}") | ||
|
||
@property | ||
def unique_parameters(self) -> list[Parameter]: | ||
"""Return the unique parameters in the circuit | ||
These parameters are the actual user-facing parameters which | ||
can be assigned by the user. Multiple gates can contain the | ||
same unique parameter | ||
Returns: | ||
list[Parameter]: List of unique parameters in the circuit | ||
""" | ||
symbols = [] | ||
for p in parameters(self.block): | ||
if isinstance(p, Array): | ||
continue | ||
elif not p.is_number and p not in symbols: | ||
symbols.append(p) | ||
return symbols | ||
|
||
@property | ||
def num_unique_parameters(self) -> int: | ||
return len(self.unique_parameters) if self.unique_parameters else 0 | ||
|
||
@property | ||
def num_parameters(self) -> int: | ||
return len(self.parameters()) | ||
|
||
def parameters(self) -> list[Parameter | Basic] | list[tuple[Parameter | Basic, ...]]: | ||
"""Extract all parameters for primitive blocks in the circuit | ||
Notice that this function returns all the unique Parameters used | ||
in the quantum circuit. These can correspond to constants too. | ||
Returns: | ||
List[tuple[Parameter]]: A list of tuples containing the Parameter | ||
instance of each of the primitive blocks in the circuit or, if the `flatten` | ||
flag is set to True, a flattened list of all circuit parameters | ||
""" | ||
return parameters(self.block) | ||
|
||
def get_blocks_by_tag(self, tag: str) -> list[AbstractBlock]: | ||
"""Extract one or more blocks using the human-readable tag | ||
This function recurservily explores all composite blocks to find | ||
all the occurrences of a certain tag in the blocks | ||
Args: | ||
tag (str): the tag to look for | ||
Returns: | ||
list[AbstractBlock]: The block(s) corresponding to the given tag | ||
""" | ||
|
||
def _get_block(block: AbstractBlock) -> list[AbstractBlock]: | ||
blocks = [] | ||
if block.tag == tag: | ||
blocks += [block] | ||
if isinstance(block, CompositeBlock): | ||
blocks += flatten(*[_get_block(b) for b in block.blocks]) | ||
return blocks | ||
|
||
return _get_block(self.block) | ||
|
||
def is_empty(self) -> bool: | ||
return len(primitive_blocks(self.block)) == 0 | ||
|
||
def serialize(self) -> str: | ||
raise NotImplementedError | ||
|
||
@staticmethod | ||
def deserialize(json: str) -> QuantumCircuit: | ||
raise NotImplementedError | ||
|
||
def __repr__(self) -> str: | ||
return self.block.__repr__() | ||
|
||
def _to_dict(self) -> dict: | ||
return { | ||
"block": self.block._to_dict(), | ||
"register": self.register._to_dict(), | ||
} | ||
|
||
def _to_json(self, path: Path | str | None = None) -> str: | ||
import json | ||
|
||
qc_dumped = json.dumps(self._to_dict()) | ||
if path is not None: | ||
path = Path(path) | ||
try: | ||
with open(path, "w") as file: | ||
file.write(qc_dumped) | ||
except Exception as e: | ||
print(f"Unable to write QuantumCircuit to disk due to {e}") | ||
|
||
return qc_dumped | ||
|
||
@classmethod | ||
def _from_dict(cls, d: dict) -> QuantumCircuit: | ||
from qadence import blocks as qadenceblocks | ||
from qadence import operations | ||
|
||
RootBlock = ( | ||
getattr(operations, d["block"]["type"]) | ||
if hasattr(operations, d["block"]["type"]) | ||
else getattr(qadenceblocks, d["block"]["type"]) | ||
) | ||
|
||
return QuantumCircuit( | ||
Register._from_dict(d["register"]), | ||
RootBlock._from_dict(d["block"]), | ||
) | ||
|
||
@classmethod | ||
def _from_json(cls, path: str | Path) -> QuantumCircuit: | ||
import json | ||
|
||
loaded_dict: dict = {} | ||
if isinstance(path, str): | ||
path = Path(path) | ||
try: | ||
with open(path, "r") as file: | ||
loaded_dict = json.load(file) | ||
|
||
except Exception as e: | ||
print(f"Unable to load QuantumCircuit due to {e}") | ||
|
||
return QuantumCircuit._from_dict(loaded_dict) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from __future__ import annotations | ||
|
||
from collections import Counter | ||
|
||
import numpy as np | ||
|
||
|
||
def shannon_entropy(counter: Counter) -> float: | ||
return float(-np.sum([count * np.log(count) for count in counter.values()])) | ||
|
||
|
||
def js_divergence(counter_p: Counter, counter_q: Counter) -> float: | ||
""" | ||
Compute the Jensen-Shannon divergence between two probability distributions | ||
represented as Counter objects. | ||
The JSD is calculated using only the shared keys between the two input Counter objects. | ||
Args: | ||
counter_p (Counter): Counter of bitstring counts for probability mass function P. | ||
counter_q (Counter): Counter of bitstring counts for probability mass function Q. | ||
Returns: | ||
float: The Jensen-Shannon divergence between counter_p and counter_q. | ||
""" | ||
# Normalise counters | ||
normalisation_p = np.sum([count for count in counter_p.values()]) | ||
normalisation_q = np.sum([count for count in counter_q.values()]) | ||
counter_p = Counter({k: v / normalisation_p for k, v in counter_p.items()}) | ||
counter_q = Counter({k: v / normalisation_q for k, v in counter_q.items()}) | ||
|
||
average_proba_counter = counter_p + counter_q | ||
average_proba_counter = Counter({k: v / 2.0 for k, v in average_proba_counter.items()}) | ||
average_entropy = shannon_entropy(average_proba_counter) | ||
|
||
entropy_p = shannon_entropy(counter_p) | ||
entropy_q = shannon_entropy(counter_q) | ||
return float(average_entropy - (entropy_p + entropy_q) / 2.0) |
Oops, something went wrong.