def _prepare_state(state, qubits): c = Circuit() for q, s in zip(qubits, state): if s == '0': pass elif s == '1': c.append(Gate('X', [q])) elif s == '+': c.append(Gate('H', [q])) elif s == '-': c.extend([Gate('X', [q]), Gate('H', [q])]) else: raise ValueError(f"Unexpected token '{s}'") return c
def remove_swap(circuit: Circuit) -> tuple[Circuit, dict[any, any]]: """ Iteratively remove SWAP's from circuit by actually swapping qubits. The output map will have the form new_qubit -> old_qubit. """ # Initialize map _qubits_map = {q: q for q in circuit.all_qubits()} # Initialize circuit _circ = Circuit() # Get ideal SWAP _SWAP = Gate('SWAP').matrix() # For each gate in circuit .. for gate in circuit: # Check if gate is close to SWAP if gate.n_qubits == 2 and gate.qubits and np.allclose( gate.matrix(), _SWAP): # If true, swap qubits _q0 = next(k for k, v in _qubits_map.items() if v == gate.qubits[0]) _q1 = next(k for k, v in _qubits_map.items() if v == gate.qubits[1]) _qubits_map[_q0], _qubits_map[_q1] = _qubits_map[_q1], _qubits_map[ _q0] # Otherwise, remap qubits and append else: # Get the right qubits _qubits = [ next(k for k, v in _qubits_map.items() if v == q) for q in gate.qubits ] # Append to the new circuit _circ.append(gate.on(_qubits)) # Return circuit and map return _circ, _qubits_map
def expand_iswap(circuit: Circuit) -> Circuit: """ Expand ISWAP's by iteratively replacing with SWAP's, CZ's and Phases. """ from copy import deepcopy # Get ideal iSWAP _iSWAP = Gate('ISWAP').matrix() # Initialize circuit _circ = Circuit() # For each gate in circuit .. for gate in circuit: # Check if gate is close to SWAP if gate.n_qubits == 2 and gate.qubits and np.allclose( gate.matrix(), _iSWAP): # Get tags _tags = gate.tags if gate.provides('tags') else {} # Expand iSWAP _ext = [ Gate('SWAP', qubits=gate.qubits, tags=_tags), Gate('CZ', qubits=gate.qubits, tags=_tags), Gate('P', qubits=[gate.qubits[0]], tags=_tags), Gate('P', qubits=[gate.qubits[1]], tags=_tags), ] # Append to circuit _circ.extend(_ext if gate.power == 1 else ( g**-1 for g in reversed(_ext))) # Otherwise, just append else: _circ.append(deepcopy(gate)) # Return circuit return _circ
def from_qasm(qasm_string: str) -> Circuit: """ Convert a QASM circuit to `Circuit`. Parameters ---------- qasm_string: str QASM circuit to convert to `Circuit`. Returns ------- Circuit QAMS circuit converted to `Circuit`. Notes ----- The QASM language used in HybridQ is compatible with the standard QASM. However, HybridQ introduces few extensions, which are recognized by the parser using ``#@`` at the beginning of the line (``#`` at the beginning of the line represent a general comment in QASM). At the moment, the following QAMS extensions are supported: * **qubits**, used to store `qubits_map`, * **power**, used to store the power of the gate, * **tags**, used to store the tags associated to the gate, * **U**, used to store the matrix reprentation of the gate if gate name is `MATRIX` If `Gate.qubits` are not specified, a single `.` is used to represent the missing qubits. If `Gate.params` are missing, parameters are just omitted. Example ------- >>> from hybridq.extras.qasm import from_qasm >>> qasm_str = \"\"\" >>> 1 >>> #@ qubits = >>> #@ { >>> #@ "0": "42" >>> #@ } >>> #@ tags = >>> #@ { >>> #@ "params": false, >>> #@ "qubits": false >>> #@ } >>> rx . >>> #@ tags = >>> #@ { >>> #@ "params": true, >>> #@ "qubits": false >>> #@ } >>> ry . 1.23 >>> #@ tags = >>> #@ { >>> #@ "params": false, >>> #@ "qubits": true >>> #@ } >>> #@ power = 1.23 >>> rz 0 >>> #@ U = >>> #@ [ >>> #@ [ >>> #@ "0.7071067811865475", >>> #@ "0.7071067811865475" >>> #@ ], >>> #@ [ >>> #@ "0.7071067811865475", >>> #@ "-0.7071067811865475" >>> #@ ] >>> #@ ] >>> matrix . >>> \"\"\" >>> from_qasm(qasm_str) Circuit([ Gate(name=RX, tags={'params': False, 'qubits': False}) Gate(name=RY, params=[1.23], tags={'params': True, 'qubits': False}) Gate(name=RZ, qubits=[42], tags={'params': False, 'qubits': True})**1.23 Gate(name=MATRIX, U=np.array(shape=(2, 2), dtype=float64)) ]) """ # Initialize circuit circuit = Circuit() # Initialize tags _extra = None _power = None _conj = False _T = False _tags = None _qubits_map = None _U = None for line in (line for line in qasm_string.split('\n') if line and (line[0] != '#' or line[:2] == '#@')): if line[:2] == '#@': # Strip line _line = re.sub(r'\s+', '', line) if '#@tags=' in _line: if _tags is not None: raise ValueError('Format error.') # Initialize tags _tags = line.split('=')[-1] _extra = 'tags' elif '#@U=' in _line: if _U is not None: raise ValueError('Format error.') # Initialize matrix _U = line.split('=')[-1] _extra = 'U' elif '#@power=' in _line: if _power is not None: raise ValueError('Format error.') # Initialize power _power = line.split('=')[-1] _extra = 'power' elif '#@conj' in _line: if _conj is not False: raise ValueError('Format error.') # Initialize conjugation _conj = True elif '#@T' in _line: if _T is not False: raise ValueError('Format error.') # Initialize transposition _T = True elif '#@qubits=' in _line: if _qubits_map is not None: raise ValueError('Format error.') # Initialize qubits _qubits_map = line.split('=')[-1] _extra = 'qubits' elif _extra: # Update tags if _extra == 'tags': _tags += line.replace('#@', '') # Update matrix elif _extra == 'U': _U += line.replace('#@', '') # Update power elif _extra == 'power': _power += line.replace('#@', '') # Update qubits_map elif _extra == 'qubits': _qubits_map += line.replace('#@', '') # Otherwise, error else: raise ValueError('Format error.') else: # Restart _extra _extra = None # Strip everything after the first # line = line.split('#')[0].split() # Make few guesses about format if len(line) == 1: if _isint(line[0]): warn( f"Skipping '{' '.join(line)}' (most likely the number of qubits in the circuit)." ) continue else: warn( f"Skipping '{' '.join(line)}' (format is not understood)." ) continue # Make few guesses about format if _isint(line[0]): warn( f"Skipping {line[0]} in '{' '.join(line)}' (most likely the circuit layer)." ) del (line[0]) # Make few guesses about format if not _isstring(line[0]): warn( f"Skipping '{' '.join(line)}' (format is not understood).") continue if line[0].upper() == 'MATRIX': # Remove name from line del (line[0]) # Add tags if not _U: raise ValueError('Format error.') # Set gate _U = np.real_if_close( np.array([[complex(y) for y in x] for x in json.loads(_U)])) gate = Gate('MATRIX', U=_U) # Set qubits if present if line[0] != '.': # Set qubits gate.on([int(x) for x in line], inplace=True) # Reset _U = None else: # Set position _p = 0 # Initialize gate with name gate = Gate(line[_p]) # Check if qubits are provided _p += 1 if line[_p] != '.': # Set qubits gate.on([int(x) for x in line[_p:_p + gate.n_qubits]], inplace=True) _p += gate.n_qubits else: # Skip qubits _p += 1 if _p != len(line): if not gate.provides('params') and ( _p + gate.n_params) != len(line): raise ValueError('Format error.') gate.set_params( [float(x) for x in line[_p:_p + gate.n_params]], inplace=True) _p += gate.n_params if len(line) != _p: print(line, gate) # Add tags if _tags: gate._set_tags(json.loads(_tags)) # Apply power if _power: gate._set_power(float(_power)) # Add conjugation if _conj: gate._conj() # Add transposition if _T: gate._T() # Append gate to circuit circuit.append(gate) # Reset _tags = None # Reset power _power = None # Reset conjugation _conj = False # Reset transposition _T = False # Remap qubits if _qubits_map is not None: def _int(x): try: return int(x) except: return x _qubits_map = json.loads(_qubits_map) _qubits_map = {int(x): _int(y) for x, y in _qubits_map.items()} for gate in circuit: if gate.provides('qubits') and gate.qubits is not None: gate._on([_qubits_map[x] for x in gate.qubits]) return circuit