예제 #1
0
class FockBackend(BaseFock):
    r"""Implements a simulation of quantum optical circuits in a truncated
    Fock basis using NumPy, returning a :class:`~.BaseFock`
    state object.

    The primary component of the FockBackend is a
    :attr:`~.FockBackend.circuit` object which is used to simulate a multi-mode quantum optical system. The
    :class:`FockBackend` provides the basic API-compatible interface to the simulator, while the
    :attr:`~.FockBackend.circuit` object actually carries out the mathematical simulation.

    The :attr:`~.FockBackend.circuit` simulator maintains an internal tensor representation of the quantum state of a multi-mode quantum optical system
    using a (truncated) Fock basis representation. As its various state manipulation methods are called, the quantum state is updated
    to reflect these changes. The simulator will try to keep the internal state in a pure (vector) representation
    for as long as possible. Unitary gates will not change the type of representation, while state preparations and measurements will.

    A number of factors determine the shape and dimensionality of the state tensor:

    * the underlying state representation being used (either a ket vector or a density matrix)
    * the number of modes :math:`n` actively being simulated
    * the cutoff dimension :math:`D` for the Fock basis

    The state tensor corresponds to a multimode quantum system. If the
    representation is a pure state, the state tensor has shape
    :math:`(\underbrace{D,...,D}_{n~\text{times}})`.
    In a mixed state representation, the state tensor has shape
    :math:`(\underbrace{D,D,...,D,D}_{2n~\text{times}})`.
    Indices for the same mode appear consecutively. Hence, for a mixed state, the first two indices
    are for the first mode, the second are for the second mode, etc.

    ..
        .. currentmodule:: strawberryfields.backends.fockbackend
        .. autosummary::
            :toctree:

            ~circuit.Circuit
            ~ops
    """

    short_name = "fock"
    circuit_spec = "fock"

    def __init__(self):
        """Instantiate a FockBackend object."""
        super().__init__()
        self._supported["mixed_states"] = True
        self._init_modes = None  #: int: initial number of modes in the circuit
        self._modemap = None  #: Modemap: maps external mode indices to internal ones
        self.circuit = (
            None  #: ~.fockbackend.circuit.Circuit: representation of the simulated quantum state
        )

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modemap.show()
        submap = [map_[m] for m in modes]
        if not self._modemap.valid(modes) or None in submap:
            raise ValueError("The specified modes are not valid.")

        remapped_modes = self._modemap.remap(modes)
        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self, num_subsystems, **kwargs):
        r"""Instantiate a quantum circuit.

        Instantiates a representation of a quantum optical state with ``num_subsystems`` modes.
        The state is initialized to vacuum.

        The modes in the circuit are indexed sequentially using integers, starting from zero.
        Once an index is assigned to a mode, it can never be re-assigned to another mode.
        If the mode is deleted its index becomes invalid.
        An operation acting on an invalid or unassigned mode index raises an ``IndexError`` exception.

        Args:
            num_subsystems (int): number of modes in the circuit

        Keyword Args:
            cutoff_dim (int): Numerical Hilbert space cutoff dimension for the modes.
                For each mode, the simulator can represent the Fock states :math:`\ket{0}, \ket{1}, \ldots, \ket{\text{cutoff_dim}-1}`.
            pure (bool): If True (default), use a pure state representation (otherwise will use a mixed state representation).
        """
        cutoff_dim = kwargs.get("cutoff_dim", None)
        pure = kwargs.get("pure", True)
        if cutoff_dim is None:
            raise ValueError(
                "Argument 'cutoff_dim' must be passed to the Fock backend")
        if not isinstance(cutoff_dim, int):
            raise ValueError(
                "Argument 'cutoff_dim' must be a positive integer")
        if not isinstance(num_subsystems, int):
            raise ValueError(
                "Argument 'num_subsystems' must be a positive integer")
        if not isinstance(pure, bool):
            raise ValueError("Argument 'pure' must be either True or False")

        self._init_modes = num_subsystems
        self.circuit = Circuit(num_subsystems, cutoff_dim, pure)
        self._modemap = ModeMap(num_subsystems)

    def add_mode(self, n=1):
        self.circuit.alloc(n)
        self._modemap.add(n)

    def del_mode(self, modes):
        remapped_modes = self._remap_modes(modes)
        if isinstance(remapped_modes, int):
            remapped_modes = [remapped_modes]
        self.circuit.dealloc(remapped_modes)
        self._modemap.delete(modes)

    def get_modes(self):
        return [i for i, j in enumerate(self._modemap._map) if j is not None]

    def reset(self, pure=True, **kwargs):
        cutoff = kwargs.get("cutoff_dim", self.circuit._trunc)
        self._modemap.reset()
        self.circuit.reset(pure,
                           num_subsystems=self._init_modes,
                           cutoff_dim=cutoff)

    def prepare_vacuum_state(self, mode):
        self.circuit.prepare_mode_fock(0, self._remap_modes(mode))

    def prepare_coherent_state(self, r, phi, mode):
        self.circuit.prepare_mode_coherent(r, phi, self._remap_modes(mode))

    def prepare_squeezed_state(self, r, phi, mode):
        self.circuit.prepare_mode_squeezed(r, phi, self._remap_modes(mode))

    def prepare_displaced_squeezed_state(self, r_d, phi_d, r_s, phi_s, mode):
        self.circuit.prepare_mode_displaced_squeezed(r_d, phi_d, r_s, phi_s,
                                                     self._remap_modes(mode))

    def prepare_thermal_state(self, nbar, mode):
        self.circuit.prepare_mode_thermal(nbar, self._remap_modes(mode))

    def rotation(self, phi, mode):
        self.circuit.phase_shift(phi, self._remap_modes(mode))

    def displacement(self, r, phi, mode):
        self.circuit.displacement(r, phi, self._remap_modes(mode))

    def squeeze(self, r, phi, mode):
        self.circuit.squeeze(r, phi, self._remap_modes(mode))

    def two_mode_squeeze(self, r, phi, mode1, mode2):
        self.circuit.two_mode_squeeze(r, phi, self._remap_modes(mode1),
                                      self._remap_modes(mode2))

    def beamsplitter(self, theta, phi, mode1, mode2):
        self.circuit.beamsplitter(theta, phi, self._remap_modes(mode1),
                                  self._remap_modes(mode2))

    def measure_homodyne(self, phi, mode, shots=1, select=None, **kwargs):
        """Perform a homodyne measurement on the specified mode.

        See :meth:`.BaseBackend.measure_homodyne`.

        Keyword Args:
            num_bins (int): Number of equally spaced bins for the probability distribution function
                (pdf) simulating the homodyne measurement (default: 100000).
            max (float): The pdf is discretized onto the 1D grid [-max,max] (default: 10).
        """
        if shots != 1:
            raise NotImplementedError(
                "fock backend currently does not support "
                "shots != 1 for homodyne measurement")
        return self.circuit.measure_homodyne(phi,
                                             self._remap_modes(mode),
                                             select=select,
                                             **kwargs)

    def loss(self, T, mode):
        self.circuit.loss(T, self._remap_modes(mode))

    def is_vacuum(self, tol=0.0, **kwargs):
        return self.circuit.is_vacuum(tol)

    def get_cutoff_dim(self):
        return self.circuit._trunc

    def state(self, modes=None, **kwargs):
        s, pure = self.circuit.get_state()

        if modes is None:
            # reduced state is full state
            red_state = s
            num_modes = len(s.shape) if pure else len(s.shape) // 2
            modes = [m for m in range(num_modes)]
        else:
            # convert to mixed state representation
            if pure:
                num_modes = len(s.shape)
                left_str = [indices[i] for i in range(0, 2 * num_modes, 2)]
                right_str = [indices[i] for i in range(1, 2 * num_modes, 2)]
                out_str = [indices[:2 * num_modes]]
                einstr = "".join(left_str + [","] + right_str + ["->"] +
                                 out_str)
                rho = np.einsum(einstr, s, s.conj())
            else:
                rho = s

            # reduce rho down to specified subsystems
            if isinstance(modes, int):
                modes = [modes]

            if len(modes) != len(set(modes)):
                raise ValueError("The specified modes cannot be duplicated.")

            num_modes = len(rho.shape) // 2
            if len(modes) > num_modes:
                raise ValueError(
                    "The number of specified modes cannot be larger than the number of subsystems."
                )

            keep_indices = indices[:2 * len(modes)]
            trace_indices = indices[2 * len(modes):len(modes) + num_modes]
            ind = [i * 2 for i in trace_indices]
            ctr = 0

            for m in range(num_modes):
                if m in modes:
                    ind.insert(m, keep_indices[2 * ctr:2 * (ctr + 1)])
                    ctr += 1

            indStr = "".join(ind) + "->" + keep_indices
            red_state = np.einsum(indStr, rho)

            # permute indices of returned state to reflect the ordering of modes (we know and hence can assume that red_state is a mixed state)
        if modes != sorted(modes):
            mode_permutation = np.argsort(modes)
            index_permutation = [
                2 * x + i for x in mode_permutation for i in (0, 1)
            ]
            red_state = np.transpose(red_state, np.argsort(index_permutation))

        cutoff = self.circuit._trunc
        mode_names = [
            "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
        ]
        state = BaseFockState(red_state, len(modes), pure, cutoff, mode_names)
        return state

    # ==============================================
    # Fock state specific
    # ==============================================

    def prepare_fock_state(self, n, mode):
        self.circuit.prepare_mode_fock(n, self._remap_modes(mode))

    def prepare_ket_state(self, state, modes):
        self.circuit.prepare_multimode(state, self._remap_modes(modes))

    def prepare_dm_state(self, state, modes):
        self.circuit.prepare_multimode(state, self._remap_modes(modes))

    def cubic_phase(self, gamma, mode):
        self.circuit.cubic_phase_shift(gamma, self._remap_modes(mode))

    def kerr_interaction(self, kappa, mode):
        self.circuit.kerr_interaction(kappa, self._remap_modes(mode))

    def cross_kerr_interaction(self, kappa, mode1, mode2):
        self.circuit.cross_kerr_interaction(kappa, self._remap_modes(mode1),
                                            self._remap_modes(mode2))

    def measure_fock(self, modes, shots=1, select=None, **kwargs):
        if shots != 1:
            raise NotImplementedError(
                "fock backend currently does not support "
                "shots != 1 for Fock measurement")
        return self.circuit.measure_fock(self._remap_modes(modes),
                                         select=select)
예제 #2
0
class TFBackend(BaseFock):
    r"""Implements a simulation of quantum optical circuits in a truncated
    Fock basis using `TensorFlow <http://tensorflow.org/>`_, returning a :class:`~.FockStateTF`
    state object.
    """

    short_name = "tf"
    circuit_spec = "tf"

    def __init__(self):
        """Initialize a TFBackend object.
        """
        super().__init__()
        self._supported["mixed_states"] = True
        self._supported["batched"] = True
        self._supported["symbolic"] = True
        self._init_modes = None  #: int: initial number of modes in the circuit
        self._modemap = None  #: Modemap: maps external mode indices to internal ones
        self.circuit = (
            None  #: ~.tfbackend.circuit.Circuit: representation of the simulated quantum state
        )

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modemap.show()
        submap = [map_[m] for m in modes]
        if not self._modemap.valid(modes) or None in submap:
            raise ValueError("The specified modes are not valid.")
        remapped_modes = self._modemap.remap(modes)

        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self, num_subsystems, **kwargs):
        r"""Instantiate a quantum circuit.

        Instantiates a representation of a quantum optical state with ``num_subsystems`` modes.
        The state is initialized to vacuum.

        The modes in the circuit are indexed sequentially using integers, starting from zero.
        Once an index is assigned to a mode, it can never be re-assigned to another mode.
        If the mode is deleted its index becomes invalid.
        An operation acting on an invalid or unassigned mode index raises an ``IndexError`` exception.

        Args:
            num_subsystems (int): number of modes in the circuit

        Keyword Args:
            cutoff_dim (int): Numerical Hilbert space cutoff dimension for the modes.
                For each mode, the simulator can represent the Fock states :math:`\ket{0}, \ket{1}, \ldots, \ket{\text{cutoff_dim}-1}`.
            pure (bool): If True (default), use a pure state representation (otherwise will use a mixed state representation).
            batch_size (None or int): Size of the batch-axis dimension. If None, no batch-axis will be used.
        """
        cutoff_dim = kwargs.get("cutoff_dim", None)
        pure = kwargs.get("pure", True)
        batch_size = kwargs.get("batch_size", None)

        if cutoff_dim is None:
            raise ValueError(
                "Argument 'cutoff_dim' must be passed to the TensorFlow backend"
            )

        if not isinstance(num_subsystems, int):
            raise ValueError(
                "Argument 'num_subsystems' must be a positive integer")
        if not isinstance(cutoff_dim, int):
            raise ValueError(
                "Argument 'cutoff_dim' must be a positive integer")
        if not isinstance(pure, bool):
            raise ValueError("Argument 'pure' must be either True or False")
        if batch_size == 1:
            raise ValueError(
                "batch_size of 1 not supported, please use different batch_size or set batch_size=None"
            )

        with tf.name_scope("Begin_circuit"):
            self._modemap = ModeMap(num_subsystems)
            circuit = Circuit(num_subsystems, cutoff_dim, pure, batch_size)

        self._init_modes = num_subsystems
        self.circuit = circuit

    def reset(self, pure=True, **kwargs):
        """Reset the circuit so that all the modes are in the vacuum state.

        After the reset the circuit is in the same state as it was after
        the last :meth:`begin_circuit` call. It will have the original number
        of modes, all initialized in the vacuum state. Some circuit parameters
        may be changed during the reset, see the keyword args below.

        Args:
            pure (bool): if True, initialize the circuit in a pure state representation
                (will use a mixed state representation if pure is False)

        Keyword Args:
            cutoff_dim (int): new Hilbert space truncation dimension
        """

        with tf.name_scope("Reset"):
            self._modemap.reset()
            self.circuit.reset(num_subsystems=self._init_modes,
                               pure=pure,
                               **kwargs)

    def get_cutoff_dim(self):
        return self.circuit.cutoff_dim

    def get_modes(self):
        # pylint: disable=protected-access
        return [i for i, j in enumerate(self._modemap._map) if j is not None]

    def prepare_vacuum_state(self, mode):
        with tf.name_scope("Prepare_vacuum"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_vacuum_state(remapped_mode)

    def prepare_coherent_state(self, r, phi, mode):
        with tf.name_scope("Prepare_coherent"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_coherent_state(r, phi, remapped_mode)

    def prepare_squeezed_state(self, r, phi, mode):
        with tf.name_scope("Prepare_squeezed"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_squeezed_state(r, phi, remapped_mode)

    def prepare_displaced_squeezed_state(self, r_d, phi_d, r_s, phi_s, mode):
        with tf.name_scope("Prepare_displaced_squeezed"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_displaced_squeezed_state(
                r_d, phi_d, r_s, phi_s, remapped_mode)

    def prepare_fock_state(self, n, mode):
        with tf.name_scope("Prepare_fock"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_fock_state(n, remapped_mode)

    def prepare_ket_state(self, state, modes):
        with tf.name_scope("Prepare_state"):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           True)

    def prepare_dm_state(self, state, modes):
        with tf.name_scope("Prepare_state"):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           False)

    def prepare_thermal_state(self, nbar, mode):
        with tf.name_scope("Prepare_thermal"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_thermal_state(nbar, remapped_mode)

    def rotation(self, phi, mode):
        with tf.name_scope("Rotation"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.phase_shift(phi, remapped_mode)

    def displacement(self, r, phi, mode):
        with tf.name_scope("Displacement"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.displacement(r, phi, remapped_mode)

    def squeeze(self, r, phi, mode):
        with tf.name_scope("Squeeze"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.squeeze(r, phi, remapped_mode)

    def beamsplitter(self, theta, phi, mode1, mode2):
        with tf.name_scope("Beamsplitter"):
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.beamsplitter(theta, phi, remapped_modes[0],
                                      remapped_modes[1])

    def two_mode_squeeze(self, r, phi, mode1, mode2):
        with tf.name_scope("Two-mode_squeezing"):
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.two_mode_squeeze(r, phi, remapped_modes[0],
                                          remapped_modes[1])

    def loss(self, T, mode):
        with tf.name_scope("Loss"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.loss(T, remapped_mode)

    def cubic_phase(self, gamma, mode):
        with tf.name_scope("Cubic_phase"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.cubic_phase(gamma, remapped_mode)

    def kerr_interaction(self, kappa, mode):
        with tf.name_scope("Kerr_interaction"):
            remapped_mode = self._remap_modes(mode)
            self.circuit.kerr_interaction(kappa, remapped_mode)

    def cross_kerr_interaction(self, kappa, mode1, mode2):
        with tf.name_scope("Cross-Kerr_interaction"):
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.cross_kerr_interaction(kappa, remapped_modes[0],
                                                remapped_modes[1])

    def state(self, modes=None, **kwargs):
        r"""Returns the state of the quantum simulation, restricted to the subsystems defined by `modes`.

        See :meth:`.BaseBackend.state`.

        Returns:
            FockStateTF: state description
        """
        with tf.name_scope("State"):
            s = self.circuit.state
            pure = self.circuit.state_is_pure
            num_modes = self.circuit.num_modes
            batched = self.circuit.batched

            # reduce rho down to specified subsystems
            if modes is None:
                # reduced state is full state
                reduced_state = s
                modes = list(range(num_modes))
            else:
                if isinstance(modes, int):
                    modes = [modes]
                if len(modes) != len(set(modes)):
                    raise ValueError(
                        "The specified modes cannot be duplicated.")
                if len(modes) > num_modes:
                    raise ValueError(
                        "The number of specified modes cannot be larger than the number of subsystems."
                    )

                if pure:
                    # convert to mixed state representation
                    reduced_state = mixed(s, batched)
                    pure = False
                else:
                    reduced_state = s

                    # trace our all modes not in modes
                    # todo: Doing this one by one is very inefficient. The partial trace function should be improved.
                for mode in sorted(
                    [m for m in range(num_modes) if m not in modes],
                        reverse=True):
                    reduced_state = partial_trace(reduced_state, mode, False,
                                                  batched)
                reduced_state_pure = False

            # unless the modes were requested in order, we need to swap indices around
            if modes != sorted(modes):
                mode_permutation = np.argsort(np.argsort(modes))
                reduced_state = reorder_modes(reduced_state, mode_permutation,
                                              reduced_state_pure, batched)

            s = reduced_state

            modenames = [
                "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
            ]
            state_ = FockStateTF(s,
                                 len(modes),
                                 pure,
                                 self.circuit.cutoff_dim,
                                 batched=batched,
                                 mode_names=modenames)
        return state_

    def measure_fock(self, modes, shots=1, select=None, **kwargs):
        """Measure the given modes in the Fock basis.

        See :meth:`.BaseFock.measure_fock`.

        Keyword Args:

        Returns:
            tuple[int] or tuple[Tensor]: measurement outcomes
        """
        if shots != 1:
            raise NotImplementedError("TF backend currently does not support "
                                      "shots != 1 for Fock measurement")
        with tf.name_scope("Measure_fock"):
            remapped_modes = self._remap_modes(modes)
            meas = self.circuit.measure_fock(remapped_modes,
                                             select=select,
                                             **kwargs)
        return meas

    def measure_homodyne(self, phi, mode, shots=1, select=None, **kwargs):
        """Perform a homodyne measurement on the specified modes.

        See :meth:`.BaseBackend.measure_homodyne`.

        Keyword Args:
            num_bins (int): Number of equally spaced bins for the probability distribution function
                (pdf) simulating the homodyne measurement (default: 100000).
            max (float): The pdf is discretized onto the 1D grid [-max,max] (default: 10).

        Returns:
            float or tf.Tensor: measurement outcome
        """
        if shots != 1:
            raise NotImplementedError("TF backend currently does not support "
                                      "shots != 1 for homodyne measurement")
        with tf.name_scope("Measure_homodyne"):
            remapped_mode = self._remap_modes(mode)
            meas = self.circuit.measure_homodyne(phi, remapped_mode, select,
                                                 **kwargs)
        return meas

    def is_vacuum(self, tol=0.0, **kwargs):
        with tf.name_scope("Is_vacuum"):
            vac_elem = self.circuit.vacuum_element()
            return np.abs(vac_elem - 1) <= tol

    def del_mode(self, modes):
        with tf.name_scope("Del_mode"):
            remapped_modes = self._remap_modes(modes)
            if isinstance(remapped_modes, int):
                remapped_modes = [remapped_modes]
            self.circuit.del_mode(remapped_modes)
            self._modemap.delete(modes)

    def add_mode(self, n=1):
        with tf.name_scope("Add_mode"):
            self.circuit.add_mode(n)
            self._modemap.add(n)
예제 #3
0
class FockBackend(BaseFock):
    """ Backend in the Fock basis """
    def __init__(self):
        """Instantiate a FockBackend object."""
        super().__init__()
        self._supported["mixed_states"] = True
        self._short_name = "fock"

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modeMap.show()
        submap = [map_[m] for m in modes]
        if not self._modeMap.valid(modes) or None in submap:
            raise ValueError('The specified modes are not valid.')
        else:
            remapped_modes = self._modeMap.remap(modes)
        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self,
                      num_subsystems,
                      cutoff_dim=None,
                      hbar=2,
                      pure=True,
                      **kwargs):
        r"""
        Create a quantum circuit (initialized in vacuum state) with the number of modes
        equal to num_subsystems and a Fock-space cutoff dimension of cutoff_dim.

        Args:
            num_subsystems (int): number of modes the circuit should begin with
            cutoff_dim (int): numerical cutoff dimension in Fock space for each mode.
                ``cutoff_dim=D`` represents the Fock states :math:`|0\rangle,\dots,|D-1\rangle`.
                This argument is **required** for the Fock backend.
            hbar (float): The value of :math:`\hbar` to initialise the circuit with, depending on the conventions followed.
                By default, :math:`\hbar=2`. See :ref:`conventions` for more details.
            pure (bool): whether to begin the circuit in a pure state representation
        """
        # pylint: disable=attribute-defined-outside-init
        if cutoff_dim is None:
            raise ValueError(
                "Argument 'cutoff_dim' must be passed to the Fock backend")
        elif not isinstance(cutoff_dim, int):
            raise ValueError(
                "Argument 'cutoff_dim' must be a positive integer")
        elif not isinstance(num_subsystems, int):
            raise ValueError(
                "Argument 'num_subsystems' must be a positive integer")
        elif not isinstance(pure, bool):
            raise ValueError("Argument 'pure' must be either True or False")

        self._init_modes = num_subsystems
        self.qreg = QReg(num_subsystems, cutoff_dim, hbar, pure)
        self._modeMap = ModeMap(num_subsystems)

    def add_mode(self, n=1):
        """Add num_modes new modes to the underlying circuit state. Indices for new modes
        always occur at the end of the state tensor.
        Note: this will increase the number of indices used for the state representation.

        Args:
            n (int): the number of modes to be added to the circuit
        """
        self.qreg.alloc(n)
        self._modeMap.add(n)

    def del_mode(self, modes):
        """Trace out the specified modes from the underlying circuit state.
        Note: this will reduce the number of indices used for the state representation,
        and also convert the state representation to mixed.

        Args:
            modes (list[int]): the modes to be removed from the circuit

        """
        remapped_modes = self._remap_modes(modes)
        if isinstance(remapped_modes, int):
            remapped_modes = [remapped_modes]
        self.qreg.dealloc(remapped_modes)
        self._modeMap.delete(modes)

    def get_modes(self):
        """Return a list of the active mode indices for the circuit.

        Returns:
            list[int]: sorted list of active (assigned, not invalid) mode indices
        """
        return [i for i, j in enumerate(self._modeMap._map) if j is not None]

    def reset(self, pure=True, **kwargs):
        """Resets the circuit state back to an all-vacuum state.

        Args:
            pure (bool): whether to use a pure state representation upon reset
        """
        self._modeMap.reset()
        self.qreg.reset(pure, num_subsystems=self._init_modes)

    def prepare_vacuum_state(self, mode):
        """Prepare the vacuum state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            mode (int): index of mode where state is prepared
        """
        self.qreg.prepare_mode_fock(0, self._remap_modes(mode))

    def prepare_coherent_state(self, alpha, mode):
        """Prepare a coherent state with parameter alpha on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            alpha (complex): coherent state displacement parameter
            mode (int): index of mode where state is prepared
        """
        self.qreg.prepare_mode_coherent(alpha, self._remap_modes(mode))

    def prepare_squeezed_state(self, r, phi, mode):
        r"""Prepare a squeezed vacuum state in the specified mode.
        Note: this may convert the state representation to mixed.

        The requested mode is traced out and replaced with the squeezed state :math:`\ket{z}`,
        where :math:`z=re^{i\phi}`.
        As a result the state may have to be described using a density matrix.

        Args:
            r (float): squeezing amplitude
            phi (float): squeezing angle
            mode (int): which mode to prepare the squeezed state in
        """
        self.qreg.prepare_mode_squeezed(r, phi, self._remap_modes(mode))

    def prepare_displaced_squeezed_state(self, alpha, r, phi, mode):
        """Prepare a displaced squezed state with parameters (alpha, r, phi) on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            alpha (complex): displacement parameter
            r (float): squeezing amplitude
            phi (float): squeezing phase
            mode (int): index of mode where state is prepared

        """
        self.qreg.prepare_mode_displaced_squeezed(alpha, r, phi,
                                                  self._remap_modes(mode))

    def prepare_thermal_state(self, nbar, mode):
        """Prepare the thermal state with mean photon number nbar on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            nbar (int): thermal population of the mode
            mode (int): which mode to prepare the thermal state in
        """
        self.qreg.prepare_mode_thermal(nbar, self._remap_modes(mode))

    def rotation(self, phi, mode):
        """Apply the phase-space rotation operation to the specified mode.

        Args:
            phi (float): rotation angle
            mode (int): which mode to apply the rotation to
        """
        self.qreg.phase_shift(phi, self._remap_modes(mode))

    def displacement(self, alpha, mode):
        """Perform a displacement operation on the specified mode.

        Args:
            alpha (float): displacement parameter
            mode (int): index of mode where operation is carried out

        """
        self.qreg.displacement(alpha, self._remap_modes(mode))

    def squeeze(self, z, mode):
        """Perform a squeezing operation on the specified mode.

        Args:
            z (complex): squeezing parameter
            mode (int): index of mode where operation is carried out

        """
        self.qreg.squeeze(abs(z), phase(z), self._remap_modes(mode))

    def beamsplitter(self, t, r, mode1, mode2):
        """Perform a beamsplitter operation on the specified modes.

        Args:
            t (complex): transmittivity parameter
            r (complex): reflectivity parameter
            mode1 (int): index of first mode where operation is carried out
            mode2 (int): index of second mode where operation is carried out

        """
        self.qreg.beamsplitter(t, abs(r), phase(r), self._remap_modes(mode1),
                               self._remap_modes(mode2))

    def kerr_interaction(self, kappa, mode):
        r"""Apply the Kerr interaction :math:`exp{(i\kappa \hat{n}^2)}` to the specified mode.

        Args:
            kappa (float): strength of the interaction
            mode (int): which mode to apply it to
        """
        self.qreg.kerr_interaction(kappa, self._remap_modes(mode))

    def cubic_phase(self, gamma, mode):
        r"""Apply the cubic phase operation to the specified mode.

        .. warning:: The cubic phase gate can suffer heavily from numerical inaccuracies due to finite-dimensional cutoffs in the Fock basis.
                     The gate implementation in Strawberry Fields is unitary, but it does not implement an exact cubic phase gate.
                     The Kerr gate provides an alternative non-Gaussian gate.

        Args:
            gamma (float): cubic phase shift
            mode (int): which mode to apply it to
        """
        self.qreg.cubic_phase_shift(gamma, self._remap_modes(mode))

    def measure_homodyne(self, phi, mode, select=None, **kwargs):
        """Perform a homodyne measurement on the specified mode.

        Args:
            phi (float): angle (relative to x-axis) for the measurement
            mode (int): index of mode where operation is carried out
            select (float): (Optional) desired values of measurement results
            **kwargs: Can be used to (optionally) pass user-specified numerical parameters `max` and `num_bins`.
                                These are used numerically to build the probability distribution function (pdf) for the homdyne measurement
                                Specifically, the pdf is discretized onto the 1D grid [-max,max], with num_bins equally spaced bins

        Returns:
            float: measurement outcome
        """
        return self.qreg.measure_homodyne(phi,
                                          self._remap_modes(mode),
                                          select=select,
                                          **kwargs)

    def loss(self, T, mode):
        """Perform a loss channel operation on the specified mode.

        Args:
            T: loss parameter
            mode (int): index of mode where operation is carried out

        """
        self.qreg.loss(T, self._remap_modes(mode))

    def is_vacuum(self, tol=0.0, **kwargs):
        r"""Test whether the current circuit state is in vacuum (up to tolerance tol).

        Args:
            tol (float): numerical tolerance for how close state must be to true vacuum state

        Returns:
            bool: True if vacuum state up to tolerance tol
        """
        return self.qreg.is_vacuum(tol)

    def get_cutoff_dim(self):
        """Returns the Hilbert space cutoff dimension used.

        Returns:
            int: cutoff dimension
        """
        return self.qreg._trunc

    def state(self, modes=None, **kwargs):
        r"""Returns the state of the quantum simulation, restricted to the subsystems defined by `modes`.

        Args:
                modes (int or Sequence[int]): specifies the mode or modes to restrict the return state to.
                        This argument is optional; the default value ``modes=None`` returns the state containing all modes.
        Returns:
                An instance of the Strawberry Fields FockState class.
        """
        s, pure = self.qreg.get_state()

        if modes is None:
            # reduced state is full state
            red_state = s
            num_modes = len(s.shape) if pure else len(s.shape) // 2
            modes = [m for m in range(num_modes)]
        else:
            # convert to mixed state representation
            if pure:
                num_modes = len(s.shape)
                left_str = [indices[i] for i in range(0, 2 * num_modes, 2)]
                right_str = [indices[i] for i in range(1, 2 * num_modes, 2)]
                out_str = [indices[:2 * num_modes]]
                einstr = ''.join(left_str + [','] + right_str + ['->'] +
                                 out_str)
                rho = np.einsum(einstr, s, s.conj())
            else:
                rho = s

            # reduce rho down to specified subsystems
            if isinstance(modes, int):
                modes = [modes]

            if modes != sorted(modes):
                raise ValueError("The specified modes cannot be duplicated.")

            num_modes = len(rho.shape) // 2
            if len(modes) > num_modes:
                raise ValueError(
                    "The number of specified modes cannot be larger than the number of subsystems."
                )

            keep_indices = indices[:2 * len(modes)]
            trace_indices = indices[2 * len(modes):len(modes) + num_modes]
            ind = [i * 2 for i in trace_indices]
            ctr = 0

            for m in range(num_modes):
                if m in modes:
                    ind.insert(m, keep_indices[2 * ctr:2 * (ctr + 1)])
                    ctr += 1

            indStr = ''.join(ind) + '->' + keep_indices
            red_state = np.einsum(indStr, rho)

        hbar = self.qreg._hbar
        cutoff = self.qreg._trunc  # pylint: disable=protected-access
        mode_names = [
            "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
        ]
        state = BaseFockState(red_state, len(modes), pure, cutoff, hbar,
                              mode_names)
        return state

    # ==============================================
    # Fock state specific
    # ==============================================

    def prepare_fock_state(self, n, mode):
        """Prepare a Fock state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            n (int): number state to prepare
            mode (int): index of mode where state is prepared

        """
        self.qreg.prepare_mode_fock(n, self._remap_modes(mode))

    def prepare_ket_state(self, state, mode):
        """Prepare an arbitrary pure state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            state (array): vector representation of ket state to prepare
            mode (int): index of mode where state is prepared

        """
        self.qreg.prepare(state, self._remap_modes(mode))

    def measure_fock(self, modes, select=None, **kwargs):
        """Perform a Fock measurement on the specified modes.

        Args:
            modes (list[int]): indices of mode where operation is carried out
            select (list[int]): (Optional) desired values of measurement results.
                                                     The length of this list must match the length of the modes list.

        Returns:
            list[int]: measurement outcomes
        """
        return self.qreg.measure_fock(self._remap_modes(modes), select=select)
예제 #4
0
class FockBackend(BaseFock):
    """ Backend in the Fock basis """
    def __init__(self):
        """Instantiate a FockBackend object."""
        super().__init__()
        self._supported["mixed_states"] = True
        self._short_name = "fock"
        self._init_modes = None  #: int: initial number of modes in the circuit
        self._modemap = None  #: Modemap: maps external mode indices to internal ones
        self.circuit = None  #: ~.fockbackend.circuit.Circuit: representation of the simulated quantum state

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modemap.show()
        submap = [map_[m] for m in modes]
        if not self._modemap.valid(modes) or None in submap:
            raise ValueError('The specified modes are not valid.')

        remapped_modes = self._modemap.remap(modes)
        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self, num_subsystems, **kwargs):
        r"""Instantiate a quantum circuit.

        Instantiates a representation of a quantum optical state with ``num_subsystems`` modes.
        The state is initialized to vacuum.

        The modes in the circuit are indexed sequentially using integers, starting from zero.
        Once an index is assigned to a mode, it can never be re-assigned to another mode.
        If the mode is deleted its index becomes invalid.
        An operation acting on an invalid or unassigned mode index raises an ``IndexError`` exception.

        Args:
            num_subsystems (int): number of modes in the circuit

        Keyword Args:
            cutoff_dim (int): Numerical Hilbert space cutoff dimension for the modes.
                For each mode, the simulator can represent the Fock states :math:`\ket{0}, \ket{1}, \ldots, \ket{\text{cutoff_dim}-1}`.
            pure (bool): If True (default), use a pure state representation (otherwise will use a mixed state representation).
        """
        cutoff_dim = kwargs.get('cutoff_dim', None)
        pure = kwargs.get('pure', True)
        if cutoff_dim is None:
            raise ValueError(
                "Argument 'cutoff_dim' must be passed to the Fock backend")
        if not isinstance(cutoff_dim, int):
            raise ValueError(
                "Argument 'cutoff_dim' must be a positive integer")
        if not isinstance(num_subsystems, int):
            raise ValueError(
                "Argument 'num_subsystems' must be a positive integer")
        if not isinstance(pure, bool):
            raise ValueError("Argument 'pure' must be either True or False")

        self._init_modes = num_subsystems
        self.circuit = Circuit(num_subsystems, cutoff_dim, pure)
        self._modemap = ModeMap(num_subsystems)

    def add_mode(self, n=1):
        self.circuit.alloc(n)
        self._modemap.add(n)

    def del_mode(self, modes):
        remapped_modes = self._remap_modes(modes)
        if isinstance(remapped_modes, int):
            remapped_modes = [remapped_modes]
        self.circuit.dealloc(remapped_modes)
        self._modemap.delete(modes)

    def get_modes(self):
        return [i for i, j in enumerate(self._modemap._map) if j is not None]

    def reset(self, pure=True, **kwargs):
        cutoff = kwargs.get('cutoff_dim', self.circuit._trunc)
        self._modemap.reset()
        self.circuit.reset(pure,
                           num_subsystems=self._init_modes,
                           cutoff_dim=cutoff)

    def prepare_vacuum_state(self, mode):
        self.circuit.prepare_mode_fock(0, self._remap_modes(mode))

    def prepare_coherent_state(self, alpha, mode):
        self.circuit.prepare_mode_coherent(alpha, self._remap_modes(mode))

    def prepare_squeezed_state(self, r, phi, mode):
        self.circuit.prepare_mode_squeezed(r, phi, self._remap_modes(mode))

    def prepare_displaced_squeezed_state(self, alpha, r, phi, mode):
        self.circuit.prepare_mode_displaced_squeezed(alpha, r, phi,
                                                     self._remap_modes(mode))

    def prepare_thermal_state(self, nbar, mode):
        self.circuit.prepare_mode_thermal(nbar, self._remap_modes(mode))

    def rotation(self, phi, mode):
        self.circuit.phase_shift(phi, self._remap_modes(mode))

    def displacement(self, alpha, mode):
        self.circuit.displacement(alpha, self._remap_modes(mode))

    def squeeze(self, z, mode):
        self.circuit.squeeze(abs(z), phase(z), self._remap_modes(mode))

    def beamsplitter(self, t, r, mode1, mode2):
        if isinstance(t, complex):
            raise ValueError("Beamsplitter transmittivity t must be a float.")
        self.circuit.beamsplitter(t, abs(r), phase(r),
                                  self._remap_modes(mode1),
                                  self._remap_modes(mode2))

    def measure_homodyne(self, phi, mode, shots=1, select=None, **kwargs):
        """Perform a homodyne measurement on the specified mode.

        See :meth:`.BaseBackend.measure_homodyne`.

        Keyword Args:
            num_bins (int): Number of equally spaced bins for the probability distribution function
                (pdf) simulating the homodyne measurement (default: 100000).
            max (float): The pdf is discretized onto the 1D grid [-max,max] (default: 10).
        """
        if shots != 1:
            raise NotImplementedError(
                "fock backend currently does not support "
                "shots != 1 for homodyne measurement")
        return self.circuit.measure_homodyne(phi,
                                             self._remap_modes(mode),
                                             select=select,
                                             **kwargs)

    def loss(self, T, mode):
        self.circuit.loss(T, self._remap_modes(mode))

    def is_vacuum(self, tol=0.0, **kwargs):
        return self.circuit.is_vacuum(tol)

    def get_cutoff_dim(self):
        return self.circuit._trunc

    def state(self, modes=None, **kwargs):
        s, pure = self.circuit.get_state()

        if modes is None:
            # reduced state is full state
            red_state = s
            num_modes = len(s.shape) if pure else len(s.shape) // 2
            modes = [m for m in range(num_modes)]
        else:
            # convert to mixed state representation
            if pure:
                num_modes = len(s.shape)
                left_str = [indices[i] for i in range(0, 2 * num_modes, 2)]
                right_str = [indices[i] for i in range(1, 2 * num_modes, 2)]
                out_str = [indices[:2 * num_modes]]
                einstr = ''.join(left_str + [','] + right_str + ['->'] +
                                 out_str)
                rho = np.einsum(einstr, s, s.conj())
            else:
                rho = s

            # reduce rho down to specified subsystems
            if isinstance(modes, int):
                modes = [modes]

            if len(modes) != len(set(modes)):
                raise ValueError("The specified modes cannot be duplicated.")

            num_modes = len(rho.shape) // 2
            if len(modes) > num_modes:
                raise ValueError(
                    "The number of specified modes cannot be larger than the number of subsystems."
                )

            keep_indices = indices[:2 * len(modes)]
            trace_indices = indices[2 * len(modes):len(modes) + num_modes]
            ind = [i * 2 for i in trace_indices]
            ctr = 0

            for m in range(num_modes):
                if m in modes:
                    ind.insert(m, keep_indices[2 * ctr:2 * (ctr + 1)])
                    ctr += 1

            indStr = ''.join(ind) + '->' + keep_indices
            red_state = np.einsum(indStr, rho)

            # permute indices of returned state to reflect the ordering of modes (we know and hence can assume that red_state is a mixed state)
        if modes != sorted(modes):
            mode_permutation = np.argsort(modes)
            index_permutation = [
                2 * x + i for x in mode_permutation for i in (0, 1)
            ]
            red_state = np.transpose(red_state, np.argsort(index_permutation))

        cutoff = self.circuit._trunc
        mode_names = [
            "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
        ]
        state = BaseFockState(red_state, len(modes), pure, cutoff, mode_names)
        return state

    # ==============================================
    # Fock state specific
    # ==============================================

    def prepare_fock_state(self, n, mode):
        self.circuit.prepare_mode_fock(n, self._remap_modes(mode))

    def prepare_ket_state(self, state, modes):
        self.circuit.prepare_multimode(state, self._remap_modes(modes))

    def prepare_dm_state(self, state, modes):
        self.circuit.prepare_multimode(state, self._remap_modes(modes))

    def cubic_phase(self, gamma, mode):
        self.circuit.cubic_phase_shift(gamma, self._remap_modes(mode))

    def kerr_interaction(self, kappa, mode):
        self.circuit.kerr_interaction(kappa, self._remap_modes(mode))

    def cross_kerr_interaction(self, kappa, mode1, mode2):
        self.circuit.cross_kerr_interaction(kappa, self._remap_modes(mode1),
                                            self._remap_modes(mode2))

    def measure_fock(self, modes, shots=1, select=None, **kwargs):
        if shots != 1:
            raise NotImplementedError(
                "fock backend currently does not support "
                "shots != 1 for Fock measurement")
        return self.circuit.measure_fock(self._remap_modes(modes),
                                         select=select)
예제 #5
0
class TFBackend(BaseFock):
    """Tensorflow Backend implementation."""
    def __init__(self, graph=None):
        """
        Instantiate a TFBackend object.

        Args:
            graph (Graph): optional Tensorflow Graph object where circuit should be defined
        """
        super().__init__()
        self._supported["mixed_states"] = True
        self._supported["batched"] = True
        self._supported["symbolic"] = True
        self._short_name = "tf"
        if graph is None:
            self._graph = tf.get_default_graph()
        else:
            self._graph = graph

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modemap.show()
        submap = [map_[m] for m in modes]
        if not self._modemap.valid(modes) or None in submap:
            raise ValueError('The specified modes are not valid.')
        else:
            remapped_modes = self._modemap.remap(modes)
        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self,
                      num_subsystems,
                      cutoff_dim=None,
                      hbar=2,
                      pure=True,
                      **kwargs):
        r"""Create a quantum circuit (initialized in vacuum state) with the number of modes
        equal to num_subsystems and a Fock-space cutoff dimension of cutoff_dim.

        Args:
            num_subsystems (int): number of modes the circuit should begin with
            cutoff_dim (int): numerical cutoff dimension in Fock space for each mode.
                ``cutoff_dim=D`` represents the Fock states :math:`|0\rangle,\dots,|D-1\rangle`.
                This argument is **required** for the Tensorflow backend.
            hbar (float): The value of :math:`\hbar` to initialise the circuit with, depending on the conventions followed.
                By default, :math:`\hbar=2`. See :ref:`conventions` for more details.
            pure (bool): whether to begin the circuit in a pure state representation
            **kwargs: optional keyword arguments which will be passed to the underlying circuit class

                * **batch_size** (*None* or *int*): the size of the batch-axis dimension. If None, no batch-axis will be used.
        """
        # pylint: disable=too-many-arguments,attribute-defined-outside-init
        with tf.name_scope('Begin_circuit'):
            batch_size = kwargs.get('batch_size', None)

            if cutoff_dim is None:
                raise ValueError(
                    "Argument 'cutoff_dim' must be passed to the Tensorflow backend"
                )

            if not isinstance(num_subsystems, int):
                raise ValueError(
                    "Argument 'num_subsystems' must be a positive integer")
            elif not isinstance(cutoff_dim, int):
                raise ValueError(
                    "Argument 'cutoff_dim' must be a positive integer")
            elif not isinstance(pure, bool):
                raise ValueError(
                    "Argument 'pure' must be either True or False")
            elif batch_size == 1:
                raise ValueError(
                    "batch_size of 1 not supported, please use different batch_size or set batch_size=None"
                )
            else:
                self._modemap = ModeMap(num_subsystems)
                circuit = Circuit(self._graph, num_subsystems, cutoff_dim,
                                  hbar, pure, batch_size)

        self._init_modes = num_subsystems
        self.circuit = circuit

    def reset(self, pure=True, **kwargs):
        r"""
        Resets the circuit state tensor back to an all-vacuum state.

        Args:
          pure (bool): whether to use a pure state representation upon reset

        Keyword Args:
          hard (bool): whether to reset the underlying tensorflow graph.
                  If hard reset is specified, then resets the underlying tensor graph as well.
                  If False, then the circuit is reset to its initial state, but ops that
                  have already been declared are still accessible.
          cutoff_dim (int): new cutoff dimension for the simulated circuit.
          hbar (float): New :math:`\hbar` value. See :ref:`conventions` for more details.

        """
        hard = kwargs.get('hard', True)
        if hard:
            tf.reset_default_graph()
            self._graph = tf.get_default_graph()

        with tf.name_scope('Reset'):
            self._modemap.reset()
            self.circuit.reset(pure,
                               graph=self._graph,
                               num_subsystems=self._init_modes,
                               **kwargs)

    def get_cutoff_dim(self):
        """Returns the Hilbert space cutoff dimension used.

        Returns:
            int: cutoff dimension
        """
        return self.circuit.cutoff_dim

    def get_modes(self):
        """Return a list of the active mode indices for the circuit.

        Returns:
            list[int]: sorted list of active (assigned, not invalid) mode indices
        """
        # pylint: disable=protected-access
        return [i for i, j in enumerate(self._modemap._map) if j is not None]

    def prepare_vacuum_state(self, mode):
        """
        Prepare the vacuum state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            mode (int): index of mode where state is prepared
        """
        with tf.name_scope('Prepare_vacuum'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_vacuum_state(remapped_mode)

    def prepare_coherent_state(self, alpha, mode):
        """
        Prepare a coherent state with parameter alpha on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            alpha (complex): coherent state displacement parameter
            mode (int): index of mode where state is prepared
        """
        with tf.name_scope('Prepare_coherent'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_coherent_state(alpha, remapped_mode)

    def prepare_squeezed_state(self, r, phi, mode):
        """
        Prepare a coherent state with parameters (r, phi) on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            r (float): squeezing amplitude
            phi (float): squeezing phase
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_squeezed'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_squeezed_state(r, phi, remapped_mode)

    def prepare_displaced_squeezed_state(self, alpha, r, phi, mode):
        """
        Prepare a displaced squezed state with parameters (alpha, r, phi) on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            alpha (complex): displacement parameter
            r (float): squeezing amplitude
            phi (float): squeezing phase
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_displaced_squeezed'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_displaced_squeezed_state(
                alpha, r, phi, remapped_mode)

    def prepare_fock_state(self, n, mode):
        """
        Prepare a Fock state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            n (int): number state to prepare
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_fock'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_fock_state(n, remapped_mode)

    def prepare_ket_state(self, state, modes):
        """
        Prepare an arbitrary pure state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            state (array): vector representation of ket state to prepare
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_state'):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           True)

    def prepare_dm_state(self, state, modes):
        """
        Prepare an arbitrary mixed state on the specified mode.
        Note: this converts the state representation to mixed.

        Args:
            state (array): matrix representation of the state to prepare
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_state'):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           False)

    def prepare_thermal_state(self, nbar, mode):
        """
        Prepare a thermal state on the specified mode.
        Note: this may convert the state representation to mixed.

        Args:
            nbar (float): mean photon number of the thermal state
            mode (int): index of mode where state is prepared

        """
        with tf.name_scope('Prepare_thermal'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_thermal_state(nbar, remapped_mode)

    def rotation(self, phi, mode):
        """
        Perform a phase shift by angle phi on the specified mode.

        Args:
            phi (float):
            mode (int): index of mode where operation is carried out

        """
        with tf.name_scope('Rotation'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.phase_shift(phi, remapped_mode)

    def displacement(self, alpha, mode):
        """
        Perform a displacement operation on the specified mode.

        Args:
            alpha (float): displacement parameter
            mode (int): index of mode where operation is carried out

        """
        with tf.name_scope('Displacement'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.displacement(alpha, remapped_mode)

    def squeeze(self, z, mode):
        """
        Perform a squeezing operation on the specified mode.

        Args:
            z (complex): squeezing parameter
            mode (int): index of mode where operation is carried out

        """
        with tf.name_scope('Squeeze'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.squeeze(z, remapped_mode)

    def beamsplitter(self, t, r, mode1, mode2):
        """
        Perform a beamsplitter operation on the specified modes.

        Args:
            t (float): transmittivity parameter
            r (complex): reflectivity parameter
            mode1 (int): index of first mode where operation is carried out
            mode2 (int): index of second mode where operation is carried out

        """
        with tf.name_scope('Beamsplitter'):
            if isinstance(t, complex):
                raise ValueError(
                    "Beamsplitter transmittivity t must be a float.")
            if isinstance(t, tf.Tensor):
                if t.dtype.is_complex:
                    raise ValueError(
                        "Beamsplitter transmittivity t must be a float.")
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.beamsplitter(t, r, remapped_modes[0],
                                      remapped_modes[1])

    def loss(self, T, mode):
        """
        Perform a loss channel operation on the specified mode.

        Args:
            T: loss parameter
            mode (int): index of mode where operation is carried out

        """
        with tf.name_scope('Loss'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.loss(T, remapped_mode)

    def cubic_phase(self, gamma, mode):
        r"""Apply the cubic phase operation to the specified mode.

        .. warning:: The cubic phase gate can suffer heavily from numerical inaccuracies due to finite-dimensional cutoffs in the Fock basis.
                     The gate implementation in Strawberry Fields is unitary, but it does not implement an exact cubic phase gate.
                     The Kerr gate provides an alternative non-Gaussian gate.

        Args:
            gamma (float): cubic phase shift
            mode (int): which mode to apply it to
        """
        with tf.name_scope('Cubic_phase'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.cubic_phase(gamma, remapped_mode)

    def kerr_interaction(self, kappa, mode):
        r"""Apply the Kerr interaction :math:`\exp{(i\kappa \hat{n}^2)}` to the specified mode.

        Args:
            kappa (float): strength of the interaction
            mode (int): which mode to apply it to
        """
        with tf.name_scope('Kerr_interaction'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.kerr_interaction(kappa, remapped_mode)

    def cross_kerr_interaction(self, kappa, mode1, mode2):
        r"""Apply the two mode cross-Kerr interaction :math:`\exp{(i\kappa \hat{n}_1\hat{n}_2)}` to the specified modes.

        Args:
            kappa (float): strength of the interaction
            mode1 (int): first mode that cross-Kerr interaction acts on
            mode2 (int): second mode that cross-Kerr interaction acts on
        """
        with tf.name_scope('Cross-Kerr_interaction'):
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.cross_kerr_interaction(kappa, remapped_modes[0],
                                                remapped_modes[1])

    def state(self, modes=None, **kwargs):
        r"""Returns the state of the quantum simulation, restricted to the subsystems defined by `modes`.

        Args:
            modes (int or Sequence[int]): specifies the mode or modes to restrict the return state to.
                                          This argument is optional; the default value ``modes=None`` returns the state containing all modes.
            **kwargs: optional keyword args (`session`: a Tensorflow session; `feed_dict`: a Python dictionary feeding the desired numerical values for Tensors)
                      which will be used by the Tensorflow simulator for numerically evaluating the measurement results.
        Returns:
            An instance of the Strawberry Fields FockStateTF class.
        """
        with tf.name_scope('State'):
            s = self.circuit.state
            pure = self.circuit.state_is_pure
            num_modes = self.circuit.num_modes
            batched = self.circuit.batched

            # reduce rho down to specified subsystems
            if modes is None:
                # reduced state is full state
                reduced_state = s
                modes = list(range(num_modes))
            else:
                if isinstance(modes, int):
                    modes = [modes]
                if len(modes) != len(set(modes)):
                    raise ValueError(
                        "The specified modes cannot be duplicated.")
                if len(modes) > num_modes:
                    raise ValueError(
                        "The number of specified modes cannot be larger than the number of subsystems."
                    )

                if pure:
                    # convert to mixed state representation
                    reduced_state = mixed(s, batched)
                    pure = False
                else:
                    reduced_state = s

                    # trace our all modes not in modes
                    # todo: Doing this one by one is very inefficient. The partial trace function should be improved.
                for mode in sorted(
                    [m for m in range(num_modes) if m not in modes],
                        reverse=True):
                    reduced_state = partial_trace(reduced_state, mode, False,
                                                  batched)
                reduced_state_pure = False

            # unless the modes were requested in order, we need to swap indices around
            if modes != sorted(modes):
                mode_permutation = np.argsort(np.argsort(modes))
                reduced_state = reorder_modes(reduced_state, mode_permutation,
                                              reduced_state_pure, batched)

            evaluate_results, session, feed_dict, close_session = _check_for_eval(
                kwargs)
            if evaluate_results:
                s = session.run(reduced_state, feed_dict=feed_dict)
                if close_session:
                    session.close()
            else:
                s = reduced_state

            modenames = [
                "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
            ]
            state_ = FockStateTF(s,
                                 len(modes),
                                 pure,
                                 self.circuit.cutoff_dim,
                                 graph=self._graph,
                                 batched=batched,
                                 hbar=self.circuit.hbar,
                                 mode_names=modenames,
                                 eval=evaluate_results)
        return state_

    def measure_fock(self, modes, select=None, **kwargs):
        """
        Perform a Fock measurement on the specified modes.

        Args:
            modes (Sequence[int]): indices of mode where operation is carried out
            select (Sequence[int]): (Optional) desired values of measurement results. Allows user to post-select on specific measurement results instead of randomly sampling.
            **kwargs: optional keyword args (`session`: a Tensorflow session; `feed_dict`: a Python dictionary feeding the desired numerical values for Tensors)
                                which will be used by the Tensorflow simulator for numerically evaluating the measurement results.

        Returns:
            tuple[int] or tuple[Tensor]: measurement outcomes
        """
        with tf.name_scope('Measure_fock'):
            remapped_modes = self._remap_modes(modes)
            meas = self.circuit.measure_fock(remapped_modes,
                                             select=select,
                                             **kwargs)
        return meas

    def measure_homodyne(self, phi, mode, select=None, **kwargs):
        """
        Perform a homodyne measurement on the specified modes.

        Args:
            phi (float): angle (relative to x-axis) for the measurement.
            select (float): (Optional) desired values of measurement results. Allows user to post-select on specific measurement results instead of randomly sampling.
            mode (Sequence[int]): index of mode where operation is carried out
            **kwargs: optional keyword args (`session`: a Tensorflow session; `feed_dict`: a Python dictionary feeding the desired numerical values for Tensors)
                                which will be used by the Tensorflow simulator for numerically evaluating the measurement results.
                                In addition, kwargs can be used to (optionally) pass user-specified numerical parameters `max` and `num_bins`.
                                These are used numerically to build the probability distribution function (pdf) for the homodyne measurement.
                                Specifically, the pdf is discretized onto the 1D grid [-max,max], with num_bins equally spaced bins.

        Returns:
            tuple[float] or tuple[Tensor]: measurement outcomes
        """
        with tf.name_scope('Measure_homodyne'):
            remapped_mode = self._remap_modes(mode)
            meas = self.circuit.measure_homodyne(phi, remapped_mode, select,
                                                 **kwargs)
        return meas

    def is_vacuum(self, tol=0.0, **kwargs):
        r"""Test whether the current circuit state is in vacuum (up to tolerance tol).
        Args:
            tol (float): numerical tolerance for how close state must be to true vacuum state
        Returns:
            bool: True if vacuum state up to tolerance tol
        """
        with tf.name_scope('Is_vacuum'):
            with self.circuit.graph.as_default():
                vac_elem = self.circuit.vacuum_element()
                if "eval" in kwargs and kwargs["eval"] is False:
                    v = vac_elem
                else:
                    sess = tf.Session()
                    v = sess.run(vac_elem)
                    sess.close()

            result = (1 - v) <= tol
        return result

    def del_mode(self, modes):
        """
        Trace out the specified modes from the underlying circuit state.
        Note: This will reduce the number of indices used for the state representation,
        and also convert the state representation to mixed.

        Args:
            modes (Sequence[int]): the modes to be removed from the circuit
        """
        with tf.name_scope('Del_mode'):
            remapped_modes = self._remap_modes(modes)
            if isinstance(remapped_modes, int):
                remapped_modes = [remapped_modes]
            self.circuit.del_mode(remapped_modes)
            self._modemap.delete(modes)

    def add_mode(self, n=1):
        """
        Add n new modes to the underlying circuit state. Indices for new modes
        always occur at the end of the state tensor.
        Note: This will increase the number of indices used for the state representation.

        Args:
            n (int): the number of modes to be added to the circuit.
        """
        with tf.name_scope('Add_mode'):
            self.circuit.add_mode(n)
            self._modemap.add(n)

    @property
    def graph(self):
        """
        Get the Tensorflow Graph object where the current quantum circuit is defined.

        Returns:
            (Graph): the circuit's graph
        """
        return self._graph
예제 #6
0
class TFBackend(BaseFock):
    """TensorFlow backend implementation."""

    short_name = 'tf'
    circuit_spec = 'tf'

    def __init__(self, graph=None):
        """Initialize a TFBackend object.

        Args:
            graph (tf.Graph): optional Tensorflow Graph object where circuit should be defined
        """
        super().__init__()
        self._supported["mixed_states"] = True
        self._supported["batched"] = True
        self._supported["symbolic"] = True
        if graph is None:
            self._graph = tf.get_default_graph()
        else:
            self._graph = graph
        self._init_modes = None  #: int: initial number of modes in the circuit
        self._modemap = None  #: Modemap: maps external mode indices to internal ones
        self.circuit = None  #: ~.tfbackend.circuit.Circuit: representation of the simulated quantum state

    def _remap_modes(self, modes):
        if isinstance(modes, int):
            modes = [modes]
            was_int = True
        else:
            was_int = False
        map_ = self._modemap.show()
        submap = [map_[m] for m in modes]
        if not self._modemap.valid(modes) or None in submap:
            raise ValueError('The specified modes are not valid.')
        remapped_modes = self._modemap.remap(modes)

        if was_int:
            remapped_modes = remapped_modes[0]
        return remapped_modes

    def begin_circuit(self, num_subsystems, **kwargs):
        r"""Instantiate a quantum circuit.

        Instantiates a representation of a quantum optical state with ``num_subsystems`` modes.
        The state is initialized to vacuum.

        The modes in the circuit are indexed sequentially using integers, starting from zero.
        Once an index is assigned to a mode, it can never be re-assigned to another mode.
        If the mode is deleted its index becomes invalid.
        An operation acting on an invalid or unassigned mode index raises an ``IndexError`` exception.

        Args:
            num_subsystems (int): number of modes in the circuit

        Keyword Args:
            cutoff_dim (int): Numerical Hilbert space cutoff dimension for the modes.
                For each mode, the simulator can represent the Fock states :math:`\ket{0}, \ket{1}, \ldots, \ket{\text{cutoff_dim}-1}`.
            pure (bool): If True (default), use a pure state representation (otherwise will use a mixed state representation).
            batch_size (None or int): Size of the batch-axis dimension. If None, no batch-axis will be used.
        """
        cutoff_dim = kwargs.get('cutoff_dim', None)
        pure = kwargs.get('pure', True)
        batch_size = kwargs.get('batch_size', None)

        if cutoff_dim is None:
            raise ValueError(
                "Argument 'cutoff_dim' must be passed to the Tensorflow backend"
            )

        if not isinstance(num_subsystems, int):
            raise ValueError(
                "Argument 'num_subsystems' must be a positive integer")
        if not isinstance(cutoff_dim, int):
            raise ValueError(
                "Argument 'cutoff_dim' must be a positive integer")
        if not isinstance(pure, bool):
            raise ValueError("Argument 'pure' must be either True or False")
        if batch_size == 1:
            raise ValueError(
                "batch_size of 1 not supported, please use different batch_size or set batch_size=None"
            )

        with tf.name_scope('Begin_circuit'):
            self._modemap = ModeMap(num_subsystems)
            circuit = Circuit(self._graph, num_subsystems, cutoff_dim, pure,
                              batch_size)

        self._init_modes = num_subsystems
        self.circuit = circuit

    def reset(self, pure=True, **kwargs):
        """Reset the circuit so that all the modes are in the vacuum state.

        After the reset the circuit is in the same state as it was after
        the last :meth:`begin_circuit` call. It will have the original number
        of modes, all initialized in the vacuum state. Some circuit parameters
        may be changed during the reset, see the keyword args below.

        Args:
            pure (bool): if True, initialize the circuit in a pure state representation
                (will use a mixed state representation if pure is False)

        Keyword Args:
            cutoff_dim (int): new Hilbert space truncation dimension
            hard (bool): Whether to reset the underlying TensorFlow graph.
                If True (default), then resets the underlying tensor graph as well.
                If False, then the circuit is reset to its initial state, but ops that
                have already been declared are still accessible.
        """
        hard = kwargs.pop('hard', True)
        if hard:
            tf.reset_default_graph()
            self._graph = tf.get_default_graph()

        with tf.name_scope('Reset'):
            self._modemap.reset()
            self.circuit.reset(graph=self._graph,
                               num_subsystems=self._init_modes,
                               pure=pure,
                               **kwargs)

    def get_cutoff_dim(self):
        return self.circuit.cutoff_dim

    def get_modes(self):
        # pylint: disable=protected-access
        return [i for i, j in enumerate(self._modemap._map) if j is not None]

    def prepare_vacuum_state(self, mode):
        with tf.name_scope('Prepare_vacuum'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_vacuum_state(remapped_mode)

    def prepare_coherent_state(self, alpha, mode):
        with tf.name_scope('Prepare_coherent'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_coherent_state(alpha, remapped_mode)

    def prepare_squeezed_state(self, r, phi, mode):
        with tf.name_scope('Prepare_squeezed'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_squeezed_state(r, phi, remapped_mode)

    def prepare_displaced_squeezed_state(self, alpha, r, phi, mode):
        with tf.name_scope('Prepare_displaced_squeezed'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_displaced_squeezed_state(
                alpha, r, phi, remapped_mode)

    def prepare_fock_state(self, n, mode):
        with tf.name_scope('Prepare_fock'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_fock_state(n, remapped_mode)

    def prepare_ket_state(self, state, modes):
        with tf.name_scope('Prepare_state'):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           True)

    def prepare_dm_state(self, state, modes):
        with tf.name_scope('Prepare_state'):
            self.circuit.prepare_multimode(state, self._remap_modes(modes),
                                           False)

    def prepare_thermal_state(self, nbar, mode):
        with tf.name_scope('Prepare_thermal'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.prepare_thermal_state(nbar, remapped_mode)

    def rotation(self, phi, mode):
        with tf.name_scope('Rotation'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.phase_shift(phi, remapped_mode)

    def displacement(self, alpha, mode):
        with tf.name_scope('Displacement'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.displacement(alpha, remapped_mode)

    def squeeze(self, z, mode):
        with tf.name_scope('Squeeze'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.squeeze(z, remapped_mode)

    def beamsplitter(self, t, r, mode1, mode2):
        with tf.name_scope('Beamsplitter'):
            if isinstance(t, complex):
                raise ValueError(
                    "Beamsplitter transmittivity t must be a float.")
            if isinstance(t, tf.Tensor):
                if t.dtype.is_complex:
                    raise ValueError(
                        "Beamsplitter transmittivity t must be a float.")
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.beamsplitter(t, r, remapped_modes[0],
                                      remapped_modes[1])

    def loss(self, T, mode):
        with tf.name_scope('Loss'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.loss(T, remapped_mode)

    def cubic_phase(self, gamma, mode):
        with tf.name_scope('Cubic_phase'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.cubic_phase(gamma, remapped_mode)

    def kerr_interaction(self, kappa, mode):
        with tf.name_scope('Kerr_interaction'):
            remapped_mode = self._remap_modes(mode)
            self.circuit.kerr_interaction(kappa, remapped_mode)

    def cross_kerr_interaction(self, kappa, mode1, mode2):
        with tf.name_scope('Cross-Kerr_interaction'):
            remapped_modes = self._remap_modes([mode1, mode2])
            self.circuit.cross_kerr_interaction(kappa, remapped_modes[0],
                                                remapped_modes[1])

    def state(self, modes=None, **kwargs):
        r"""Returns the state of the quantum simulation, restricted to the subsystems defined by `modes`.

        See :meth:`.BaseBackend.state`.

        Keyword Args:
            session (tf.Session): TensorFlow session
            feed_dict (Dict): Dictionary containing the desired numerical values for Tensors
                for numerically evaluating the state. Used with ``session``.

        Returns:
            FockStateTF: state description
        """
        with tf.name_scope('State'):
            s = self.circuit.state
            pure = self.circuit.state_is_pure
            num_modes = self.circuit.num_modes
            batched = self.circuit.batched

            # reduce rho down to specified subsystems
            if modes is None:
                # reduced state is full state
                reduced_state = s
                modes = list(range(num_modes))
            else:
                if isinstance(modes, int):
                    modes = [modes]
                if len(modes) != len(set(modes)):
                    raise ValueError(
                        "The specified modes cannot be duplicated.")
                if len(modes) > num_modes:
                    raise ValueError(
                        "The number of specified modes cannot be larger than the number of subsystems."
                    )

                if pure:
                    # convert to mixed state representation
                    reduced_state = mixed(s, batched)
                    pure = False
                else:
                    reduced_state = s

                    # trace our all modes not in modes
                    # todo: Doing this one by one is very inefficient. The partial trace function should be improved.
                for mode in sorted(
                    [m for m in range(num_modes) if m not in modes],
                        reverse=True):
                    reduced_state = partial_trace(reduced_state, mode, False,
                                                  batched)
                reduced_state_pure = False

            # unless the modes were requested in order, we need to swap indices around
            if modes != sorted(modes):
                mode_permutation = np.argsort(np.argsort(modes))
                reduced_state = reorder_modes(reduced_state, mode_permutation,
                                              reduced_state_pure, batched)

            evaluate_results, session, feed_dict, close_session = _check_for_eval(
                kwargs)
            if evaluate_results:
                s = session.run(reduced_state, feed_dict=feed_dict)
                if close_session:
                    session.close()
            else:
                s = reduced_state

            modenames = [
                "q[{}]".format(i) for i in np.array(self.get_modes())[modes]
            ]
            state_ = FockStateTF(s,
                                 len(modes),
                                 pure,
                                 self.circuit.cutoff_dim,
                                 graph=self._graph,
                                 batched=batched,
                                 mode_names=modenames,
                                 eval=evaluate_results)
        return state_

    def measure_fock(self, modes, shots=1, select=None, **kwargs):
        """Measure the given modes in the Fock basis.

        See :meth:`.BaseFock.measure_fock`.

        Keyword Args:
            session (tf.Session): TensorFlow session
            feed_dict (Dict): Dictionary containing the desired numerical values for Tensors
                for numerically evaluating the measurement results. Used with ``session``.

        Returns:
            tuple[int] or tuple[Tensor]: measurement outcomes
        """
        if shots != 1:
            raise NotImplementedError("TF backend currently does not support "
                                      "shots != 1 for Fock measurement")
        with tf.name_scope('Measure_fock'):
            remapped_modes = self._remap_modes(modes)
            meas = self.circuit.measure_fock(remapped_modes,
                                             select=select,
                                             **kwargs)
        return meas

    def measure_homodyne(self, phi, mode, shots=1, select=None, **kwargs):
        """Perform a homodyne measurement on the specified modes.

        See :meth:`.BaseBackend.measure_homodyne`.

        Keyword Args:
            session (tf.Session): TensorFlow session
            feed_dict (Dict): Dictionary containing the desired numerical values for Tensors
                for numerically evaluating the measurement results. Used with ``session``.
            num_bins (int): Number of equally spaced bins for the probability distribution function
                (pdf) simulating the homodyne measurement (default: 100000).
            max (float): The pdf is discretized onto the 1D grid [-max,max] (default: 10).

        Returns:
            float or tf.Tensor: measurement outcome
        """
        if shots != 1:
            raise NotImplementedError("TF backend currently does not support "
                                      "shots != 1 for homodyne measurement")
        with tf.name_scope('Measure_homodyne'):
            remapped_mode = self._remap_modes(mode)
            meas = self.circuit.measure_homodyne(phi, remapped_mode, select,
                                                 **kwargs)
        return meas

    def is_vacuum(self, tol=0.0, **kwargs):
        with tf.name_scope('Is_vacuum'):
            with self.circuit.graph.as_default():
                vac_elem = self.circuit.vacuum_element()
                if "eval" in kwargs and kwargs["eval"] is False:
                    v = vac_elem
                else:
                    sess = tf.Session()
                    v = sess.run(vac_elem)
                    sess.close()

            result = np.abs(v - 1) <= tol
        return result

    def del_mode(self, modes):
        with tf.name_scope('Del_mode'):
            remapped_modes = self._remap_modes(modes)
            if isinstance(remapped_modes, int):
                remapped_modes = [remapped_modes]
            self.circuit.del_mode(remapped_modes)
            self._modemap.delete(modes)

    def add_mode(self, n=1):
        with tf.name_scope('Add_mode'):
            self.circuit.add_mode(n)
            self._modemap.add(n)

    @property
    def graph(self):
        """
        Get the Tensorflow Graph object where the current quantum circuit is defined.

        Returns:
            tf.Graph: the circuit's graph
        """
        return self._graph