1# Copyright 2018 The Cirq Developers
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""A protocol for implementing high performance clifford tableau evolutions
15 for Clifford Simulator."""
16
17from typing import Any, Dict, TYPE_CHECKING, List, Sequence, Iterable, Union
18
19import numpy as np
20
21from cirq.ops import common_gates
22from cirq.ops import pauli_gates
23from cirq.ops.clifford_gate import SingleQubitCliffordGate
24from cirq.protocols import has_unitary, num_qubits, unitary
25from cirq.qis.clifford_tableau import CliffordTableau
26from cirq.sim.act_on_args import ActOnArgs
27from cirq.type_workarounds import NotImplementedType
28
29if TYPE_CHECKING:
30    import cirq
31
32
33class ActOnCliffordTableauArgs(ActOnArgs):
34    """State and context for an operation acting on a clifford tableau.
35
36    To act on this object, directly edit the `tableau` property, which is
37    storing the density matrix of the quantum system with one axis per qubit.
38    """
39
40    def __init__(
41        self,
42        tableau: CliffordTableau,
43        prng: np.random.RandomState,
44        log_of_measurement_results: Dict[str, Any],
45        qubits: Sequence['cirq.Qid'] = None,
46    ):
47        """Inits ActOnCliffordTableauArgs.
48
49        Args:
50            tableau: The CliffordTableau to act on. Operations are expected to
51                perform inplace edits of this object.
52            qubits: Determines the canonical ordering of the qubits. This
53                is often used in specifying the initial state, i.e. the
54                ordering of the computational basis states.
55            prng: The pseudo random number generator to use for probabilistic
56                effects.
57            log_of_measurement_results: A mutable object that measurements are
58                being recorded into.
59        """
60        super().__init__(prng, qubits, log_of_measurement_results)
61        self.tableau = tableau
62
63    def _act_on_fallback_(
64        self,
65        action: Union['cirq.Operation', 'cirq.Gate'],
66        qubits: Sequence['cirq.Qid'],
67        allow_decompose: bool = True,
68    ) -> Union[bool, NotImplementedType]:
69        strats = []
70        if allow_decompose:
71            strats.append(_strat_act_on_clifford_tableau_from_single_qubit_decompose)
72        for strat in strats:
73            result = strat(action, self, qubits)
74            if result is False:
75                break  # coverage: ignore
76            if result is True:
77                return True
78            assert result is NotImplemented, str(result)
79
80        return NotImplemented
81
82    def _perform_measurement(self, qubits: Sequence['cirq.Qid']) -> List[int]:
83        """Returns the measurement from the tableau."""
84        return [self.tableau._measure(self.qubit_map[q], self.prng) for q in qubits]
85
86    def _on_copy(self, target: 'ActOnCliffordTableauArgs'):
87        target.tableau = self.tableau.copy()
88
89    def sample(
90        self,
91        qubits: Sequence['cirq.Qid'],
92        repetitions: int = 1,
93        seed: 'cirq.RANDOM_STATE_OR_SEED_LIKE' = None,
94    ) -> np.ndarray:
95        # Unnecessary for now but can be added later if there is a use case.
96        raise NotImplementedError()
97
98
99def _strat_act_on_clifford_tableau_from_single_qubit_decompose(
100    val: Any, args: 'cirq.ActOnCliffordTableauArgs', qubits: Sequence['cirq.Qid']
101) -> bool:
102    if num_qubits(val) == 1:
103        if not has_unitary(val):
104            return NotImplemented
105        u = unitary(val)
106        clifford_gate = SingleQubitCliffordGate.from_unitary(u)
107        if clifford_gate is not None:
108            for axis, quarter_turns in clifford_gate.decompose_rotation():
109                if axis == pauli_gates.X:
110                    common_gates.XPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits)
111                elif axis == pauli_gates.Y:
112                    common_gates.YPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits)
113                else:
114                    assert axis == pauli_gates.Z
115                    common_gates.ZPowGate(exponent=quarter_turns / 2)._act_on_(args, qubits)
116            return True
117
118    return NotImplemented
119