1import numbers 2from collections.abc import Iterable 3from copy import deepcopy 4import numpy as np 5 6from qutip.qobjevo import QobjEvo 7from qutip.qip.operations import expand_operator 8from qutip.qobj import Qobj 9from qutip.operators import sigmaz, destroy, num 10from qutip.qip.pulse import Pulse 11 12 13__all__ = ["Noise", "DecoherenceNoise", "RelaxationNoise", 14 "ControlAmpNoise", "RandomNoise", "process_noise"] 15 16 17def process_noise(pulses, noise_list, dims, t1=None, t2=None, 18 device_noise=False): 19 """ 20 Apply noise to the input list of pulses. It does not modify the input 21 pulse, but return a new one containing the noise. 22 23 Parameters 24 ---------- 25 pulses: list of :class:`.Pulse` 26 The input pulses, on which the noise object will be applied. 27 noise_list: list of :class:`.Noise` 28 A list of noise objects. 29 dims: int or list 30 Dimension of the system. 31 If int, we assume it is the number of qubits in the system. 32 If list, it is the dimension of the component systems. 33 t1: list or float, optional 34 Characterize the decoherence of amplitude damping for 35 each qubit. A list of size `N` or a float for all qubits. 36 t2: list of float, optional 37 Characterize the decoherence of dephasing for 38 each qubit. A list of size `N` or a float for all qubits. 39 device_noise: bool 40 If pulse independent noise such as relaxation are included. 41 Default is False. 42 43 Returns 44 ------- 45 noisy_pulses: list of :class:`qutip.qip.Pulse` 46 The noisy pulses, including the system noise. 47 """ 48 noisy_pulses = deepcopy(pulses) 49 systematic_noise = Pulse(None, None, label="systematic_noise") 50 51 if (t1 is not None) or (t2 is not None): 52 noise_list.append(RelaxationNoise(t1, t2)) 53 54 for noise in noise_list: 55 if isinstance(noise, (DecoherenceNoise, RelaxationNoise)) \ 56 and not device_noise: 57 pass 58 else: 59 noisy_pulses, systematic_noise = noise._apply_noise( 60 dims=dims, pulses=noisy_pulses, 61 systematic_noise=systematic_noise) 62 63 if device_noise: 64 return noisy_pulses + [systematic_noise] 65 else: 66 return noisy_pulses 67 68 69class Noise(object): 70 """ 71 The base class representing noise in a processor. 72 The noise object can be added to :class:`.Processor` and 73 contributes to evolution. 74 """ 75 def __init__(self): 76 pass 77 78 def get_noisy_dynamics(self, dims, pulses, systematic_noise): 79 """ 80 Return a pulses list added with noise and 81 the pulse independent noise in a dummy Pulse object. 82 83 Parameters 84 ---------- 85 dims: list, optional 86 The dimension of the components system, the default value is 87 [2,2...,2] for qubits system. 88 89 pulses: list of :class:`.Pulse` 90 The input pulses, on which the noise object is to be applied. 91 92 systematic_noise: :class:`.Pulse` 93 The dummy pulse with no ideal control element. 94 95 Returns 96 ------- 97 noisy_pulses: list of :class:`.Pulse` 98 Noisy pulses. 99 100 systematic_noise: :class:`.Pulse` 101 The dummy pulse representing pulse independent noise. 102 """ 103 raise NotImplementedError( 104 "Subclass error needs a method" 105 "`get_noisy_dynamics` to process the noise.") 106 107 def _apply_noise(self, pulses=None, systematic_noise=None, dims=None): 108 """ 109 For backward compatibility, in case the method has no return value 110 or only return the pulse. 111 """ 112 result = self.get_noisy_dynamics( 113 pulses=pulses, systematic_noise=systematic_noise, dims=dims) 114 if result is None: # in-place change 115 pass 116 elif isinstance(result, tuple) and len(result) == 2: 117 pulses, systematic_noise = result 118 # only pulse 119 elif isinstance(result, list) and len(result) == len(pulses): 120 pulses = result 121 else: 122 raise TypeError( 123 "Returned value of get_noisy_dynamics not understood.") 124 return pulses, systematic_noise 125 126 127class DecoherenceNoise(Noise): 128 """ 129 The decoherence noise in a processor. It generates lindblad noise 130 according to the given collapse operator `c_ops`. 131 132 Parameters 133 ---------- 134 c_ops: :class:`qutip.Qobj` or list 135 The Hamiltonian representing the dynamics of the noise. 136 targets: int or list, optional 137 The indices of qubits that are acted on. Default is on all 138 qubits 139 coeff: list, optional 140 A list of the coefficients for the control Hamiltonians. 141 tlist: array_like, optional 142 A NumPy array specifies the time of each coefficient. 143 all_qubits: bool, optional 144 If `c_ops` contains only single qubits collapse operator, 145 ``all_qubits=True`` will allow it to be applied to all qubits. 146 147 Attributes 148 ---------- 149 c_ops: :class:`qutip.Qobj` or list 150 The Hamiltonian representing the dynamics of the noise. 151 targets: int or list 152 The indices of qubits that are acted on. 153 coeff: list 154 A list of the coefficients for the control Hamiltonians. 155 tlist: array_like 156 A NumPy array specifies the time of each coefficient. 157 all_qubits: bool 158 If `c_ops` contains only single qubits collapse operator, 159 ``all_qubits=True`` will allow it to be applied to all qubits. 160 """ 161 def __init__(self, c_ops, targets=None, coeff=None, tlist=None, 162 all_qubits=False): 163 if isinstance(c_ops, Qobj): 164 self.c_ops = [c_ops] 165 else: 166 self.c_ops = c_ops 167 self.coeff = coeff 168 self.tlist = tlist 169 self.targets = targets 170 if all_qubits: 171 if not all([c_op.dims == [[2], [2]] for c_op in self.c_ops]): 172 raise ValueError( 173 "The operator is not a single qubit operator, " 174 "thus cannot be applied to all qubits") 175 self.all_qubits = all_qubits 176 177 def get_noisy_dynamics( 178 self, dims=None, pulses=None, systematic_noise=None): 179 if systematic_noise is None: 180 systematic_noise = Pulse(None, None, label="system") 181 N = len(dims) 182 # time-independent 183 if (self.coeff is None) and (self.tlist is None): 184 self.coeff = True 185 186 for c_op in self.c_ops: 187 if self.all_qubits: 188 for targets in range(N): 189 systematic_noise.add_lindblad_noise( 190 c_op, targets, self.tlist, self.coeff) 191 else: 192 systematic_noise.add_lindblad_noise( 193 c_op, self.targets, self.tlist, self.coeff) 194 return pulses, systematic_noise 195 196 197class RelaxationNoise(Noise): 198 """ 199 The decoherence on each qubit characterized by two time scales t1 and t2. 200 201 Parameters 202 ---------- 203 t1: float or list, optional 204 Characterize the decoherence of amplitude damping for 205 each qubit. 206 t2: float or list, optional 207 Characterize the decoherence of dephasing for 208 each qubit. 209 targets: int or list, optional 210 The indices of qubits that are acted on. Default is on all 211 qubits 212 213 Attributes 214 ---------- 215 t1: float or list 216 Characterize the decoherence of amplitude damping for 217 each qubit. 218 t2: float or list 219 Characterize the decoherence of dephasing for 220 each qubit. 221 targets: int or list 222 The indices of qubits that are acted on. 223 """ 224 def __init__(self, t1=None, t2=None, targets=None): 225 self.t1 = t1 226 self.t2 = t2 227 self.targets = targets 228 229 def _T_to_list(self, T, N): 230 """ 231 Check if the relaxation time is valid 232 233 Parameters 234 ---------- 235 T: list of float 236 The relaxation time 237 N: int 238 The number of component systems. 239 240 Returns 241 ------- 242 T: list 243 The relaxation time in Python list form 244 """ 245 if (isinstance(T, numbers.Real) and T > 0) or T is None: 246 return [T] * N 247 elif isinstance(T, Iterable) and len(T) == N: 248 return T 249 else: 250 raise ValueError( 251 "Invalid relaxation time T={}," 252 "either the length is not equal to the number of qubits, " 253 "or T is not a positive number.".format(T)) 254 255 def get_noisy_dynamics( 256 self, dims=None, pulses=None, systematic_noise=None): 257 if systematic_noise is None: 258 systematic_noise = Pulse(None, None, label="system") 259 N = len(dims) 260 261 self.t1 = self._T_to_list(self.t1, N) 262 self.t2 = self._T_to_list(self.t2, N) 263 if len(self.t1) != N or len(self.t2) != N: 264 raise ValueError( 265 "Length of t1 or t2 does not match N, " 266 "len(t1)={}, len(t2)={}".format( 267 len(self.t1), len(self.t2))) 268 269 if self.targets is None: 270 targets = range(N) 271 else: 272 targets = self.targets 273 for qu_ind in targets: 274 t1 = self.t1[qu_ind] 275 t2 = self.t2[qu_ind] 276 if t1 is not None: 277 op = 1/np.sqrt(t1) * destroy(dims[qu_ind]) 278 systematic_noise.add_lindblad_noise(op, qu_ind, coeff=True) 279 if t2 is not None: 280 # Keep the total dephasing ~ exp(-t/t2) 281 if t1 is not None: 282 if 2*t1 < t2: 283 raise ValueError( 284 "t1={}, t2={} does not fulfill " 285 "2*t1>t2".format(t1, t2)) 286 T2_eff = 1./(1./t2-1./2./t1) 287 else: 288 T2_eff = t2 289 op = 1/np.sqrt(2*T2_eff) * 2 * num(dims[qu_ind]) 290 systematic_noise.add_lindblad_noise(op, qu_ind, coeff=True) 291 return pulses, systematic_noise 292 293 294class ControlAmpNoise(Noise): 295 """ 296 The noise in the amplitude of the control pulse. 297 298 Parameters 299 ---------- 300 coeff: list 301 A list of the coefficients for the control Hamiltonians. 302 For available choices, see :class:`qutip.QobjEvo`. 303 tlist: array_like, optional 304 A NumPy array specifies the time of each coefficient. 305 indices: list of int, optional 306 The indices of target pulse in the list of pulses. 307 Attributes 308 ---------- 309 coeff: list 310 A list of the coefficients for the control Hamiltonians. 311 For available choices, see :class:`qutip.QobjEvo`. 312 tlist: array_like 313 A NumPy array specifies the time of each coefficient. 314 indices: list of int 315 The indices of target pulse in the list of pulses. 316 317 """ 318 def __init__(self, coeff, tlist=None, indices=None): 319 self.coeff = coeff 320 self.tlist = tlist 321 self.indices = indices 322 323 def get_noisy_dynamics( 324 self, dims=None, pulses=None, systematic_noise=None): 325 if pulses is None: 326 pulses = [] 327 if self.indices is None: 328 indices = range(len(pulses)) 329 else: 330 indices = self.indices 331 for i in indices: 332 pulse = pulses[i] 333 if isinstance(self.coeff, (int, float)): 334 coeff = pulse.coeff * self.coeff 335 else: 336 coeff = self.coeff 337 if self.tlist is None: 338 tlist = pulse.tlist 339 else: 340 tlist = self.tlist 341 pulses[i].add_coherent_noise( 342 pulse.qobj, pulse.targets, tlist, coeff) 343 return pulses, systematic_noise 344 345 346class RandomNoise(ControlAmpNoise): 347 """ 348 Random noise in the amplitude of the control pulse. The arguments for 349 the random generator need to be given as key word arguments. 350 351 Parameters 352 ---------- 353 dt: float, optional 354 The time interval between two random amplitude. The coefficients 355 of the noise are the same within this time range. 356 rand_gen: numpy.random, optional 357 A random generator in numpy.random, it has to take a ``size`` 358 parameter as the size of random numbers in the output array. 359 indices: list of int, optional 360 The indices of target pulse in the list of pulses. 361 **kwargs: 362 Key word arguments for the random number generator. 363 364 Attributes 365 ---------- 366 dt: float, optional 367 The time interval between two random amplitude. The coefficients 368 of the noise are the same within this time range. 369 rand_gen: numpy.random, optional 370 A random generator in numpy.random, it has to take a ``size`` 371 parameter. 372 indices: list of int 373 The indices of target pulse in the list of pulses. 374 **kwargs: 375 Key word arguments for the random number generator. 376 377 Examples 378 -------- 379 >>> gaussnoise = RandomNoise( \ 380 dt=0.1, rand_gen=np.random.normal, loc=mean, scale=std) \ 381 # doctest: +SKIP 382 """ 383 def __init__( 384 self, dt, rand_gen, indices=None, **kwargs): 385 super(RandomNoise, self).__init__(coeff=None, tlist=None) 386 self.rand_gen = rand_gen 387 self.kwargs = kwargs 388 if "size" in kwargs: 389 raise ValueError("size is preditermined inside the noise object.") 390 self.dt = dt 391 self.indices = indices 392 393 def get_noisy_dynamics( 394 self, dims=None, pulses=None, systematic_noise=None): 395 if pulses is None: 396 pulses = [] 397 if self.indices is None: 398 indices = range(len(pulses)) 399 else: 400 indices = self.indices 401 t_max = -np.inf 402 t_min = np.inf 403 for pulse in pulses: 404 t_max = max(max(pulse.tlist), t_max) 405 t_min = min(min(pulse.tlist), t_min) 406 # create new tlist and random coeff 407 num_rand = int(np.floor((t_max - t_min) / self.dt)) + 1 408 tlist = (np.arange(0, self.dt*num_rand, self.dt)[:num_rand] + t_min) 409 # [:num_rand] for round of error like 0.2*6=1.2000000000002 410 411 for i in indices: 412 pulse = pulses[i] 413 coeff = self.rand_gen(**self.kwargs, size=num_rand) 414 pulses[i].add_coherent_noise( 415 pulse.qobj, pulse.targets, tlist, coeff) 416 return pulses, systematic_noise 417