Skip to content

Commit ca1861b

Browse files
Merge branch 'noisemodels' of https://github.com/yutuer21/tensorcircuit into tqlpr70
2 parents bb6a851 + 7a08d7f commit ca1861b

File tree

5 files changed

+371
-7
lines changed

5 files changed

+371
-7
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
- Fixed `kraus_to_super_gate` bug when multi-qubit kraus channels are presented on tensorflow backend
2424

25+
2526
## 0.5.0
2627

2728
### Added

tensorcircuit/channels.py

+15-7
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@
2424
Matrix = Any
2525

2626

27+
class KrausList(list): # type: ignore
28+
def __init__(self, iterable, name, is_unitary): # type: ignore
29+
super().__init__(iterable)
30+
self.name = name
31+
self.is_unitary = is_unitary
32+
33+
2734
def _sqrt(a: Tensor) -> Tensor:
2835
r"""
2936
Return the square root of Tensor with default global dtype
@@ -93,7 +100,7 @@ def depolarizingchannel(px: float, py: float, pz: float) -> Sequence[Gate]:
93100
x = Gate(_sqrt(px) * gates.x().tensor) # type: ignore
94101
y = Gate(_sqrt(py) * gates.y().tensor) # type: ignore
95102
z = Gate(_sqrt(pz) * gates.z().tensor) # type: ignore
96-
return [i, x, y, z]
103+
return KrausList([i, x, y, z], name="depolarizing", is_unitary=True)
97104

98105

99106
def generaldepolarizingchannel(
@@ -194,7 +201,7 @@ def generaldepolarizingchannel(
194201
for pro, paugate in zip(probs, tup):
195202
Gkarus.append(Gate(_sqrt(pro) * paugate))
196203

197-
return Gkarus
204+
return KrausList(Gkarus, name="depolarizing", is_unitary=True)
198205

199206

200207
def amplitudedampingchannel(gamma: float, p: float) -> Sequence[Gate]:
@@ -247,7 +254,7 @@ def amplitudedampingchannel(gamma: float, p: float) -> Sequence[Gate]:
247254
m1 = _sqrt(p) * (_sqrt(gamma) * g01)
248255
m2 = _sqrt(1 - p) * (_sqrt(1 - gamma) * g00 + g11)
249256
m3 = _sqrt(1 - p) * (_sqrt(gamma) * g10)
250-
return [m0, m1, m2, m3]
257+
return KrausList([m0, m1, m2, m3], name="amplitude_damping", is_unitary=False)
251258

252259

253260
def resetchannel() -> Sequence[Gate]:
@@ -274,7 +281,7 @@ def resetchannel() -> Sequence[Gate]:
274281
"""
275282
m0 = Gate(np.array([[1, 0], [0, 0]], dtype=cons.npdtype))
276283
m1 = Gate(np.array([[0, 1], [0, 0]], dtype=cons.npdtype))
277-
return [m0, m1]
284+
return KrausList([m0, m1], name="reset", is_unitary=False)
278285

279286

280287
def phasedampingchannel(gamma: float) -> Sequence[Gate]:
@@ -305,7 +312,7 @@ def phasedampingchannel(gamma: float) -> Sequence[Gate]:
305312
g11 = Gate(np.array([[0, 0], [0, 1]], dtype=cons.npdtype))
306313
m0 = 1.0 * (g00 + _sqrt(1 - gamma) * g11) # 1* ensure gate
307314
m1 = _sqrt(gamma) * g11
308-
return [m0, m1]
315+
return KrausList([m0, m1], name="phase_damping", is_unitary=False)
309316

310317

311318
def thermalrelaxationchannel(
@@ -394,7 +401,7 @@ def thermalrelaxationchannel(
394401
Gkraus = []
395402
for pro, paugate in zip(probs, tup):
396403
Gkraus.append(Gate(_sqrt(pro) * paugate))
397-
return Gkraus
404+
return KrausList(Gkraus, name="thermal_relaxation", is_unitary=False)
398405

399406
elif method == "ByChoi" or (
400407
method == "AUTO" and backend.real(t2) >= backend.real(t1)
@@ -439,7 +446,8 @@ def thermalrelaxationchannel(
439446
nmax = 3
440447

441448
listKraus = choi_to_kraus(choi, truncation_rules={"max_singular_values": nmax})
442-
return [Gate(i) for i in listKraus]
449+
Gatelist = [Gate(i) for i in listKraus]
450+
return KrausList(Gatelist, name="thermal_relaxation", is_unitary=False)
443451

444452
else:
445453
raise ValueError("No valid method is provided")

tensorcircuit/interfaces/tensortrans.py

+13
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,8 @@ def f(a, b, c, d):
250250
:return: The wrapped function
251251
:rtype: Callable[..., Any]
252252
"""
253+
from ..channels import KrausList
254+
253255
if isinstance(argnums, int):
254256
argnumslist = [argnums]
255257
else:
@@ -260,6 +262,14 @@ def wrapper(*args: Any, **kws: Any) -> Any:
260262
nargs = []
261263
for i, arg in enumerate(args):
262264
if i in argnumslist:
265+
if isinstance(arg, KrausList):
266+
is_krauslist = True
267+
name = arg.name
268+
is_unitary = arg.is_unitary
269+
arg = list(arg)
270+
else:
271+
is_krauslist = False
272+
263273
if gate_to_tensor:
264274
arg = backend.tree_map(
265275
partial(gate_to_matrix, is_reshapem=gate_as_matrix), arg
@@ -274,6 +284,9 @@ def wrapper(*args: Any, **kws: Any) -> Any:
274284
arg = backend.tree_map(partial(backend.cast, dtype=dtypestr), arg)
275285
if tensor_as_matrix:
276286
arg = backend.tree_map(backend.reshapem, arg)
287+
288+
if is_krauslist is True:
289+
arg = KrausList(arg, name, is_unitary)
277290
nargs.append(arg)
278291
return f(*nargs, **kws)
279292

tensorcircuit/noisemodel.py

+273
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
"""
2+
General Noise Model Construction.
3+
"""
4+
import logging
5+
from typing import Any, Sequence, Optional, List, Dict
6+
7+
from .abstractcircuit import AbstractCircuit
8+
from . import gates
9+
from . import Circuit, DMCircuit
10+
from .cons import backend
11+
from .channels import KrausList
12+
13+
Gate = gates.Gate
14+
Tensor = Any
15+
logger = logging.getLogger(__name__)
16+
17+
18+
class NoiseConf:
19+
"""
20+
``Noise Configuration`` class.
21+
22+
.. code-block:: python
23+
24+
error1 = tc.channels.generaldepolarizingchannel(0.1, 1)
25+
error2 = tc.channels.thermalrelaxationchannel(300, 400, 100, "ByChoi", 0)
26+
readout_error = [[0.9, 0.75], [0.4, 0.7]]
27+
28+
noise_conf = NoiseConf()
29+
noise_conf.add_noise("x", error1)
30+
noise_conf.add_noise("h", [error1, error2], [[0], [1]])
31+
noise_conf.add_noise("readout", readout_error)
32+
"""
33+
34+
def __init__(self) -> None:
35+
"""
36+
Establish a noise configuration.
37+
"""
38+
self.nc = {} # type: ignore
39+
self.has_quantum = False
40+
self.has_readout = False
41+
42+
def add_noise(
43+
self,
44+
gate_name: str,
45+
kraus: Sequence[KrausList],
46+
qubit: Optional[Sequence[Any]] = None,
47+
) -> None:
48+
"""
49+
Add noise channels on specific gates and specific qubits in form of Kraus operators.
50+
51+
:param gate_name: noisy gate
52+
:type gate_name: str
53+
:param kraus: noise channel
54+
:type kraus: Sequence[Gate]
55+
:param qubit: the list of noisy qubit, defaults to None, indicating applying the noise channel on all qubits
56+
:type qubit: Optional[Sequence[Any]], optional
57+
"""
58+
if gate_name not in self.nc:
59+
qubit_kraus = {}
60+
else:
61+
qubit_kraus = self.nc[gate_name]
62+
63+
if qubit is None:
64+
qubit_kraus["Default"] = kraus
65+
else:
66+
for i in range(len(qubit)):
67+
qubit_kraus[tuple(qubit[i])] = kraus[i]
68+
self.nc[gate_name] = qubit_kraus
69+
70+
if gate_name == "readout":
71+
self.has_readout = True
72+
else:
73+
self.has_quantum = True
74+
75+
76+
def apply_qir_with_noise(
77+
c: Any,
78+
qir: List[Dict[str, Any]],
79+
noise_conf: NoiseConf,
80+
status: Optional[Tensor] = None,
81+
) -> Any:
82+
"""
83+
84+
:param c: A newly defined circuit
85+
:type c: AbstractCircuit
86+
:param qir: The qir of the clean circuit
87+
:type qir: List[Dict[str, Any]]
88+
:param noise_conf: Noise Configuration
89+
:type noise_conf: NoiseConf
90+
:param status: The status for Monte Carlo sampling, defaults to None
91+
:type status: 1D Tensor, optional
92+
:return: A newly constructed circuit with noise
93+
:rtype: AbstractCircuit
94+
"""
95+
quantum_index = 0
96+
for d in qir:
97+
if "parameters" not in d: # paramized gate
98+
c.apply_general_gate_delayed(d["gatef"], d["name"])(c, *d["index"])
99+
else:
100+
c.apply_general_variable_gate_delayed(d["gatef"], d["name"])(
101+
c, *d["index"], **d["parameters"]
102+
)
103+
104+
if isinstance(c, DMCircuit):
105+
if d["name"] in noise_conf.nc:
106+
if (
107+
"Default" in noise_conf.nc[d["name"]]
108+
or d["index"] in noise_conf.nc[d["name"]]
109+
):
110+
111+
if "Default" in noise_conf.nc[d["name"]]:
112+
noise_kraus = noise_conf.nc[d["name"]]["Default"]
113+
if d["index"] in noise_conf.nc[d["name"]]:
114+
noise_kraus = noise_conf.nc[d["name"]][d["index"]]
115+
116+
c.general_kraus(noise_kraus, *d["index"])
117+
118+
else:
119+
if d["name"] in noise_conf.nc:
120+
if (
121+
"Default" in noise_conf.nc[d["name"]]
122+
or d["index"] in noise_conf.nc[d["name"]]
123+
):
124+
125+
if "Default" in noise_conf.nc[d["name"]]:
126+
noise_kraus = noise_conf.nc[d["name"]]["Default"]
127+
if d["index"] in noise_conf.nc[d["name"]]:
128+
noise_kraus = noise_conf.nc[d["name"]][d["index"]]
129+
130+
if noise_kraus.is_unitary is True:
131+
c.unitary_kraus(
132+
noise_kraus,
133+
*d["index"],
134+
status=status[quantum_index] # type: ignore
135+
)
136+
else:
137+
c.general_kraus(
138+
noise_kraus,
139+
*d["index"],
140+
status=status[quantum_index] # type: ignore
141+
)
142+
quantum_index += 1
143+
144+
return c
145+
146+
147+
def circuit_with_noise(
148+
c: AbstractCircuit, noise_conf: NoiseConf, status: Optional[Tensor] = None
149+
) -> Any:
150+
"""Noisify a clean circuit.
151+
152+
:param c: A clean circuit
153+
:type c: AbstractCircuit
154+
:param noise_conf: Noise Configuration
155+
:type noise_conf: NoiseConf
156+
:param status: The status for Monte Carlo sampling, defaults to None
157+
:type status: 1D Tensor, optional
158+
:return: A newly constructed circuit with noise
159+
:rtype: AbstractCircuit
160+
"""
161+
qir = c.to_qir()
162+
cnew: AbstractCircuit
163+
if isinstance(c, DMCircuit):
164+
cnew = DMCircuit(c._nqubits)
165+
else:
166+
cnew = Circuit(c._nqubits)
167+
cnew = apply_qir_with_noise(cnew, qir, noise_conf, status)
168+
return cnew
169+
170+
171+
def expectation_ps_noisfy(
172+
c: Any,
173+
x: Optional[Sequence[int]] = None,
174+
y: Optional[Sequence[int]] = None,
175+
z: Optional[Sequence[int]] = None,
176+
noise_conf: Optional[NoiseConf] = None,
177+
nmc: int = 1000,
178+
status: Optional[Tensor] = None,
179+
) -> Tensor:
180+
181+
if noise_conf is None:
182+
noise_conf = NoiseConf()
183+
else:
184+
pass
185+
186+
num_quantum = c.gate_count(list(noise_conf.nc.keys()))
187+
188+
if noise_conf.has_readout is True:
189+
logger.warning("expectation_ps_noisfy can't support readout error.")
190+
else:
191+
pass
192+
193+
if noise_conf.has_quantum is True:
194+
195+
# density matrix
196+
if isinstance(c, DMCircuit):
197+
cnoise = circuit_with_noise(c, noise_conf)
198+
return cnoise.expectation_ps(x=x, y=y, z=z)
199+
200+
# monte carlo
201+
else:
202+
203+
def mcsim(status: Optional[Tensor]) -> Tensor:
204+
cnoise = circuit_with_noise(c, noise_conf, status) # type: ignore
205+
return cnoise.expectation_ps(x=x, y=y, z=z)
206+
207+
mcsim_vmap = backend.vmap(mcsim, vectorized_argnums=0)
208+
if status is None:
209+
status = backend.implicit_randu([nmc, num_quantum])
210+
else:
211+
pass
212+
value = backend.mean(mcsim_vmap(status))
213+
214+
return value
215+
216+
else:
217+
return c.expectation_ps(x=x, y=y, z=z)
218+
219+
220+
def sample_expectation_ps_noisfy(
221+
c: Any,
222+
x: Optional[Sequence[int]] = None,
223+
y: Optional[Sequence[int]] = None,
224+
z: Optional[Sequence[int]] = None,
225+
noise_conf: Optional[NoiseConf] = None,
226+
nmc: int = 1000,
227+
shots: Optional[int] = None,
228+
status: Optional[Tensor] = None,
229+
) -> Tensor:
230+
231+
if noise_conf is None:
232+
noise_conf = NoiseConf()
233+
else:
234+
pass
235+
236+
num_quantum = c.gate_count(list(noise_conf.nc.keys()))
237+
238+
if noise_conf.has_readout is True:
239+
readout_error = noise_conf.nc["readout"]["Default"]
240+
else:
241+
readout_error = None
242+
243+
if noise_conf.has_quantum is True:
244+
245+
# density matrix
246+
if isinstance(c, DMCircuit):
247+
cnoise = circuit_with_noise(c, noise_conf) # type: ignore
248+
return cnoise.sample_expectation_ps(
249+
x=x, y=y, z=z, shots=shots, readout_error=readout_error
250+
)
251+
252+
# monte carlo
253+
else:
254+
255+
def mcsim(status: Optional[Tensor]) -> Tensor:
256+
cnoise = circuit_with_noise(c, noise_conf, status) # type: ignore
257+
return cnoise.sample_expectation_ps(
258+
x=x, y=y, z=z, shots=shots, readout_error=readout_error
259+
)
260+
261+
mcsim_vmap = backend.vmap(mcsim, vectorized_argnums=0)
262+
if status is None:
263+
status = backend.implicit_randu([nmc, num_quantum])
264+
else:
265+
pass
266+
value = backend.mean(mcsim_vmap(status))
267+
return value
268+
269+
else:
270+
value = c.sample_expectation_ps(
271+
x=x, y=y, z=z, shots=shots, readout_error=readout_error
272+
)
273+
return value

0 commit comments

Comments
 (0)