1# This file is part of QuTiP: Quantum Toolbox in Python. 2# 3# Copyright (c) 2011 and later, Paul D. Nation and Robert J. Johansson. 4# All rights reserved. 5# 6# Redistribution and use in source and binary forms, with or without 7# modification, are permitted provided that the following conditions are 8# met: 9# 10# 1. Redistributions of source code must retain the above copyright notice, 11# this list of conditions and the following disclaimer. 12# 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# 17# 3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names 18# of its contributors may be used to endorse or promote products derived 19# from this software without specific prior written permission. 20# 21# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 24# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32############################################################################### 33import numpy as np 34from .instruction import Instruction 35from .scheduler import Scheduler 36from ..circuit import QubitCircuit, Gate 37 38 39__all__ = ['GateCompiler'] 40 41 42class GateCompiler(object): 43 """ 44 Base class. It compiles a :class:`.QubitCircuit` into 45 the pulse sequence for the processor. The core member function 46 `compile` calls compiling method from the sub-class and concatenate 47 the compiled pulses. 48 49 Parameters 50 ---------- 51 N: int 52 The number of the component systems. 53 54 params: dict, optional 55 A Python dictionary contains the name and the value of the parameters, 56 such as laser frequency, detuning etc. 57 It will be saved in the class attributes and can be used to calculate 58 the control pulses. 59 60 pulse_dict: dict, optional 61 A map between the pulse label and its index in the pulse list. 62 If given, the compiled pulse can be identified with 63 ``(pulse_label, coeff)``, instead of ``(pulse_index, coeff)``. 64 The number of key-value pairs should match the number of pulses 65 in the processor. 66 If it is empty, an integer ``pulse_index`` needs to be used 67 in the compiling routine saved under the attributes ``gate_compiler``. 68 69 Attributes 70 ---------- 71 gate_compiler: dict 72 The Python dictionary in the form of {gate_name: compiler_function}. 73 It saves the compiling routine for each gate. See sub-classes 74 for implementation. 75 Note that for continuous pulse, the first coeff should always be 0. 76 77 args: dict 78 Arguments for individual compiling routines. 79 It adds more flexibility in customizing compiler. 80 """ 81 def __init__(self, N, params=None, pulse_dict=None): 82 self.gate_compiler = {} 83 self.N = N 84 self.params = params if params is not None else {} 85 self.pulse_dict = pulse_dict if pulse_dict is not None else {} 86 self.gate_compiler = {"GLOBALPHASE": self.globalphase_compiler} 87 self.args = {"params": self.params} 88 self.global_phase = 0. 89 90 def globalphase_compiler(self, gate, args): 91 """ 92 Compiler for the GLOBALPHASE gate 93 """ 94 pass 95 96 def compile(self, circuit, schedule_mode=None, args=None): 97 """ 98 Compile the the native gates into control pulse sequence. 99 It calls each compiling method and concatenates 100 the compiled pulses. 101 102 Parameters 103 ---------- 104 circuit: :class:`.QubitCircuit` or list of 105 :class:`.Gate` 106 A list of elementary gates that can be implemented in the 107 corresponding hardware. 108 The gate names have to be in `gate_compiler`. 109 110 schedule_mode: str, optional 111 ``"ASAP"`` for "as soon as possible" or 112 ``"ALAP"`` for "as late as possible" or 113 ``False`` or ``None`` for no schedule. 114 Default is None. 115 116 args: dict, optional 117 A dictionary of arguments used in a specific gate compiler 118 function. 119 120 Returns 121 ------- 122 tlist: array_like 123 A NumPy array specifies the time of each coefficient 124 125 coeffs: array_like 126 A 2d NumPy array of the shape ``(len(ctrls), len(tlist))``. Each 127 row corresponds to the control pulse sequence for 128 one Hamiltonian. 129 """ 130 if isinstance(circuit, QubitCircuit): 131 gates = circuit.gates 132 else: 133 gates = circuit 134 if args is not None: 135 self.args.update(args) 136 instruction_list = [] 137 138 # compile gates 139 for gate in gates: 140 if gate.name not in self.gate_compiler: 141 raise ValueError("Unsupported gate %s" % gate.name) 142 instruction = self.gate_compiler[gate.name](gate, self.args) 143 if instruction is None: 144 continue # neglecting global phase gate 145 instruction_list += instruction 146 if not instruction_list: 147 return None, None 148 if self.pulse_dict: 149 num_controls = len(self.pulse_dict) 150 else: # if pulse_dict is not given, compute the number of pulses 151 num_controls = 0 152 for instruction in instruction_list: 153 for pulse_index, _ in instruction.pulse_info: 154 num_controls = max(num_controls, pulse_index) 155 num_controls += 1 156 157 # schedule 158 # scheduled_start_time: 159 # An ordered list of the start_time for each pulse, 160 # corresponding to gates in the instruction_list. 161 # instruction_list reordered according to the scheduled result 162 instruction_list, scheduled_start_time = \ 163 self._schedule(instruction_list, schedule_mode) 164 165 # An instruction can be composed from several different pulse elements. 166 # We separate them an assign them to each pulse index. 167 pulse_instructions = [[] for tmp in range(num_controls)] 168 for instruction, start_time in \ 169 zip(instruction_list, scheduled_start_time): 170 for pulse_name, coeff in instruction.pulse_info: 171 if self.pulse_dict: 172 try: 173 pulse_ind = self.pulse_dict[pulse_name] 174 except KeyError: 175 raise ValueError( 176 f"Pulse name {pulse_name} not found" 177 " in pulse_dict.") 178 else: 179 pulse_ind = pulse_name 180 pulse_instructions[pulse_ind].append( 181 (start_time, instruction.tlist, coeff)) 182 183 # concatenate pulses 184 compiled_tlist, compiled_coeffs = \ 185 self._concatenate_pulses( 186 pulse_instructions, scheduled_start_time, num_controls) 187 return compiled_tlist, compiled_coeffs 188 189 def _schedule(self, instruction_list, schedule_mode): 190 """ 191 Schedule the instructions if required and 192 reorder instruction_list accordingly 193 """ 194 if schedule_mode: 195 scheduler = Scheduler(schedule_mode) 196 scheduled_start_time = scheduler.schedule(instruction_list) 197 time_ordered_pos = np.argsort(scheduled_start_time) 198 instruction_list = [instruction_list[i] for i in time_ordered_pos] 199 scheduled_start_time.sort() 200 else: # no scheduling 201 scheduled_start_time = [0.] 202 for instruction in instruction_list[:-1]: 203 scheduled_start_time.append( 204 instruction.duration + scheduled_start_time[-1]) 205 return instruction_list, scheduled_start_time 206 207 def _concatenate_pulses( 208 self, pulse_instructions, scheduled_start_time, num_controls): 209 """ 210 Concatenate compiled pulses coefficients and tlist for each pulse. 211 If there is idling time, add zeros properly to prevent wrong spline. 212 """ 213 # Concatenate tlist and coeffs for each control pulses 214 compiled_tlist = [[] for tmp in range(num_controls)] 215 compiled_coeffs = [[] for tmp in range(num_controls)] 216 for pulse_ind in range(num_controls): 217 last_pulse_time = 0. 218 for start_time, tlist, coeff in pulse_instructions[pulse_ind]: 219 # compute the gate time, step size and coeffs 220 # according to different pulse mode 221 gate_tlist, coeffs, step_size, pulse_mode = \ 222 self._process_gate_pulse(start_time, tlist, coeff) 223 224 if abs(last_pulse_time) < step_size * 1.0e-6: # if first pulse 225 compiled_tlist[pulse_ind].append([0.]) 226 if pulse_mode == "continuous": 227 compiled_coeffs[pulse_ind].append([0.]) 228 # for discrete pulse len(coeffs) = len(tlist) - 1 229 230 # If there is idling time between the last pulse and 231 # the current one, we need to add zeros in between. 232 if np.abs(start_time - last_pulse_time) > step_size * 1.0e-6: 233 idling_tlist = self._process_idling_tlist( 234 pulse_mode, start_time, last_pulse_time, step_size) 235 compiled_tlist[pulse_ind].append(idling_tlist) 236 compiled_coeffs[pulse_ind].append(np.zeros(len(idling_tlist))) 237 238 # Add the gate time and coeffs to the list. 239 execution_time = gate_tlist + start_time 240 last_pulse_time = execution_time[-1] 241 compiled_tlist[pulse_ind].append(execution_time) 242 compiled_coeffs[pulse_ind].append(coeffs) 243 244 for i in range(num_controls): 245 if not compiled_coeffs[i]: 246 compiled_tlist[i] = None 247 compiled_coeffs[i] = None 248 else: 249 compiled_tlist[i] = np.concatenate(compiled_tlist[i]) 250 compiled_coeffs[i] = np.concatenate(compiled_coeffs[i]) 251 return compiled_tlist, compiled_coeffs 252 253 def _process_gate_pulse( 254 self, start_time, tlist, coeff): 255 # compute the gate time, step size and coeffs 256 # according to different pulse mode 257 if np.isscalar(tlist): 258 pulse_mode = "discrete" 259 # a single constant rectanglar pulse, where 260 # tlist and coeff are just float numbers 261 step_size = tlist 262 coeff = np.array([coeff]) 263 gate_tlist = np.array([tlist]) 264 elif len(tlist) - 1 == len(coeff): 265 # discrete pulse 266 pulse_mode = "discrete" 267 step_size = tlist[1] - tlist[0] 268 coeff = np.asarray(coeff) 269 gate_tlist = np.asarray(tlist)[1:] # first t always 0 by def 270 elif len(tlist) == len(coeff): 271 # continuos pulse 272 pulse_mode = "continuous" 273 step_size = tlist[1] - tlist[0] 274 coeff = np.asarray(coeff)[1:] 275 gate_tlist = np.asarray(tlist)[1:] 276 else: 277 raise ValueError( 278 "The shape of the compiled pulse is not correct.") 279 return gate_tlist, coeff, step_size, pulse_mode 280 281 def _process_idling_tlist( 282 self, pulse_mode, start_time, last_pulse_time, step_size): 283 idling_tlist = [] 284 if pulse_mode == "continuous": 285 # We add sufficient number of zeros at the begining 286 # and the end of the idling to prevent wrong cubic spline. 287 if start_time - last_pulse_time > 3 * step_size: 288 idling_tlist1 = np.linspace( 289 last_pulse_time + step_size/5, 290 last_pulse_time + step_size, 291 5 292 ) 293 idling_tlist2 = np.linspace( 294 start_time - step_size, 295 start_time, 296 5 297 ) 298 idling_tlist.extend([idling_tlist1, idling_tlist2]) 299 else: 300 idling_tlist.append( 301 np.arange( 302 last_pulse_time + step_size, 303 start_time, step_size 304 ) 305 ) 306 elif pulse_mode == "discrete": 307 # idling until the start time 308 idling_tlist.append([start_time]) 309 return np.concatenate(idling_tlist) 310