diff --git a/CHANGELOG.md b/CHANGELOG.md index bf672471..e34882fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #455: Causal-flow finding algorithm (`graphix.flow._find_cflow.py`) does not raise `RecursionError` now. +- #458: The type for the `input_state` parameter in the simulator now allows `None` to indicate that the input qubits have already been specified in the backend. + ### Changed - #181, #423: Structural separation of Pauli measurements diff --git a/graphix/pattern.py b/graphix/pattern.py index 5e26933d..ec82f9c0 100644 --- a/graphix/pattern.py +++ b/graphix/pattern.py @@ -1332,7 +1332,8 @@ def simulate_pattern( | Statevec | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + | Iterable[Iterable[ExpressionOrSupportsComplex]] + | None = ..., rng: Generator | None = ..., **kwargs: Any, ) -> Statevec: ... @@ -1345,7 +1346,8 @@ def simulate_pattern( | DensityMatrix | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + | Iterable[Iterable[ExpressionOrSupportsComplex]] + | None = ..., rng: Generator | None = ..., **kwargs: Any, ) -> DensityMatrix: ... @@ -1357,7 +1359,8 @@ def simulate_pattern( input_state: State | Iterable[State] | Iterable[ExpressionOrSupportsComplex] - | Iterable[Iterable[ExpressionOrSupportsComplex]] = ..., + | Iterable[Iterable[ExpressionOrSupportsComplex]] + | None = ..., rng: Generator | None = ..., **kwargs: Any, ) -> MBQCTensorNet: ... @@ -1366,7 +1369,7 @@ def simulate_pattern( def simulate_pattern( self, backend: Backend[_StateT_co], - input_state: Data = ..., + input_state: Data | None = ..., rng: Generator | None = ..., **kwargs: Any, ) -> _StateT_co: ... @@ -1374,7 +1377,7 @@ def simulate_pattern( def simulate_pattern( self, backend: Backend[_StateT_co] | _BackendLiteral = "statevector", - input_state: Data = BasicStates.PLUS, + input_state: Data | None = BasicStates.PLUS, rng: Generator | None = None, **kwargs: Any, ) -> _StateT_co | _BuiltinBackendState: diff --git a/graphix/sim/statevec.py b/graphix/sim/statevec.py index ea7abe1a..d106031b 100644 --- a/graphix/sim/statevec.py +++ b/graphix/sim/statevec.py @@ -388,14 +388,14 @@ def expectation_value(self, op: Matrix, qargs: Sequence[int]) -> complex: st1.evolve(op, qargs) return complex(np.dot(st2.psi.flatten().conjugate(), st1.psi.flatten())) - def fidelity(self, other: Statevec) -> float: + def fidelity(self, other: Matrix | Statevec) -> float: r"""Calculate the fidelity against another statevector. The fidelity is defined as :math:`|\langle\psi_1|\psi_2\rangle|^2`. Parameters ---------- - other : :class:`Statevec` + other : :class:`Matrix` | :class:`Statevec` statevector to compare with Returns @@ -406,14 +406,14 @@ def fidelity(self, other: Statevec) -> float: inner = np.dot(self.flatten().conjugate(), other.flatten()) return float(np.abs(inner) ** 2) - def isclose(self, other: Statevec, *, rtol: float = 1e-09, atol: float = 0.0) -> bool: + def isclose(self, other: Matrix | Statevec, *, rtol: float = 1e-09, atol: float = 0.0) -> bool: """Check if two quantum states are equal up to global phase. Two states are considered close if their fidelity is close to 1. Parameters ---------- - other : :class:`Statevec` + other : :class:`Matrix` | :class:`Statevec` statevector to compare with rtol : float relative tolerance for :func:`math.isclose` diff --git a/graphix/simulator.py b/graphix/simulator.py index 19e58eff..d3b14a2d 100644 --- a/graphix/simulator.py +++ b/graphix/simulator.py @@ -355,7 +355,7 @@ def measure_method(self) -> MeasureMethod: """Return the measure method.""" return self.__measure_method - def run(self, input_state: Data = BasicStates.PLUS, rng: Generator | None = None) -> None: + def run(self, input_state: Data | None = BasicStates.PLUS, rng: Generator | None = None) -> None: """Perform the simulation. Returns diff --git a/tests/test_simulator.py b/tests/test_simulator.py new file mode 100644 index 00000000..1cc09f05 --- /dev/null +++ b/tests/test_simulator.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from graphix import Circuit +from graphix.sim.statevec import Statevec, StatevectorBackend +from graphix.states import BasicStates + + +def test_input_state_none() -> None: + circuit = Circuit(1) + circuit.h(0) + pattern = circuit.transpile().pattern + # By default, the initial state is |+>, therefore H|+> = |0>. + state = pattern.simulate_pattern() + assert state.isclose(BasicStates.ZERO.to_statevector()) + # With the initial state |0>, we obtain H|0> = |+>. + input_state = BasicStates.ZERO + state = pattern.simulate_pattern(input_state=input_state) + assert state.isclose(BasicStates.PLUS.to_statevector()) + # With the initial state |0> prepared in the backend, we obtain + # H|0> = |+>. + backend = StatevectorBackend() + backend.add_nodes(pattern.input_nodes, input_state) + state = pattern.simulate_pattern(backend=backend, input_state=None) + assert state.isclose(BasicStates.PLUS.to_statevector()) + # The backend already prepares |0>. If the simulator also prepares + # the input qubits in |+> (because we do not pass + # `input_state=None`), an additional qubit is introduced. The + # simulation therefore applies (I ⊗ H) on |0+>, resulting in |00>. + backend = StatevectorBackend() + backend.add_nodes(pattern.input_nodes, input_state) + state = pattern.simulate_pattern(backend=backend) + assert state.isclose(Statevec(BasicStates.ZERO, nqubit=2)) diff --git a/tests/test_statevec.py b/tests/test_statevec.py index 6593c8dd..9b064a49 100644 --- a/tests/test_statevec.py +++ b/tests/test_statevec.py @@ -193,17 +193,15 @@ def test_isclose_same_state(self) -> None: def test_isclose_orthogonal(self) -> None: zero = Statevec(data=BasicStates.ZERO) - one = Statevec(data=BasicStates.ONE) - assert not zero.isclose(one) + assert not zero.isclose(BasicStates.ONE.to_statevector()) def test_isclose_global_phase(self) -> None: plus = Statevec(data=BasicStates.PLUS) - rotated = Statevec(data=np.array([1, 1]) / np.sqrt(2) * np.exp(1j * 0.7)) - assert plus.isclose(rotated) + assert plus.isclose(np.array([1, 1]) / np.sqrt(2) * np.exp(1j * 0.7)) def test_isclose_tolerance(self) -> None: zero = Statevec(data=BasicStates.ZERO) - almost = Statevec(data=np.array([np.sqrt(1 - 1e-8), np.sqrt(1e-8)])) + almost = np.array([np.sqrt(1 - 1e-8), np.sqrt(1e-8)]) assert not zero.isclose(almost) assert zero.isclose(almost, atol=1e-6)