def _build(self) -> None: """Construct the circuit representing the desired state vector.""" super()._build() num_qubits = self.num_qubits if num_qubits == 0: return circuit = QuantumCircuit(*self.qregs, name=self.name) for j in reversed(range(num_qubits)): circuit.h(j) num_entanglements = max( 0, j - max(0, self.approximation_degree - (num_qubits - j - 1))) for k in reversed(range(j - num_entanglements, j)): lam = np.pi / (2**(j - k)) circuit.cp(lam, j, k) if self.insert_barriers: circuit.barrier() if self._do_swaps: for i in range(num_qubits // 2): circuit.swap(i, num_qubits - i - 1) if self._inverse: circuit._data = circuit.inverse() wrapped = circuit.to_instruction( ) if self.insert_barriers else circuit.to_gate() self.compose(wrapped, qubits=self.qubits, inplace=True)
def _build(self) -> None: """Construct the circuit representing the desired state vector.""" super()._build() num_qubits = self.num_qubits if num_qubits == 0: return circuit = QuantumCircuit(*self.qregs, name=self.name) for j in reversed(range(num_qubits)): circuit.h(j) num_entanglements = max( 0, j - max(0, self.approximation_degree - (num_qubits - j - 1))) for k in reversed(range(j - num_entanglements, j)): # Use negative exponents so that the angle safely underflows to zero, rather than # using a temporary variable that overflows to infinity in the worst case. lam = np.pi * (2.0**(k - j)) circuit.cp(lam, j, k) if self.insert_barriers: circuit.barrier() if self._do_swaps: for i in range(num_qubits // 2): circuit.swap(i, num_qubits - i - 1) if self._inverse: circuit._data = circuit.inverse() wrapped = circuit.to_instruction( ) if self.insert_barriers else circuit.to_gate() self.compose(wrapped, qubits=self.qubits, inplace=True)
def _gate_rules_to_qiskit_circuit(self, node, params): """From a gate definition in qasm, to a QuantumCircuit format.""" rules = [] qreg = QuantumRegister(node['n_bits']) bit_args = {node['bits'][i]: q for i, q in enumerate(qreg)} exp_args = {node['args'][i]: Real(q) for i, q in enumerate(params)} for child_op in node['body'].children: qparams = [] eparams = [] for param_list in child_op.children[1:]: if param_list.type == 'id_list': qparams = [bit_args[param.name] for param in param_list.children] elif param_list.type == 'expression_list': for param in param_list.children: eparams.append(param.sym(nested_scope=[exp_args])) op = self._create_op(child_op.name, params=eparams) rules.append((op, qparams, [])) circ = QuantumCircuit(qreg) circ._data = rules return circ
def apply_grad_gate(circuit, gate, param_index, grad_gate, grad_coeff, qr_superpos, open_ctrl=False, trim_after_grad_gate=False): """Util function to apply a gradient gate for the linear combination of unitaries method. Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit. Args: circuit (QuantumCircuit): The circuit in which to do the replacements. gate (Gate): The gate instance to replace. param_index (int): The index of the parameter in ``gate``. grad_gate (Gate): A controlled gate encoding the gradient of ``gate``. grad_coeff (float): A coefficient to the gradient component. Might not be one if the gradient contains multiple summed terms. qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit`` that is used as control for ``grad_gate``. open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed. trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can be used to reduce the circuit depth in e.g. computing an overlap of gradients. Returns: QuantumCircuit: A copy of the original circuit with the gradient gate added. Raises: RuntimeError: If ``gate`` is not in ``circuit``. """ # copy the input circuit taking the gates by reference out = QuantumCircuit(*circuit.qregs) out._data = circuit._data.copy() out._parameter_table = ParameterTable({ param: values.copy() for param, values in circuit._parameter_table.items() }) # get the data index and qubits of the target gate TODO use built-in gate_idx, gate_qubits = None, None for i, (op, qarg, _) in enumerate(out._data): if op is gate: gate_idx, gate_qubits = i, qarg break if gate_idx is None: raise RuntimeError('The specified gate could not be found in the circuit data.') # initialize replacement instructions replacement = [] # insert the phase fix before the target gate better documentation sign = np.sign(grad_coeff) is_complex = np.iscomplex(grad_coeff) if sign < 0 and is_complex: replacement.append((SdgGate(), qr_superpos[:], [])) elif sign < 0: replacement.append((ZGate(), qr_superpos[:], [])) elif is_complex: replacement.append((SGate(), qr_superpos[:], [])) # else no additional gate required # open control if specified if open_ctrl: replacement += [(XGate(), qr_superpos[:], [])] # compute the replacement if isinstance(gate, UGate) and param_index == 0: theta = gate.params[2] rz_plus, rz_minus = RZGate(theta), RZGate(-theta) replacement += [(rz_plus, [qubit], []) for qubit in gate_qubits] replacement += [(RXGate(np.pi / 2), [qubit], []) for qubit in gate_qubits] replacement.append((grad_gate, qr_superpos[:] + gate_qubits, [])) replacement += [(RXGate(-np.pi / 2), [qubit], []) for qubit in gate_qubits] replacement += [(rz_minus, [qubit], []) for qubit in gate_qubits] # update parametertable if necessary if isinstance(theta, ParameterExpression): out._update_parameter_table(rz_plus) out._update_parameter_table(rz_minus) if open_ctrl: replacement += [(XGate(), qr_superpos[:], [])] if not trim_after_grad_gate: replacement.append((gate, gate_qubits, [])) elif isinstance(gate, UGate) and param_index == 1: # gradient gate is applied after the original gate in this case replacement.append((gate, gate_qubits, [])) replacement.append((grad_gate, qr_superpos[:] + gate_qubits, [])) if open_ctrl: replacement += [(XGate(), qr_superpos[:], [])] else: replacement.append((grad_gate, qr_superpos[:] + gate_qubits, [])) if open_ctrl: replacement += [(XGate(), qr_superpos[:], [])] if not trim_after_grad_gate: replacement.append((gate, gate_qubits, [])) # replace the parameter we compute the derivative of with the replacement # TODO can this be done more efficiently? if trim_after_grad_gate: # remove everything after the gradient gate out._data[gate_idx:] = replacement # reset parameter table table = ParameterTable() for op, _, _ in out._data: for idx, param_expression in enumerate(op.params): if isinstance(param_expression, ParameterExpression): for param in param_expression.parameters: if param not in table.keys(): table[param] = [(op, idx)] else: table[param].append((op, idx)) out._parameter_table = table else: out._data[gate_idx:gate_idx + 1] = replacement return out
def apply_grad_gate( circuit, gate, param_index, grad_gate, grad_coeff, qr_superpos, open_ctrl=False, trim_after_grad_gate=False, ): """Util function to apply a gradient gate for the linear combination of unitaries method. Replaces the ``gate`` instance in ``circuit`` with ``grad_gate`` using ``qr_superpos`` as superposition qubit. Also adds the appropriate sign-fix gates on the superposition qubit. Args: circuit (QuantumCircuit): The circuit in which to do the replacements. gate (Gate): The gate instance to replace. param_index (int): The index of the parameter in ``gate``. grad_gate (Gate): A controlled gate encoding the gradient of ``gate``. grad_coeff (float): A coefficient to the gradient component. Might not be one if the gradient contains multiple summed terms. qr_superpos (QuantumRegister): A ``QuantumRegister`` of size 1 contained in ``circuit`` that is used as control for ``grad_gate``. open_ctrl (bool): If True use an open control for ``grad_gate`` instead of closed. trim_after_grad_gate (bool): If True remove all gates after the ``grad_gate``. Can be used to reduce the circuit depth in e.g. computing an overlap of gradients. Returns: QuantumCircuit: A copy of the original circuit with the gradient gate added. Raises: RuntimeError: If ``gate`` is not in ``circuit``. """ qr_superpos_qubits = tuple(qr_superpos) # copy the input circuit taking the gates by reference out = QuantumCircuit(*circuit.qregs) out._data = circuit._data.copy() out._parameter_table = ParameterTable({ param: values.copy() for param, values in circuit._parameter_table.items() }) # get the data index and qubits of the target gate TODO use built-in gate_idx, gate_qubits = None, None for i, instruction in enumerate(out._data): if instruction.operation is gate: gate_idx, gate_qubits = i, instruction.qubits break if gate_idx is None: raise RuntimeError( "The specified gate could not be found in the circuit data.") # initialize replacement instructions replacement = [] # insert the phase fix before the target gate better documentation sign = np.sign(grad_coeff) is_complex = np.iscomplex(grad_coeff) if sign < 0 and is_complex: replacement.append( CircuitInstruction(SdgGate(), qr_superpos_qubits, ())) elif sign < 0: replacement.append( CircuitInstruction(ZGate(), qr_superpos_qubits, ())) elif is_complex: replacement.append( CircuitInstruction(SGate(), qr_superpos_qubits, ())) # else no additional gate required # open control if specified if open_ctrl: replacement += [ CircuitInstruction(XGate(), qr_superpos_qubits, []) ] # compute the replacement if isinstance(gate, UGate) and param_index == 0: theta = gate.params[2] rz_plus, rz_minus = RZGate(theta), RZGate(-theta) replacement += [ CircuitInstruction(rz_plus, (qubit, ), ()) for qubit in gate_qubits ] replacement += [ CircuitInstruction(RXGate(np.pi / 2), (qubit, ), ()) for qubit in gate_qubits ] replacement.append( CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, [])) replacement += [ CircuitInstruction(RXGate(-np.pi / 2), (qubit, ), ()) for qubit in gate_qubits ] replacement += [ CircuitInstruction(rz_minus, (qubit, ), ()) for qubit in gate_qubits ] # update parametertable if necessary if isinstance(theta, ParameterExpression): # This dangerously subverts ParameterTable by abusing the fact that binding will # mutate the exact instruction instance, and relies on all instances of `rz_plus` # that were added before being the same in memory, which QuantumCircuit usually # ensures is not the case. I'm leaving this as close to its previous form as # possible, to avoid introducing further complications, but this whole method # accesses internal attributes of `QuantumCircuit` and needs rewriting. # - Jake Lishman, 2022-03-02. out._update_parameter_table( CircuitInstruction(rz_plus, (gate_qubits[0], ), ())) out._update_parameter_table( CircuitInstruction(rz_minus, (gate_qubits[0], ), ())) if open_ctrl: replacement.append( CircuitInstruction(XGate(), qr_superpos_qubits, ())) if not trim_after_grad_gate: replacement.append(CircuitInstruction(gate, gate_qubits, ())) elif isinstance(gate, UGate) and param_index == 1: # gradient gate is applied after the original gate in this case replacement.append(CircuitInstruction(gate, gate_qubits, ())) replacement.append( CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) if open_ctrl: replacement.append( CircuitInstruction(XGate(), qr_superpos_qubits, ())) else: replacement.append( CircuitInstruction(grad_gate, qr_superpos_qubits + gate_qubits, ())) if open_ctrl: replacement.append( CircuitInstruction(XGate(), qr_superpos_qubits, ())) if not trim_after_grad_gate: replacement.append(CircuitInstruction(gate, gate_qubits, ())) # replace the parameter we compute the derivative of with the replacement # TODO can this be done more efficiently? if trim_after_grad_gate: # remove everything after the gradient gate out._data[gate_idx:] = replacement # reset parameter table table = ParameterTable() for instruction in out._data: for idx, param_expression in enumerate( instruction.operation.params): if isinstance(param_expression, ParameterExpression): for param in param_expression.parameters: if param not in table.keys(): table[param] = ParameterReferences( ((instruction.operation, idx), )) else: table[param].add((instruction.operation, idx)) out._parameter_table = table else: out._data[gate_idx:gate_idx + 1] = replacement return out