Пример #1
0
    def __init__(
        self,
        qubit_map: Dict['cirq.Qid', int],
        rsum2_cutoff: float,
        sum_prob_atol: float,
        grouping: Optional[Dict['cirq.Qid', int]] = None,
        initial_state: int = 0,
    ):
        """Creates and MPSState

        Args:
            qubit_map: A map from Qid to an integer that uniquely identifies it.
            rsum2_cutoff: We drop singular values so that the sum of the
                square of the dropped singular values divided by the sum of the
                square of all the singular values is less than rsum2_cutoff.
                This is related to the fidelity of the computation. If we have
                N 2D gates, then the estimated fidelity is
                (1 - rsum2_cutoff) ** N.
            sum_prob_atol: Because the computation is approximate, the sum of
                the probabilities is not 1.0. This parameter is the absolute
                deviation from 1.0 that is allowed.
            grouping: How to group qubits together, if None all are individual.
            initial_state: An integer representing the initial state.
        """
        self.qubit_map = qubit_map
        self.grouping = qubit_map if grouping is None else grouping
        if self.grouping.keys() != self.qubit_map.keys():
            raise ValueError('Grouping must cover exactly the qubits.')
        self.M = []
        for _ in range(max(self.grouping.values()) + 1):
            self.M.append(qtn.Tensor())

        # The order of the qubits matters, because the state |01> is different from |10>. Since
        # Quimb uses strings to name tensor indices, we want to be able to sort them too. If we are
        # working with, say, 123 qubits then we want qubit 3 to come before qubit 100, but then
        # we want write the string '003' which comes before '100' in lexicographic order. The code
        # below is just simple string formatting.
        max_num_digits = len('{}'.format(max(qubit_map.values())))
        self.format_i = 'i_{{:0{}}}'.format(max_num_digits)
        self.format_mu = 'mu_{}_{}'

        # TODO(tonybruguier): Instead of relying on sortable indices could you keep a parallel
        # mapping of e.g. qubit to string-index and do all "logic" on the qubits themselves and
        # only translate to string-indices when calling a quimb API.

        # TODO(tonybruguier): Refactor out so that the code below can also be used by
        # circuit_to_tensors in cirq.contrib.quimb.state_vector.

        for qubit in reversed(list(qubit_map.keys())):
            d = qubit.dimension
            x = np.zeros(d)
            x[initial_state % d] = 1.0

            i = qubit_map[qubit]
            n = self.grouping[qubit]
            self.M[n] @= qtn.Tensor(x, inds=(self.i_str(i), ))
            initial_state = initial_state // d
        self.rsum2_cutoff = rsum2_cutoff
        self.sum_prob_atol = sum_prob_atol
        self.num_svd_splits = 0
Пример #2
0
    def __init__(
        self,
        qubit_map: Dict['cirq.Qid', int],
        prng: np.random.RandomState,
        simulation_options: MPSOptions = MPSOptions(),
        grouping: Optional[Dict['cirq.Qid', int]] = None,
        initial_state: int = 0,
        axes: Iterable[int] = None,
        log_of_measurement_results: Dict[str, Any] = None,
    ):
        """Creates and MPSState

        Args:
            qubit_map: A map from Qid to an integer that uniquely identifies it.
            prng: A random number generator, used to simulate measurements.
            simulation_options: Numerical options for the simulation.
            grouping: How to group qubits together, if None all are individual.
            initial_state: An integer representing the initial state.
            axes: The indices of axes corresponding to the qubits that the
                operation is supposed to act upon.
            log_of_measurement_results: A mutable object that measurements are
                being recorded into.
        """
        super().__init__(prng, axes, log_of_measurement_results)
        self.qubit_map = qubit_map
        self.grouping = qubit_map if grouping is None else grouping
        if self.grouping.keys() != self.qubit_map.keys():
            raise ValueError('Grouping must cover exactly the qubits.')
        self.M = []
        for _ in range(max(self.grouping.values()) + 1):
            self.M.append(qtn.Tensor())

        # The order of the qubits matters, because the state |01> is different from |10>. Since
        # Quimb uses strings to name tensor indices, we want to be able to sort them too. If we are
        # working with, say, 123 qubits then we want qubit 3 to come before qubit 100, but then
        # we want write the string '003' which comes before '100' in lexicographic order. The code
        # below is just simple string formatting.
        max_num_digits = len(f'{max(qubit_map.values())}')
        self.format_i = f'i_{{:0{max_num_digits}}}'
        self.format_mu = 'mu_{}_{}'

        # TODO(tonybruguier): Instead of relying on sortable indices could you keep a parallel
        # mapping of e.g. qubit to string-index and do all "logic" on the qubits themselves and
        # only translate to string-indices when calling a quimb API.

        # TODO(tonybruguier): Refactor out so that the code below can also be used by
        # circuit_to_tensors in cirq.contrib.quimb.state_vector.

        for qubit in reversed(list(qubit_map.keys())):
            d = qubit.dimension
            x = np.zeros(d)
            x[initial_state % d] = 1.0

            i = qubit_map[qubit]
            n = self.grouping[qubit]
            self.M[n] @= qtn.Tensor(x, inds=(self.i_str(i), ))
            initial_state = initial_state // d
        self.simulation_options = simulation_options
        self.estimated_gate_error_list: List[float] = []
Пример #3
0
def to_quimb_tensor(g: BaseGraph) -> 'qtn.TensorNetwork':
    """Converts tensor network representing the given :func:`pyzx.graph.Graph`.
    Pretty printing: to_tensor(g).draw(color = ['V', 'H'])
    
    Args:
        g: graph to be converted."""

    if qu is None:
        raise ImportError("quimb must be installed to use this function.")

    # copying a graph guarantees consecutive indices, which are needed for the tensor net
    g = g.copy()

    # only Z spiders are handled below
    to_gh(g)

    tensors = []

    # Here we have phase tensors corresponding to Z-spiders with only one output and no input.
    for v in g.vertices():
        if g.type(v) == VertexType.Z and g.phase(v) != 0:
            tensors.append(
                qtn.Tensor(data=[1, np.exp(1j * np.pi * g.phase(v))],
                           inds=(f'{v}', ),
                           tags=("V", )))

    # Hadamard or Kronecker tensors, one for each edge of the diagram.
    for i, edge in enumerate(g.edges()):
        x, y = edge
        isHadamard = g.edge_type(edge) == EdgeType.HADAMARD
        t = qtn.Tensor(data=qu.hadamard()
                       if isHadamard else np.array([1, 0, 0, 1]).reshape(2, 2),
                       inds=(f'{x}', f'{y}'),
                       tags=("H", ) if isHadamard else ("N", ))
        tensors.append(t)

    # TODO: This is not taking care of all the stuff that can be in g.scalar
    # In particular, it doesn't check g.scalar.phasenodes
    # TODO: This will give the wrong tensor when g.scalar.is_zero == True.
    # Grab the float factor and exponent from the scalar
    scalar_float = np.exp(1j * np.pi * g.scalar.phase) * g.scalar.floatfactor
    for node in g.scalar.phasenodes:  # Each node is a Fraction
        scalar_float *= 1 + np.exp(1j * np.pi * node)
    scalar_exp = math.log10(math.sqrt(2)) * g.scalar.power2

    # If the TN is empty, create a single 0-tensor with scalar factor, otherwise
    # multiply the scalar into one of the tensors.
    if len(tensors) == 0:
        tensors.append(qtn.Tensor(data=scalar_float, inds=(), tags=("S", )))
    else:
        tensors[0].modify(data=tensors[0].data * scalar_float)

    network = qtn.TensorNetwork(tensors)

    # the exponent can be very large, so distribute it evenly through the TN
    network.exponent = scalar_exp
    network.distribute_exponent()
    return network
Пример #4
0
def circuit_to_tensors(
    circuit: cirq.Circuit,
    qubits: Optional[Sequence[cirq.Qid]] = None,
    initial_state: Union[int, None] = 0,
) -> Tuple[List[qtn.Tensor], Dict['cirq.Qid', int], None]:
    """Given a circuit, construct a tensor network representation.

    Indices are named "i{i}_q{x}" where i is a time index and x is a
    qubit index.

    Args:
        circuit: The circuit containing operations that implement the
            cirq.unitary() protocol.
        qubits: A list of qubits in the circuit.
        initial_state: Either `0` corresponding to the |0..0> state, in
            which case the tensor network will represent the final
            state vector; or `None` in which case the starting indices
            will be left open and the tensor network will represent the
            circuit unitary.
    Returns:
        tensors: A list of quimb Tensor objects
        qubit_frontier: A mapping from qubit to time index at the end of
            the circuit. This can be used to deduce the names of the free
            tensor indices.
        positions: Currently None. May be changed in the future to return
            a suitable mapping for tn.graph()'s `fix` argument. Currently,
            `fix=None` will draw the resulting tensor network using a spring
            layout.
    """
    if qubits is None:
        qubits = sorted(circuit.all_qubits())  # coverage: ignore

    qubit_frontier = {q: 0 for q in qubits}
    positions = None
    tensors: List[qtn.Tensor] = []

    if initial_state == 0:
        for q in qubits:
            tensors += [qtn.Tensor(data=quimb.up().squeeze(), inds=(f'i0_q{q}',), tags={'Q0'})]
    elif initial_state is None:
        # no input tensors, return a network representing the unitary
        pass
    else:
        raise ValueError("Right now, only |0> or `None` " "initial states are supported.")

    for moment in circuit.moments:
        for op in moment.operations:
            assert op.gate._has_unitary_()
            start_inds = [f'i{qubit_frontier[q]}_q{q}' for q in op.qubits]
            for q in op.qubits:
                qubit_frontier[q] += 1
            end_inds = [f'i{qubit_frontier[q]}_q{q}' for q in op.qubits]

            U = cirq.unitary(op).reshape((2,) * 2 * len(op.qubits))
            t = qtn.Tensor(data=U, inds=end_inds + start_inds, tags={f'Q{len(op.qubits)}'})
            tensors.append(t)

    return tensors, qubit_frontier, positions
Пример #5
0
    def create(
            cls,
            *,
            qid_shape: Tuple[int, ...],
            grouping: Dict[int, int],
            initial_state: int = 0,
            simulation_options: MPSOptions = MPSOptions(),
    ):
        """Creates an MPSQuantumState

        Args:
            qid_shape: Dimensions of the qubits represented.
            grouping: How to group qubits together, if None all are individual.
            initial_state: The initial computational basis state.
            simulation_options: Numerical options for the simulation.

        Raises:
            ValueError: If the grouping does not cover the qubits.
        """
        M = []
        for _ in range(max(grouping.values()) + 1):
            M.append(qtn.Tensor())

        # The order of the qubits matters, because the state |01> is different from |10>. Since
        # Quimb uses strings to name tensor indices, we want to be able to sort them too. If we are
        # working with, say, 123 qubits then we want qubit 3 to come before qubit 100, but then
        # we want write the string '003' which comes before '100' in lexicographic order. The code
        # below is just simple string formatting.
        max_num_digits = len(f'{max(grouping.values())}')
        format_i = f'i_{{:0{max_num_digits}}}'

        # TODO(tonybruguier): Instead of relying on sortable indices could you keep a parallel
        # mapping of e.g. qubit to string-index and do all "logic" on the qubits themselves and
        # only translate to string-indices when calling a quimb API.

        # TODO(tonybruguier): Refactor out so that the code below can also be used by
        # circuit_to_tensors in cirq.contrib.quimb.state_vector.

        for axis in reversed(range(len(qid_shape))):
            d = qid_shape[axis]
            x = np.zeros(d)
            x[initial_state % d] = 1.0

            n = grouping[axis]
            M[n] @= qtn.Tensor(x, inds=(format_i.format(axis), ))
            initial_state = initial_state // d
        return _MPSHandler(
            qid_shape=qid_shape,
            grouping=grouping,
            M=M,
            format_i=format_i,
            estimated_gate_error_list=[],
            simulation_options=simulation_options,
        )
Пример #6
0
 def test_id_tensor(self):
     g = Graph()
     x = g.add_vertex(VertexType.BOUNDARY)
     y = g.add_vertex(VertexType.BOUNDARY)
     g.add_edge(g.edge(x, y), edgetype = EdgeType.SIMPLE)
     
     tn = to_quimb_tensor(g)
     self.assertTrue((tn & qtn.Tensor(data = [0, 1], inds = ("0",)) 
                         & qtn.Tensor(data = [0, 1], inds = ("1",)))
                     .contract(output_inds = ()) == 1)
     self.assertTrue((tn & qtn.Tensor(data = [1, 0], inds = ("0",)) 
                         & qtn.Tensor(data = [1, 0], inds = ("1",)))
                     .contract(output_inds = ()) == 1)
Пример #7
0
 def test_hadamard_tensor(self):
     g = Graph()
     x = g.add_vertex(VertexType.BOUNDARY)
     y = g.add_vertex(VertexType.BOUNDARY)
     g.add_edge(g.edge(x, y), edgetype = EdgeType.HADAMARD)
     
     tn = to_quimb_tensor(g)
     self.assertTrue(abs((tn & qtn.Tensor(data = [1, 0], inds = ("0",)) 
                         & qtn.Tensor(data = [1 / np.sqrt(2), 1 / np.sqrt(2)], inds = ("1",)))
                     .contract(output_inds = ()) - 1) < 1e-9)
     self.assertTrue(abs((tn & qtn.Tensor(data = [0, 1], inds = ("0",)) 
                         & qtn.Tensor(data = [1 / np.sqrt(2), -1 / np.sqrt(2)], inds = ("1",)))
                     .contract(output_inds = ()) - 1) < 1e-9)
Пример #8
0
def tensor_expectation_value(circuit: cirq.Circuit,
                             pauli_string: cirq.PauliString,
                             max_ram_gb=16,
                             tol=1e-6) -> float:
    """Compute an expectation value for an operator and a circuit via tensor
    contraction.

    This will give up if it looks like the computation will take too much RAM.
    """
    circuit_sand = circuit_for_expectation_value(
        circuit, pauli_string / pauli_string.coefficient)
    qubits = sorted(circuit_sand.all_qubits())

    tensors, qubit_frontier, _ = circuit_to_tensors(circuit=circuit_sand,
                                                    qubits=qubits)
    end_bras = [
        qtn.Tensor(data=quimb.up().squeeze(),
                   inds=(f'i{qubit_frontier[q]}_q{q}', ),
                   tags={'Q0', 'bra0'}) for q in qubits
    ]
    tn = qtn.TensorNetwork(tensors + end_bras)
    tn.rank_simplify(inplace=True)
    path_info = tn.contract(get='path-info')
    ram_gb = path_info.largest_intermediate * 128 / 8 / 1024 / 1024 / 1024
    if ram_gb > max_ram_gb:
        raise MemoryError("We estimate that this contraction "
                          "will take too much RAM! {} GB".format(ram_gb))
    e_val = tn.contract(inplace=True)
    assert e_val.imag < tol
    assert pauli_string.coefficient.imag < tol
    return e_val.real * pauli_string.coefficient
Пример #9
0
def main_test():
    psi = beeky.QubitEncodeVector.rand(3, 3)
    X, Y, Z = (qu.pauli(i) for i in 'xyz')
    where = (3, 4, 9)  #which qubits to act on
    numsites = len(where)
    dp = 2  #phys ind dimension
    gate = X & X & X

    ## take over from here ##
    g_xx = qtn.tensor_1d.maybe_factor_gate_into_tensor(
        gate, dp, numsites, where)  #shape (2,2,2,2) or (2,2,2,2,2,2)

    site_inds = [psi.phys_ind_id.format(q) for q in where]
    bond_inds = [qtn.rand_uuid() for _ in range(numsites)]
    reindex_map = dict(zip(site_inds, bond_inds))

    TG = qtn.Tensor(g_xx,
                    inds=site_inds + bond_inds,
                    left_inds=bond_inds,
                    tags=['GATE'])

    original_ts = [psi[q] for q in where]
    bonds_along = [
        next(iter(qtn.bonds(t1, t2)))
        for t1, t2 in qu.utils.pairwise(original_ts)
    ]

    triangle_gate_absorb(TG=TG,
                         reindex_map=reindex_map,
                         vertex_tensors=(psi[where[0]], psi[where[1]]),
                         face_tensor=psi[where[2]],
                         phys_inds=site_inds)
Пример #10
0
def TNReducer(TN, sobits, nq, nr, shave):
    counter = 0
    for rep in range(0, nr):
        for b in range(0, nq - 1):
            if counter < shave:
                bit = sobits[rep * (nq - 1) + b]
                indb = ('sf{:d}r{:d}'.format(b, rep), )
                tagb = ('SF{:d}r{:d}BIT{:d}'.format(b, rep, bit))
                if bit == 0:
                    TN = TN & qtn.Tensor(np.array([1, 0]), indb, tagb)
                elif bit == 1:
                    TN = TN & qtn.Tensor(np.array([0, 1]), indb, tagb)
                else:
                    print('BAD')
            counter = counter + 1
    TNC = TN.contract(all, optimize='auto-hq')
    out = TNC.data
    return out
Пример #11
0
    def __init__(self,
                 d=2,
                 L_y=4,
                 L_x=10,
                 dist_type='uniform',
                 data_type='float64',
                 seed_0=10):
        self.L_x = L_x
        self.d = d
        self.L_y = L_y
        self.type = data_type
        self.dist = dist_type

        peps = qtn.PEPS.rand(Lx=L_x, Ly=L_y, bond_dim=D, phys_dim=d, seed=4)

        rotate_ten_list = []
        for j in range(L_y - 1):
            for i in range(L_x):
                A = qtn.Tensor(qu.rand(2,
                                       seed=seed_0 + i + j,
                                       dist='uniform',
                                       dtype=data_type).reshape(2),
                               inds={f"k{i},{j}"},
                               tags={})
                rotate_ten_list.append(A)

        R_l = qtn.TensorNetwork(rotate_ten_list)
        self.tn = peps & R_l
        for j in range(L_y - 1):
            for i in range(L_x):
                self.tn.contract_ind(f"k{i},{j}", optimize='auto-hq')

        #TN_l.graph(color=peps.site_tags, show_tags=True, figsize=(10, 10))

        for j in range(L_y):
            for i in range(L_x):
                t = list((self.tn[{f'I{i},{j}', f'ROW{i}', f'COL{j}'}].inds))
                for m in range(len(t)):
                    if t[m] == f'k{i},{j}':
                        t[m] = f'k{i}'
                self.tn[{f'I{i},{j}', f'ROW{i}', f'COL{j}'}].modify(inds=t)

#   for j in range(L_y):
#    for i in range(L_x):
#      dim_tuple=self.tn[{ f'I{i},{j}', f'ROW{i}', f'COL{j}' }].shape
#      lis_a=list(dim_tuple)
#      dim=1
#      for i_val in range(len(lis_a)):
#       dim*=lis_a[i_val]
#      rand_tn=qu.rand(dim, dist='uniform', seed=seed_0, dtype=data_type).reshape(*dim_tuple)
#      rand_tn=rand_tn*LA.norm(rand_tn)**(-1.0)
#      self.tn[{f'COL{j}', f'I{i},{j}', f'ROW{i}' }].modify(data=rand_tn)

        self.tn.balance_bonds_()
        self.tn.equalize_norms_(2.0)
Пример #12
0
    def test_xor_tensor(self):
        g = Graph()
        x = g.add_vertex(VertexType.BOUNDARY)
        y = g.add_vertex(VertexType.BOUNDARY)
        v = g.add_vertex(VertexType.Z)
        z = g.add_vertex(VertexType.BOUNDARY)

        g.add_edge(g.edge(x, v), edgetype = EdgeType.HADAMARD)
        g.add_edge(g.edge(y, v), edgetype = EdgeType.HADAMARD)
        g.add_edge(g.edge(v, z), edgetype = EdgeType.HADAMARD)
        tn = to_quimb_tensor(g)
        
        for x in range(2):
            for y in range(2):
                for z in range(2):
                    self.assertTrue(abs((tn &
                            qtn.Tensor(data = [1 - x, x], inds = ("0",)) &
                            qtn.Tensor(data = [1 - y, y], inds = ("1",)) &
                            qtn.Tensor(data = [1 - z, z], inds = ("3",))).contract(output_inds = ()) - 
                            ((x ^ y) == z) / np.sqrt(2)) < 1e-9)
Пример #13
0
    def perform_measurement(self,
                            qubits: Sequence['cirq.Qid'],
                            prng: np.random.RandomState,
                            collapse_state_vector=True) -> List[int]:
        """Performs a measurement over one or more qubits.

        Args:
            qubits: The sequence of qids to measure, in that order.
            prng: A random number generator, used to simulate measurements.
            collapse_state_vector: A Boolean specifying whether we should mutate
                the state after the measurement.

        Raises:
            ValueError: If the probabilities for the measurements differ too much from one for the
                tolerance specified in simulation options.
        """
        results: List[int] = []

        if collapse_state_vector:
            state = self
        else:
            state = self.copy()

        for qubit in qubits:
            n = state.qubit_map[qubit]

            # Trace out other qubits
            M = state.partial_trace(keep_qubits={qubit})
            probs = np.diag(M).real
            sum_probs = sum(probs)

            # Because the computation is approximate, the probabilities do not
            # necessarily add up to 1.0, and thus we re-normalize them.
            if abs(sum_probs - 1.0) > self.simulation_options.sum_prob_atol:
                raise ValueError(
                    f'Sum of probabilities exceeds tolerance: {sum_probs}')
            norm_probs = [x / sum_probs for x in probs]

            d = qubit.dimension
            result: int = int(prng.choice(d, p=norm_probs))

            collapser = np.zeros((d, d))
            collapser[result][result] = 1.0 / math.sqrt(probs[result])

            old_n = state.i_str(n)
            new_n = 'new_' + old_n

            collapser = qtn.Tensor(collapser, inds=(new_n, old_n))

            state.M[n] = (collapser @ state.M[n]).reindex({new_n: old_n})

            results.append(result)

        return results
Пример #14
0
 def test_phases_tensor(self):
     # This diagram represents a 1-input 1-output Z-spider of phase pi/2,
     # but written using two Z-spiders of phases pi/6 and pi/3 that are
     # connected by a simple edge.
     g = Graph()
     x = g.add_vertex(VertexType.BOUNDARY)
     v = g.add_vertex(VertexType.Z, phase = 1. / 6.)
     w = g.add_vertex(VertexType.Z, phase = 1. / 3.)
     y = g.add_vertex(VertexType.BOUNDARY)
     
     g.add_edge(g.edge(x, v), edgetype = EdgeType.SIMPLE)
     g.add_edge(g.edge(v, w), edgetype = EdgeType.SIMPLE)
     g.add_edge(g.edge(w, y), edgetype = EdgeType.SIMPLE)
     tn = to_quimb_tensor(g)
     
     self.assertTrue(abs((tn & qtn.Tensor(data = [1, 0], inds = ("0",)) 
                         & qtn.Tensor(data = [1, 0], inds = ("3",)))
                     .contract(output_inds = ()) - 1) < 1e-9)
     self.assertTrue(abs((tn & qtn.Tensor(data = [0, 1], inds = ("0",)) 
                         & qtn.Tensor(data = [0, 1j], inds = ("3",)))
                     .contract(output_inds = ()) + 1) < 1e-9)
Пример #15
0
    def absorb_three_body_gate(self,
                               G,
                               coos,
                               gate_tags=('GATE', ),
                               restore_dummies=True,
                               inplace=False,
                               **compress_opts):
        '''Converts the raw gate ``G`` into a tensor and passes it
        to ``self.absorb_three_body_tensor``.

        G: raw qarray
            The gate to apply
        coos: sequence of tuple[int]
            The 3 coos to act on, e.g. ((0,0),(0,2),(1,1))
        restore_dummies: bool, optional
            Whether to "restore" dummy identities
            and keep square lattice structure or 
            "triangles" in the lattice.
        '''
        gate_tags = qtn.tensor_2d.tags_to_oset(gate_tags)
        # assuming physical dimension = 2
        G = qtn.tensor_1d.maybe_factor_gate_into_tensor(G,
                                                        dp=2,
                                                        ng=3,
                                                        where=coos)
        # new physical indices "k{x},{y}"
        phys_inds = [self._site_ind_id.format(*c) for c in coos]
        # old physical indices joined to new gate
        bond_inds = [qtn.rand_uuid() for _ in range(3)]
        # replace physical inds with gate bonds
        reindex_map = dict(zip(phys_inds, bond_inds))

        TG = qtn.Tensor(G,
                        inds=phys_inds + bond_inds,
                        left_inds=bond_inds,
                        tags=gate_tags)

        return self.absorb_three_body_tensor(TG,
                                             coos,
                                             reindex_map,
                                             phys_inds,
                                             gate_tags,
                                             restore_dummies=restore_dummies,
                                             inplace=inplace,
                                             **compress_opts)
Пример #16
0
    def _measure(self,
                 axes: Sequence[int],
                 prng: np.random.RandomState,
                 collapse_state_vector=True) -> List[int]:
        results: List[int] = []

        if collapse_state_vector:
            state = self
        else:
            state = self.copy()

        for axis in axes:
            # Trace out other qubits
            M = state.partial_trace(keep_axes={axis})
            probs = np.diag(M).real
            sum_probs = sum(probs)

            # Because the computation is approximate, the probabilities do not
            # necessarily add up to 1.0, and thus we re-normalize them.
            if abs(sum_probs - 1.0) > self._simulation_options.sum_prob_atol:
                raise ValueError(
                    f'Sum of probabilities exceeds tolerance: {sum_probs}')
            norm_probs = [x / sum_probs for x in probs]

            d = self._qid_shape[axis]
            result: int = int(prng.choice(d, p=norm_probs))

            collapser = np.zeros((d, d))
            collapser[result][result] = 1.0 / math.sqrt(probs[result])

            old_n = state.i_str(axis)
            new_n = 'new_' + old_n

            collapser = qtn.Tensor(collapser, inds=(new_n, old_n))

            state._M[axis] = (collapser @ state._M[axis]).reindex(
                {new_n: old_n})

            results.append(result)

        return results
Пример #17
0
    def _get_node(t, gate):

        # Get matrix
        U = np.reshape(gate.matrix().astype(complex_type),
                       [2] * (2 * len(gate.qubits)))

        # Get indexes
        inds = [f'{leaves_prefix}_{qubits_map[q]}_{t}' for q in gate.qubits] + [
            f'{leaves_prefix}_{qubits_map[q]}_{last_tag[q]}'
            for q in gate.qubits
        ]

        # Update last_tag
        for q in gate.qubits:
            last_tag[q] = t

        # Return node
        return tn.Tensor(
            U.astype(complex_type),
            inds=inds,
            tags=[f'{leaves_prefix}_{qubits_map[q]}' for q in gate.qubits] +
            [f'gate-idx_{t}'])
Пример #18
0
def tensor_expectation_value(circuit: cirq.Circuit,
                             pauli_string: cirq.PauliString,
                             max_ram_gb=16,
                             tol=1e-6) -> float:
    """Compute an expectation value for an operator and a circuit via tensor
    contraction.

    This will give up if it looks like the computation will take too much RAM.
    """
    circuit_sand = circuit_for_expectation_value(
        circuit, pauli_string / pauli_string.coefficient)
    qubits = sorted(circuit_sand.all_qubits())

    tensors, qubit_frontier, _ = circuit_to_tensors(circuit=circuit_sand,
                                                    qubits=qubits)
    end_bras = [
        qtn.Tensor(data=quimb.up().squeeze(),
                   inds=(f'i{qubit_frontier[q]}_q{q}', ),
                   tags={'Q0', 'bra0'}) for q in qubits
    ]
    tn = qtn.TensorNetwork(tensors + end_bras)
    if QUIMB_VERSION[0] < (1, 3):
        # coverage: ignore
        warnings.warn(f'quimb version {QUIMB_VERSION[1]} detected. Please use '
                      f'quimb>=1.3 for optimal performance in '
                      '`tensor_expectation_value`. '
                      'See https://github.com/quantumlib/Cirq/issues/3263')
    else:
        tn.rank_simplify(inplace=True)
    path_info = tn.contract(get='path-info')
    ram_gb = path_info.largest_intermediate * 128 / 8 / 1024 / 1024 / 1024
    if ram_gb > max_ram_gb:
        raise MemoryError(
            f"We estimate that this contraction will take too much RAM! {ram_gb} GB"
        )
    e_val = tn.contract(inplace=True)
    assert e_val.imag < tol
    assert pauli_string.coefficient.imag < tol
    return e_val.real * pauli_string.coefficient
Пример #19
0
    def gate(
            self,
            G,
            coos,
            contract='auto_split',
            tags=('GATE', ),
            inplace=False,
            info=None,
            **compress_opts,
    ):
        '''
        contract: {False, 'reduce_split', 'triangle_absorb', 'reduce_split_lr'}
                -False: leave gate uncontracted at sites
            [For 2-body ops:]
                -reduce_split: Absorb dummy, apply gate with `qtn.tensor_2d.reduce_split`, 
                    then reinsert dummy. (NOTE: this one seems very slow)
                -reduce_split_lr: leave dummy as-is, treat gate as a LR interaction.
                    The final bonds are much smaller this way!
            [For 3-body ops:]
                -triangle_absorb: use `three_body_op.triangle_gate_absorb` to 
                    apply the 3-body gate. Assumes `coos` is ordered like 
                    ~ (vertex, vertex, face)!
            [For any n-body:]
                -auto_split: will automatically choose depending on n.
                    n=1 -> contract = True
                    n=2 -> contract = 'reduce_split_lr'
                    n=3 -> contract = 'triangle_absorb'
        '''

        check_opt("contract", contract,
                  (False, True, 'reduce_split', 'triangle_absorb',
                   'reduce_split_lr', 'auto_split'))

        psi = self if inplace else self.copy()

        if is_lone_coo(coos):
            coos = (coos, )
        else:
            coos = tuple(coos)

        numsites = len(coos)  #num qubits acted on

        if contract == 'auto_split':
            contract = {
                1: True,
                2: 'reduce_split_lr',
                3: 'triangle_absorb'
            }[numsites]

        #inds like 'k{x},{y}'
        site_inds = [self._site_ind_id.format(*c) for c in coos]
        # physical dimension, d=2 for qubits
        dp = self.ind_size(site_inds[0])
        gate_tags = tags_to_oset(tags)

        G = qtn.tensor_1d.maybe_factor_gate_into_tensor(G, dp, numsites, coos)

        #old physical indices joined to new gate
        bond_inds = [qtn.rand_uuid() for _ in range(numsites)]
        reindex_map = dict(zip(site_inds, bond_inds))
        TG = qtn.Tensor(G,
                        inds=site_inds + bond_inds,
                        left_inds=bond_inds,
                        tags=gate_tags)

        if contract is False:
            #attach gates without contracting any bonds
            #
            #     'qA' 'qB'
            #       │   │      <- site_inds
            #       GGGGG
            #       │╱  │╱     <- bond_inds
            #     ──●───●──
            #      ╱   ╱
            #
            psi.reindex_(reindex_map)
            psi |= TG
            return psi

        elif (contract is True) or (numsites == 1):
            #
            #       │╱  │╱
            #     ──GGGGG──
            #      ╱   ╱
            #
            psi.reindex_(reindex_map)

            # get the sites that used to have the physical indices
            site_tids = psi._get_tids_from_inds(bond_inds, which='any')
            # pop the sites, contract, then re-add
            pts = [psi._pop_tensor(tid) for tid in site_tids]
            psi |= qtn.tensor_contract(*pts, TG)
            return psi

        elif contract == 'triangle_absorb' and numsites == 3:
            # absorbs 3-body gate while preserving lattice structure.
            psi.absorb_three_body_tensor_(TG=TG,
                                          coos=coos,
                                          reindex_map=reindex_map,
                                          phys_inds=site_inds,
                                          gate_tags=gate_tags,
                                          **compress_opts)
            return psi

        # NOTE: this one seems very inefficient for
        # "next-nearest" neighbor interactions.
        elif contract == 'reduce_split' and numsites == 2:
            # First absorb identity into a site, then
            # restore after gate has been applied.
            #
            # 1. Absorb identity step:
            #
            #       │     │    Absorb     │     │
            #       GGGGGGG     ident.    GGGGGGG
            #       │╱  ╱ │╱     ==>      │╱    │╱╱
            #     ──●──I──●──           ──●─────●─
            #    a ╱  ╱  ╱ b             ╱     ╱╱
            #
            # 2. Gate 'reduce_split' step:
            #
            #       │   │             │ │
            #       GGGGG             GGG               │ │
            #       │╱  │╱   ==>     ╱│ │  ╱   ==>     ╱│ │  ╱          │╱  │╱
            #     ──●───●──       ──>─●─●─<──       ──>─GGG─<──  ==>  ──G┄┄┄G──
            #      ╱   ╱           ╱     ╱           ╱     ╱           ╱   ╱
            #    <QR> <LQ>                            <SVD>
            #
            # 3. Reinsert identity:
            #
            #       │╱    │╱╱           │╱  ╱ │╱
            #     ──G┄┄┄┄┄G──   ==>   ──G┄┄I┄┄G──
            #      ╱     ╱╱            ╱  ╱  ╱
            #

            (x1, y1), (x2, y2) = coos
            mid_coo = (int((x1 + x2) / 2), int((y1 + y2) / 2))
            dummy_coo_tag = psi.site_tag_id.format(*mid_coo)

            # keep track of dummy identity's tags and neighbors
            prev_dummy_info = {
                'tags':
                psi[dummy_coo_tag].tags,
                'neighbor_tags':
                tuple(t.tags for t in psi.select_neighbors(dummy_coo_tag))
            }

            which_bond = int(
                psi.bond_size(coos[0], mid_coo) >= psi.bond_size(
                    coos[1], mid_coo))

            if which_bond == 0:  # (vertex_0 ── identity) bond is larger
                vertex_tag = psi.site_tag_id.format(*coos[0])

            else:  # (vertex_1 -- identity) bond larger
                vertex_tag = psi.site_tag_id.format(*coos[1])

            tids = psi._get_tids_from_tags(tags=(vertex_tag, dummy_coo_tag),
                                           which='any')

            # pop and reattach the (vertex & identity) tensor
            pts = [psi._pop_tensor(tid) for tid in tids]
            new_vertex = qtn.tensor_contract(*pts)

            # new_vertex.drop_tags(prev_dummy_info['tags'] - )
            new_vertex.drop_tags(pts[1].tags - pts[0].tags)

            psi |= new_vertex  # reattach [vertex & identity]

            # insert 2-body gate!
            qtn.tensor_2d.gate_string_reduce_split_(
                TG=TG,
                where=coos,
                string=coos,
                original_ts=[psi[c] for c in coos],
                bonds_along=(psi.bond(*coos), ),
                reindex_map=reindex_map,
                site_ix=site_inds,
                info=info,
                **compress_opts)

            # now restore the dummy identity between vertices
            vtensor = psi[
                coos[which_bond]]  # the vertex we absorbed dummy into

            ts_to_connect = set(
                psi[tags]
                for tags in prev_dummy_info['neighbor_tags']) - set([vtensor])

            for T2 in ts_to_connect:  # restore previous dummy bonds
                psi |= qubit_networks.insert_identity_between_tensors(
                    T1=vtensor, T2=T2, add_tags='TEMP')

            # contract new dummies into a single identity
            psi ^= 'TEMP'
            for t in prev_dummy_info['tags']:
                psi['TEMP'].add_tag(t)  # restore previous dummy tags
            psi.drop_tags('TEMP')

            return psi.fuse_multibonds_()

        elif contract == 'reduce_split_lr' and numsites == 2:
            # There will be a 'dummy' identity tensor between the
            # sites, so the 2-body operator will look "long-range"
            #
            #       │     │
            #       GGGGGGG
            #       │╱  ╱ │╱              │╱  ╱ │╱
            #     ──●──I──●──    ==>    ──G┄┄I┄┄G──
            #      ╱  ╱  ╱               ╱  ╱  ╱
            #
            (x1, y1), (x2, y2) = coos
            mid_coo = (int((x1 + x2) / 2), int((y1 + y2) / 2))

            dummy_coo_tag = psi.site_tag_id.format(*mid_coo)
            string = (coos[0], mid_coo, coos[1])

            original_ts = [psi[coo] for coo in string]
            bonds_along = [
                next(iter(qtn.bonds(t1, t2)))
                for t1, t2 in qu.utils.pairwise(original_ts)
            ]

            qtn.tensor_2d.gate_string_reduce_split_(TG=TG,
                                                    where=coos,
                                                    string=string,
                                                    original_ts=original_ts,
                                                    bonds_along=bonds_along,
                                                    reindex_map=reindex_map,
                                                    site_ix=site_inds,
                                                    info=info,
                                                    **compress_opts)

            return psi
Пример #20
0
def CLTNRepHWQC(nq, nr, p):
    px = 0.004
    kappa2 = 10**7
    alpha2 = 8
    pi1 = [1 - 10 * p, 10 * p]
    pi2 = [1 - 0.299 * (p**0.5), 0.299 * (p**0.5)]
    p3 = p * kappa2 * alpha2 * (350 * (10**(-9)) + (10 / (kappa2 * alpha2)))
    pi3 = [1 - p3, p3]
    pz1 = 0.845 * (p**0.5)
    pz2 = 0.133 * (p**0.5)
    pz1z2 = 0.133 * (p**0.5)
    pm = [1 - px, px]
    pp = [1 - 15 * p / 2, 15 * p / 2]
    perf = [1, 0]
    ptq = [1 - pz1 - pz2 - pz1z2, pz1, pz2, pz1z2]
    #print(psq)
    #print(ptq)
    #print(pm)
    #print(pp)
    gh0 = format(0, 'b').zfill(nq + 1)
    gh1 = format(2**(nq + 1) - 1, 'b').zfill(nq + 1)
    ghz = (qtn.MPS_computational_state(gh0) + qtn.MPS_computational_state(gh1))
    ghz = ghz.contract(all)
    ghzd = ghz.data
    for r in range(0, nr):
        if r == 0:
            tagsD = ('D{:d}T0'.format(nq - 1))
            indsD = ('d{:d}g0'.format(nq - 1), )
            TN = qtn.Tensor(DTermWait(pi1), indsD, tagsD)
            for dq in range(0, nq - 1):
                tagsD = ('D{:d}T0'.format(dq))
                indsD = ('d{:d}g0'.format(dq), )
                TN = TN & qtn.Tensor(DTermWait(pi1), indsD, tagsD)
        else:
            for dq in range(0, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r - 1),
                         'd{:d}g{:d}'.format(dq, 4 * r))
                TN = TN & qtn.Tensor(IdTensor(perf), indsD, tagsD)
        for aq in range(0, nq - 1):
            tagsA = ('A{:d}T{:d}'.format(aq, 4 * r))
            indsA = ('sf{:d}r{:d}'.format(aq,
                                          r), 'a{:d}g{:d}'.format(aq, 4 * r))
            TN = TN & qtn.Tensor(APrepTensor1(pp), indsA, tagsA)
        #first round of CNOTs
        for aq in range(0, nq - 1):
            tagsD = ('D{:d}A{:d}T{:d}'.format(aq, aq, 4 * r + 1))
            indsD = ('d{:d}g{:d}'.format(aq, 4 * r), 'a{:d}g{:d}'.format(
                aq, 4 * r), 'd{:d}g{:d}'.format(aq, 4 * r + 1),
                     'a{:d}g{:d}'.format(aq, 4 * r + 1))
            TN = TN & qtn.Tensor(RepCNOTTensorHW(ptq), indsD, tagsD)
        tagsD = ('D{:d}T{:d}'.format(nq - 1, 4 * r + 1))
        indsD = ('d{:d}g{:d}'.format(nq - 1, 4 * r),
                 'd{:d}g{:d}'.format(nq - 1, 4 * r + 1))
        TN = TN & qtn.Tensor(IdTensor(pi2), indsD, tagsD)
        #second round of CNOTs
        for aq in range(0, nq - 1):
            tagsD = ('D{:d}A{:d}T{:d}'.format(aq + 1, aq, 4 * r + 2))
            indsD = ('d{:d}g{:d}'.format(aq + 1, 4 * r + 1),
                     'a{:d}g{:d}'.format(aq, 4 * r + 1), 'd{:d}g{:d}'.format(
                         aq + 1,
                         4 * r + 2), 'a{:d}g{:d}'.format(aq, 4 * r + 2))
            TN = TN & qtn.Tensor(RepCNOTTensorHW(ptq), indsD, tagsD)
        tagsD = ('D{:d}T{:d}'.format(0, 4 * r + 2))
        indsD = ('d{:d}g{:d}'.format(0, 4 * r + 1),
                 'd{:d}g{:d}'.format(0, 4 * r + 2))
        TN = TN & qtn.Tensor(IdTensor(pi2), indsD, tagsD)
        #measurement round/wait locations
        #first do the terminal data wait locations
        if r == nr - 1:
            for dq in range(0, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}g{:d}'.format(dq, 4 * r + 3))
                TN = TN & qtn.Tensor(IdTensor(pm), indsD, tagsD)
            for dq in range(0, (nq - 1) // 2):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 4))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 3),
                         'd{:d}lz'.format(dq), 'd{:d}p'.format(dq))
                TN = TN & qtn.Tensor(DatTermTensor(pi3), indsD, tagsD)
            tagsD = ('D{:d}T{:d}'.format((nq - 1) // 2, 4 * r + 4))
            indsD = ('d{:d}g{:d}'.format(
                (nq - 1) // 2, 4 * r + 3), 'd{:d}lz'.format((nq - 1) // 2))
            TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            for dq in range(((nq - 1) // 2) + 1, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 4))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 3),
                         'd{:d}lz'.format(dq), 'd{:d}p'.format(dq - 1))
                TN = TN & qtn.Tensor(DatTermTensor(pi3), indsD, tagsD)
        else:
            for dq in range(0, (nq - 1) // 2):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}g{:d}'.format(dq, 4 * r + 3))
                TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            tagsD = ('D{:d}T{:d}'.format((nq - 1) // 2, 4 * r + 3))
            indsD = ('d{:d}g{:d}'.format(
                (nq - 1) // 2, 4 * r + 2), 'd{:d}g{:d}'.format((nq - 1) // 2,
                                                               4 * r + 3))
            TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            for dq in range(((nq - 1) // 2) + 1, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}g{:d}'.format(dq, 4 * r + 3))
                TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
        #now do the ancilla measurement failure locations
        for aq in range(0, nq - 1):
            tagsA = ('A{:d}T{:d}'.format(aq, 4 * r + 3))
            indsA = ('a{:d}g{:d}'.format(aq, 4 * r + 2), )
            TN = TN & qtn.Tensor(AMeasTensor1(pm), indsA, tagsA)
    #finally, put in the logical control tensor
    tagsL = ('LZ')
    indsL = ('lz', )
    for dq in range(0, nq):
        indsL = indsL + ('d{:d}lz'.format(dq), )
    TN = TN & qtn.Tensor(ghzd, indsL, tagsL)
    return TN
Пример #21
0
def CLTNRepHWTraceMeas(nq, nr, p):
    #First initialize all HW noise parameters. Note that p=k1/k2

    pxm = 0.004  #X readout failure probability
    kappa2 = 10**7
    alpha2 = 8  #photon number
    p1 = 10 * p  #fail prob for data qubit wait location during very first ancilla initialization
    p2 = 0.299 * (
        p**0.5
    )  #fail prob for data qubit wait location during execution of a CNOT on other qubits
    p3 = p * kappa2 * alpha2 * (
        350 * (10**(-9)) + (10 / (kappa2 * alpha2))
    )  #fail prob for data qubit wait location during readout of ancilla
    pz1 = 0.845 * (
        p**0.5
    )  #prob of z1 otimes I failure after CNOT (qubit 1 is control, 2 is target)
    pz2 = 0.133 * (
        p**0.5
    )  #prob of I otimes z2 failure after CNOT (qubit 1 is control, 2 is target)
    pz1z2 = 0.133 * (p**0.5)  #prob of z1 otimes z2 failure after CNOT

    #create probability distributions to be passed to local tensor constructors which model different locations
    pi1 = [1 - p1, p1]  #data qubit wait during first ancilla init
    pi2 = [1 - p2, p2]  #data qubit wait during CNOT
    pi3 = [1 - p3, p3]  #data qubit wait during ancilla measurement and re-init
    pm = [1 - pxm, pxm]  #X measurement location
    pp = [1 - 15 * p / 2, 15 * p / 2]  #ancilla initialization
    perf = [1, 0]  #perfect wait location
    ptq = [1 - pz1 - pz2 - pz1z2, pz1, pz2, pz1z2]  #CNOT location

    gh0 = format(0, 'b').zfill(nq + 1)
    gh1 = format(2**(nq + 1) - 1, 'b').zfill(nq + 1)
    ghz = (qtn.MPS_computational_state(gh0) + qtn.MPS_computational_state(gh1))
    ghz = ghz.contract(all)
    ghzd = ghz.data
    for r in range(0, nr):
        if r == 0:
            tagsD = ('D{:d}T0'.format(nq - 1))
            indsD = ('d{:d}g0'.format(nq - 1), )
            TN = qtn.Tensor(DTermWait(pi1), indsD, tagsD)
            for dq in range(0, nq - 1):
                tagsD = ('D{:d}T0'.format(dq))
                indsD = ('d{:d}g0'.format(dq), )
                TN = TN & qtn.Tensor(DTermWait(pi1), indsD, tagsD)
        else:
            for dq in range(0, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r - 1),
                         'd{:d}g{:d}'.format(dq, 4 * r))
                TN = TN & qtn.Tensor(IdTensor(perf), indsD, tagsD)
        for aq in range(0, nq - 1):
            tagsA = ('A{:d}T{:d}'.format(aq, 4 * r))
            indsA = ('sf{:d}r{:d}'.format(aq,
                                          r), 'a{:d}g{:d}'.format(aq, 4 * r))
            TN = TN & qtn.Tensor(APrepTensor1(pp), indsA, tagsA)
            tagsST = ('A{:d}T{:d}TR'.format(aq, 4 * r))
            indsST = ('sf{:d}r{:d}'.format(aq, r), )
            TN = TN & qtn.Tensor(np.ones(2), indsST, tagsST)
        #first round of CNOTs
        for aq in range(0, nq - 1):
            tagsD = ('D{:d}A{:d}T{:d}'.format(aq, aq, 4 * r + 1))
            indsD = ('d{:d}g{:d}'.format(aq, 4 * r), 'a{:d}g{:d}'.format(
                aq, 4 * r), 'd{:d}g{:d}'.format(aq, 4 * r + 1),
                     'a{:d}g{:d}'.format(aq, 4 * r + 1))
            TN = TN & qtn.Tensor(RepCNOTTensorHW(ptq), indsD, tagsD)
        tagsD = ('D{:d}T{:d}'.format(nq - 1, 4 * r + 1))
        indsD = ('d{:d}g{:d}'.format(nq - 1, 4 * r),
                 'd{:d}g{:d}'.format(nq - 1, 4 * r + 1))
        TN = TN & qtn.Tensor(IdTensor(pi2), indsD, tagsD)
        #second round of CNOTs
        for aq in range(0, nq - 1):
            tagsD = ('D{:d}A{:d}T{:d}'.format(aq + 1, aq, 4 * r + 2))
            indsD = ('d{:d}g{:d}'.format(aq + 1, 4 * r + 1),
                     'a{:d}g{:d}'.format(aq, 4 * r + 1), 'd{:d}g{:d}'.format(
                         aq + 1,
                         4 * r + 2), 'a{:d}g{:d}'.format(aq, 4 * r + 2))
            TN = TN & qtn.Tensor(RepCNOTTensorHW(ptq), indsD, tagsD)
        tagsD = ('D{:d}T{:d}'.format(0, 4 * r + 2))
        indsD = ('d{:d}g{:d}'.format(0, 4 * r + 1),
                 'd{:d}g{:d}'.format(0, 4 * r + 2))
        TN = TN & qtn.Tensor(IdTensor(pi2), indsD, tagsD)
        #measurement round/wait locations
        #first do the terminal data wait locations
        if r == nr - 1:
            for dq in range(0, (nq - 1) // 2):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}lz'.format(dq), 'd{:d}p'.format(dq))
                TN = TN & qtn.Tensor(DatTermTensor(pi3), indsD, tagsD)
            tagsD = ('D{:d}T{:d}'.format((nq - 1) // 2, 4 * r + 3))
            indsD = ('d{:d}g{:d}'.format(
                (nq - 1) // 2, 4 * r + 2), 'd{:d}lz'.format((nq - 1) // 2))
            TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            for dq in range(((nq - 1) // 2) + 1, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}lz'.format(dq), 'd{:d}p'.format(dq - 1))
                TN = TN & qtn.Tensor(DatTermTensor(pi3), indsD, tagsD)
        else:
            for dq in range(0, (nq - 1) // 2):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}g{:d}'.format(dq, 4 * r + 3))
                TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            tagsD = ('D{:d}T{:d}'.format((nq - 1) // 2, 4 * r + 3))
            indsD = ('d{:d}g{:d}'.format(
                (nq - 1) // 2, 4 * r + 2), 'd{:d}g{:d}'.format((nq - 1) // 2,
                                                               4 * r + 3))
            TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
            for dq in range(((nq - 1) // 2) + 1, nq):
                tagsD = ('D{:d}T{:d}'.format(dq, 4 * r + 3))
                indsD = ('d{:d}g{:d}'.format(dq, 4 * r + 2),
                         'd{:d}g{:d}'.format(dq, 4 * r + 3))
                TN = TN & qtn.Tensor(IdTensor(pi3), indsD, tagsD)
        #now do the ancilla measurement failure locations
        for aq in range(0, nq - 1):
            tagsA = ('A{:d}T{:d}'.format(aq, 4 * r + 3))
            indsA = ('a{:d}g{:d}'.format(aq, 4 * r + 2), )
            TN = TN & qtn.Tensor(AMeasTensor1(pm), indsA, tagsA)
    #finally, put in the logical control tensor
    tagsL = ('LZ')
    indsL = ('lz', )
    for dq in range(0, nq):
        indsL = indsL + ('d{:d}lz'.format(dq), )
    TN = TN & qtn.Tensor(ghzd, indsL, tagsL)
    return TN
Пример #22
0
    def apply_op(self, op: 'cirq.Operation', prng: np.random.RandomState):
        """Applies a unitary operation, mutating the object to represent the new state.

        op:
            The operation that mutates the object. Note that currently, only 1-
            and 2- qubit operations are currently supported.
        """

        old_inds = tuple(
            [self.i_str(self.qubit_map[qubit]) for qubit in op.qubits])
        new_inds = tuple(['new_' + old_ind for old_ind in old_inds])

        if protocols.has_unitary(op):
            U = protocols.unitary(op)
        else:
            mixtures = protocols.mixture(op)
            mixture_idx = int(
                prng.choice(len(mixtures),
                            p=[mixture[0] for mixture in mixtures]))
            U = mixtures[mixture_idx][1]
        U = qtn.Tensor(U.reshape([qubit.dimension for qubit in op.qubits] * 2),
                       inds=(new_inds + old_inds))

        # TODO(tonybruguier): Explore using the Quimb's tensor network natively.

        if len(op.qubits) == 1:
            n = self.grouping[op.qubits[0]]

            self.M[n] = (U @ self.M[n]).reindex({new_inds[0]: old_inds[0]})
        elif len(op.qubits) == 2:
            n, p = [self.grouping[qubit] for qubit in op.qubits]

            if n == p:
                self.M[n] = (U @ self.M[n]).reindex({
                    new_inds[0]: old_inds[0],
                    new_inds[1]: old_inds[1]
                })
            else:
                # This is the index on which we do the contraction. We need to add it iff it's
                # the first time that we do the joining for that specific pair.
                mu_ind = self.mu_str(n, p)
                if mu_ind not in self.M[n].inds:
                    self.M[n].new_ind(mu_ind)
                if mu_ind not in self.M[p].inds:
                    self.M[p].new_ind(mu_ind)

                T = U @ self.M[n] @ self.M[p]

                left_inds = tuple(set(T.inds)
                                  & set(self.M[n].inds)) + (new_inds[0], )
                X, Y = T.split(
                    left_inds,
                    method=self.simulation_options.method,
                    max_bond=self.simulation_options.max_bond,
                    cutoff=self.simulation_options.cutoff,
                    cutoff_mode=self.simulation_options.cutoff_mode,
                    get='tensors',
                    absorb='both',
                    bond_ind=mu_ind,
                )

                # Equations (13), (14), and (15):
                # TODO(tonybruguier): When Quimb 2.0.0 is released, the split()
                # function should have a 'renorm' that, when set to None, will
                # allow to compute e_n exactly as:
                # np.sum(abs((X @ Y).data) ** 2).real / np.sum(abs(T) ** 2).real
                #
                # The renormalization would then have to be done manually.
                #
                # However, for now, e_n are just the estimated value.
                e_n = self.simulation_options.cutoff
                self.estimated_gate_error_list.append(e_n)

                self.M[n] = X.reindex({new_inds[0]: old_inds[0]})
                self.M[p] = Y.reindex({new_inds[1]: old_inds[1]})
        else:
            # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could
            # involve HOSVDs:
            # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition
            #
            # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more
            # about HOSVDs.
            raise ValueError('Can only handle 1 and 2 qubit operations')
Пример #23
0
def _simulate_tn_mpi(circuit: Circuit, initial_state: any, final_state: any,
                     optimize: any, backend: any, complex_type: any,
                     tensor_only: bool, verbose: bool, **kwargs):
    import quimb.tensor as tn
    import cotengra as ctg

    # Get MPI
    _mpi_comm = MPI.COMM_WORLD
    _mpi_size = _mpi_comm.Get_size()
    _mpi_rank = _mpi_comm.Get_rank()

    # Set default parameters
    kwargs.setdefault('compress', 2)
    kwargs.setdefault('simplify_tn', 'RC')
    kwargs.setdefault('max_iterations', 1)
    kwargs.setdefault('methods', ['kahypar', 'greedy'])
    kwargs.setdefault('max_time', 120)
    kwargs.setdefault('max_repeats', 16)
    kwargs.setdefault('minimize', 'combo')
    kwargs.setdefault('target_largest_intermediate', 0)
    kwargs.setdefault('max_largest_intermediate', 2**26)
    kwargs.setdefault('temperatures', [1.0, 0.1, 0.01])
    kwargs.setdefault('parallel', None)
    kwargs.setdefault('cotengra', {})
    kwargs.setdefault('max_n_slices', None)
    kwargs.setdefault('return_info', False)

    # Get random leaves_prefix
    leaves_prefix = ''.join(
        np.random.choice(list('abcdefghijklmnopqrstuvwxyz'), size=20))

    # Initialize info
    _sim_info = {}

    # Alias for tn
    if optimize == 'tn':
        optimize = 'cotengra'

    if isinstance(circuit, Circuit):

        if not kwargs['parallel']:
            kwargs['parallel'] = 1
        else:
            # If number of threads not provided, just use half of the number of available cpus
            if isinstance(kwargs['parallel'],
                          bool) and kwargs['parallel'] == True:
                kwargs['parallel'] = cpu_count() // 2

        if optimize is not None and kwargs['parallel'] and kwargs[
                'max_iterations'] == 1:
            warn("Parallelization for MPI works for multiple iterations only. "
                 "For a better performance, use: 'max_iterations' > 1")

        # Get number of qubits
        qubits = circuit.all_qubits()
        n_qubits = len(qubits)

        # If initial/final state is None, set to all .'s
        initial_state = '.' * n_qubits if initial_state is None else initial_state
        final_state = '.' * n_qubits if final_state is None else final_state

        # Initial and final states must be valid strings
        for state, sname in [(initial_state, 'initial_state'),
                             (final_state, 'final_state')]:
            # Get alphabet
            from string import ascii_letters

            # Check if string
            if not isinstance(state, str):
                raise ValueError(f"'{sname}' must be a valid string.")

            # Deprecated error
            if any(x in 'xX' for x in state):
                from warnings import warn

                # Define new DeprecationWarning (to always print the warning
                # signal)
                class DeprecationWarning(Warning):
                    pass

                # Warn the user that '.' is used to represent open qubits
                warn(
                    "Since '0.6.3', letters in the alphabet are used to "
                    "trace selected qubits (including 'x' and 'X'). "
                    "Instead, '.' is used to represent an open qubit.",
                    DeprecationWarning)

            # Check only valid symbols are present
            if set(state).difference('01+-.' + ascii_letters):
                raise ValueError(f"'{sname}' contains invalid symbols.")

            # Check number of qubits
            if len(state) != n_qubits:
                raise ValueError(f"'{sname}' has the wrong number of qubits "
                                 f"(expected {n_qubits}, got {len(state)})")

        # Check memory
        if 2**(initial_state.count('.') +
               final_state.count('.')) > kwargs['max_largest_intermediate']:
            raise MemoryError("Memory for the given number of open qubits "
                              "exceeds the 'max_largest_intermediate'.")

        # Compress circuit
        if kwargs['compress']:
            if verbose:
                print(
                    f"Compress circuit (max_n_qubits={kwargs['compress']}): ",
                    end='',
                    file=stderr)
                _time = time()

            circuit = utils.compress(
                circuit,
                kwargs['compress']['max_n_qubits'] if isinstance(
                    kwargs['compress'], dict) else kwargs['compress'],
                verbose=verbose,
                **({
                    k: v
                    for k, v in kwargs['compress'].items()
                    if k != 'max_n_qubits'
                } if isinstance(kwargs['compress'], dict) else {}))

            circuit = Circuit(
                utils.to_matrix_gate(c, complex_type=complex_type)
                for c in circuit)
            if verbose:
                print(f"Done! ({time()-_time:1.2f}s)", file=stderr)

        # Get tensor network representation of circuit
        tensor, tn_qubits_map = utils.to_tn(circuit,
                                            return_qubits_map=True,
                                            leaves_prefix=leaves_prefix)

        # Define basic MPS
        _mps = {
            '0': np.array([1, 0]),
            '1': np.array([0, 1]),
            '+': np.array([1, 1]) / np.sqrt(2),
            '-': np.array([1, -1]) / np.sqrt(2)
        }

        # Attach initial/final state
        for state, ext in [(initial_state, 'i'), (final_state, 'f')]:
            for s, q in ((s, q) for s, q in zip(state, qubits) if s in _mps):
                inds = [f'{leaves_prefix}_{tn_qubits_map[q]}_{ext}']
                tensor &= tn.Tensor(_mps[s], inds=inds, tags=inds)

        # For each unique letter, apply trace
        for x in set(initial_state + final_state).difference(''.join(_mps) +
                                                             '.'):
            # Get indexes
            inds = [
                f'{leaves_prefix}_{tn_qubits_map[q]}_i'
                for s, q in zip(initial_state, qubits) if s == x
            ]
            inds += [
                f'{leaves_prefix}_{tn_qubits_map[q]}_f'
                for s, q in zip(final_state, qubits) if s == x
            ]

            # Apply trace
            tensor &= tn.Tensor(np.reshape([1] + [0] * (2**len(inds) - 2) +
                                           [1], (2, ) * len(inds)),
                                inds=inds)

        # Simplify if requested
        if kwargs['simplify_tn']:
            tensor.full_simplify_(kwargs['simplify_tn']).astype_(complex_type)
        else:
            # Otherwise, just convert to the given complex_type
            tensor.astype_(complex_type)

        # Get contraction from heuristic
        if optimize == 'cotengra' and kwargs['max_iterations'] > 0:

            # Set cotengra parameters
            def cotengra_params():
                # Get HyperOptimizer
                q = ctg.HyperOptimizer(methods=kwargs['methods'],
                                       max_time=kwargs['max_time'],
                                       max_repeats=kwargs['max_repeats'],
                                       minimize=kwargs['minimize'],
                                       progbar=False,
                                       parallel=False,
                                       **kwargs['cotengra'])

                # For some optlib, HyperOptimizer._retrieve_params is not
                # pickeable. Let's fix the problem by hand.
                q._retrieve_params = __FunctionWrap(q._retrieve_params)

                # Return HyperOptimizer
                return q

            # Get target size
            tli = kwargs['target_largest_intermediate']

            with Pool(kwargs['parallel']) as pool:

                # Sumbit jobs
                _opts = [
                    cotengra_params() for _ in range(kwargs['max_iterations'])
                ]
                _map = [
                    pool.apply_async(tensor.contract, (all, ),
                                     dict(optimize=_opt, get='path-info'))
                    for _opt in _opts
                ]

                with tqdm(total=len(_map),
                          disable=not verbose,
                          desc='Collecting contractions') as pbar:

                    _old_completed = 0
                    while 1:

                        # Count number of completed
                        _completed = 0
                        for _w in _map:
                            _completed += _w.ready()
                            if _w.ready() and not _w.successful():
                                _w.get()

                        # Update pbar
                        pbar.update(_completed - _old_completed)
                        _old_completed = _completed

                        if _completed == len(_map):
                            break

                        # Wait
                        sleep(1)

                # Collect results
                _infos = [_w.get() for _w in _map]

            if kwargs['minimize'] == 'size':
                opt, info = sort(
                    zip(_opts, _infos),
                    key=lambda w:
                    (w[1].largest_intermediate, w[0].best['flops']))[0]
            else:
                opt, info = sort(
                    zip(_opts, _infos),
                    key=lambda w:
                    (w[0].best['flops'], w[1].largest_intermediate))[0]

        if optimize == 'cotengra':

            # Gather best contractions
            _cost = _mpi_comm.gather(
                (info.largest_intermediate, info.opt_cost, _mpi_rank), root=0)
            if _mpi_rank == 0:
                if kwargs['minimize'] == 'size':
                    _best_rank = sort(_cost, key=lambda x: (x[0], x[1]))[0][-1]
                else:
                    _best_rank = sort(_cost, key=lambda x: (x[1], x[0]))[0][-1]
            else:
                _best_rank = None
            _best_rank = _mpi_comm.bcast(_best_rank, root=0)

            if hasattr(opt, '_pool'):
                del (opt._pool)

            # Distribute opt/info
            tensor, info, opt = _mpi_comm.bcast((tensor, info, opt),
                                                root=_best_rank)

        # Just return tensor if required
        if tensor_only:
            if optimize == 'cotengra' and kwargs['max_iterations'] > 0:
                return tensor, (info, opt)
            else:
                return tensor

    else:

        # Set tensor
        tensor = circuit

        if len(optimize) == 2 and isinstance(
                optimize[0], PathInfo) and isinstance(
                    optimize[1], ctg.hyper.HyperOptimizer):

            # Get info and opt from optimize
            info, opt = optimize

            # Set optimization
            optimize = 'cotengra'

        else:

            # Get tensor and path
            tensor = circuit

    # Print some info
    if verbose and _mpi_rank == 0:
        print(
            f'Largest Intermediate: 2^{np.log2(float(info.largest_intermediate)):1.2f}',
            file=stderr)
        print(
            f'Max Largest Intermediate: 2^{np.log2(float(kwargs["max_largest_intermediate"])):1.2f}',
            file=stderr)
        print(f'Flops: 2^{np.log2(float(info.opt_cost)):1.2f}', file=stderr)

    if optimize == 'cotengra':

        if _mpi_rank == 0:

            # Get indexes
            _inds = tensor.outer_inds()

            # Get input indexes and output indexes
            _i_inds = sort([x for x in _inds if x[-2:] == '_i'],
                           key=lambda x: int(x.split('_')[1]))
            _f_inds = sort([x for x in _inds if x[-2:] == '_f'],
                           key=lambda x: int(x.split('_')[1]))

            # Get order
            _inds = [_inds.index(x) for x in _i_inds + _f_inds]

            # Get slice finder
            sf = ctg.SliceFinder(
                info,
                target_size=kwargs['max_largest_intermediate'],
                allow_outer=False)

            # Find slices
            with tqdm(kwargs['temperatures'], disable=not verbose,
                      leave=False) as pbar:
                for _temp in pbar:
                    pbar.set_description(f'Find slices (T={_temp})')
                    ix_sl, cost_sl = sf.search(temperature=_temp)

            # Get slice contractor
            sc = sf.SlicedContractor([t.data for t in tensor])

            # Make sure that no open qubits are sliced
            assert (not {
                ix: i
                for i, ix in enumerate(sc.output) if ix in sc.sliced
            })

            # Print some infos
            if verbose:
                print(
                    f'Number of slices: 2^{np.log2(float(cost_sl.nslices)):1.2f}',
                    file=stderr)
                print(
                    f'Flops+Cuts: 2^{np.log2(float(cost_sl.total_flops)):1.2f}',
                    file=stderr)

            # Update infos
            _sim_info.update({
                'flops': info.opt_cost,
                'largest_intermediate': info.largest_intermediate,
                'n_slices': cost_sl.nslices,
                'total_flops': cost_sl.total_flops
            })

            # Get slices
            slices = list(range(cost_sl.nslices + 1)) + [None] * (
                _mpi_size -
                cost_sl.nslices) if cost_sl.nslices < _mpi_size else [
                    cost_sl.nslices / _mpi_size * i for i in range(_mpi_size)
                ] + [cost_sl.nslices]
            if not np.alltrue(
                [int(x) == x
                 for x in slices if x is not None]) or not np.alltrue([
                     slices[i] < slices[i + 1] for i in range(_mpi_size)
                     if slices[i] is not None and slices[i + 1] is not None
                 ]):
                raise RuntimeError('Something went wrong')

            # Convert all to integers
            slices = [int(x) if x is not None else None for x in slices]

        else:

            sc = slices = None

        # Distribute slicer and slices
        sc, slices = _mpi_comm.bcast((sc, slices), root=0)

        _n_slices = max(x for x in slices if x)
        if kwargs['max_n_slices'] and _n_slices > kwargs['max_n_slices']:
            raise RuntimeError(
                f'Too many slices ({_n_slices} > {kwargs["max_n_slices"]})')

        # Contract slices
        _tensor = None
        if slices[_mpi_rank] is not None and slices[_mpi_rank + 1] is not None:
            for i in tqdm(range(slices[_mpi_rank], slices[_mpi_rank + 1]),
                          desc='Contracting slices',
                          disable=not verbose,
                          leave=False):
                if _tensor is None:
                    _tensor = np.copy(sc.contract_slice(i, backend=backend))
                else:
                    _tensor += sc.contract_slice(i, backend=backend)

        # Gather tensors
        if _mpi_rank != 0:
            _mpi_comm.send(_tensor, dest=0, tag=11)
        elif _mpi_rank == 0:
            for i in tqdm(range(1, _mpi_size),
                          desc='Collecting tensors',
                          disable=not verbose):
                _p_tensor = _mpi_comm.recv(source=i, tag=11)
                if _p_tensor is not None:
                    _tensor += _p_tensor

        if _mpi_rank == 0:

            # Create map
            _map = ''.join([get_symbol(x) for x in range(len(_inds))])
            _map += '->'
            _map += ''.join([get_symbol(x) for x in _inds])

            # Reorder tensor
            tensor = contract(_map, _tensor)

            # Deprecated
            ## Reshape tensor
            #if _inds:
            #    if _i_inds and _f_inds:
            #        tensor = np.reshape(tensor,
            #                            (2**len(_i_inds), 2**len(_f_inds)))
            #    else:
            #        tensor = np.reshape(tensor,
            #                            (2**max(len(_i_inds), len(_f_inds)),))

        else:

            tensor = None

    else:

        if _mpi_rank == 0:

            # Contract tensor
            tensor = tensor.contract(optimize=optimize, backend=backend)

            if hasattr(tensor, 'inds'):

                # Get input indexes and output indexes
                _i_inds = sort([x for x in tensor.inds if x[-2:] == '_i'],
                               key=lambda x: int(x.split('_')[1]))
                _f_inds = sort([x for x in tensor.inds if x[-2:] == '_f'],
                               key=lambda x: int(x.split('_')[1]))

                # Transpose tensor
                tensor.transpose(*(_i_inds + _f_inds), inplace=True)

                # Deprecated
                ## Reshape tensor
                #if _i_inds and _f_inds:
                #    tensor = np.reshape(tensor,
                #                        (2**len(_i_inds), 2**len(_f_inds)))
                #else:
                #    tensor = np.reshape(tensor,
                #                        (2**max(len(_i_inds), len(_f_inds)),))

        else:

            tensor = None

    if kwargs['return_info']:
        return tensor, _sim_info
    else:
        return tensor
                                     tags={'KET'
                                           })  #Build a random starting tensor
    ame_trial = qtn.TensorNetwork([
        normalize_state(ket)
    ])  # Take this tensor to be its own tensor network
else:  # Or choose to split it up into a network of smaller tensors
    data_tensors = []
    for i in range(phys_inds):
        data_tensors.append(
            np.random.rand(phys_dim, virt_dim,
                           virt_dim))  # Make random ndarrays for each tensor
    kets = []
    # Make tensors naming the physical and virtual indices so they match up
    kets.append(
        qtn.Tensor(data_tensors[0],
                   inds=(f'k{0}', f'v{phys_inds-1}', f'v{0}'),
                   tags={'KET'}))
    for i in range(1, phys_inds):
        kets.append(
            qtn.Tensor(data_tensors[i],
                       inds=(f'k{i}', f'v{i-1}', f'v{i}'),
                       tags={'KET'}))
    # Combine the tensors in to a tensor network with the & sign
    ame_trial = kets[0]
    for i in range(phys_inds - 1):
        ame_trial &= kets[i + 1]
    # If i split it up then normalize the full network as a ket
    ame_trial = normalize_state(ame_trial)

#list of all partitions
half_partitions = list(combinations(range(phys_inds), int(phys_inds / 2)))
Пример #25
0
def _simulate_tn(circuit: any, initial_state: any, final_state: any,
                 optimize: any, backend: any, complex_type: any,
                 tensor_only: bool, verbose: bool, **kwargs):
    import quimb.tensor as tn
    import cotengra as ctg

    # Get random leaves_prefix
    leaves_prefix = ''.join(
        np.random.choice(list('abcdefghijklmnopqrstuvwxyz'), size=20))

    # Initialize info
    _sim_info = {}

    # Alias for tn
    if optimize == 'tn':
        optimize = 'cotengra'

    if isinstance(circuit, Circuit):

        # Get number of qubits
        qubits = circuit.all_qubits()
        n_qubits = len(qubits)

        # If initial/final state is None, set to all .'s
        initial_state = '.' * n_qubits if initial_state is None else initial_state
        final_state = '.' * n_qubits if final_state is None else final_state

        # Initial and final states must be valid strings
        for state, sname in [(initial_state, 'initial_state'),
                             (final_state, 'final_state')]:
            # Get alphabet
            from string import ascii_letters

            # Check if string
            if not isinstance(state, str):
                raise ValueError(f"'{sname}' must be a valid string.")

            # Deprecated error
            if any(x in 'xX' for x in state):
                from hybridq.utils import DeprecationWarning
                from warnings import warn

                # Warn the user that '.' is used to represent open qubits
                warn(
                    "Since '0.6.3', letters in the alphabet are used to "
                    "trace selected qubits (including 'x' and 'X'). "
                    "Instead, '.' is used to represent an open qubit.",
                    DeprecationWarning)

            # Check only valid symbols are present
            if set(state).difference('01+-.' + ascii_letters):
                raise ValueError(f"'{sname}' contains invalid symbols.")

            # Check number of qubits
            if len(state) != n_qubits:
                raise ValueError(f"'{sname}' has the wrong number of qubits "
                                 f"(expected {n_qubits}, got {len(state)})")

        # Check memory
        if 2**(initial_state.count('.') +
               final_state.count('.')) > kwargs['max_largest_intermediate']:
            raise MemoryError("Memory for the given number of open qubits "
                              "exceeds the 'max_largest_intermediate'.")

        # Compress circuit
        if kwargs['compress']:
            if verbose:
                print(
                    f"Compress circuit (max_n_qubits={kwargs['compress']}): ",
                    end='',
                    file=stderr)
                _time = time()

            circuit = utils.compress(
                circuit,
                kwargs['compress']['max_n_qubits'] if isinstance(
                    kwargs['compress'], dict) else kwargs['compress'],
                verbose=verbose,
                **({
                    k: v
                    for k, v in kwargs['compress'].items()
                    if k != 'max_n_qubits'
                } if isinstance(kwargs['compress'], dict) else {}))

            circuit = Circuit(
                utils.to_matrix_gate(c, complex_type=complex_type)
                for c in circuit)
            if verbose:
                print(f"Done! ({time()-_time:1.2f}s)", file=stderr)

        # Get tensor network representation of circuit
        tensor, tn_qubits_map = utils.to_tn(circuit,
                                            return_qubits_map=True,
                                            leaves_prefix=leaves_prefix)

        # Define basic MPS
        _mps = {
            '0': np.array([1, 0]),
            '1': np.array([0, 1]),
            '+': np.array([1, 1]) / np.sqrt(2),
            '-': np.array([1, -1]) / np.sqrt(2)
        }

        # Attach initial/final state
        for state, ext in [(initial_state, 'i'), (final_state, 'f')]:
            for s, q in ((s, q) for s, q in zip(state, qubits) if s in _mps):
                inds = [f'{leaves_prefix}_{tn_qubits_map[q]}_{ext}']
                tensor &= tn.Tensor(_mps[s], inds=inds, tags=inds)

        # For each unique letter, apply trace
        for x in set(initial_state + final_state).difference(''.join(_mps) +
                                                             '.'):
            # Get indexes
            inds = [
                f'{leaves_prefix}_{tn_qubits_map[q]}_i'
                for s, q in zip(initial_state, qubits) if s == x
            ]
            inds += [
                f'{leaves_prefix}_{tn_qubits_map[q]}_f'
                for s, q in zip(final_state, qubits) if s == x
            ]

            # Apply trace
            tensor &= tn.Tensor(np.reshape([1] + [0] * (2**len(inds) - 2) +
                                           [1], (2, ) * len(inds)),
                                inds=inds)

        # Simplify if requested
        if kwargs['simplify_tn']:
            tensor.full_simplify_(kwargs['simplify_tn']).astype_(complex_type)
        else:
            # Otherwise, just convert to the given complex_type
            tensor.astype_(complex_type)

        # Get contraction from heuristic
        if optimize == 'cotengra' and kwargs['max_iterations'] > 0:

            # Create local client if MPI has been detected (not compatible with Dask at the moment)
            if _mpi_env and kwargs['parallel']:

                from distributed import Client, LocalCluster
                _client = Client(LocalCluster(processes=False))

            else:

                _client = None

            # Set cotengra parameters
            cotengra_params = lambda: ctg.HyperOptimizer(
                methods=kwargs['methods'],
                max_time=kwargs['max_time'],
                max_repeats=kwargs['max_repeats'],
                minimize=kwargs['minimize'],
                progbar=verbose,
                parallel=kwargs['parallel'],
                **kwargs['cotengra'])

            # Get optimized path
            opt = cotengra_params()
            info = tensor.contract(all, optimize=opt, get='path-info')

            # Get target size
            tli = kwargs['target_largest_intermediate']

            # Repeat for the requested number of iterations
            for _ in range(1, kwargs['max_iterations']):

                # Break if largest intermediate is equal or smaller than target
                if info.largest_intermediate <= tli:
                    break

                # Otherwise, restart
                _opt = cotengra_params()
                _info = tensor.contract(all, optimize=_opt, get='path-info')

                # Store the best
                if kwargs['minimize'] == 'size':

                    if _info.largest_intermediate < info.largest_intermediate or (
                            _info.largest_intermediate
                            == info.largest_intermediate
                            and _opt.best['flops'] < opt.best['flops']):
                        info = _info
                        opt = _opt

                else:

                    if _opt.best['flops'] < opt.best['flops'] or (
                            _opt.best['flops'] == opt.best['flops']
                            and _info.largest_intermediate <
                            info.largest_intermediate):
                        info = _info
                        opt = _opt

            # Close client if exists
            if _client:

                _client.shutdown()
                _client.close()

        # Just return tensor if required
        if tensor_only:
            if optimize == 'cotengra' and kwargs['max_iterations'] > 0:
                return tensor, (info, opt)
            else:
                return tensor

    else:

        # Set tensor
        tensor = circuit

        if len(optimize) == 2 and isinstance(
                optimize[0], PathInfo) and isinstance(
                    optimize[1], ctg.hyper.HyperOptimizer):

            # Get info and opt from optimize
            info, opt = optimize

            # Set optimization
            optimize = 'cotengra'

        else:

            # Get tensor and path
            tensor = circuit

    # Print some info
    if verbose:
        print(
            f'Largest Intermediate: 2^{np.log2(float(info.largest_intermediate)):1.2f}',
            file=stderr)
        print(
            f'Max Largest Intermediate: 2^{np.log2(float(kwargs["max_largest_intermediate"])):1.2f}',
            file=stderr)
        print(f'Flops: 2^{np.log2(float(info.opt_cost)):1.2f}', file=stderr)

    if optimize == 'cotengra':

        # Get indexes
        _inds = tensor.outer_inds()

        # Get input indexes and output indexes
        _i_inds = sort([x for x in _inds if x[-2:] == '_i'],
                       key=lambda x: int(x.split('_')[1]))
        _f_inds = sort([x for x in _inds if x[-2:] == '_f'],
                       key=lambda x: int(x.split('_')[1]))

        # Get order
        _inds = [_inds.index(x) for x in _i_inds + _f_inds]

        # Get slice finder
        sf = ctg.SliceFinder(info,
                             target_size=kwargs['max_largest_intermediate'])

        # Find slices
        with tqdm(kwargs['temperatures'], disable=not verbose,
                  leave=False) as pbar:
            for _temp in pbar:
                pbar.set_description(f'Find slices (T={_temp})')
                ix_sl, cost_sl = sf.search(temperature=_temp)

        # Get slice contractor
        sc = sf.SlicedContractor([t.data for t in tensor])

        # Update infos
        _sim_info.update({
            'flops': info.opt_cost,
            'largest_intermediate': info.largest_intermediate,
            'n_slices': cost_sl.nslices,
            'total_flops': cost_sl.total_flops
        })

        # Print some infos
        if verbose:
            print(
                f'Number of slices: 2^{np.log2(float(cost_sl.nslices)):1.2f}',
                file=stderr)
            print(f'Flops+Cuts: 2^{np.log2(float(cost_sl.total_flops)):1.2f}',
                  file=stderr)

        if kwargs['max_n_slices'] and sc.nslices > kwargs['max_n_slices']:
            raise RuntimeError(
                f'Too many slices ({sc.nslices} > {kwargs["max_n_slices"]})')

        # Contract tensor
        _li = np.log2(float(info.largest_intermediate))
        _mli = np.log2(float(kwargs["max_largest_intermediate"]))
        _tensor = sc.gather_slices((sc.contract_slice(
            i, backend=backend
        ) for i in tqdm(
            range(sc.nslices),
            desc=f'Contracting tensor (li=2^{_li:1.0f}, mli=2^{_mli:1.1f})',
            leave=False)))

        # Create map
        _map = ''.join([get_symbol(x) for x in range(len(_inds))])
        _map += '->'
        _map += ''.join([get_symbol(x) for x in _inds])

        # Reorder tensor
        tensor = contract(_map, _tensor)

        # Deprecated
        ## Reshape tensor
        #if _inds:
        #    if _i_inds and _f_inds:
        #        tensor = np.reshape(tensor, (2**len(_i_inds), 2**len(_f_inds)))
        #    else:
        #        tensor = np.reshape(tensor,
        #                            (2**max(len(_i_inds), len(_f_inds)),))

    else:

        # Contract tensor
        tensor = tensor.contract(optimize=optimize, backend=backend)

        if hasattr(tensor, 'inds'):

            # Get input indexes and output indexes
            _i_inds = sort([x for x in tensor.inds if x[-2:] == '_i'],
                           key=lambda x: int(x.split('_')[1]))
            _f_inds = sort([x for x in tensor.inds if x[-2:] == '_f'],
                           key=lambda x: int(x.split('_')[1]))

            # Transpose tensor
            tensor.transpose(*(_i_inds + _f_inds), inplace=True)

            # Deprecated
            ## Reshape tensor
            #if _i_inds and _f_inds:
            #    tensor = np.reshape(tensor, (2**len(_i_inds), 2**len(_f_inds)))
            #else:
            #    tensor = np.reshape(tensor,
            #                        (2**max(len(_i_inds), len(_f_inds)),))

    if kwargs['return_info']:
        return tensor, _sim_info
    else:
        return tensor
Пример #26
0
    def apply_unitary(self, op: 'cirq.Operation'):
        """Applies a unitary operation, mutating the object to represent the new state.

        op:
            The operation that mutates the object. Note that currently, only 1-
            and 2- qubit operations are currently supported.
        """

        U = protocols.unitary(op).reshape(
            [qubit.dimension for qubit in op.qubits] * 2)

        # TODO(tonybruguier): Explore using the Quimb's tensor network natively.

        if len(op.qubits) == 1:
            n = self.qubit_map[op.qubits[0]]

            old_n = self.i_str(n)
            new_n = 'new_' + old_n

            U = qtn.Tensor(U, inds=(new_n, old_n))
            self.M[n] = (U @ self.M[n]).reindex({new_n: old_n})
        elif len(op.qubits) == 2:
            self.num_svd_splits += 1

            n, p = [self.qubit_map[qubit] for qubit in op.qubits]

            old_n = self.i_str(n)
            old_p = self.i_str(p)
            new_n = 'new_' + old_n
            new_p = 'new_' + old_p

            U = qtn.Tensor(U, inds=(new_n, new_p, old_n, old_p))

            # This is the index on which we do the contraction. We need to add it iff it's the first
            # time that we do the joining for that specific pair.
            mu_ind = self.mu_str(n, p)
            if mu_ind not in self.M[n].inds:
                self.M[n].new_ind(mu_ind)
            if mu_ind not in self.M[p].inds:
                self.M[p].new_ind(mu_ind)

            T = U @ self.M[n] @ self.M[p]

            left_inds = tuple(set(T.inds) & set(self.M[n].inds)) + (new_n, )
            X, Y = T.split(
                left_inds,
                cutoff=self.rsum2_cutoff,
                cutoff_mode='rsum2',
                get='tensors',
                absorb='both',
                bond_ind=mu_ind,
            )

            self.M[n] = X.reindex({new_n: old_n})
            self.M[p] = Y.reindex({new_p: old_p})
        else:
            # NOTE(tonybruguier): There could be a way to handle higher orders. I think this could
            # involve HOSVDs:
            # https://en.wikipedia.org/wiki/Higher-order_singular_value_decomposition
            #
            # TODO(tonybruguier): Evaluate whether it's even useful to implement and learn more
            # about HOSVDs.
            raise ValueError('Can only handle 1 and 2 qubit operations')
Пример #27
0


list_ids=[]
list_alphabet=list(string.ascii_lowercase)
for i in range(N_lay):
 list_ids.append(f"__ind_{list_alphabet[i]}{{}}__")

list_tn=align_TN_1D(*list_mpo, ind_ids={*list_ids}, inplace=False)


TN_l=qtn.TensorNetwork(list_tn)
  
rotate_ten_list=[]
for i in range(L):
 rotate_ten_list.append(qtn.Tensor(qu.rand(D*d).reshape(D, d), inds=(f'b{i}',f'k{i}'), tags={"R"}))

R_l=qtn.TensorNetwork(rotate_ten_list)

TN_l=TN_l & R_l


for i in range(L):
 TN_l.contract_ind(f'b{i}', optimize='auto-hq')

list_tags=[]
for i in range(N_lay):
  list_tags.append(f"G{i}")


#print (TN_l.graph(color=list_tags,show_inds=all, show_tags=False, iterations=4800, k=None, fix=None, figsize=(30, 30),node_size=200) )
Пример #28
0
def circuit_to_density_matrix_tensors(
    circuit: cirq.Circuit,
    qubits: Optional[Sequence[cirq.Qid]] = None
) -> Tuple[List[qtn.Tensor], Dict['cirq.Qid', int], Dict[Tuple[str, str],
                                                         Tuple[float, float]]]:
    """Given a circuit with mixtures or channels, construct a tensor network
    representation of the density matrix.

    This assumes you start in the |0..0><0..0| state. Indices are named
    "nf{i}_q{x}" and "nb{i}_q{x}" where i is a time index and x is a
    qubit index. nf- and nb- refer to the "forwards" and "backwards"
    copies of the circuit. Kraus indices are named "k{j}" where j is an
    independent "kraus" internal index which you probably never need to access.

    Args:
        circuit: The circuit containing operations that support the
            cirq.unitary() or cirq.kraus() protocols.
        qubits: The qubits in the circuit. The `positions` return argument
            will position qubits according to their index in this list.

    Returns:
        tensors: A list of Quimb Tensor objects
        qubit_frontier: A mapping from qubit to time index at the end of
            the circuit. This can be used to deduce the names of the free
            tensor indices.
        positions: A positions dictionary suitable for passing to tn.graph()'s
            `fix` argument to draw the resulting tensor network similar to a
            quantum circuit.

    Raises:
        ValueError: If an op is encountered that cannot be converted.
    """
    if qubits is None:
        # coverage: ignore
        qubits = sorted(circuit.all_qubits())
    qubits = tuple(qubits)

    qubit_frontier: Dict[cirq.Qid, int] = {q: 0 for q in qubits}
    kraus_frontier = 0
    positions: Dict[Tuple[str, str], Tuple[float, float]] = {}
    tensors: List[qtn.Tensor] = []

    x_scale = 2
    y_scale = 3
    x_nudge = 0.3
    n_qubits = len(qubits)
    yb_offset = (n_qubits + 0.5) * y_scale

    def _positions(_mi, _these_qubits):
        return _add_to_positions(
            positions,
            _mi,
            _these_qubits,
            all_qubits=qubits,
            x_scale=x_scale,
            y_scale=y_scale,
            x_nudge=x_nudge,
            yb_offset=yb_offset,
        )

    # Initialize forwards and backwards qubits into the 0 state, i.e. prepare
    # rho_0 = |0><0|.
    for q in qubits:
        tensors += [
            qtn.Tensor(data=quimb.up().squeeze(),
                       inds=(f'nf0_q{q}', ),
                       tags={'Q0', 'i0f', _qpos_tag(q)}),
            qtn.Tensor(data=quimb.up().squeeze(),
                       inds=(f'nb0_q{q}', ),
                       tags={'Q0', 'i0b', _qpos_tag(q)}),
        ]
        _positions(0, q)

    for mi, moment in enumerate(circuit.moments):
        for op in moment.operations:
            start_inds_f = [f'nf{qubit_frontier[q]}_q{q}' for q in op.qubits]
            start_inds_b = [f'nb{qubit_frontier[q]}_q{q}' for q in op.qubits]
            for q in op.qubits:
                qubit_frontier[q] += 1
            end_inds_f = [f'nf{qubit_frontier[q]}_q{q}' for q in op.qubits]
            end_inds_b = [f'nb{qubit_frontier[q]}_q{q}' for q in op.qubits]

            if cirq.has_unitary(op):
                U = cirq.unitary(op).reshape(
                    (2, ) * 2 * len(op.qubits)).astype(np.complex128)
                tensors.append(
                    qtn.Tensor(
                        data=U,
                        inds=end_inds_f + start_inds_f,
                        tags={
                            f'Q{len(op.qubits)}', f'i{mi + 1}f',
                            _qpos_tag(op.qubits)
                        },
                    ))
                tensors.append(
                    qtn.Tensor(
                        data=np.conj(U),
                        inds=end_inds_b + start_inds_b,
                        tags={
                            f'Q{len(op.qubits)}', f'i{mi + 1}b',
                            _qpos_tag(op.qubits)
                        },
                    ))
            elif cirq.has_kraus(op):
                K = np.asarray(cirq.kraus(op), dtype=np.complex128)
                kraus_inds = [f'k{kraus_frontier}']
                tensors.append(
                    qtn.Tensor(
                        data=K,
                        inds=kraus_inds + end_inds_f + start_inds_f,
                        tags={
                            f'kQ{len(op.qubits)}', f'i{mi + 1}f',
                            _qpos_tag(op.qubits)
                        },
                    ))
                tensors.append(
                    qtn.Tensor(
                        data=np.conj(K),
                        inds=kraus_inds + end_inds_b + start_inds_b,
                        tags={
                            f'kQ{len(op.qubits)}', f'i{mi + 1}b',
                            _qpos_tag(op.qubits)
                        },
                    ))
                kraus_frontier += 1
            else:
                raise ValueError(repr(op))  # coverage: ignore

            _positions(mi + 1, op.qubits)
    return tensors, qubit_frontier, positions