def test_simple(self): p = neel_state(1) assert_allclose(p, up()) p = neel_state(2) assert_allclose(p, up() & down()) p = neel_state(3) assert_allclose(p, up() & down() & up())
def test_simple(self): Z = qu.pauli('Z') P = qu.projector(Z & Z) uu = qu.dop(qu.up()) & qu.dop(qu.up()) dd = qu.dop(qu.down()) & qu.dop(qu.down()) assert_allclose(P, uu + dd) assert qu.expec(P, qu.bell_state('phi+')) == pytest.approx(1.0) assert qu.expec(P, qu.bell_state('psi+')) == pytest.approx(0.0)
def test_classically_no_correlated(self, s, qtype, pre_c): p = qu.up(qtype=qtype) & qu.up(qtype=qtype) c = qu.correlation(p, qu.pauli(s), qu.pauli(s), 0, 1, precomp_func=pre_c) c = c(p) if pre_c else c assert_allclose(c, 0.0)
def test_classically_correlated(self, s, ct, pre_c): p = 0.5 * ((qu.up(qtype='dop') & qu.up(qtype='dop')) + (qu.down(qtype='dop') & qu.down(qtype='dop'))) c = qu.correlation(p, qu.pauli(s), qu.pauli(s), 0, 1, precomp_func=pre_c) c = c(p) if pre_c else c assert_allclose(c, ct)
def test_entangled_permute(self): dims = [2, 2, 2] a = qu.bell_state(0) & qu.up() assert_allclose(qu.mutinf_subsys(a, dims, 0, 1), 2.) b = qu.permute(a, dims, [1, 2, 0]) assert_allclose(qu.mutinf_subsys(b, dims, 0, 1), 0., atol=1e-12) assert_allclose(qu.mutinf_subsys(b, dims, 0, 2), 2.)
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
def test_bell_state(self): p = bell_state('psi-') assert_allclose(schmidt_gap(p, [2, 2], 0), 0.0) p = up() & down() assert_allclose(schmidt_gap(p, [2, 2], 0), 1.0) p = rand_ket(2**3) assert 0 < schmidt_gap(p, [2] * 3, sysa=[0, 1]) < 1.0
def test_bell_state(self): p = qu.bell_state('psi-') assert_allclose(qu.schmidt_gap(p, [2, 2], 0), 0.0) p = qu.up() & qu.down() assert_allclose(qu.schmidt_gap(p, [2, 2], 0), 1.0) p = qu.rand_ket(2**3) assert 0 < qu.schmidt_gap(p, [2] * 3, sysa=[0, 1]) < 1.0
def test_quevo_multi_compute(self, method, qtype): ham = ham_heis(2, cyclic=False) p0 = qu(up() & down(), qtype=qtype) def some_quantity(t, _): return t def some_other_quantity(_, pt): return logneg(pt) evo = QuEvo(p0, ham, method=method, compute={ 't': some_quantity, 'logneg': some_other_quantity }) manual_lns = [] for pt in evo.at_times(np.linspace(0, 1, 6)): manual_lns.append(logneg(pt)) ts = evo.results['t'] lns = evo.results['logneg'] assert len(lns) >= len(manual_lns) # check a specific value of logneg at t=0.8 was computed automatically checked = False for t, ln in zip(ts, lns): if abs(t - 0.8) < 1e-12: assert abs(ln - manual_lns[4]) < 1e-12 checked = True assert checked
def test_mutual_information_pure_sub(self): a = qu.up() & qu.bell_state(1) ixy = qu.mutual_information(a, [2, 2, 2], 0, 1) assert_allclose(0.0, ixy, atol=1e-12) ixy = qu.mutual_information(a, [2, 2, 2], 0, 2) assert_allclose(0.0, ixy, atol=1e-12) ixy = qu.mutual_information(a, [2, 2, 2], 2, 1) assert_allclose(2.0, ixy, atol=1e-12)
def test_progbar_update_to_integrate(self, capsys): ham = ham_heis(2, cyclic=False) p0 = up() & down() sim = QuEvo(p0, ham, method='integrate', progbar=True) sim.update_to(100) # check something as been printed _, err = capsys.readouterr() assert err and "%" in err
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
def test_evo_at_times(self): ham = qu.ham_heis(2, cyclic=False) p0 = qu.up() & qu.down() sim = qu.Evolution(p0, ham, method='solve') ts = np.linspace(0, 10) for t, pt in zip(ts, sim.at_times(ts)): x = cos(t) y = qu.expec(pt, qu.ikron(qu.pauli('z'), [2, 2], 0)) assert_allclose(x, y, atol=1e-15)
def test_quevo_at_times(self): ham = ham_heis(2, cyclic=False) p0 = up() & down() sim = QuEvo(p0, ham, method='solve') ts = np.linspace(0, 10) for t, pt in zip(ts, sim.at_times(ts)): x = cos(t) y = expec(pt, eyepad(pauli('z'), [2, 2], 0)) assert_allclose(x, y, atol=1e-15)
def test_progbar_at_times_solve(self, capsys): ham = qu.ham_heis(2, cyclic=False) p0 = qu.up() & qu.down() sim = qu.Evolution(p0, ham, method='solve', progbar=True) for _ in sim.at_times(np.linspace(0, 100, 11)): pass # check something as been printed _, err = capsys.readouterr() assert err and "%" in err
def test_progbar_at_times_expm(self, capsys): ham = ham_heis(2, cyclic=False) p0 = up() & down() sim = QuEvo(p0, ham, method='expm', progbar=True) for _ in sim.at_times(np.linspace(0, 100, 11)): pass # check something as been printed _, err = capsys.readouterr() assert err and "%" in err
def test_quevo_compute_callback(self, qtype, method): ham = ham_heis(2, cyclic=False) p0 = qu(up() & down(), qtype=qtype) def some_quantity(t, pt): return t, logneg(pt) evo = QuEvo(p0, ham, method=method, compute=some_quantity) manual_lns = [] for pt in evo.at_times(np.linspace(0, 1, 6)): manual_lns.append(logneg(pt)) ts, lns = zip(*evo.results) assert len(lns) >= len(manual_lns) # check a specific value of logneg at t=0.8 was computed automatically checked = False for t, ln in zip(ts, lns): if abs(t - 0.8) < 1e-12: assert abs(ln - manual_lns[4]) < 1e-12 checked = True assert checked
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
def test_evo_multi_compute(self, method, qtype): ham = qu.ham_heis(2, cyclic=False) p0 = qu.qu(qu.up() & qu.down(), qtype=qtype) def some_quantity(t, _): return t def some_other_quantity(_, pt): return qu.logneg(pt) # check that hamiltonian gets accepted without error for all methods def some_other_quantity_accepting_ham(t, pt, H): return qu.logneg(pt) compute = { 't': some_quantity, 'logneg': some_other_quantity, 'logneg_ham': some_other_quantity_accepting_ham } evo = qu.Evolution(p0, ham, method=method, compute=compute) manual_lns = [] for pt in evo.at_times(np.linspace(0, 1, 6)): manual_lns.append(qu.logneg(pt)) ts = evo.results['t'] lns = evo.results['logneg'] lns_ham = evo.results['logneg_ham'] assert len(lns) >= len(manual_lns) # check a specific value of logneg at t=0.8 was computed automatically checked = False for t, ln, ln_ham in zip(ts, lns, lns_ham): if abs(t - 0.8) < 1e-12: assert abs(ln - manual_lns[4]) < 1e-12 # check that accepting hamiltonian didn't mess it up assert ln == ln_ham checked = True assert checked
class TestDecomp: @pytest.mark.parametrize("qtype", ['ket', 'dop']) def test_pauli_decomp_singlet(self, qtype): p = qu.singlet(qtype=qtype) names_cffs = qu.pauli_decomp(p, mode='cp') assert_allclose(names_cffs['II'], 0.25) assert_allclose(names_cffs['ZZ'], -0.25) assert_allclose(names_cffs['YY'], -0.25) assert_allclose(names_cffs['ZZ'], -0.25) for name in itertools.permutations('IXYZ', 2): assert_allclose(names_cffs["".join(name)], 0.0) def test_pauli_reconstruct(self): p1 = qu.rand_rho(4) names_cffs = qu.pauli_decomp(p1, mode='c') pr = sum( qu.kron(*(qu.pauli(s) for s in name)) * names_cffs["".join(name)] for name in itertools.product('IXYZ', repeat=2)) assert_allclose(pr, p1) @pytest.mark.parametrize("state, out", [(qu.up() & qu.down(), { 0: 0.5, 1: 0.5, 2: 0, 3: 0 }), (qu.down() & qu.down(), { 0: 0, 1: 0, 2: 0.5, 3: 0.5 }), (qu.singlet() & qu.singlet(), { '00': 1.0, '23': 0.0 })]) def test_bell_decomp(self, state, out): names_cffs = qu.bell_decomp(state, mode='c') for key in out: assert_allclose(names_cffs[str(key)], out[key])
def test_up(self): p = up(qtype='dop') assert_allclose(tr(p @ pauli('z')), 1.0)
def test_permute_ket(self): a = qu.up() & qu.plus() & qu.yplus() b = qu.permute(a, [2, 2, 2], [2, 0, 1]) assert_allclose(b, qu.yplus() & qu.up() & qu.plus())
def test_pure(self): rho = qu.up(qtype='dop') psi = qu.purify(rho) assert abs(qu.concurrence(psi)) < 1e-14
def test_swap_qubits(self, sparse): a = up() & down() s = swap(2, sparse=sparse) assert_allclose(s @ a, down() & up())
def test_distinguishable(self, uqtype, dqtype): assert qu.trace_distance(qu.up(qtype=uqtype), qu.down(qtype=dqtype)) > 1 - 1e-10
def test_classically_no_correlated(self, dir, qtype, pre_c): p = up(qtype=qtype) & up(qtype=qtype) c = correlation(p, pauli(dir), pauli(dir), 0, 1, precomp_func=pre_c) c = c(p) if pre_c else c assert_allclose(c, 0.0)
def test_classically_correlated(self, dir, ct, pre_c): p = 0.5 * ((up(qtype='dop') & up(qtype='dop')) + (down(qtype='dop') & down(qtype='dop'))) c = correlation(p, pauli(dir), pauli(dir), 0, 1, precomp_func=pre_c) c = c(p) if pre_c else c assert_allclose(c, ct)
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
def test_swap_qubits(self, sparse): a = qu.up() & qu.down() s = qu.swap(2, sparse=sparse) assert_allclose(s @ a, qu.down() & qu.up())
def test_n2(self): p = perm_state([up(), down()]) assert_allclose(p, bell_state('psi-'))