Ejemplo n.º 1
0
    def test_variable(self):
        """Positional Variable reference tests."""
        self.logTestName()

        n = 10
        m = nr.randn(n)  # parameter multipliers
        par_fixed = nr.randn(n)  # fixed parameter values
        par_free = nr.randn(n)  # free parameter values
        Variable.free_param_values = par_free

        # test __str__()
        p = Variable(0)
        self.assertEqual(str(p), "Variable 0: name = None, ")
        self.assertEqual(str(-p), "Variable 0: name = None,  * -1")
        self.assertEqual(str(1.2 * p * 0.4),
                         "Variable 0: name = None,  * 0.48")

        def check(par, res):
            "Apply the parameter mapping, compare with the expected result."
            temp = np.array(
                [p.val if isinstance(p, Variable) else p for p in par])
            self.assertAllAlmostEqual(temp, res, self.tol)

        # mapping function must yield the correct parameter values
        par = [m[k] * Variable(k) for k in range(n)]
        check(par, par_free * m)
        par = [Variable(k) * m[k] for k in range(n)]
        check(par, par_free * m)
        par = [-Variable(k) for k in range(n)]
        check(par, -par_free)
        par = [m[k] * -Variable(k) * m[k] for k in range(n)]
        check(par, -par_free * m**2)

        # fixed values remain constant
        check(par_fixed, par_fixed)
Ejemplo n.º 2
0
    def test_regular_arguments(self, mock_device):
        """Test that regular arguments are properly converted to Variable 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 = [
            Variable(0, "a"),
            Variable(1, "b"),
            Variable(2, "c"),
            Variable(3, "d"),
        ]

        for var, expected in zip(qml.utils._flatten(arg_vars),
                                 expected_arg_vars):
            assert var == expected

        assert not kwarg_vars
Ejemplo n.º 3
0
    def test_array_arguments(self, mock_device):
        """Test that array arguments are properly converted to Variable 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 = [
            Variable(0, "weights[0,0]"),
            Variable(1, "weights[0,1]"),
            Variable(2, "weights[1,0]"),
            Variable(3, "weights[1,1]"),
        ]

        for var, expected in zip(qml.utils._flatten(arg_vars),
                                 expected_arg_vars):
            assert var == expected

        assert not kwarg_vars
Ejemplo n.º 4
0
    def test_regular_keyword_arguments(self, mock_device):
        """Test that regular keyword arguments are properly converted to Variable 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": [Variable(0, "a", is_kwarg=True)],
            "b": [Variable(0, "b", is_kwarg=True)],
            "c": [Variable(0, "c", is_kwarg=True)],
            "d": [Variable(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
Ejemplo n.º 5
0
def test_variable_str():
    """Variable informal string rep."""
    p = Variable(0)
    assert str(p) == "Variable: name = None, idx = 0"
    assert str(-p) == "Variable: name = None, idx = 0, * -1"

    p = Variable(0, name="kw1")
    assert str(p) == "Variable: name = kw1, idx = 0"
    assert str(2.1 * p) == "Variable: name = kw1, idx = 0, * 2.1"
Ejemplo n.º 6
0
def test_variable_str():
    """variable: Tests the positional variable reference string."""
    p = Variable(0)
    assert str(p) == "Variable 0: name = None, "
    assert str(-p) == "Variable 0: name = None,  * -1"
    assert str(1.2 * p * 0.4) == "Variable 0: name = None,  * 0.48"
    assert str(1.2 * p / 2.5) == "Variable 0: name = None,  * 0.48"

    p = Variable(0, name="kw1")
    assert str(p) == "Variable 0: name = kw1, "
    assert str(-p) == "Variable 0: name = kw1,  * -1"
    assert str(1.2 * p * 0.4) == "Variable 0: name = kw1,  * 0.48"
    assert str(1.2 * p / 2.5) == "Variable 0: name = kw1,  * 0.48"
Ejemplo n.º 7
0
def test_variable_repr():
    """Variable string rep."""
    p = Variable(0)
    assert repr(p) == "<Variable(None:0)>"
    assert repr(-p) == "<Variable(None:0 * -1)>"
    assert repr(1.2 * p * 0.4) == "<Variable(None:0 * 0.48)>"
    assert repr(1.2 * p / 2.5) == "<Variable(None:0 * 0.48)>"

    p = Variable(0, name="kw1")
    assert repr(p) == "<Variable(kw1:0)>"
    assert repr(-p) == "<Variable(kw1:0 * -1)>"
    assert repr(1.2 * p * 0.4) == "<Variable(kw1:0 * 0.48)>"
    assert repr(1.2 * p / 2.5) == "<Variable(kw1:0 * 0.48)>"
Ejemplo n.º 8
0
def test_keyword_variable(par_keyword, name, ind, mult, tol):
    """Keyword variable evaluation."""
    v = Variable(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)
Ejemplo n.º 9
0
    def test_keyword_variable(self):
        """Keyword Variable reference tests."""
        self.logTestName()

        n = 10
        m = nr.randn(n)  # parameter multipliers
        par_fixed = nr.randn(n)  # fixed parameter values
        par_free = nr.randn(n)  # free parameter values
        Variable.kwarg_values = {
            k: v
            for k, v in zip(ascii_lowercase, par_free)
        }

        # test __str__()
        p = Variable(0, name='kw1')
        self.assertEqual(str(p), "Variable 0: name = kw1, ")
        self.assertEqual(str(-p), "Variable 0: name = kw1,  * -1")
        self.assertEqual(str(1.2 * p * 0.4), "Variable 0: name = kw1,  * 0.48")
        self.assertEqual(str(1.2 * p / 2.5), "Variable 0: name = kw1,  * 0.48")

        def check(par, res):
            "Apply the parameter mapping, compare with the expected result."
            temp = np.array([
                p.val if isinstance(p, Variable) else p for p in par
            ]).flatten()
            self.assertAllAlmostEqual(temp, res, self.tol)

        # mapping function must yield the correct parameter values
        par = [
            m[k] * Variable(name=n) for k, n in zip(range(n), ascii_lowercase)
        ]
        check(par, par_free * m)
        par = [
            Variable(name=n) * m[k] for k, n in zip(range(n), ascii_lowercase)
        ]
        check(par, par_free * m)
        par = [-Variable(name=n) for k, n in zip(range(n), ascii_lowercase)]
        check(par, -par_free)
        par = [
            m[k] * -Variable(name=n) * m[k]
            for k, n in zip(range(n), ascii_lowercase)
        ]
        check(par, -par_free * m**2)

        # fixed values remain constant
        check(par_fixed, par_fixed)
Ejemplo n.º 10
0
def test_variable_val(par_positional, ind, mult, tol):
    """Positional variable evaluation."""
    v = Variable(ind)

    assert v.name is None
    assert v.mult == 1
    assert v.idx == ind
    variable_eval_asserts(v, par_positional[ind], mult, tol)
Ejemplo n.º 11
0
    def test_variadic_arguments(self, mock_device):
        """Test that variadic arguments are properly converted to Variable 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 = [
            Variable(0, "a"),
            Variable(1, "b[0]"),
            Variable(2, "b[1][0]"),
            Variable(3, "b[1][1]"),
            Variable(4, "b[1][2]"),
            Variable(5, "b[1][3]"),
            Variable(6, "b[2]"),
        ]

        assert not kwarg_vars

        for var, expected in zip(qml.utils._flatten(arg_vars),
                                 expected_arg_vars):
            assert var == expected
Ejemplo n.º 12
0
def test_keyword_variable():
    """
    variable: Keyword Variable reference tests.\
    """
    n = 10
    m = nr.randn(n)  # parameter multipliers
    par_fixed = nr.randn(n)  # fixed parameter values
    par_free = nr.randn(n)  # free parameter values
    Variable.kwarg_values = {k: v for k, v in zip(ascii_lowercase, par_free)}

    # test __str__()
    p = Variable(0, name="kw1")
    assert str(p) == "Variable 0: name = kw1, "
    assert str(-p) == "Variable 0: name = kw1,  * -1"
    assert str(1.2 * p * 0.4) == "Variable 0: name = kw1,  * 0.48"
    assert str(1.2 * p / 2.5) == "Variable 0: name = kw1,  * 0.48"

    # mapping function must yield the correct parameter values
    assert ((par_free[k] * m[k]) == (m[k] * Variable(name=n))
            for (k, n) in zip(range(n), ascii_lowercase))
    assert ((par_free[k] * m[k]) == (Variable(name=n) * m[k])
            for (k, n) in zip(range(n), ascii_lowercase))
    assert ((-par_free[k] * m[k]) == (-Variable(name=n))
            for (k, n) in zip(range(n), ascii_lowercase))
    assert ((-par_free[k] * m[k]**2) == (m[k] * -Variable(name=n) * m[k])
            for (k, n) in zip(range(n), ascii_lowercase))

    # Check for a single kwarg_value
    Variable.kwarg_values = {"kw1": 1.0}
    assert Variable(name="kw1").val == 1.0

    # fixed values remain constant
    assert [(par_fixed[k] == par_fixed[k]) for k in range(n)]
Ejemplo n.º 13
0
def test_variable_str():
    """variable: Tests the positional variable reference string."""
    n = 10
    m = nr.randn(n)  # parameter multipliers
    par_fixed = nr.randn(n)  # fixed parameter values
    par_free = nr.randn(n)  # free parameter values
    Variable.free_param_values = par_free

    # test __str__()
    p = Variable(0)
    assert str(p) == "Variable 0: name = None, "
    assert str(-p) == "Variable 0: name = None,  * -1"
    assert str(1.2 * p * 0.4) == "Variable 0: name = None,  * 0.48"
    assert str(1.2 * p / 2.5) == "Variable 0: name = None,  * 0.48"
Ejemplo n.º 14
0
    def test_array_keyword_arguments(self, mock_device):
        """Test that array keyword arguments are properly converted to Variable 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": [
                Variable(0, "a[0,0]", is_kwarg=True),
                Variable(1, "a[0,1]", is_kwarg=True),
                Variable(2, "a[1,0]", is_kwarg=True),
                Variable(3, "a[1,1]", is_kwarg=True),
            ],
            "b": [
                Variable(0, "b[0]", is_kwarg=True),
                Variable(1, "b[1]", is_kwarg=True),
                Variable(2, "b[2]", is_kwarg=True),
                Variable(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
Ejemplo n.º 15
0
def test_variable_val():
    """variable: Tests the positional variable values.
    """
    # mapping function must yield the correct parameter values
    n = 10
    m = nr.randn(n)  # parameter multipliers
    par_fixed = nr.randn(n)  # fixed parameter values
    par_free = nr.randn(n)  # free parameter values
    Variable.free_param_values = par_free
    assert [(par_free[k] == m[k] * Variable(k)) for k in range(n)]
    assert [(par_free[k] == (m[k] * Variable(k)).val) for k in range(n)]

    assert [(par_free[k] == Variable(k) * m[k]) for k in range(n)]
    assert [(par_free[k] == (Variable(k) * m[k]).val) for k in range(n)]

    assert [(-par_free[k] * m**2) == m[k] * (-Variable(k)) * m[k]
            for k in range(n)]
    assert [(-par_free[k] * m**2) == (m[k] * (-Variable(k)) * m[k]).val
            for k in range(n)]
    assert [(par_fixed[k] == par_fixed[k]) for k in range(n)]
Ejemplo n.º 16
0
def kwarg_variable(monkeypatch):
    """A mocked Variable instance for a keyword variable."""
    monkeypatch.setattr(Variable, "kwarg_values", {"kwarg_test": [0, 1, 2, 3]})
    yield Variable(1, "kwarg_test", True)
Ejemplo n.º 17
0
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 = Variable(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 = Variable(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 = Variable(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 = Variable(1)
    variable2 = Variable(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()
Ejemplo n.º 18
0
    def _construct(self, args, kwargs):
        """Construct the quantum circuit graph by calling the quantum function.

        For immutable nodes this method is called the first time :meth:`QNode.evaluate`
        or :meth:`QNode.jacobian` is called, and for mutable nodes *each time*
        they are called. It executes the quantum function,
        stores the resulting sequence of :class:`.Operator` instances,
        converts it into a circuit graph, and creates the Variable mapping.

        .. note::
           The Variables are only required for analytic differentiation,
           for evaluation we could simply reconstruct the circuit each time.

        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.Variable` instance.
            kwargs (dict[str, Any]): Auxiliary arguments passed to the quantum function.

        Raises:
            QuantumFunctionError: if the :class:`pennylane.QNode`'s _current_context is attempted to be modified
                inside of this method, the quantum function returns incorrect values or if
                both continuous and discrete operations are specified in the same quantum circuit
        """
        # pylint: disable=attribute-defined-outside-init, too-many-branches

        # flatten the args, replace each argument with a Variable instance carrying a unique index
        arg_vars = [Variable(idx) for idx, _ in enumerate(_flatten(args))]
        self.num_variables = len(arg_vars)
        # arrange the newly created Variables in the nested structure of args
        arg_vars = unflatten(arg_vars, args)

        # temporary queues for operations and observables
        self.queue = []  #: list[Operation]: applied operations
        self.obs_queue = []  #: list[Observable]: applied observables

        # set up the context for Operator entry
        if qml._current_context is None:
            qml._current_context = self
        else:
            raise QuantumFunctionError(
                "qml._current_context must not be modified outside this method."
            )
        try:
            # generate the program queue by executing the quantum circuit function
            if self.mutable:
                # it's ok to directly pass auxiliary arguments since the circuit is re-constructed each time
                # (positional args must be replaced because parameter-shift differentiation requires Variables)
                res = self.func(*arg_vars, **kwargs)
            else:
                # must convert auxiliary arguments to named Variables so they can be updated without re-constructing the circuit
                kwarg_vars = {}
                for key, val in kwargs.items():
                    temp = [Variable(idx, name=key) for idx, _ in enumerate(_flatten(val))]
                    kwarg_vars[key] = unflatten(temp, val)

                res = self.func(*arg_vars, **kwarg_vars)
        finally:
            qml._current_context = None

        # check the validity of the circuit
        self._check_circuit(res)

        # map each free variable to the operators which depend on it
        self.variable_deps = {k: [] for k in range(self.num_variables)}
        for k, op in enumerate(self.ops):
            for j, p in enumerate(_flatten(op.params)):
                if isinstance(p, Variable):
                    if p.name is None:  # ignore auxiliary arguments
                        self.variable_deps[p.idx].append(ParameterDependency(op, j))

        # generate the DAG
        self.circuit = CircuitGraph(self.ops, self.variable_deps)

        # check for unused positional params
        if self.properties.get("par_check", False):
            unused = [k for k, v in self.variable_deps.items() if not v]
            if unused:
                raise QuantumFunctionError(
                    "The positional parameters {} are unused.".format(unused)
                )

        # check for operations that cannot affect the output
        if self.properties.get("vis_check", False):
            visible = self.circuit.ancestors(self.circuit.observables)
            invisible = set(self.circuit.operations) - visible
            if invisible:
                raise QuantumFunctionError(
                    "The operations {} cannot affect the output of the circuit.".format(invisible)
                )
Ejemplo n.º 19
0
    def _make_variables(self, args, kwargs):
        """Create the :class:`~.variable.Variable` instances representing the QNode's arguments.

        The created :class:`~.variable.Variable` instances are given in the same nested structure
        as the original arguments. The :class:`~.variable.Variable` 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.Variable` 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 Variable 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.Variable` 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 = [
            Variable(idx, name)
            for idx, name in enumerate(_flatten(variable_name_strings))
        ]
        self.num_variables = len(arg_vars)

        # Arrange the newly created Variables in the nested structure of args.
        # Make sure that NumPy scalars are converted into Python scalars.
        arg_vars = [
            i.item() if isinstance(i, np.ndarray) and i.ndim == 0 else i
            for i in unflatten(arg_vars, args)
        ]

        if self._trainable_args is not None and len(
                self._trainable_args) != len(args):
            # If some of the input arguments are marked as non-differentiable,
            # then replace the variable instances in arg_vars back with the
            # original objects.
            for a, _ in enumerate(args):
                if a not in self._trainable_args:
                    arg_vars[a] = args[a]

        # kwargs
        # if not mutable: must convert auxiliary arguments to named Variables so they can be updated without re-constructing the circuit
        # kwarg_vars = {}
        # for key, val in kwargs.items():
        #    temp = [Variable(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 = [
                    Variable(idx, name=name, is_kwarg=True)
                    for idx, name in enumerate(_flatten(variable_name_string))
                ]
            else:
                kwarg_variable = Variable(0, name=variable_name, is_kwarg=True)

            kwarg_vars[variable_name] = kwarg_variable

        return arg_vars, kwarg_vars
Ejemplo n.º 20
0
                  ([[-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 = [[[Variable(0.1)], Variable([0.1])],
                     np.array([Variable(0.3), Variable(4.)]),
                     Variable(-1.)]

OPTIONS_PASS = [("a", ["a", "b"])]

OPTIONS_FAIL = [("c", ["a", "b"])]

TYPE_PASS = [(["a"], list, type(None)),
             (1, int, type(None)),
             ("a", int, str),
             (Variable(1.), list, Variable)
             ]

TYPE_FAIL = [("a", list, type(None)),
             (Variable(1.), int, list),
Ejemplo n.º 21
0
class TestParametricCompilation(BaseTest):
    """Test that parametric compilation works fine and the same program only compiles once."""
    def test_compiled_program_was_stored_in_dict(self, qvm, monkeypatch):
        """Test that QVM device stores the compiled program correctly in a dictionary"""
        dev = qml.device("forest.qvm", device="2q-qvm")
        theta = 0.432
        phi = 0.123

        O1 = qml.expval(qml.Identity(wires=[0]))
        O2 = qml.expval(qml.Identity(wires=[1]))

        circuit_graph = CircuitGraph([
            qml.RX(theta, wires=[0]),
            qml.RX(phi, wires=[1]),
            qml.CNOT(wires=[0, 1])
        ], [O1, O2])

        dev.apply(circuit_graph.operations,
                  rotations=circuit_graph.diagonalizing_gates)

        dev._circuit_hash = circuit_graph.hash

        call_history = []

        with monkeypatch.context() as m:
            m.setattr(QuantumComputer, "compile",
                      lambda self, prog: call_history.append(prog))
            m.setattr(QuantumComputer, "run", lambda self, **kwargs: None)
            dev.generate_samples()

        assert dev.circuit_hash in dev._compiled_program_dict
        assert len(dev._compiled_program_dict.items()) == 1
        assert len(call_history) == 1

        # Testing that the compile() method was not called
        # Calling generate_samples with unchanged hash
        for i in range(6):
            with monkeypatch.context() as m:
                m.setattr(QuantumComputer, "compile",
                          lambda self, prog: call_history.append(prog))
                m.setattr(QuantumComputer, "run", lambda self, **kwargs: None)
                dev.generate_samples()

            assert dev.circuit_hash in dev._compiled_program_dict
            assert len(dev._compiled_program_dict.items()) == 1
            assert len(call_history) == 1

    def test_circuit_hash_none_no_compiled_program_was_stored_in_dict(
            self, qvm, monkeypatch):
        """Test that QVM device does not store the compiled program in a dictionary if the
        _circuit_hash attribute is None"""
        dev = qml.device("forest.qvm", device="2q-qvm")
        theta = 0.432
        phi = 0.123

        O1 = qml.expval(qml.Identity(wires=[0]))
        O2 = qml.expval(qml.Identity(wires=[1]))

        circuit_graph = CircuitGraph([
            qml.RX(theta, wires=[0]),
            qml.RX(phi, wires=[1]),
            qml.CNOT(wires=[0, 1])
        ], [O1, O2])

        dev.apply(circuit_graph.operations,
                  rotations=circuit_graph.diagonalizing_gates)

        dev._circuit_hash = None

        call_history = []

        with monkeypatch.context() as m:
            m.setattr(QuantumComputer, "compile",
                      lambda self, prog: call_history.append(prog))
            m.setattr(QuantumComputer, "run", lambda self, **kwargs: None)
            dev.generate_samples()

        assert dev.circuit_hash is None
        assert len(dev._compiled_program_dict.items()) == 0
        assert len(call_history) == 1

    variable1 = Variable(1)
    variable2 = Variable(2)

    multiple_symbolic_queue = [
        ([qml.RX(variable1, wires=[0]),
          qml.RX(variable2, wires=[1])], [])
    ]

    @pytest.mark.parametrize("queue, observable_queue",
                             multiple_symbolic_queue)
    def test_parametric_compilation_with_numeric_and_symbolic_queue(
            self, queue, observable_queue, monkeypatch):
        """Tests that a program containing numeric and symbolic variables as well is only compiled once."""

        Variable.positional_arg_values = {}

        dev = qml.device("forest.qvm", device="2q-qvm", timeout=100)

        dev._circuit_hash = None

        number_of_runs = 10

        first = True

        call_history = []
        for run_idx in range(number_of_runs):
            Variable.positional_arg_values[1] = 0.232 * run_idx
            Variable.positional_arg_values[2] = 0.8764 * run_idx
            circuit_graph = CircuitGraph(queue, observable_queue)

            dev.apply(circuit_graph.operations,
                      rotations=circuit_graph.diagonalizing_gates)

            if first:
                dev._circuit_hash = circuit_graph.hash
                first = False
            else:
                # Check that we are still producing the same circuit hash
                assert dev._circuit_hash == circuit_graph.hash

            with monkeypatch.context() as m:
                m.setattr(QuantumComputer, "compile",
                          lambda self, prog: call_history.append(prog))
                m.setattr(QuantumComputer, "run", lambda self, **kwargs: None)
                dev.generate_samples()

        assert len(dev._compiled_program_dict.items()) == 1
        assert len(call_history) == 1

    def test_apply_basis_state_raises_an_error_if_not_first(self):
        """Test that there is an error raised when the BasisState is not
        applied as the first operation."""
        dev = qml.device("forest.qvm",
                         device="3q-qvm",
                         parametric_compilation=True)

        operation = qml.BasisState(np.array([1, 0, 0]), wires=list(range(3)))
        queue = [qml.PauliX(0), operation]
        with pytest.raises(
                qml.DeviceError,
                match=
                "Operation {} cannot be used after other Operations have already been applied"
                .format(operation.name),
        ):
            dev.apply(queue)

    def test_apply_qubitstatesvector_raises_an_error_if_not_first(self):
        """Test that there is an error raised when the QubitStateVector is not
        applied as the first operation."""
        dev = qml.device("forest.qvm",
                         device="2q-qvm",
                         parametric_compilation=True)

        operation = qml.QubitStateVector(np.array([1, 0]),
                                         wires=list(range(2)))
        queue = [qml.PauliX(0), operation]
        with pytest.raises(
                qml.DeviceError,
                match=
                "Operation {} cannot be used after other Operations have already been applied"
                .format(operation.name),
        ):
            dev.apply(queue)
Ejemplo n.º 22
0
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 = Variable(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 variable(monkeypatch):
    """A mocked Variable instance for a non-keyword variable."""
    monkeypatch.setattr(Variable, "free_param_values", [0, 1, 2, 3])
    yield Variable(2, "test")
Ejemplo n.º 24
0
                  ([[-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 = [[[Variable(0.1)], Variable([0.1])],
                     np.array([Variable(0.3), Variable(4.)]),
                     Variable(-1.)]

OPTIONS_PASS = [("a", ["a", "b"])]

OPTIONS_FAIL = [("c", ["a", "b"])]

TYPE_PASS = [(["a"], list, type(None)), (1, int, type(None)), ("a", int, str),
             (Variable(1.), list, Variable)]

TYPE_FAIL = [("a", list, type(None)), (Variable(1.), int, list),
             (1., Variable, type(None))]

##############################
Ejemplo n.º 25
0
def variable(monkeypatch):
    """A mocked Variable instance for a non-keyword variable."""
    monkeypatch.setattr(Variable, "positional_arg_values", [0, 1, 2, 3])
    yield Variable(2, "test")
Ejemplo n.º 26
0
    def construct(self, args, kwargs=None):
        """Constructs a representation of the quantum circuit.

        The user should never have to call this method.

        This method is called automatically the first time :meth:`QNode.evaluate`
        or :meth:`QNode.jacobian` is called. It executes the quantum function,
        stores the resulting sequence of :class:`~.operation.Operation` instances,
        and creates the variable mapping.

        Args:
            args (tuple): Represent the free parameters passed to the circuit.
                Here we are not concerned with their values, but with their structure.
                Each free param is replaced with a :class:`~.variable.Variable` instance.
            kwargs (dict): Additional keyword arguments may be passed to the quantum circuit function,
                however PennyLane does not support differentiating with respect to keyword arguments.
                Instead, keyword arguments are useful for providing data or 'placeholders'
                to the quantum circuit function.
        """
        # pylint: disable=too-many-branches,too-many-statements
        self.queue = []
        self.ev = []  # temporary queue for EVs

        if kwargs is None:
            kwargs = {}

        # flatten the args, replace each with a Variable instance with a unique index
        temp = [Variable(idx) for idx, val in enumerate(_flatten(args))]
        self.num_variables = len(temp)

        # store the nested shape of the arguments for later unflattening
        self.model = args

        # arrange the newly created Variables in the nested structure of args
        variables = unflatten(temp, args)

        # get default kwargs that weren't passed
        keyword_sig = _get_default_args(self.func)
        self.keyword_defaults = {k: v[1] for k, v in keyword_sig.items()}
        self.keyword_positions = {v[0]: k for k, v in keyword_sig.items()}

        keyword_values = {}
        keyword_values.update(self.keyword_defaults)
        keyword_values.update(kwargs)

        if self.cache:
            # caching mode, must use variables for kwargs
            # wrap each keyword argument as a Variable
            kwarg_variables = {}
            for key, val in keyword_values.items():
                temp = [Variable(idx, name=key) for idx, _ in enumerate(_flatten(val))]
                kwarg_variables[key] = unflatten(temp, val)

        Variable.free_param_values = np.array(list(_flatten(args)))
        Variable.kwarg_values = {k: np.array(list(_flatten(v))) for k, v in keyword_values.items()}

        # set up the context for Operation entry
        if QNode._current_context is None:
            QNode._current_context = self
        else:
            raise QuantumFunctionError('QNode._current_context must not be modified outside this method.')
        # generate the program queue by executing the quantum circuit function
        try:
            if self.cache:
                # caching mode, must use variables for kwargs
                # so they can be updated without reconstructing
                res = self.func(*variables, **kwarg_variables)
            else:
                # no caching, fine to directly pass kwarg values
                res = self.func(*variables, **keyword_values)
        finally:
            # remove the context
            QNode._current_context = None

        #----------------------------------------------------------
        # check the validity of the circuit

        # quantum circuit function return validation
        if isinstance(res, qml.operation.Observable):
            if res.return_type is qml.operation.Sample:
                # Squeezing ensures that there is only one array of values returned
                # when only a single-mode sample is requested
                self.output_conversion = np.squeeze
            else:
                self.output_conversion = float

            self.output_dim = 1
            res = (res,)
        elif isinstance(res, Sequence) and res and all(isinstance(x, qml.operation.Observable) for x in res):
            # for multiple observables values, any valid Python sequence of observables
            # (i.e., lists, tuples, etc) are supported in the QNode return statement.

            # Device already returns the correct numpy array,
            # so no further conversion is required
            self.output_conversion = np.asarray
            self.output_dim = len(res)

            res = tuple(res)
        else:
            raise QuantumFunctionError("A quantum function must return either a single measured observable "
                                       "or a nonempty sequence of measured observables.")

        # check that all returned observables have a return_type specified
        for x in res:
            if x.return_type is None:
                raise QuantumFunctionError("Observable '{}' does not have the measurement "
                                           "type specified.".format(x.name))

        # check that all ev's are returned, in the correct order
        if res != tuple(self.ev):
            raise QuantumFunctionError("All measured observables must be returned in the "
                                       "order they are measured.")

        self.ev = list(res)  #: list[Observable]: returned observables
        self.ops = self.queue + self.ev  #: list[Operation]: combined list of circuit operations

        # list all operations except for the identity
        non_identity_ops = [op for op in self.ops if not isinstance(op, qml.ops.Identity)]

        # contains True if op is a CV, False if it is a discrete variable
        are_cvs = [isinstance(op, qml.operation.CV) for op in non_identity_ops]

        if not all(are_cvs) and any(are_cvs):
            raise QuantumFunctionError("Continuous and discrete operations are not "
                                       "allowed in the same quantum circuit.")

        # TODO: we should enforce plugins using the Device.capabilities dictionary to specify
        # whether they are qubit or CV devices, and remove this logic here.
        self.type = 'CV' if all(are_cvs) else 'qubit'

        # map each free variable to the operations which depend on it
        self.variable_deps = {}
        for k, op in enumerate(self.ops):
            for j, p in enumerate(_flatten(op.params)):
                if isinstance(p, Variable):
                    if p.name is None: # ignore keyword arguments
                        self.variable_deps.setdefault(p.idx, []).append(ParameterDependency(op, j))

        # generate directed acyclic graph
        self.circuit = CircuitGraph(self.ops, self.variable_deps)

        #: dict[int->str]: map from free parameter index to the gradient method to be used with that parameter
        self.grad_method_for_par = {k: self._best_method(k) for k in self.variable_deps}