Skip to content

Commit

Permalink
Implement strict optional type checks
Browse files Browse the repository at this point in the history
  • Loading branch information
gecrooks committed May 28, 2023
1 parent 7102e88 commit eb74669
Show file tree
Hide file tree
Showing 29 changed files with 162 additions and 97 deletions.
3 changes: 3 additions & 0 deletions pyrightconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"reportPrivateImportUsage": false
}
10 changes: 6 additions & 4 deletions quantumflow/channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@

from functools import reduce
from operator import add
from typing import Sequence
from typing import Optional, Sequence

import numpy as np
from scipy import linalg
Expand Down Expand Up @@ -85,7 +85,7 @@ class Kraus(Operation):
# FIXME: Shouldn't take Gate, since may not be Unitary operators

def __init__(
self, operators: Sequence[Gate], weights: Sequence[float] = None
self, operators: Sequence[Gate], weights: Optional[Sequence[float]] = None
) -> None:
self.operators = operators

Expand Down Expand Up @@ -164,7 +164,7 @@ class UnitaryMixture(Kraus):
"""

def __init__(
self, operators: Sequence[Gate], weights: Sequence[float] = None
self, operators: Sequence[Gate], weights: Optional[Sequence[float]] = None
) -> None:
from .info import almost_unitary

Expand Down Expand Up @@ -285,7 +285,9 @@ def kraus_iscomplete(kraus: Kraus) -> bool:

# TODO: as class RandomChannel?
# Author: GEC (2019)
def random_channel(qubits: Qubits, rank: int = None, unital: bool = False) -> Channel:
def random_channel(
qubits: Qubits, rank: Optional[int] = None, unital: bool = False
) -> Channel:
"""
Returns: A randomly sampled Channel drawn from the BCSZ ensemble with
the specified Kraus rank.
Expand Down
8 changes: 5 additions & 3 deletions quantumflow/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ class Circuit(Sequence, Operation):
"""

def __init__(
self, *elements: Union[Iterable[Operation], Operation], qubits: Qubits = None
self,
*elements: Union[Iterable[Operation], Operation],
qubits: Optional[Qubits] = None,
) -> None:
elements = tuple(elements)

Expand Down Expand Up @@ -168,7 +170,7 @@ def rewire(self, labels: Dict[Qubit, Qubit]) -> "Circuit":
[elem.rewire(labels) for elem in self], qubits=list(labels.values())
)

def run(self, ket: State = None) -> State:
def run(self, ket: Optional[State] = None) -> State:
"""
Apply the action of this circuit upon a state.
Expand All @@ -182,7 +184,7 @@ def run(self, ket: State = None) -> State:
ket = elem.run(ket)
return ket

def evolve(self, rho: Density = None) -> Density:
def evolve(self, rho: Optional[Density] = None) -> Density:
if rho is None:
qubits = self.qubits
rho = zero_state(qubits=qubits).asdensity()
Expand Down
3 changes: 2 additions & 1 deletion quantumflow/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import re
import sys
import typing
from typing import Optional

from .future import importlib_metadata

Expand Down Expand Up @@ -56,7 +57,7 @@
SQRT = "√"


def about(file: typing.TextIO = None) -> None:
def about(file: Optional[typing.TextIO] = None) -> None:
"""Print information about the configuration
``> python -m quantumflow.about``
Expand Down
15 changes: 12 additions & 3 deletions quantumflow/dagcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,16 @@

import itertools
import textwrap
from typing import Any, Dict, Generator, Iterable, Iterator, List, Tuple
from typing import (
Any,
Dict,
Generator,
Iterable,
Iterator,
List,
Optional,
Tuple,
)

import networkx as nx
import numpy as np
Expand Down Expand Up @@ -245,14 +254,14 @@ def __iter__(self) -> Iterator[Operation]:
yield elem

# DOCME TESTME
def next_element(self, elem: Operation, qubit: Qubit = None) -> Operation:
def next_element(self, elem: Operation, qubit: Optional[Qubit] = None) -> Operation:
for _, node, key in self.graph.edges(elem, keys=True):
if qubit is None or key == qubit:
return node
assert False # Insanity check # FIXME, raise exception

# DOCME TESTME
def prev_element(self, elem: Operation, qubit: Qubit = None) -> Operation:
def prev_element(self, elem: Operation, qubit: Optional[Qubit] = None) -> Operation:
for node, _, key in self.graph.in_edges(elem, keys=True):
if qubit is None or key == qubit:
return node
Expand Down
13 changes: 8 additions & 5 deletions quantumflow/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
Iterator,
List,
Mapping,
Optional,
Sequence,
Tuple,
Union,
Expand Down Expand Up @@ -144,7 +145,7 @@ class CompositeGate(Gate):
A quantum gate represented by a sequence of quantum gates.
"""

def __init__(self, *elements: Gate, qubits: Qubits = None) -> None:
def __init__(self, *elements: Gate, qubits: Optional[Qubits] = None) -> None:
circ = Circuit(Circuit(elements).flat(), qubits=qubits)

for elem in circ:
Expand All @@ -154,10 +155,10 @@ def __init__(self, *elements: Gate, qubits: Qubits = None) -> None:
super().__init__(qubits=circ.qubits)
self.circuit = circ

def run(self, ket: State = None) -> State:
def run(self, ket: Optional[State] = None) -> State:
return self.circuit.run(ket)

def evolve(self, rho: Density = None) -> Density:
def evolve(self, rho: Optional[Density] = None) -> Density:
return self.circuit.evolve(rho)

def aschannel(self) -> "Channel":
Expand Down Expand Up @@ -220,7 +221,9 @@ class ControlGate(Gate):
# Note: ControlGate and StdCtrlGate share interface and code.
# But unification probably not worth the trouble

def __init__(self, target: Gate, controls: Qubits, axes: str = None) -> None:
def __init__(
self, target: Gate, controls: Qubits, axes: Optional[str] = None
) -> None:
controls = tuple(controls)
qubits = tuple(controls) + tuple(target.qubits)
if len(set(qubits)) != len(qubits):
Expand Down Expand Up @@ -493,7 +496,7 @@ def resolve(self, subs: Mapping[str, float]) -> "PauliGate":
return self

def decompose(
self, topology: nx.Graph = None
self, topology: Optional[nx.Graph] = None
) -> Iterator[Union[CNot, XPow, YPow, ZPow]]:
"""
Returns a Circuit corresponding to the exponential of
Expand Down
4 changes: 2 additions & 2 deletions quantumflow/gradients.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"""

from typing import Callable, Sequence, Tuple
from typing import Callable, Optional, Sequence, Tuple

import numpy as np
from numpy import pi
Expand Down Expand Up @@ -85,7 +85,7 @@ def expectation_gradients(
ket0: State,
circ: Circuit,
hermitian: Operation,
dfunc: Callable[[float], float] = None,
dfunc: Optional[Callable[[float], float]] = None,
) -> Sequence[float]:
"""
Calculate the gradients of a function of expectation for a
Expand Down
11 changes: 8 additions & 3 deletions quantumflow/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
.. autofunction:: almost_unital
"""

from typing import Optional

import numpy as np
import scipy.stats
from scipy.linalg import sqrtm # matrix square root
Expand Down Expand Up @@ -245,7 +247,7 @@ def densities_close(rho0: Density, rho1: Density, atol: float = ATOL) -> bool:
return fubini_study_close(rho0.tensor, rho1.tensor, atol)


def entropy(rho: Density, base: float = None) -> float:
def entropy(rho: Density, base: Optional[float] = None) -> float:
"""
Returns the von-Neumann entropy of a mixed quantum state.
Expand All @@ -265,7 +267,10 @@ def entropy(rho: Density, base: float = None) -> float:

# TESTME
def mutual_info(
rho: Density, qubits0: Qubits, qubits1: Qubits = None, base: float = None
rho: Density,
qubits0: Qubits,
qubits1: Optional[Qubits] = None,
base: Optional[float] = None,
) -> float:
"""Compute the bipartite von-Neumann mutual information of a mixed
quantum state.
Expand Down Expand Up @@ -400,7 +405,7 @@ def channels_close(chan0: Channel, chan1: Channel, atol: float = ATOL) -> bool:


# TESTME multiqubits
def average_gate_fidelity(kraus: Kraus, target: Gate = None) -> float:
def average_gate_fidelity(kraus: Kraus, target: Optional[Gate] = None) -> float:
"""Return the average gate fidelity between a noisy gate (specified by a
Kraus representation of a superoperator), and a purely unitary target gate.
Expand Down
25 changes: 17 additions & 8 deletions quantumflow/ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
Tuple,
Type,
TypeVar,
Union,
)

import numpy as np
Expand Down Expand Up @@ -116,10 +117,10 @@ class Operation(ABC):
"""Is this a multi-qubit operation that is known to be invariant under
permutations of qubits?"""

cv_qubit_nb: ClassVar[int] = None
cv_qubit_nb: ClassVar[int] = 0
"""The number of qubits, for operations with a fixed number of qubits"""

cv_args: ClassVar[Optional[Tuple[str, ...]]] = None
cv_args: ClassVar[Union[Tuple[str, ...], Tuple]] = ()
"""The names of the parameters for this operation (For operations with a fixed
number of float parameters)"""

Expand All @@ -134,15 +135,15 @@ def __init_subclass__(cls) -> None:
def __init__(
self,
qubits: Qubits,
params: Sequence[Variable] = None,
params: Optional[Sequence[Variable]] = None,
) -> None:
self._qubits: Qubits = tuple(qubits)
self._params: Tuple[Variable, ...] = ()
if params is not None:
self._params = tuple(params)
self._tensor: QubitTensor = None
self._tensor: Optional[QubitTensor] = None

if self.cv_qubit_nb is not None:
if self.cv_qubit_nb != 0:
if self.cv_qubit_nb != len(self._qubits):
raise ValueError(
"Wrong number of qubits for Operation"
Expand Down Expand Up @@ -201,6 +202,8 @@ def param(self, name: str) -> Variable:
Raise:
KeyError: If unrecognized parameter name
"""
if self.cv_args is None:
raise KeyError("No parameters")
try:
idx = self.cv_args.index(name)
except ValueError:
Expand All @@ -209,7 +212,9 @@ def param(self, name: str) -> Variable:
return self._params[idx]

# rename? param_asfloat? Then use where needed.
def float_param(self, name: str, subs: Mapping[str, float] = None) -> float:
def float_param(
self, name: str, subs: Optional[Mapping[str, float]] = None
) -> float:
"""Return a a named parameters of this Operation as a float.
Args:
Expand Down Expand Up @@ -585,6 +590,8 @@ def from_hamiltonian(cls, hamiltonian: "Pauli", qubits: Qubits) -> "UnitaryGate"
@cached_property
def tensor(self) -> QubitTensor:
"""Returns the tensor representation of gate operator"""
if self._tensor is None:
raise ValueError("No tensor representation")
return self._tensor


Expand All @@ -602,8 +609,8 @@ def __init__(
self,
tensor: "ArrayLike",
qubits: Qubits,
params: Sequence[var.Variable] = None,
name: str = None, # FIXME
params: Optional[Sequence[var.Variable]] = None,
name: Optional[str] = None, # FIXME
) -> None:
tensor = tensors.asqutensor(tensor)

Expand All @@ -618,6 +625,8 @@ def __init__(
@cached_property
def tensor(self) -> QubitTensor:
"""Return the tensor representation of the channel's superoperator"""
if self._tensor is None:
raise ValueError("No tensor representation")
return self._tensor

@property
Expand Down
8 changes: 5 additions & 3 deletions quantumflow/paulialgebra.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from itertools import groupby, product
from numbers import Complex
from operator import itemgetter, mul
from typing import Any, Dict, Iterator, List, Set, Tuple, cast
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, cast

import numpy as np
import sympy
Expand Down Expand Up @@ -305,7 +305,7 @@ def __eq__(self, other: Any) -> bool:
def __hash__(self) -> int:
return hash(self.terms)

def asoperator(self, qubits: Qubits = None) -> QubitTensor:
def asoperator(self, qubits: Optional[Qubits] = None) -> QubitTensor:
# DOCME: Use of qubits argument here.

# Late import to prevent circular imports
Expand Down Expand Up @@ -525,7 +525,9 @@ def pauli_commuting_sets(element: Pauli) -> Tuple[Pauli, ...]:
return tuple(groups)


def pauli_decompose_hermitian(matrix: np.ndarray, qubits: Qubits = None) -> Pauli:
def pauli_decompose_hermitian(
matrix: np.ndarray, qubits: Optional[Qubits] = None
) -> Pauli:
"""Decompose a Hermitian matrix into an element of the Pauli algebra.
This works because tensor products of Pauli matrices form an orthonormal
Expand Down
2 changes: 1 addition & 1 deletion quantumflow/qubits.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class Qubit(Protocol):
e.g. strings, integers, tuples of strings and integers, etc.
"""

def __lt__(self, other: Any) -> bool:
def __lt__(self, other: Any, /) -> bool:
pass

def __hash__(self) -> int:
Expand Down
Loading

0 comments on commit eb74669

Please sign in to comment.