17
17
from __future__ import annotations
18
18
19
19
import inspect
20
+ from collections .abc import Mapping
20
21
21
22
import numpy as np
22
23
@@ -38,22 +39,30 @@ class PauliInsertionTransformer:
38
39
def __init__ (
39
40
self ,
40
41
target : ops .Gate | ops .GateFamily | ops .Gateset | type [ops .Gate ],
41
- probabilities : np .ndarray | None = None ,
42
+ probabilities : np .ndarray | Mapping [ tuple [ ops . Qid , ops . Qid ], np . ndarray ] | None = None ,
42
43
):
43
44
"""Makes a pauli insertion transformer that samples 2Q paulis with the given probabilities.
44
45
45
46
Args:
46
47
target: The target gate, gatefamily, gateset, or type (e.g. ZZPowGAte).
47
- probabilities: Optional ndarray representing the probabilities of sampling 2Q paulis.
48
- The order of the paulis is IXYZ. If None, assume uniform distribution.
49
- Returns:
50
- A gauge transformer.
48
+ probabilities: Optional ndarray or mapping[qubit-pair, nndarray] representing the
49
+ probabilities of sampling 2Q paulis. The order of the paulis is IXYZ.
50
+ If at operation `op` a pair (i, j) is sampled then _PAULIS[i] is applied
51
+ to op.qubits[0] and _PAULIS[j] is applied to op.qubits[1].
52
+ If None, assume uniform distribution.
51
53
"""
52
54
if probabilities is None :
53
55
probabilities = np .ones ((4 , 4 )) / 16
54
- probabilities = np .asarray (probabilities )
55
- assert probabilities .shape == (4 , 4 )
56
- assert np .isclose (probabilities .sum (), 1 )
56
+ elif isinstance (probabilities , dict ):
57
+ probabilities = {k : np .asarray (v ) for k , v in probabilities .items ()}
58
+ for probs in probabilities .values ():
59
+ assert np .isclose (probs .sum (), 1 )
60
+ assert probs .shape == (4 , 4 )
61
+ else :
62
+ probabilities = np .asarray (probabilities )
63
+ assert np .isclose (probabilities .sum (), 1 )
64
+ assert probabilities .shape == (4 , 4 )
65
+ self .probabilities = probabilities
57
66
58
67
if inspect .isclass (target ):
59
68
self .target = ops .GateFamily (target )
@@ -62,7 +71,21 @@ def __init__(
62
71
else :
63
72
assert isinstance (target , (ops .Gateset , ops .GateFamily ))
64
73
self .target = target
65
- self ._flat_probs = probabilities .reshape (- 1 )
74
+
75
+ def _is_target (self , op : ops .Operation ) -> bool :
76
+ if isinstance (self .probabilities , dict ) and op .qubits not in self .probabilities :
77
+ return False
78
+ return op in self .target
79
+
80
+ def _sample (
81
+ self , qubits : tuple [ops .Qid , ops .Qid ], rng : np .random .Generator
82
+ ) -> tuple [ops .Gate , ops .Gate ]:
83
+ if isinstance (self .probabilities , dict ):
84
+ flat_probs = self .probabilities [qubits ].reshape (- 1 )
85
+ else :
86
+ flat_probs = self .probabilities .reshape (- 1 )
87
+ i , j = np .unravel_index (rng .choice (16 , p = flat_probs ), (4 , 4 ))
88
+ return _PAULIS [i ], _PAULIS [j ]
66
89
67
90
def __call__ (
68
91
self ,
@@ -95,14 +118,14 @@ def __call__(
95
118
for op in moment :
96
119
if any (tag in tags_to_ignore for tag in op .tags ):
97
120
continue
98
- if op not in self .target :
121
+ if not self ._is_target ( op ) :
99
122
continue
100
- pair = np . unravel_index ( rng . choice ( 16 , p = self . _flat_probs ), ( 4 , 4 ) )
101
- for pauli_index , q in zip (pair , op .qubits ):
123
+ pair = self . _sample ( op . qubits , rng )
124
+ for pauli , q in zip (pair , op .qubits ):
102
125
if new_circuit and (q not in new_circuit [- 1 ].qubits ):
103
- new_circuit [- 1 ] += _PAULIS [ pauli_index ] (q )
126
+ new_circuit [- 1 ] += pauli (q )
104
127
else :
105
- new_moment .append (_PAULIS [ pauli_index ] (q ))
128
+ new_moment .append (pauli (q ))
106
129
if new_moment :
107
130
new_circuit .append (circuits .Moment (new_moment ))
108
131
new_circuit .append (moment )
0 commit comments