def _apply_qubit_state_vector(self, qubit_state_vector_operation): # pylint: disable=missing-function-docstring if not self.analytic: raise qml.DeviceError( "The operation QubitStateVector is only supported in analytic mode." ) state_vector = np.array(qubit_state_vector_operation.parameters[0], dtype=np.complex64) if len(state_vector) != 2 ** len(self.qubits): raise qml.DeviceError( "For QubitStateVector, the state has to be specified for the correct number of qubits. Got a state of length {}, expected {}.".format( len(state_vector), 2 ** len(self.qubits) ) ) norm_squared = np.sum(np.abs(state_vector) ** 2) if not np.isclose(norm_squared, 1.0, atol=1e-3, rtol=0): raise qml.DeviceError( "The given state for QubitStateVector is not properly normalized to 1.0. Got norm {}".format( math.sqrt(norm_squared) ) ) self._initial_state = state_vector
def _apply_basis_state(self, basis_state_operation): # pylint: disable=missing-function-docstring if not self.shots is None: raise qml.DeviceError( "The operation BasisState is only supported in analytic mode.") wires = basis_state_operation.wires if len(basis_state_operation.parameters[0]) != len(wires): raise qml.DeviceError( "For BasisState, the state has to be specified for the correct number of qubits. Got a state for {} qubits, expected {}." .format(len(basis_state_operation.parameters[0]), len(self.qubits))) if not np.all( np.isin(basis_state_operation.parameters[0], np.array([0, 1 ]))): raise qml.DeviceError( "Argument for BasisState can only contain 0 and 1. Got {}". format(basis_state_operation.parameters[0])) # expand basis state to device wires basis_state_array = np.zeros(self.num_wires, dtype=int) basis_state_array[wires] = basis_state_operation.parameters[0] self._initial_state = np.zeros(2**len(self.qubits), dtype=np.complex64) basis_state_idx = np.sum( 2**np.argwhere(np.flip(basis_state_array) == 1)) self._initial_state[basis_state_idx] = 1.0
def apply(self, operation, wires, par): super().apply(operation, wires, par) if operation == "BasisState": if not self._first_apply: raise qml.DeviceError( "The operation BasisState is only supported at the beginning of a circuit." ) if not self.analytic: raise qml.DeviceError( "The operation BasisState is only supported in analytic mode." ) basis_state_array = np.array(par[0]) if len(basis_state_array) != len(self.qubits): raise qml.DeviceError( "For BasisState, the state has to be specified for the correct number of qubits. Got a state for {} qubits, expected {}." .format(len(basis_state_array), len(self.qubits))) if not np.all(np.isin(basis_state_array, np.array([0, 1]))): raise qml.DeviceError( "Argument for BasisState can only contain 0 and 1. Got {}". format(par[0])) self.initial_state = np.zeros(2**len(self.qubits), dtype=np.complex64) basis_state_idx = np.sum( 2**np.argwhere(np.flip(basis_state_array) == 1)) self.initial_state[basis_state_idx] = 1.0 elif operation == "QubitStateVector": if not self._first_apply: raise qml.DeviceError( "The operation QubitStateVector is only supported at the beginning of a circuit." ) if not self.analytic: raise qml.DeviceError( "The operation QubitStateVector is only supported in analytic mode." ) state_vector = np.array(par[0], dtype=np.complex64) if len(state_vector) != 2**len(self.qubits): raise qml.DeviceError( "For QubitStateVector, the state has to be specified for the correct number of qubits. Got a state of length {}, expected {}." .format(len(state_vector), 2**len(self.qubits))) norm_squared = np.sum(np.abs(state_vector)**2) if not np.isclose(norm_squared, 1.0, atol=1e-3, rtol=0): raise qml.DeviceError( "The given state for QubitStateVector is not properly normalized to 1.0. Got norm {}" .format(math.sqrt(norm_squared))) self.initial_state = state_vector if self._first_apply: self._first_apply = False
def inv(self): """Inverses the CirqOperation.""" # We can also support inversion after parametrization, but this is not necessary for the # PennyLane-Cirq codebase at the moment. if self.parametrized_cirq_gates: raise qml.DeviceError( "CirqOperation can't be inverted after it was parametrized.") self.is_inverse = not self.is_inverse
def probability(self): if self.state is None: raise qml.DeviceError( "Probability can not be computed because the internal state is None." ) states = itertools.product(range(2), repeat=self.num_wires) probs = np.abs(self.state)**2 return OrderedDict(zip(states, probs))
def apply(self, *qubits): """Applies the CirqOperation. Args: *qubits (Cirq:Qid): the qubits on which the Cirq gates should be performed. """ if not self.parametrized_cirq_gates: raise qml.DeviceError( "CirqOperation must be parametrized before it can be applied.") return (parametrized_gate(*qubits) for parametrized_gate in self.parametrized_cirq_gates)
def unitary_matrix_gate(U): """Creates a Cirq unitary matrix gate from a given matrix. Args: U (numpy.ndarray): an array representing the gate matrix. """ if U.shape == (2, 2): return cirq.SingleQubitMatrixGate(U) if U.shape == (4, 4): return cirq.TwoQubitMatrixGate(U) else: raise qml.DeviceError( "Cirq only supports single-qubit and two-qubit unitary matrix gates. The given matrix had shape {}" .format(U.shape))
def __init__(self, wires, shots, qubits=None): super().__init__(wires, shots) self._eigs = dict() self.circuit = None if qubits: if wires != len(qubits): raise qml.DeviceError( "The number of given qubits and the specified number of wires have to match. Got {} wires and {} qubits." .format(wires, len(qubits))) self.qubits = qubits else: self.qubits = [cirq.LineQubit(wire) for wire in range(wires)]
def pre_measure(self): # Cirq only measures states in the computational basis, i.e. 0 and 1 # To measure different observables, we have to go to their eigenbases # This code is adapted from the pennylane-qiskit plugin for e in self.obs_queue: # Identity and PauliZ need no changes if e.name == "PauliX": # X = H.Z.H self.apply("Hadamard", wires=e.wires, par=[]) elif e.name == "PauliY": # Y = (HS^)^.Z.(HS^) and S^=SZ self.apply("PauliZ", wires=e.wires, par=[]) self.apply("S", wires=e.wires, par=[]) self.apply("Hadamard", wires=e.wires, par=[]) elif e.name == "Hadamard": # H = Ry(-pi/4)^.Z.Ry(-pi/4) self.apply("RY", e.wires, [-np.pi / 4]) elif e.name == "Hermitian": # For arbitrary Hermitian matrix H, let U be the unitary matrix # that diagonalises it, and w_i be the eigenvalues. Hmat = e.parameters[0] Hkey = tuple(Hmat.flatten().tolist()) if Hmat.shape not in [(2, 2), (4, 4)]: raise qml.DeviceError( "Cirq only supports single-qubit and two-qubit unitary gates and thus only single-qubit and two-qubit Hermitian observables." ) if Hkey in self._eigs: # retrieve eigenvectors U = self._eigs[Hkey]["eigvec"] else: # store the eigenvalues corresponding to H # in a dictionary, so that they do not need to # be calculated later w, U = np.linalg.eigh(Hmat) self._eigs[Hkey] = {"eigval": w, "eigvec": U} # Perform a change of basis before measuring by applying U^ to the circuit self.apply("QubitUnitary", e.wires, [U.conj().T])
def __init__(self, wires, shots, analytic, qubits=None): if not isinstance(wires, Iterable): # interpret wires as the number of consecutive wires wires = range(wires) num_wires = len(wires) if qubits: if num_wires != len(qubits): raise qml.DeviceError( "The number of given qubits and the specified number of wires have to match. Got {} wires and {} qubits." .format(wires, len(qubits))) else: qubits = [cirq.LineQubit(idx) for idx in range(num_wires)] # cirq orders the subsystems based on a total order defined on qubits. # For consistency, this plugin uses that same total order self._unsorted_qubits = qubits self.qubits = sorted(qubits) super().__init__(wires, shots, analytic) self.circuit = None self.cirq_device = None # Add inverse operations self._inverse_operation_map = {} for key in self._operation_map: if not self._operation_map[key]: continue # We have to use a new CirqOperation instance because .inv() acts in-place inverted_operation = CirqOperation( self._operation_map[key].parametrization) inverted_operation.inv() self._inverse_operation_map[ key + Operation.string_for_inverse] = inverted_operation self._complete_operation_map = { **self._operation_map, **self._inverse_operation_map, }
def _load_template(self): """Load the template corresponding to the pyquil Program. Raises: qml.DeviceError: When the import of a forked gate is attempted """ self._parametrized_gates = [] for i, instruction in enumerate(self.program.instructions): # Skip all statements that are not gates (RESET, MEASURE, PRAGMA, ...) if not _is_gate(instruction): if not _is_declaration(instruction): warnings.warn( "Instruction Nr. {} is not supported by PennyLane and was ignored: {}" .format(i + 1, instruction)) continue # Rename for better readability gate = instruction if _is_forked(gate): raise qml.DeviceError( "Forked gates can not be imported into PennyLane, as this functionality is not supported. " + "Instruction Nr. {}, {} was a forked gate.".format( i + 1, gate)) resolved_gate = _resolve_gate(gate) # If the gate is a DefGate or not all CONTROLLED statements can be resolved # we resort to QubitUnitary if _is_controlled(resolved_gate) or self._is_defined_gate( resolved_gate): pl_gate = ParametrizedQubitUnitary( self._resolve_gate_matrix(resolved_gate)) else: pl_gate = pyquil_inv_operation_map[resolved_gate.name] parametrized_gate = ParametrizedGate(pl_gate, gate.qubits, gate.params, _is_inverted(gate)) self._parametrized_gates.append(parametrized_gate)
def _check_parameter_map(self, parameter_map): """Check that all variables of the program are defined. Only variables used in measurements need not be defined. Args: parameter_map (Dict[str, object]): Map that assigns values to variables Raises: qml.DeviceError: When not all variables are defined in the variable map """ for declaration in self._declarations: if not declaration.name in parameter_map: # If the variable is used in measurement we don't complain if not declaration.name in self._measurement_variable_names: raise qml.DeviceError(( "The PyQuil program defines a variable {} that is not present in the given variable map. " + "Instruction: {}").format(declaration.name, declaration))
def decompose_queue(ops, device): """Decompose operations in a queue that are not supported by a device. This is a wrapper function for :func:`~._decompose_queue`, which raises an error if an operation or its decomposition is not supported by the device. Args: ops (List[~.Operation]): operation queue device (~.Device): a PennyLane device """ new_ops = [] for op in ops: try: new_ops.extend(_decompose_queue([op], device)) except NotImplementedError: raise qml.DeviceError("Gate {} not supported on device {}".format(op.name, device.short_name)) return new_ops
def _load_qubit_to_wire_map(self, wires): """Build the map that assigns wires to qubits. Args: wires (Sequence[int]): The wires that should be assigned to the qubits Raises: qml.DeviceError: When the number of given wires does not match the number of qubits in the pyquil Program Returns: Dict[int, int]: The map that assigns wires to qubits """ if len(wires) != len(self.qubits): raise qml.DeviceError( "The number of given wires does not match the number of qubits in the PyQuil Program. " + "{} wires were given, Program has {} qubits".format( len(wires), len(self.qubits))) self._qubit_to_wire_map = dict(zip(self.qubits, wires)) return self._qubit_to_wire_map
def apply(self, operations, **kwargs): # pylint: disable=missing-function-docstring rotations = kwargs.pop("rotations", []) for i, operation in enumerate(operations): if i > 0 and operation.name in {"BasisState", "QubitStateVector"}: raise qml.DeviceError( "The operation {} is only supported at the beginning of a circuit." .format(operation.name)) if operation.name == "BasisState": self._apply_basis_state(operation) elif operation.name == "QubitStateVector": self._apply_qubit_state_vector(operation) else: self._apply_operation(operation) # TODO: get pre rotated state here # Diagonalize the given observables for operation in rotations: self._apply_operation(operation)