def test_tape_mode_detection(): """Test that the function `tape_mode_active` returns True only if tape mode is activated.""" qml.disable_tape() assert not qml.tape_mode_active() qml.enable_tape() assert qml.tape_mode_active()
def test_metric_tensor_tape_mode(self): """Test that the metric tensor can be calculated in tape mode, and that it is equal to a metric tensor calculated in non-tape mode.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=2) p = np.array([1., 1., 1.]) def ansatz(params, **kwargs): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) h = qml.Hamiltonian([1, 1], [qml.PauliZ(0), qml.PauliZ(1)]) qnodes = qml.ExpvalCost(ansatz, h, dev) mt = qml.metric_tensor(qnodes)(p) assert qml.tape_mode_active() # Check that tape mode is still active qml.disable_tape() @qml.qnode(dev) def circuit(params): qml.RX(params[0], wires=0) qml.RY(params[1], wires=0) qml.CNOT(wires=[0, 1]) qml.PhaseShift(params[2], wires=1) return qml.expval(qml.PauliZ(0)) mt2 = circuit.metric_tensor([p]) assert np.allclose(mt, mt2)
def test_backprop_gradients(self, mocker): """Test if KerasLayer is compatible with the backprop diff method.""" if not qml.tape_mode_active(): pytest.skip("This functionality is only supported in tape mode.") dev = qml.device("default.qubit.tf", wires=2) @qml.qnode(dev, interface="tf", diff_method="backprop") def f(inputs, weights): qml.templates.AngleEmbedding(inputs, wires=range(2)) qml.templates.StronglyEntanglingLayers(weights, wires=range(2)) return [qml.expval(qml.PauliZ(i)) for i in range(2)] weight_shapes = {"weights": (3, 2, 3)} qlayer = qml.qnn.KerasLayer(f, weight_shapes, output_dim=2) inputs = tf.ones((4, 2)) with tf.GradientTape() as tape: out = tf.reduce_sum(qlayer(inputs)) spy = mocker.spy(qml.tape.tapes.QubitParamShiftTape, "jacobian") grad = tape.gradient(out, qlayer.trainable_weights) assert grad is not None spy.assert_not_called()
def test_grad_tf(self, qnodes, skip_if_no_tf_support, parallel, interface): """Test correct gradient of the QNodeCollection using the tf interface""" if parallel and qml.tape_mode_active(): pytest.skip( "There appears to be a race condition when constructing TF tapes in parallel" ) qnode1, qnode2 = qnodes # calculate the gradient of the collection using tf params = Variable([0.5643, -0.45]) qc = qml.QNodeCollection([qnode1, qnode2]) with tf.GradientTape() as tape: tape.watch(params) if parallel: with pytest.warns(UserWarning): cost = sum(qc(params, parallel=parallel)) else: cost = sum(qc(params, parallel=parallel)) # the gradient will be None res = tape.gradient(cost, params).numpy() # calculate the gradient of the QNodes individually using tf params = Variable([0.5643, -0.45]) with tf.GradientTape() as tape: tape.watch(params) cost = sum(qnode1(params) + qnode2(params)) expected = tape.gradient(cost, params).numpy() assert np.all(res == expected)
def _evaluate_qnode(self, x): """Evaluates the QNode for a single input datapoint. Args: x (tensor): the datapoint Returns: tensor: output datapoint """ if qml.tape_mode_active(): return self._evaluate_qnode_tape_mode(x) qnode = self.qnode for arg in self.sig: if arg is not self.input_arg: # Non-input arguments must always be positional w = self.qnode_weights[arg] qnode = functools.partial(qnode, w) else: if self.input_is_default: # The input argument can be positional or keyword qnode = functools.partial(qnode, **{self.input_arg: x}) else: qnode = functools.partial(qnode, x) return qnode().type(x.dtype)
def _preprocess(weights, initial_layer_weights, wires): """Validate and pre-process inputs as follows: * Check the shapes of the two weights tensors. Args: weights (tensor_like): trainable parameters of the template initial_layer_weights (tensor_like): weight tensor for the initial rotation block, shape ``(M,)`` wires (Wires): wires that template acts on Returns: int: number of times that the ansatz is repeated """ if qml.tape_mode_active(): shape = qml.math.shape(weights) repeat = shape[0] if len(shape) > 1: if shape[1] != len(wires) - 1: raise ValueError( f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" ) if shape[2] != 2: raise ValueError( f"Weights tensor must have third dimension of length 2; got {shape[2]}" ) shape2 = qml.math.shape(initial_layer_weights) if shape2 != (len(wires), ): raise ValueError( f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}" ) else: repeat = check_number_of_layers([weights]) expected_shape_initial = (len(wires), ) check_shape( initial_layer_weights, expected_shape_initial, msg="Initial layer weights must be of shape {}; got {}" "".format(expected_shape_initial, get_shape(initial_layer_weights)), ) if len(wires) in [0, 1]: expected_shape_weights = (0, ) else: expected_shape_weights = (repeat, len(wires) - 1, 2) check_shape( weights, expected_shape_weights, msg="Weights tensor must be of shape {}; got {}" "".format(expected_shape_weights, get_shape(weights)), ) return repeat
def test_exception_wrong_dim(self): """Verifies that exception is raised if the number of dimensions of features is incorrect.""" if not qml.tape_mode_active(): pytest.skip("This validation is only performed in tape mode") dev = qml.device("default.qubit", wires=4) initial_layer = np.random.randn(2) @qml.qnode(dev) def circuit(initial_layer, weights): SimplifiedTwoDesign(initial_layer, weights, wires=range(2)) return qml.expval(qml.PauliZ(0)) with pytest.raises(ValueError, match="Weights tensor must have second dimension"): weights = np.random.randn(2, 2, 2) circuit(initial_layer, weights) with pytest.raises(ValueError, match="Weights tensor must have third dimension"): weights = np.random.randn(2, 1, 3) circuit(initial_layer, weights) with pytest.raises(ValueError, match="Initial layer weights must be of shape"): initial_layer = np.random.randn(3) weights = np.random.randn(2, 1, 2) circuit(initial_layer, weights)
def test_optimize_grad(self): """Test that the gradient of ExpvalCost is accessible and correct when using observable optimization and the autograd interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True) cost2 = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=False) w = qml.init.strong_ent_layers_uniform(2, 4, seed=1967) dc = qml.grad(cost)(w) exec_opt = dev.num_executions dev._num_executions = 0 dc2 = qml.grad(cost2)(w) exec_no_opt = dev.num_executions assert exec_no_opt > exec_opt assert np.allclose(dc, big_hamiltonian_grad) assert np.allclose(dc2, big_hamiltonian_grad)
def test_qnode_collection_integration(self): """Test that a PassthruQNode using default.qubit.jax works with QNodeCollections.""" dev = qml.device("default.qubit.jax", wires=2) def ansatz(weights, **kwargs): qml.RX(weights[0], wires=0) qml.RY(weights[1], wires=1) qml.CNOT(wires=[0, 1]) obs_list = [ qml.PauliX(0) @ qml.PauliY(1), qml.PauliZ(0), qml.PauliZ(0) @ qml.PauliZ(1) ] qnodes = qml.map(ansatz, obs_list, dev, interface="jax") if not qml.tape_mode_active(): assert qnodes.interface == "jax" weights = jnp.array([0.1, 0.2]) def cost(weights): return jnp.sum(jnp.array(qnodes(weights))) grad = jax.grad(cost)(weights) assert grad.shape == weights.shape
def _preprocess(weight, wires): """Validate and pre-process inputs as follows: * Check the shape of the weights tensor. * Check that there are at least 2 wires. Args: weight (tensor_like): trainable parameters of the template wires (Wires): wires that template acts on """ if len(wires) < 2: raise ValueError("expected at least two wires; got {}".format( len(wires))) if qml.tape_mode_active(): shape = qml.math.shape(weight) if shape != (): raise ValueError( f"Weight must be a scalar tensor {()}; got shape {shape}.") else: expected_shape = () check_shape( weight, expected_shape, msg="Weight must be a scalar; got shape {}".format( expected_shape, get_shape(weight)), )
def _preprocess(weights): """Validate and pre-process inputs as follows: * Check that the weights tensor is 2-dimensional. Args: weights (tensor_like): trainable parameters of the template Returns: int: number of times that the ansatz is repeated """ if qml.tape_mode_active(): shape = qml.math.shape(weights) if len(shape) != 2: raise ValueError( f"Weights tensor must be 2-dimensional; got shape {shape}") repeat = shape[0] else: repeat = check_number_of_layers([weights]) n_rots = get_shape(weights)[1] expected_shape = (repeat, n_rots) check_shape( weights, expected_shape, msg="'weights' must be of shape {}; got {}" "".format(expected_shape, get_shape(weights)), ) return repeat
def test_optimize_grad_torch(self, torch_support): """Test that the gradient of ExpvalCost is accessible and correct when using observable optimization and the Torch interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") if not torch_support: pytest.skip("This test requires Torch") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.ExpvalCost( qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True, interface="torch", ) w = torch.tensor(qml.init.strong_ent_layers_uniform(2, 4, seed=1967), requires_grad=True) res = cost(w) res.backward() dc = w.grad.detach().numpy() assert np.allclose(dc, big_hamiltonian_grad)
def test_exception_wrong_dim(self): """Verifies that exception is raised if the number of dimensions of features is incorrect.""" if not qml.tape_mode_active(): pytest.skip("This validation is only performed in tape mode") dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(state_vector): MottonenStatePreparation(state_vector, wires=range(2)) return qml.expval(qml.PauliZ(0)) with pytest.raises(ValueError, match="State vector must be a one-dimensional"): state_vector = np.array([[0, 1]]) circuit(state_vector) with pytest.raises(ValueError, match="State vector must be of length"): state_vector = np.array([0, 1]) circuit(state_vector) with pytest.raises(ValueError, match="State vector has to be of length"): state_vector = np.array([0, 2, 0, 0]) circuit(state_vector)
def test_optimize_grad_tf(self, tf_support): """Test that the gradient of ExpvalCost is accessible and correct when using observable optimization and the TensorFlow interface.""" if not qml.tape_mode_active(): pytest.skip("This test is only intended for tape mode") if not tf_support: pytest.skip("This test requires TensorFlow") dev = qml.device("default.qubit", wires=4) hamiltonian = big_hamiltonian cost = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, hamiltonian, dev, optimize=True, interface="tf") w = tf.Variable(qml.init.strong_ent_layers_uniform(2, 4, seed=1967)) with tf.GradientTape() as tape: res = cost(w) dc = tape.gradient(res, w).numpy() assert np.allclose(dc, big_hamiltonian_grad)
def test_exception_wrong_dim(self): """Verifies that exception is raised if the number of dimensions of features is incorrect.""" if not qml.tape_mode_active(): pytest.skip("This validation is only performed in tape mode") dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(weights): StronglyEntanglingLayers(weights, wires=range(2)) return qml.expval(qml.PauliZ(0)) with pytest.raises(ValueError, match="Weights tensor must have second dimension"): weights = np.random.randn(2, 1, 3) circuit(weights) with pytest.raises(ValueError, match="Weights tensor must have third dimension"): weights = np.random.randn(2, 2, 1) circuit(weights) with pytest.raises(ValueError, match="Weights tensor must be 3-dimensional"): weights = np.random.randn(2, 2, 3, 1) circuit(weights)
def metric_tensor(self, args, kwargs=None, diag_approx=False, only_construct=False): """Evaluate the value of the metric tensor. Args: args (tuple[Any]): positional (differentiable) arguments kwargs (dict[str, Any]): auxiliary arguments diag_approx (bool): iff True, use the diagonal approximation only_construct (bool): Iff True, construct the circuits used for computing the metric tensor but do not execute them, and return None. Returns: array[float]: metric tensor """ if self._multiple_devices: warnings.warn( "ExpvalCost was instantiated with multiple devices. Only the first device " "will be used to evaluate the metric tensor.") if qml.tape_mode_active(): return self._qnode_for_metric_tensor_in_tape_mode.metric_tensor( args=args, kwargs=kwargs, diag_approx=diag_approx, only_construct=only_construct) # all the qnodes share the same ansatz so we select the first return self.qnodes.qnodes[0].metric_tensor( args=args, kwargs=kwargs, diag_approx=diag_approx, only_construct=only_construct)
def test_direct_qnode_integration(self): """Test that a regular QNode renders correctly.""" dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def qfunc(a, w): qml.Hadamard(0) qml.CRX(a, wires=[0, 1]) qml.Rot(w[0], w[1], w[2], wires=[1]) qml.CRX(-a, wires=[0, 1]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) res = qfunc(2.3, [1.2, 3.2, 0.7]) assert qfunc.draw() == ( " 0: ──H──╭C────────────────────────────╭C─────────╭┤ ⟨Z ⊗ Z⟩ \n" + " 1: ─────╰RX(2.3)──Rot(1.2, 3.2, 0.7)──╰RX(-2.3)──╰┤ ⟨Z ⊗ Z⟩ \n" ) assert qfunc.draw(charset="ascii") == ( " 0: --H--+C----------------------------+C---------+| <Z @ Z> \n" + " 1: -----+RX(2.3)--Rot(1.2, 3.2, 0.7)--+RX(-2.3)--+| <Z @ Z> \n" ) if not qml.tape_mode_active(): assert qfunc.draw(show_variable_names=True) == ( " 0: ──H──╭C─────────────────────────────╭C─────────╭┤ ⟨Z ⊗ Z⟩ \n" + " 1: ─────╰RX(a)──Rot(w[0], w[1], w[2])──╰RX(-1*a)──╰┤ ⟨Z ⊗ Z⟩ \n" )
def call(self, inputs): """Evaluates the QNode on input data using the initialized weights. Args: inputs (tensor): data to be processed Returns: tensor: output data """ outputs = [] for x in inputs: # iterate over batch if qml.tape_mode_active(): res = self._evaluate_qnode_tape_mode(x) outputs.append(res) else: # The QNode can require some passed arguments to be positional and others to be # keyword. The following loops through input arguments in order and uses # functools.partial to bind the argument to the QNode. qnode = self.qnode for arg in self.sig: if arg is not self.input_arg: # Non-input arguments must always be positional w = self.qnode_weights[arg] qnode = functools.partial(qnode, w) outputs.append(self.quanv(x)) return tf.stack(outputs)
def _preprocess(weights, wires): """Validate and pre-process inputs as follows: * Check the shape of the weights tensor. Args: weights (tensor_like): trainable parameters of the template wires (Wires): wires that template acts on """ if qml.tape_mode_active(): shape = qml.math.shape(weights) if shape != (4**len(wires) - 1, ): raise ValueError( f"Weights tensor must be of shape {(4 ** len(wires) - 1,)}; got {shape}." ) else: expected_shape = (4**len(wires) - 1, ) check_shape( weights, expected_shape, msg="Weights tensor must be of shape {}; got {}." "".format(expected_shape, get_shape(weights)), )
def _preprocess(features, wires): """Validate and pre-process inputs as follows: * Check that the features tensor is one-dimensional. * Check that the first dimension of the features tensor has length :math:`n`, where :math:`n` is the number of qubits. * Check that the entries of the features tensor are zeros and ones. Args: features (tensor_like): input features to pre-process wires (Wires): wires that template acts on Returns: array: numpy array representation of the features tensor """ if qml.tape_mode_active(): shape = qml.math.shape(features) if len(shape) != 1: raise ValueError( f"Features must be one-dimensional; got shape {shape}.") n_features = shape[0] if n_features != len(wires): raise ValueError( f"Features must be of length {len(wires)}; got length {n_features}." ) features = list(qml.math.toarray(features)) if set(features) != {0, 1}: raise ValueError( f"Basis state must only consist of 0s and 1s; got {features}") return features # non-tape mode check_type( features, [Iterable], msg="Features must be iterable; got type {}".format(type(features)), ) expected_shape = (len(wires), ) check_shape( features, expected_shape, msg="Features must be of shape {}; got {}" "".format(expected_shape, get_shape(features)), ) if any([b not in [0, 1] for b in features]): raise ValueError( "Basis state must only consist of 0s and 1s; got {}".format( features)) return features
def _preprocess(parameters, pattern, wires): """Validate and pre-process inputs as follows: * Check that pattern is recognised, or use default pattern if None. * Check the dimension of the parameters * Create wire sequence of the pattern. Args: parameters (tensor_like): trainable parameters of the template pattern (str): specifies the wire pattern wires (Wires): wires that template acts on Returns: wire_sequence, parameters: preprocessed pattern and parameters """ if isinstance(pattern, str): _wires = wires if pattern not in OPTIONS: raise ValueError(f"did not recognize pattern {pattern}".format()) else: # turn custom pattern into list of Wires objects _wires = [Wires(w) for w in pattern] # set "pattern" to "custom", indicating that custom settings have to be used pattern = "custom" # check that there are enough parameters for pattern if parameters is not None: if qml.tape_mode_active(): shape = qml.math.shape(parameters) # expand dimension so that parameter sets for each unitary can be unpacked if len(shape) == 1: parameters = qml.math.expand_dims(parameters, 1) else: shape = get_shape(parameters) # expand dimension so that parameter sets for each unitary can be unpacked if len(shape) == 1: parameters = [[p] for p in parameters] # specific error message for ring edge case of 2 wires if (pattern == "ring") and (len(wires) == 2) and (shape[0] != 1): raise ValueError( "the ring pattern with 2 wires is an exception and only applies one unitary" ) num_params = PATTERN_TO_NUM_PARAMS[pattern](_wires) if shape[0] != num_params: raise ValueError( "Parameters must contain entries for {} unitaries; got {} entries".format( num_params, shape[0] ) ) wire_sequence = PATTERN_TO_WIRES[pattern](_wires) return wire_sequence, parameters
def _preprocess(weights, wires, init_state): """Validate and pre-process inputs as follows: * Check that the weights tensor has the correct shape. * Extract a wire list for the subroutines of this template. * Cast initial state to a numpy array. Args: weights (tensor_like): trainable parameters of the template wires (Wires): wires that template acts on init_state (tensor_like): shape ``(len(wires),)`` tensor Returns: int, list[Wires], array: number of times that the ansatz is repeated, wires pattern, and preprocessed initial state """ if len(wires) < 2: raise ValueError( "This template requires the number of qubits to be greater than one;" "got a wire sequence with {} elements".format(len(wires))) if qml.tape_mode_active(): shape = qml.math.shape(weights) if len(shape) != 3: raise ValueError( f"Weights tensor must be 3-dimensional; got shape {shape}") if shape[1] != len(wires) - 1: raise ValueError( f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" ) if shape[2] != 2: raise ValueError( f"Weights tensor must have third dimension of length 2; got {shape[2]}" ) repeat = shape[0] else: repeat = get_shape(weights)[0] expected_shape = (repeat, len(wires) - 1, 2) check_shape( weights, expected_shape, msg="Weights tensor must be of shape {}; got {}".format( expected_shape, get_shape(weights)), ) nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)] nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)] # we can extract the numpy representation here # since init_state can never be differentiable init_state = qml.math.toarray(init_state) return repeat, nm_wires, init_state
def test_qubit_circuit_with_variable_names( self, parameterized_qubit_qnode, drawn_parameterized_qubit_circuit_with_variable_names ): """Test that a parametrized qubit circuit renders correctly with variable names.""" if qml.tape_mode_active(): pytest.skip("show_variable_names not supported in tape mode") output = parameterized_qubit_qnode.draw(show_variable_names=True) assert output == drawn_parameterized_qubit_circuit_with_variable_names
def __init__( self, ansatz, hamiltonian, device, interface="autograd", diff_method="best", optimize=False, **kwargs, ): coeffs, observables = hamiltonian.terms self.hamiltonian = hamiltonian """Hamiltonian: the hamiltonian defining the VQE problem.""" self.qnodes = None """QNodeCollection: The QNodes to be evaluated. Each QNode corresponds to the expectation value of each observable term after applying the circuit ansatz.""" self._optimize = optimize if self._optimize: if not qml.tape_mode_active(): raise ValueError( "Observable optimization is only supported in tape mode. Tape " "mode can be enabled with the command:\n" "qml.enable_tape()" ) obs_groupings, coeffs_groupings = qml.grouping.group_observables(observables, coeffs) wires = device.wires.tolist() @qml.qnode(device, interface=interface, diff_method=diff_method, **kwargs) def circuit(*qnode_args, obs, **qnode_kwargs): """Converting ansatz into a full circuit including measurements""" ansatz(*qnode_args, wires=wires, **qnode_kwargs) return [qml.expval(o) for o in obs] def cost_fn(*qnode_args, **qnode_kwargs): """Combine results from grouped QNode executions with grouped coefficients""" total = 0 for o, c in zip(obs_groupings, coeffs_groupings): res = circuit(*qnode_args, obs=o, **qnode_kwargs) total += sum([r * c_ for r, c_ in zip(res, c)]) return total self.cost_fn = cost_fn else: self.qnodes = qml.map( ansatz, observables, device, interface=interface, diff_method=diff_method, **kwargs ) self.cost_fn = qml.dot(coeffs, self.qnodes)
def _preprocess(weights, wires, ranges): """Validate and pre-process inputs as follows: * Check the shape of the weights tensor. * If ranges is None, define a default. Args: weights (tensor_like): trainable parameters of the template wires (Wires): wires that template acts on ranges (Sequence[int]): range for each subsequent layer Returns: int, list[int]: number of times that the ansatz is repeated and preprocessed ranges """ if qml.tape_mode_active(): shape = qml.math.shape(weights) repeat = shape[0] if len(shape) != 3: raise ValueError( f"Weights tensor must be 3-dimensional; got shape {shape}") if shape[1] != len(wires): raise ValueError( f"Weights tensor must have second dimension of length {len(wires)}; got {shape[1]}" ) if shape[2] != 3: raise ValueError( f"Weights tensor must have third dimension of length 3; got {shape[2]}" ) else: repeat = check_number_of_layers([weights]) expected_shape = (repeat, len(wires), 3) check_shape( weights, expected_shape, msg="Weights tensor must be of shape {}; got {}" "".format(expected_shape, get_shape(weights)), ) if len(wires) > 1: if ranges is None: # tile ranges with iterations of range(1, n_wires) ranges = [(l % (len(wires) - 1)) + 1 for l in range(repeat)] else: ranges = [0] * repeat return repeat, ranges
def wrapper(*args, **kwargs): import pennylane as qml recorder_class = OperationRecorder if qml.tape_mode_active(): recorder_class = qml.tape.TapeOperationRecorder with recorder_class() as rec: func(*args, **kwargs) return rec.queue
def test_train_model_dm(self, model_dm, batch_size, n_qubits, output_dim): """Test if a model can train using the KerasLayer when QNode returns a density_matrix(). The model is composed of two KerasLayers sandwiched between Dense neural network layers, and the dataset is simply input and output vectors of zeros.""" if not qml.tape_mode_active(): pytest.skip() x = np.zeros((batch_size, n_qubits)) y = np.zeros((batch_size, output_dim[0] * output_dim[1])) model_dm.compile(optimizer="sgd", loss="mse") model_dm.fit(x, y, batch_size=batch_size, verbose=0)
def _preprocess(basis_state, wires): """Validate and pre-process inputs as follows: * Check the shape of the basis state. * Cast basis state to a numpy array. Args: basis_state (tensor_like): basis state to prepare wires (Wires): wires that template acts on Returns: array: preprocessed basis state """ if qml.tape_mode_active(): shape = qml.math.shape(basis_state) if len(shape) != 1: raise ValueError(f"Basis state must be one-dimensional; got shape {shape}.") n_bits = shape[0] if n_bits != len(wires): raise ValueError(f"Basis state must be of length {len(wires)}; got length {n_bits}.") basis_state = list(qml.math.toarray(basis_state)) if not all(bit in [0, 1] for bit in basis_state): raise ValueError(f"Basis state must only consist of 0s and 1s; got {basis_state}") # we return the input as a list of values, since # it is not differentiable return basis_state expected_shape = (len(wires),) check_shape( basis_state, expected_shape, msg="Basis state must be of shape {}; got {}." "".format(expected_shape, get_shape(basis_state)), ) # basis_state is guaranteed to be a list of binary values if any([b not in [0, 1] for b in basis_state]): raise ValueError( "Basis state must only contain values of 0 and 1; got {}".format(basis_state) ) return basis_state
def test_non_input_defaults_tape_mode(self): """Test that everything works when default arguments that are not the input argument are present in the QNode in tape mode""" if not qml.tape_mode_active(): pytest.skip("This functionality is only supported in tape mode.") n_qubits = 2 output_dim = 2 dev = qml.device("default.qubit", wires=n_qubits) w = { "w1": (3, n_qubits, 3), "w2": (1, ), "w3": 1, "w4": [3], "w5": (2, n_qubits, 3), "w6": 3, "w7": 0, } @qml.qnode(dev, interface="tf") def c(inputs, w1, w2, w4, w5, w6, w7, w3=0.5): """A circuit that embeds data using the AngleEmbedding and then performs a variety of operations. The output is a PauliZ measurement on the first output_dim qubits. One set of parameters, w5, are specified as non-trainable.""" qml.templates.AngleEmbedding(inputs, wires=list(range(n_qubits))) qml.templates.StronglyEntanglingLayers(w1, wires=list(range(n_qubits))) qml.RX(w2[0], wires=0 % n_qubits) qml.RX(w3, wires=1 % n_qubits) qml.Rot(*w4, wires=2 % n_qubits) qml.templates.StronglyEntanglingLayers(w5, wires=list(range(n_qubits))) qml.Rot(*w6, wires=3 % n_qubits) qml.RX(w7, wires=4 % n_qubits) return [qml.expval(qml.PauliZ(i)) for i in range(output_dim)] layer = KerasLayer(c, w, output_dim=output_dim) x = tf.ones((2, n_qubits)) layer_out = layer(x) circ_weights = layer.qnode_weights.copy() circ_weights["w4"] = tf.convert_to_tensor( circ_weights["w4"]) # To allow for slicing circ_weights["w6"] = tf.convert_to_tensor(circ_weights["w6"]) circuit_out = c(x[0], **circ_weights) assert np.allclose(layer_out, circuit_out)
def test_jax_interface_gradient(self, operation, diff_method, tol): """Tests that the gradient of an arbitrary U3 gate is correct using the Jax interface, using a variety of differentiation methods.""" dev = qml.device("default.qubit.jax", wires=1) @qml.qnode(dev, diff_method=diff_method, interface="jax") def circuit(x, weights, w=None): """In this example, a mixture of scalar arguments, array arguments, and keyword arguments are used.""" qml.QubitStateVector(1j * jnp.array([1, -1]) / jnp.sqrt(2), wires=w) operation(x, weights[0], weights[1], wires=w) return qml.expval(qml.PauliX(w)) # Check that the correct QNode type is being used. if not qml.tape_mode_active(): if diff_method == "backprop": assert isinstance(circuit, qml.qnodes.PassthruQNode) assert not hasattr(circuit, "jacobian") else: assert not isinstance(circuit, qml.qnodes.PassthruQNode) assert hasattr(circuit, "jacobian") def cost(params): """Perform some classical processing""" return (circuit(params[0], params[1:], w=0)**2).reshape(()) theta = 0.543 phi = -0.234 lam = 0.654 params = jnp.array([theta, phi, lam]) res = cost(params) expected_cost = (jnp.sin(lam) * jnp.sin(phi) - jnp.cos(theta) * jnp.cos(lam) * jnp.cos(phi))**2 assert jnp.allclose(res, expected_cost, atol=tol, rtol=0) res = jax.grad(cost)(params) expected_grad = (jnp.array([ jnp.sin(theta) * jnp.cos(lam) * jnp.cos(phi), jnp.cos(theta) * jnp.cos(lam) * jnp.sin(phi) + jnp.sin(lam) * jnp.cos(phi), jnp.cos(theta) * jnp.sin(lam) * jnp.cos(phi) + jnp.cos(lam) * jnp.sin(phi), ]) * 2 * (jnp.sin(lam) * jnp.sin(phi) - jnp.cos(theta) * jnp.cos(lam) * jnp.cos(phi))) assert jnp.allclose(res, expected_grad, atol=tol, rtol=0)