quimb.tensor.circuit.pepo ========================= .. py:module:: quimb.tensor.circuit.pepo .. autoapi-nested-parse:: PEPO simple-update circuit simulator. Classes ------- .. autoapisummary:: quimb.tensor.circuit.pepo.CircuitPEPOSimpleUpdate Module Contents --------------- .. py:class:: CircuitPEPOSimpleUpdate(N=None, *, edges=None, gates=None, max_bond=None, cutoff=1e-10, gate_contract='reduce-split', gate_opts=None, **circuit_opts) Bases: :py:obj:`quimb.tensor.circuit.exact.Circuit` Quantum circuit simulator that evolves an observable *backwards* in time, in the Heisenberg picture, by representing it as an arbitrary geometry PEPO and applying the gates with the simple update rule. Rather than evolving a state forwards, gates are simply recorded as they are applied; no contraction happens until an expectation value is requested. When :meth:`local_expectation` (or :meth:`get_evolved_operator`) is called, the local observable is built as a bond dimension 1 PEPO on the supplied ``edges`` and the recorded gates are applied in reverse order as :math:`O \rightarrow G^\dagger O G`, using :func:`~quimb.tensor.tnag.core.tensor_network_ag_gate_simple` (Vidal-style gauging plus compression). Gates that fall outside the reverse lightcone of the observable are skipped, since :math:`G^\dagger G = 1`. The evolved operator is finally projected onto the ``|00...0>`` initial state. This is the Heisenberg-picture companion to :class:`CircuitPEPSSimpleUpdate`, useful on lattices where evolving the full state is intractable but a single local observable can be evolved in a truncated, gauged operator network. :param N: The number of qubits. If not given it is inferred from the geometry. Supply it to pad the geometry up to ``N`` sites. :type N: int, optional :param edges: The edges defining the geometry. A bond is placed between each pair of sites, and two-qubit gates are only supported on these edges. If not given the geometry is inferred from the two-qubit ``gates``. :type edges: sequence[tuple[hashable, hashable]], optional :param gates: If ``edges`` is not given, infer the geometry from the two-qubit gates in this sequence (the gates are only inspected here, not applied). :type gates: sequence, optional :param max_bond: The maximum bond dimension to compress the operator to as gates are applied during the backwards evolution. :type max_bond: int, optional :param cutoff: The singular value cutoff to use when compressing. :type cutoff: float, optional :param gate_contract: How to split a two site gate, see :func:`~quimb.tensor.tnag.core.tensor_network_ag_gate_simple`. :type gate_contract: str, optional :param gate_opts: Default options forwarded to ``gate_simple_`` such as ``max_bond``, ``cutoff`` and ``renorm``. This is the single source of truth for the compression options; ``max_bond`` and ``cutoff`` are also exposed as properties. :type gate_opts: dict, optional .. attribute:: edges The unique edges defining the geometry. :type: tuple[tuple[hashable, hashable]] .. attribute:: sites The sites (qubit labels). :type: tuple[hashable] .. attribute:: gates The gates recorded so far. :type: tuple[Gate] .. rubric:: Examples >>> import quimb.tensor as qtn >>> edges = [(0, 1), (1, 2), (2, 3)] >>> circ = qtn.CircuitPEPOSimpleUpdate(edges=edges, max_bond=16) >>> circ.apply_gates(gates) # no computation happens here >>> circ.local_expectation(qu.pauli("Z"), 1) # evolve + contract here .. seealso:: :py:obj:`CircuitPEPSSimpleUpdate`, :py:obj:`CircuitMPS` .. py:attribute:: _edges .. py:attribute:: _sites .. py:attribute:: _site_set .. py:attribute:: _edge_set .. py:method:: copy() Copy the circuit, carrying over the geometry that the base :class:`Circuit` copy does not know about. .. py:property:: edges The unique edges defining the geometry. .. py:property:: sites The sites (qubit labels). .. py:property:: max_bond The maximum bond dimension to compress to. .. py:property:: cutoff The singular value cutoff to use when compressing. .. py:method:: _init_state(N, dtype='complex128') .. py:method:: _apply_gate(gate, tags=None, **gate_opts) Apply a ``Gate`` to this ``Circuit``. This is the main method that all calls to apply a gate should go through. :param gate: The gate to apply. :type gate: Gate :param tags: Tags to add to the gate tensor(s). :type tags: str or sequence of str, optional .. py:method:: apply_gates(gates, progbar=False, **gate_opts) Apply a sequence of gates to this tensor network quantum circuit. :param gates: The sequence of gates to apply. :type gates: Sequence[Gate] or Sequence[Tuple] :param gate_opts: Supplied to :meth:`~quimb.tensor.circuit.Circuit.apply_gate`. .. py:method:: _parse_where(where) .. py:method:: _initial_operator(G, where) Build the bond dimension 1 PEPO of ``G`` acting at ``where`` and the identity elsewhere, on the circuit geometry. .. py:method:: get_evolved_operator(G, where, *, max_bond=None, cutoff=None) Evolve the local observable ``G`` at ``where`` backwards through the recorded circuit, returning the Heisenberg-picture operator :math:`U^\dagger G U` as a gauged PEPO. Gates outside the reverse lightcone of the observable are skipped. :param G: The local operator acting on the site(s) in ``where``. :type G: array_like :param where: The site or sites the operator acts on. :type where: hashable or sequence[hashable] :param max_bond: Override the compression options for this call. :type max_bond: optional :param cutoff: Override the compression options for this call. :type cutoff: optional :rtype: TensorNetworkGenOperator .. py:method:: get_evolved_operator_with_state(G, where, *, max_bond=None, cutoff=None) Return the evolved operator :math:`U^\dagger G U` projected onto the ``|00...0>`` initial state on both sides, i.e. the tensor network whose full contraction is :math:`\langle 0 | U^\dagger G U | 0 \rangle`. The physical indices are projected with ``isel``; the caller can contract the returned network however they like. .. py:method:: local_expectation(G, where, *, max_bond=None, cutoff=None, optimize='auto-hq', **contract_opts) Compute :math:`\langle 0 | U^\dagger G U | 0 \rangle`, the expectation of the local operator ``G`` at ``where`` in the state prepared by the recorded circuit ``U`` acting on ``|00...0>``. :param G: The local operator acting on the site(s) in ``where``. :type G: array_like :param where: The site or sites the operator acts on. :type where: hashable or sequence[hashable] :param max_bond: Override the compression options for this call. :type max_bond: optional :param cutoff: Override the compression options for this call. :type cutoff: optional :param optimize: The contraction path optimizer for the final contraction. :type optimize: str, optional :param contract_opts: Supplied to the final :meth:`~quimb.tensor.tensor_core.TensorNetwork.contract`. :rtype: scalar .. py:method:: _unsupported(name) :abstractmethod: .. py:property:: psi Tensor network representation of the wavefunction. .. py:method:: to_dense(*args, **kwargs) Generate the dense representation of the final wavefunction. :param reverse: Whether to reverse the order of the subsystems, to match the convention of qiskit for example. :type reverse: bool, optional :param optimize: Contraction path optimizer to use for the contraction, can be a non-reusable path optimizer as only called once (though path won't be cached for later use in that case). :type optimize: str, optional :param dtype: If given, convert the tensors to this dtype prior to contraction. :type dtype: dtype or str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :param backend: Backend to perform the contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``cotengra``. :type backend: str, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :param rehearse: If ``True``, generate and cache the simplified tensor network and contraction tree but don't actually perform the contraction. Returns a dict with keys ``'tn'`` and ``'tree'`` with the tensor network that will be contracted and the corresponding contraction tree if so. :type rehearse: bool, optional :returns: **psi** -- The densely represented wavefunction with ``dtype`` data. :rtype: qarray .. py:method:: sample(*args, **kwargs) Sample the circuit given by ``gates``, ``C`` times, using lightcone cancelling and caching marginal distribution results. This is a generator. This proceeds as a chain of marginal computations. Assuming we have ``group_size=1``, and some ordering of the qubits, :math:`\{q_0, q_1, q_2, q_3, \ldots\}` we first compute: .. math:: p(q_0) = \mathrm{diag} \mathrm{Tr}_{1, 2, 3,\ldots} | \psi_{0} \rangle \langle \psi_{0} | I.e. simply the probability distribution on a single qubit, conditioned on nothing. The subscript on :math:`\psi` refers to the fact that we only need gates from the causal cone of qubit 0. From this we can sample an outcome, either 0 or 1, if we call this :math:`r_0` we can then move on to the next marginal: .. math:: p(q_1 | r_0) = \mathrm{diag} \mathrm{Tr}_{2, 3,\ldots} \langle r_0 | \psi_{0, 1} \rangle \langle \psi_{0, 1} | r_0 \rangle I.e. the probability distribution of the next qubit, given our prior result. We can sample from this to get :math:`r_1`. Then we compute: .. math:: p(q_2 | r_0 r_1) = \mathrm{diag} \mathrm{Tr}_{3,\ldots} \langle r_0 r_1 | \psi_{0, 1, 2} \rangle \langle \psi_{0, 1, 2} | r_0 r_1 \rangle Eventually we will reach the 'final marginal', which we can compute as .. math:: |\langle r_0 r_1 r_2 r_3 \ldots | \psi \rangle|^2 since there is nothing left to trace out. :param C: The number of times to sample. :type C: int :param qubits: Which qubits to measure, defaults (``None``) to all qubits. :type qubits: None or sequence of int, optional :param order: Which order to measure the qubits in, defaults (``None``) to an order based on greedily expanding the smallest reverse lightcone. If specified it should be a permutation of ``qubits``. :type order: None or sequence of int, optional :param group_size: How many qubits to group together into marginals, the larger this is the fewer marginals need to be computed, which can be faster at the cost of higher memory. The marginal themselves will each be of size ``2**group_size``. :type group_size: int, optional :param max_marginal_storage: The total cumulative number of marginal probabilites to cache, once this is exceeded caching will be turned off. :type max_marginal_storage: int, optional :param seed: A random seed, passed to ``numpy.random.seed`` if given. :type seed: None or int, optional :param optimize: Contraction path optimizer to use for the marginals, shouldn't be a non-reusable path optimizer as called on many different TNs. Passed to :func:`cotengra.array_contract_tree`. :type optimize: str, optional :param backend: Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``cotengra``. :type backend: str, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :Yields: **bitstrings** (*sequence of str*) .. py:method:: sample_rehearse(*args, **kwargs) Perform the preparations and contraction tree findings for :meth:`~quimb.tensor.circuit.Circuit.sample`, caching various intermedidate objects, but don't perform the main contractions. :param qubits: Which qubits to measure, defaults (``None``) to all qubits. :type qubits: None or sequence of int, optional :param order: Which order to measure the qubits in, defaults (``None``) to an order based on greedily expanding the smallest reverse lightcone. :type order: None or sequence of int, optional :param group_size: How many qubits to group together into marginals, the larger this is the fewer marginals need to be computed, which can be faster at the cost of higher memory. The marginal's size itself is exponential in ``group_size``. :type group_size: int, optional :param result: Explicitly check the computational cost of this result, assumed to be all zeros if not given. :type result: None or dict[int, str], optional :param optimize: Contraction path optimizer to use for the marginals, shouldn't be a non-reusable path optimizer as called on many different TNs. Passed to :func:`cotengra.array_contract_tree`. :type optimize: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :param progbar: Whether to show the progress of finding each contraction tree. :type progbar: bool, optional :returns: One contraction tree object per grouped marginal computation. The keys of the dict are the qubits the marginal is computed for, the values are a dict containing a representative simplified tensor network (key: 'tn') and the main contraction tree (key: 'tree'). :rtype: dict[tuple[int], dict] .. py:method:: sample_chaotic(*args, **kwargs) Sample from this circuit, *assuming* it to be chaotic. Which is to say, only compute and sample correctly from the final marginal, assuming that the distribution on the other qubits is uniform. Given ``marginal_qubits=5`` for instance, for each sample a random bit-string :math:`r_0 r_1 r_2 \ldots r_{N - 6}` for the remaining :math:`N - 5` qubits will be chosen, then the final marginal will be computed as .. math:: p(q_{N-5}q_{N-4}q_{N-3}q_{N-2}q_{N-1} | r_0 r_1 r_2 \ldots r_{N-6}) = |\langle r_0 r_1 r_2 \ldots r_{N - 6} | \psi \rangle|^2 and then sampled from. Note the expression on the right hand side has 5 open indices here and so is a tensor, however if ``marginal_qubits`` is not too big then the cost of contracting this is very similar to a single amplitude. .. note:: This method *assumes* the circuit is chaotic, if its not, then the samples produced will not be an accurate representation of the probability distribution. :param C: The number of times to sample. :type C: int :param marginal_qubits: The number of qubits to treat as marginal, or the actual qubits. If an int is given then the qubits treated as marginal will be ``circuit.calc_qubit_ordering()[:marginal_qubits]``. :type marginal_qubits: int or sequence of int :param fix: Measurement results on other qubits to fix. These will be randomly sampled if ``fix`` is not given or a qubit is missing. :type fix: None or dict[int, str], optional :param seed: A random seed, passed to ``numpy.random.seed`` if given. :type seed: None or int, optional :param optimize: Contraction path optimizer to use for the marginal, can be a non-reusable path optimizer as only called once (though path won't be cached for later use in that case). :type optimize: str, optional :param backend: Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``cotengra``. :type backend: str, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :Yields: *str* .. py:method:: sample_chaotic_rehearse(*args, **kwargs) Rehearse chaotic sampling (perform just the TN simplifications and contraction tree finding). :param marginal_qubits: The number of qubits to treat as marginal, or the actual qubits. If an int is given then the qubits treated as marginal will be ``circuit.calc_qubit_ordering()[:marginal_qubits]``. :type marginal_qubits: int or sequence of int :param result: Explicitly check the computational cost of this result, assumed to be all zeros if not given. :type result: None or dict[int, str], optional :param optimize: Contraction path optimizer to use for the marginal, can be a non-reusable path optimizer as only called once (though path won't be cached for later use in that case). :type optimize: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :returns: The contraction path information for the main computation, the key is the qubits that formed the final marginal. The value is itself a dict with keys ``'tn'`` - a representative tensor network - and ``'tree'`` - the contraction tree. :rtype: dict[tuple[int], dict] .. py:method:: amplitude(*args, **kwargs) Get the amplitude coefficient of bitstring ``b``. .. math:: c_b = \langle b | \psi \rangle :param b: The bitstring to compute the transition amplitude for. :type b: str or sequence of int :param optimize: Contraction path optimizer to use for the amplitude, can be a non-reusable path optimizer as only called once (though path won't be cached for later use in that case). :type optimize: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :param backend: Backend to perform the contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``cotengra``. :type backend: str, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :param rehearse: If ``True``, generate and cache the simplified tensor network and contraction tree but don't actually perform the contraction. Returns a dict with keys ``"tn"`` and ``'tree'`` with the tensor network that will be contracted and the corresponding contraction tree if so. :type rehearse: bool or "tn", optional .. py:method:: partial_trace(*args, **kwargs) Perform the partial trace on the circuit wavefunction, retaining only qubits in ``keep``, and making use of reverse lightcone cancellation: .. math:: \rho_{\bar{q}} = Tr_{\bar{p}} |\psi_{\bar{q}} \rangle \langle \psi_{\bar{q}}| Where :math:`\bar{q}` is the set of qubits to keep, :math:`\psi_{\bar{q}}` is the circuit wavefunction only with gates in the causal cone of this set, and :math:`\bar{p}` is the remaining qubits. :param keep: The qubit(s) to keep as we trace out the rest. :type keep: int or sequence of int :param optimize: Contraction path optimizer to use for the reduced density matrix, can be a non-reusable path optimizer as only called once (though path won't be cached for later use in that case). :type optimize: str, optional :param simplify_sequence: Which local tensor network simplifications to perform and in which order, see :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_sequence: str, optional :param simplify_atol: The tolerance with which to compare to zero when applying :meth:`~quimb.tensor.tensor_core.TensorNetwork.full_simplify`. :type simplify_atol: float, optional :param simplify_equalize_norms: Actively renormalize tensor norms during simplification. :type simplify_equalize_norms: bool, optional :param backend: Backend to perform the marginal contraction with, e.g. ``'numpy'``, ``'cupy'`` or ``'jax'``. Passed to ``cotengra``. :type backend: str, optional :param dtype: Data type to cast the TN to before contraction. :type dtype: str, optional :param rehearse: If ``True``, generate and cache the simplified tensor network and contraction tree but don't actually perform the contraction. Returns a dict with keys ``"tn"`` and ``'tree'`` with the tensor network that will be contracted and the corresponding contraction tree if so. :type rehearse: bool or "tn", optional :rtype: array or dict .. py:property:: uni