def test_array_arguments(self, mock_device): """Test that array arguments are properly converted to VariableRef instances.""" def circuit(weights): qml.RX(weights[0, 0], wires=[0]) qml.RY(weights[0, 1], wires=[0]) qml.RZ(weights[1, 0], wires=[0]) qml.RZ(weights[1, 1], wires=[0]) return qml.expval(qml.PauliX(0)) node = BaseQNode(circuit, mock_device) weights = np.array([[1, 2], [3, 4]]) arg_vars, kwarg_vars = node._make_variables([weights], {}) expected_arg_vars = [ VariableRef(0, "weights[0,0]"), VariableRef(1, "weights[0,1]"), VariableRef(2, "weights[1,0]"), VariableRef(3, "weights[1,1]"), ] for var, expected in zip(qml.utils._flatten(arg_vars), expected_arg_vars): assert var == expected assert not kwarg_vars
def test_regular_keyword_arguments(self, mock_device): """Test that regular keyword arguments are properly converted to VariableRef instances.""" def circuit(*, a=1, b=2, c=3, d=4): qml.RX(a, wires=[0]) qml.RY(b, wires=[0]) qml.RZ(c, wires=[0]) qml.RZ(d, wires=[0]) return qml.expval(qml.PauliX(0)) node = BaseQNode(circuit, mock_device) arg_vars, kwarg_vars = node._make_variables([], {"b" : 3}) expected_kwarg_vars = { "a" : [VariableRef(0, "a", is_kwarg=True)], "b" : [VariableRef(0, "b", is_kwarg=True)], "c" : [VariableRef(0, "c", is_kwarg=True)], "d" : [VariableRef(0, "d", is_kwarg=True)], } assert not arg_vars for expected_key in expected_kwarg_vars: for var, expected in zip(qml.utils._flatten(kwarg_vars[expected_key]), qml.utils._flatten(expected_kwarg_vars[expected_key])): assert var == expected
def test_variable_str(): """Variable informal string rep.""" p = VariableRef(0) assert str(p) == "VariableRef: name = None, idx = 0" assert str(-p) == "VariableRef: name = None, idx = 0, * -1" p = VariableRef(0, name="kw1") assert str(p) == "VariableRef: name = kw1, idx = 0" assert str(2.1 * p) == "VariableRef: name = kw1, idx = 0, * 2.1"
def test_variable_repr(): """Variable string rep.""" p = VariableRef(0) assert repr(p) == "<VariableRef(None:0)>" assert repr(-p) == "<VariableRef(None:0 * -1)>" assert repr(1.2 * p * 0.4) == "<VariableRef(None:0 * 0.48)>" assert repr(1.2 * p / 2.5) == "<VariableRef(None:0 * 0.48)>" p = VariableRef(0, name="kw1") assert repr(p) == "<VariableRef(kw1:0)>" assert repr(-p) == "<VariableRef(kw1:0 * -1)>" assert repr(1.2 * p * 0.4) == "<VariableRef(kw1:0 * 0.48)>" assert repr(1.2 * p / 2.5) == "<VariableRef(kw1:0 * 0.48)>"
def test_keyword_variable(par_keyword, name, ind, mult, tol): """Keyword variable evaluation.""" v = VariableRef(ind, name, is_kwarg=True) assert v.name == name assert v.mult == 1 assert v.idx == ind variable_eval_asserts(v, par_keyword[name][ind], mult, tol)
def test_variable_val(par_positional, ind, mult, tol): """Positional variable evaluation.""" v = VariableRef(ind) assert v.name is None assert v.mult == 1 assert v.idx == ind variable_eval_asserts(v, par_positional[ind], mult, tol)
def test_variadic_arguments(self, mock_device): """Test that variadic arguments are properly converted to VariableRef instances.""" def circuit(a, *b): qml.RX(a, wires=[0]) qml.RX(b[0], wires=[0]) qml.RX(b[1][1], wires=[0]) qml.RX(b[2], wires=[0]) return qml.expval(qml.PauliX(0)) node = BaseQNode(circuit, mock_device) arg_vars, kwarg_vars = node._make_variables([0.1, 0.2, np.array([0, 1, 2, 3]), 0.5], {}) expected_arg_vars = [ VariableRef(0, "a"), VariableRef(1, "b[0]"), VariableRef(2, "b[1][0]"), VariableRef(3, "b[1][1]"), VariableRef(4, "b[1][2]"), VariableRef(5, "b[1][3]"), VariableRef(6, "b[2]"), ] assert not kwarg_vars for var, expected in zip(qml.utils._flatten(arg_vars), expected_arg_vars): assert var == expected
def test_regular_arguments(self, mock_device): """Test that regular arguments are properly converted to VariableRef instances.""" def circuit(a, b, c, d): qml.RX(a, wires=[0]) qml.RY(b, wires=[0]) qml.RZ(c, wires=[0]) qml.RZ(d, wires=[0]) return qml.expval(qml.PauliX(0)) node = BaseQNode(circuit, mock_device) arg_vars, kwarg_vars = node._make_variables([1.0, 2.0, 3.0, 4.0], {}) expected_arg_vars = [ VariableRef(0, "a"), VariableRef(1, "b"), VariableRef(2, "c"), VariableRef(3, "d"), ] for var, expected in zip(qml.utils._flatten(arg_vars), expected_arg_vars): assert var == expected assert not kwarg_vars
def test_array_keyword_arguments(self, mock_device): """Test that array keyword arguments are properly converted to VariableRef instances.""" def circuit(*, a=np.array([[1, 0], [0, 1]]), b=np.array([1,2,3])): qml.RX(a[0, 0], wires=[0]) qml.RX(a[0, 1], wires=[0]) qml.RX(a[1, 0], wires=[0]) qml.RX(a[1, 1], wires=[0]) qml.RY(b[0], wires=[0]) qml.RY(b[1], wires=[0]) qml.RY(b[2], wires=[0]) return qml.expval(qml.PauliX(0)) node = BaseQNode(circuit, mock_device) arg_vars, kwarg_vars = node._make_variables([], {"b" : np.array([6,7,8,9])}) expected_kwarg_vars = { "a" : [ VariableRef(0, "a[0,0]", is_kwarg=True), VariableRef(1, "a[0,1]", is_kwarg=True), VariableRef(2, "a[1,0]", is_kwarg=True), VariableRef(3, "a[1,1]", is_kwarg=True), ], "b" : [ VariableRef(0, "b[0]", is_kwarg=True), VariableRef(1, "b[1]", is_kwarg=True), VariableRef(2, "b[2]", is_kwarg=True), VariableRef(3, "b[3]", is_kwarg=True), ], } assert not arg_vars for expected_key in expected_kwarg_vars: for var, expected in zip(qml.utils._flatten(kwarg_vars[expected_key]), qml.utils._flatten(expected_kwarg_vars[expected_key])): assert var == expected
class TestOperations: """Tests the logic related to operations""" def test_op_queue_accessed_outside_execution_context(self, mock_qubit_device): """Tests that a call to op_queue outside the execution context raises the correct error""" with pytest.raises( ValueError, match="Cannot access the operation queue outside of the execution context!" ): mock_qubit_device.op_queue def test_op_queue_is_filled_during_execution( self, mock_qubit_device_with_paulis_and_methods, monkeypatch ): """Tests that the op_queue is correctly filled when apply is called and that accessing op_queue raises no error""" queue = [qml.PauliX(wires=0), qml.PauliY(wires=1), qml.PauliZ(wires=2)] observables = [qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2))] circuit_graph = CircuitGraph(queue + observables, {}) call_history = [] with monkeypatch.context() as m: m.setattr(QubitDevice, "apply", lambda self, x, **kwargs: call_history.extend(x + kwargs.get('rotations', []))) mock_qubit_device_with_paulis_and_methods.execute(circuit_graph) assert call_history == queue assert len(call_history) == 3 assert isinstance(call_history[0], qml.PauliX) assert call_history[0].wires == [0] assert isinstance(call_history[1], qml.PauliY) assert call_history[1].wires == [1] assert isinstance(call_history[2], qml.PauliZ) assert call_history[2].wires == [2] def test_unsupported_operations_raise_error(self, mock_qubit_device_with_paulis_and_methods): """Tests that the operations are properly applied and queued""" queue = [qml.PauliX(wires=0), qml.PauliY(wires=1), qml.Hadamard(wires=2)] observables = [qml.expval(qml.PauliZ(0)), qml.var(qml.PauliZ(1)), qml.sample(qml.PauliZ(2))] circuit_graph = CircuitGraph(queue + observables, {}) with pytest.raises(DeviceError, match="Gate Hadamard not supported on device"): mock_qubit_device_with_paulis_and_methods.execute(circuit_graph) numeric_queues = [ [ qml.RX(0.3, wires=[0]) ], [ qml.RX(0.3, wires=[0]), qml.RX(0.4, wires=[1]), qml.RX(0.5, wires=[2]), ] ] variable = VariableRef(1) symbolic_queue = [ [qml.RX(variable, wires=[0])], ] observables = [ [qml.PauliZ(0)], [qml.PauliX(0)], [qml.PauliY(0)] ] @pytest.mark.parametrize("observables", observables) @pytest.mark.parametrize("queue", numeric_queues + symbolic_queue) def test_passing_keyword_arguments_to_execute(self, mock_qubit_device_with_paulis_rotations_and_methods, monkeypatch, queue, observables): """Tests that passing keyword arguments to execute propagates those kwargs to the apply() method""" circuit_graph = CircuitGraph(queue + observables, {}) call_history = {} with monkeypatch.context() as m: m.setattr(QubitDevice, "apply", lambda self, x, **kwargs: call_history.update(kwargs)) mock_qubit_device_with_paulis_rotations_and_methods.execute(circuit_graph, hash=circuit_graph.hash) len(call_history.items()) == 1 call_history["hash"] = circuit_graph.hash
def _make_variables(self, args, kwargs): """Create the :class:`~.variable.VariableRef` instances representing the QNode's arguments. The created :class:`~.variable.VariableRef` instances are given in the same nested structure as the original arguments. The :class:`~.variable.VariableRef` instances are named according to the argument names given in the QNode definition. Consider the following example: .. code-block:: python3 @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)) In this example, ``_make_variables`` will return the following :class:`~.variable.VariableRef` instances .. code-block:: python3 >>> qfunc(3.4, [1.2, 3.4, 5.6]) -0.031664133410566786 >>> qfunc._make_variables([3.4, [1.2, 3.4, 5.6]], {}) ["a", ["w[0]", "w[1]", "w[2]"]], {} where the VariableRef instances are replaced with their name for readability. Args: args (tuple[Any]): Positional arguments passed to the quantum function. During the construction we are not concerned with the numerical values, but with the nesting structure. Each positional argument is replaced with a :class:`~.variable.VariableRef` instance. kwargs (dict[str, Any]): Auxiliary arguments passed to the quantum function. """ # Get the name of the qfunc's arguments full_argspec = inspect.getfullargspec(self.func) # args variable_name_strings = [] for variable_name, variable_value in zip(full_argspec.args, args): variable_name_strings.append( self._determine_structured_variable_name( variable_value, variable_name)) # varargs len_diff = len(args) - len(full_argspec.args) if len_diff > 0: for idx, variable_value in enumerate(args[-len_diff:]): variable_name = "{}[{}]".format(full_argspec.varargs, idx) variable_name_strings.append( self._determine_structured_variable_name( variable_value, variable_name)) arg_vars = [ VariableRef(idx, name) for idx, name in enumerate(_flatten(variable_name_strings)) ] self.num_variables = len(arg_vars) # arrange the newly created VariableRefs in the nested structure of args arg_vars = unflatten(arg_vars, args) # kwargs # if not mutable: must convert auxiliary arguments to named VariableRefs so they can be updated without re-constructing the circuit #kwarg_vars = {} #for key, val in kwargs.items(): # temp = [VariableRef(idx, name=key) for idx, _ in enumerate(_flatten(val))] # kwarg_vars[key] = unflatten(temp, val) variable_name_strings = {} kwarg_vars = {} for variable_name in full_argspec.kwonlyargs: if variable_name in kwargs: variable_value = kwargs[variable_name] else: variable_value = full_argspec.kwonlydefaults[variable_name] if isinstance(variable_value, np.ndarray): variable_name_string = np.empty_like(variable_value, dtype=object) for index in np.ndindex(*variable_name_string.shape): variable_name_string[index] = "{}[{}]".format( variable_name, ",".join([str(i) for i in index])) kwarg_variable = [ VariableRef(idx, name=name, is_kwarg=True) for idx, name in enumerate(_flatten(variable_name_string)) ] else: kwarg_variable = VariableRef(0, name=variable_name, is_kwarg=True) kwarg_vars[variable_name] = kwarg_variable return arg_vars, kwarg_vars
([[-2.3, 0.1], -1.], [(1, ), ()], 'max'), ([[-2.3, 0.1], -1.], [(3, ), ()], 'min')] LAYERS_PASS = [ ([[1], [2], [3]], 1), ([[[1], [2], [3]], [['a'], ['b'], ['c']]], 3), ] LAYERS_FAIL = [ ([1, 2, 3], None), ([[[1], [2], [3]], [['b'], ['c']]], 3), ] NO_VARIABLES_PASS = [[[], np.array([1., 4.])], [1, 'a']] NO_VARIABLES_FAIL = [[[VariableRef(0.1)], VariableRef([0.1])], np.array([VariableRef(0.3), VariableRef(4.)]), VariableRef(-1.)] OPTIONS_PASS = [("a", ["a", "b"])] OPTIONS_FAIL = [("c", ["a", "b"])] TYPE_PASS = [(["a"], list, type(None)), (1, int, type(None)), ("a", int, str), (VariableRef(1.), list, VariableRef)] TYPE_FAIL = [("a", list, type(None)), (VariableRef(1.), int, list), (1., VariableRef, type(None))]
def kwarg_variable(monkeypatch): """A mocked VariableRef instance for a keyword variable.""" monkeypatch.setattr(VariableRef, "kwarg_values", {"kwarg_test": [0, 1, 2, 3]}) yield VariableRef(1, "kwarg_test", True)
def variable(monkeypatch): """A mocked VariableRef instance for a non-keyword variable.""" monkeypatch.setattr(VariableRef, "positional_arg_values", [0, 1, 2, 3]) yield VariableRef(2, "test")
class TestCircuitGraphHash: """Test the creation of a hash on a CircuitGraph""" numeric_queues = [([qml.RX(0.3, wires=[0])], [], 'RX!0.3![0]|||'), ([ qml.RX(0.3, wires=[0]), qml.RX(0.4, wires=[1]), qml.RX(0.5, wires=[2]), ], [], 'RX!0.3![0]RX!0.4![1]RX!0.5![2]|||')] @pytest.mark.parametrize("queue, observable_queue, expected_string", numeric_queues) def test_serialize_numeric_arguments(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have numeric arguments.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize() variable = VariableRef(1) symbolic_queue = [ ([qml.RX(variable, wires=[0])], [], 'RX!V1![0]|||'), ] @pytest.mark.parametrize("queue, observable_queue, expected_string", symbolic_queue) def test_serialize_symbolic_argument(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have symbolic arguments.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize() variable = VariableRef(1) symbolic_queue = [ ([ qml.RX(variable, wires=[0]), qml.RX(0.3, wires=[1]), qml.RX(variable, wires=[2]) ], [], 'RX!V1![0]RX!0.3![1]RX!V1![2]|||'), ] @pytest.mark.parametrize("queue, observable_queue, expected_string", symbolic_queue) def test_serialize_numeric_and_symbolic_argument(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have both numeric and symbolic arguments.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize() variable = VariableRef(1) many_symbolic_queue = [ ([qml.RX(variable, wires=[0]), qml.RX(variable, wires=[1])], [], 'RX!V1![0]' + 'RX!V1![1]' + '|||'), ] @pytest.mark.parametrize("queue, observable_queue, expected_string", many_symbolic_queue) def test_serialize_symbolic_argument_multiple_times( self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have the same symbolic argument used multiple times.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize() variable1 = VariableRef(1) variable2 = VariableRef(2) multiple_symbolic_queue = [ ([qml.RX(variable1, wires=[0]), qml.RX(variable2, wires=[1])], [], 'RX!V1![0]' + 'RX!V2![1]' + '|||'), ] @pytest.mark.parametrize("queue, observable_queue, expected_string", multiple_symbolic_queue) def test_serialize_multiple_symbolic_arguments(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have multiple symbolic arguments.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize() observable1 = qml.PauliZ(0) observable1.return_type = not None observable2 = qml.Hermitian(np.array([[1, 0], [0, -1]]), wires=[0]) observable2.return_type = not None observable3 = Tensor(qml.PauliZ(0) @ qml.PauliZ(1)) observable3.return_type = not None numeric_observable_queue = [ ([], [observable1], '|||PauliZ[0]'), ([], [observable2], '|||Hermitian![[ 1 0]\n [ 0 -1]]![0]'), ([], [observable3], '|||[\'PauliZ\', \'PauliZ\'][[0], [1]]') ] @pytest.mark.parametrize("queue, observable_queue, expected_string", numeric_observable_queue) def test_serialize_numeric_arguments_observables(self, queue, observable_queue, expected_string): """Tests that the same hash is created for two circuitgraphs that have identical queues and empty variable_deps.""" circuit_graph_1 = CircuitGraph(queue + observable_queue, {}) circuit_graph_2 = CircuitGraph(queue + observable_queue, {}) assert circuit_graph_1.serialize() == circuit_graph_2.serialize() assert expected_string == circuit_graph_1.serialize()