def gf_cost_func_v1(B, AL, AR): Bn = tn.Node(B, axis_names=["p", "L", "R"]) ALn = tn.Node(AL, axis_names=["p", "L", "R"]) ARn = tn.Node(AR, axis_names=["p", "L", "R"]) ALn_c = tn.conj(ALn) ALn["L"] ^ ALn_c["L"] ALn["R"] ^ ALn_c["R"] PLn = tn.contract_between(ALn, ALn_c, output_edge_order=[ALn_c["p"], ALn["p"]], axis_names=["p_in", "p_out"]) ARn_c = tn.conj(ARn) ARn["L"] ^ ARn_c["L"] ARn["R"] ^ ARn_c["R"] PRn = tn.contract_between(ARn, ARn_c, output_edge_order=[ARn_c["p"], ARn["p"]], axis_names=["p_in", "p_out"]) PLRn = tn.Node(PLn.get_tensor() + PRn.get_tensor(), axis_names=["p_in", "p_out"]) Bn_c = tn.conj(Bn) Bn["L"] ^ Bn_c["L"] Bn["R"] ^ Bn_c["R"] Bn["p"] ^ PLRn["p_in"] Bn_c["p"] ^ PLRn["p_out"] res = Bn @ PLRn @ Bn_c return res.get_tensor()
def test_split_node_qr_unitarity(dtype, num_charges): np.random.seed(10) a = tn.Node(get_square_matrix(50, num_charges, dtype=dtype), backend='symmetric') q, r = tn.split_node_qr(a, [a[0]], [a[1]]) r[0] | q[1] qbar = tn.conj(q) q[1] ^ qbar[1] u1 = q @ qbar qbar[0] ^ q[0] u2 = qbar @ q blocks, _, shapes = _find_diagonal_sparse_blocks(u1.tensor.flat_charges, u1.tensor.flat_flows, len(u1.tensor._order[0])) for n, block in enumerate(blocks): np.testing.assert_almost_equal( np.reshape(u1.tensor.data[block], shapes[:, n]), np.eye(N=shapes[0, n], M=shapes[1, n])) blocks, _, shapes = _find_diagonal_sparse_blocks(u2.tensor.flat_charges, u2.tensor.flat_flows, len(u2.tensor._order[0])) for n, block in enumerate(blocks): np.testing.assert_almost_equal( np.reshape(u2.tensor.data[block], shapes[:, n]), np.eye(N=shapes[0, n], M=shapes[1, n]))
def test_conj(backend): if backend == "pytorch": pytest.skip("Complex numbers currently not supported in PyTorch") a = tn.Node(np.random.rand(3, 3) + 1j * np.random.rand(3, 3), backend=backend) abar = tn.conj(a) np.testing.assert_allclose(abar.tensor, a.backend.conj(a.tensor))
def get_Cost_mpsII(gamma_beta): Cost = 0 Sz = tn.Node(g.get_Z()) for i in range(n): g_b = tn.FiniteMPS(gamma_beta.nodes) g_b.apply_one_site_gate(Sz, i) C = exp.exp_MPS(g_b, gamma_beta) C = np.real(C) C = h[i] * C Cost = Cost + C SzSz = np.tensordot(g.get_Z(), g.get_Z(), axes=0) SzSz = tn.Node(SzSz) for i in range(n - 1): for j in range((n - 1), i, -1): g_b = gamma_beta.nodes g_bcon = [tn.conj(g_b[i]) for i in range(n)] for k in range(n): if (k == i): g_b[k][1] ^ SzSz[0] g_bcon[k][1] ^ SzSz[1] elif (k == j): g_b[k][1] ^ SzSz[2] g_bcon[k][1] ^ SzSz[3] else: g_bcon[k][1] ^ g_b[k][1] if (k == (n - 1)): g_bcon[k][2] ^ g_bcon[0][0] g_b[k][2] ^ g_b[0][0] else: g_bcon[k][2] ^ g_bcon[k + 1][0] g_b[k][2] ^ g_b[k + 1][0] C = tn.contractors.greedy((g_bcon + [SzSz] + g_b)) C = C.tensor C = np.real(C.item()) C = J[i, j] * C Cost = Cost + C del g_b, g_bcon return Cost
def _ev_mps(self, obs_nodes, wires): r"""Expectation value of observables on specified wires using a MPS representation. Args: obs_nodes (Sequence[tn.Node]): the observables as TensorNetwork Nodes wires (Sequence[Sequence[int]]): measured subsystems for each observable Returns: complex: expectation value :math:`\expect{A} = \bra{\psi}A\ket{\psi}` """ if any(len(wires_seq) > 2 for wires_seq in wires): raise NotImplementedError( "Multi-wire measurement only supported for nearest-neighbour wire pairs." ) if len(obs_nodes) == 1 and len(wires[0]) == 1: # TODO: can measure multiple local expectation values at once, # but this would require change of `expval` behaviour and # refactor of `execute` logic from parent class expval = self.mps.measure_local_operator(obs_nodes, wires[0])[0] else: conj_nodes = [tn.conj(node) for node in self.mps.nodes] meas_wires = [] # connect measured bra and ket nodes with observables for obs_node, wire_seq in zip(obs_nodes, wires): if len(wire_seq) == 2 and abs(wire_seq[0] - wire_seq[1]) > 1: raise NotImplementedError( "Multi-wire measurement only supported for nearest-neighbour wire pairs." ) offset = len(wire_seq) for idx, wire in enumerate(wire_seq): tn.connect(conj_nodes[wire][1], obs_node[idx]) tn.connect(obs_node[offset + idx], self.mps.nodes[wire][1]) meas_wires.extend(wire_seq) for wire in range(self.num_wires): # connect unmeasured ket nodes with bra nodes if wire not in meas_wires: tn.connect(conj_nodes[wire][1], self.mps.nodes[wire][1]) # connect local nodes of MPS (not connected by default in tn) if wire != self.num_wires - 1: tn.connect(self.mps.nodes[wire][2], self.mps.nodes[wire + 1][0]) tn.connect(conj_nodes[wire][2], conj_nodes[wire + 1][0]) # contract MPS bonds first bra_node = conj_nodes[0] ket_node = self.mps.nodes[0] for wire in range(self.num_wires - 1): bra_node = tn.contract_between(bra_node, conj_nodes[wire + 1]) ket_node = tn.contract_between(ket_node, self.mps.nodes[wire + 1]) # contract observables into ket for obs_node in obs_nodes: ket_node = tn.contract_between(obs_node, ket_node) # contract bra into observables/ket expval_node = tn.contract_between(bra_node, ket_node) # remove dangling singleton edges expval = self._squeeze(expval_node.tensor) return expval
def test_split_node_rq_unitarity_complex(backend): if backend == "pytorch": pytest.skip("Complex numbers currently not supported in PyTorch") if backend == "jax": pytest.skip("Complex QR crashes jax") a = tn.Node(np.random.rand(3, 3) + 1j * np.random.rand(3, 3), backend=backend) _, q = tn.split_node_rq(a, [a[0]], [a[1]]) n1 = tn.Node(q.tensor, backend=backend) n2 = tn.conj(q) n1[1] ^ n2[1] u1 = tn.contract_between(n1, n2) n1 = tn.Node(q.tensor, backend=backend) n2 = tn.conj(q) n2[0] ^ n1[0] u2 = tn.contract_between(n1, n2) np.testing.assert_almost_equal(u1.tensor, np.eye(3)) np.testing.assert_almost_equal(u2.tensor, np.eye(3))
def inner(B1, B2, stateL, stateR): B1n = tn.Node(B1, axis_names=["p", "L", "R"]) B2nc = tn.conj(tn.Node(B2, axis_names=["p", "L", "R"])) ln = tn.Node(np.asarray(stateL.l[0]), axis_names=["B", "T"]) rn = tn.Node(np.asarray(stateR.r[0]), axis_names=["T", "B"]) B1n["p"] ^ B2nc["p"] B1n["L"] ^ ln["T"] B2nc["L"] ^ ln["B"] B1n["R"] ^ rn["T"] B2nc["R"] ^ rn["B"] return (ln @ B1n @ rn @ B2nc).get_tensor()
def test_conj(dtype, num_charges): np.random.seed(10) a = tn.Node(get_random((6, 7, 8, 9, 10), num_charges=num_charges, dtype=dtype), backend='symmetric') abar = tn.conj(a) np.testing.assert_allclose(abar.tensor.data, a.backend.conj(a.tensor.data)) assert np.all([ charge_equal(abar.tensor._charges[n], a.tensor._charges[n]) for n in range(len(a.tensor._charges)) ])
def test_split_node_qr_unitarity_float(backend): a = tn.Node(np.random.rand(3, 3), backend=backend) q, r = tn.split_node_qr(a, [a[0]], [a[1]]) q[1] | r[0] qbar = tn.conj(q) q[1] ^ qbar[1] u1 = q @ qbar qbar[0] ^ q[0] u2 = qbar @ q np.testing.assert_almost_equal(u1.tensor, np.eye(3)) np.testing.assert_almost_equal(u2.tensor, np.eye(3))
def test_split_node_rq_unitarity_float(backend): a = tn.Node(np.random.rand(3, 3), backend=backend) _, q = tn.split_node_rq(a, [a[0]], [a[1]]) n1 = tn.Node(q.tensor, backend=backend) n2 = tn.conj(q) n1[1] ^ n2[1] u1 = tn.contract_between(n1, n2) n1 = tn.Node(q.tensor, backend=backend) n2 = tn.Node(q.tensor, backend=backend) n2[0] ^ n1[0] u2 = tn.contract_between(n1, n2) np.testing.assert_almost_equal(u1.tensor, np.eye(3)) np.testing.assert_almost_equal(u2.tensor, np.eye(3))
def _shift_position_right(self, pos): self.renvs = self.renvs[:-(pos - self.pos)] while self.pos < pos: self.psi.position(self.pos + 1) nodes = [ self.lenvs[-1], self.psi.nodes[self.pos], self.H.nodes[self.pos], tn.conj(self.psi.nodes[self.pos]) ] L = tn.ncon(nodes, [(1, 3, 5), (1, 2, -1), (3, 2, 4, -2), (5, 4, -3)], backend=self.backend) L.set_name('left_env_{}'.format(self.pos + 1)) self.lenvs.append(L) self.pos += 1
def _shift_position_left(self, pos): self.lenvs = self.lenvs[0:pos + 1] while self.pos > pos: self.psi.position(self.pos) nodes = [ self.renvs[-1], self.psi.nodes[self.pos + 1], self.H.nodes[self.pos + 1], tn.conj(self.psi.nodes[self.pos + 1]) ] R = tn.ncon(nodes, [(1, 3, 5), (-1, 2, 1), (-2, 2, 4, 3), (-3, 4, 5)], backend=self.backend) R.set_name('right_env_{}'.format(self.pos + 1)) self.renvs.append(R) self.pos -= 1
def _build_left_envs(self, lpos): L = tn.Node(np.array([[[1]]]), backend=self.backend, name='left_env_0') lenvs = [L] for i in range(lpos): self.psi.postion(i + 1) nodes = [ L, self.psi.nodes[i], self.H.nodes[i], tn.conj(self.psi.nodes[i]) ] L = tn.ncon(nodes, [(1, 3, 5), (1, 2, -1), (3, 2, 4, -2), (5, 4, -3)], backend=self.backend) L.set_name('left_env_{}'.format(i + 1)) lenvs.append(L) return lenvs
def _ev_exact(self, obs_nodes, obs_wires): r"""Expectation value of observables on specified wires using an exact representation. Args: obs_nodes (Sequence[tn.Node]): the observables as TensorNetwork Nodes obs_wires (Sequence[Wires]): measured wires for each observable Returns: complex: expectation value :math:`\expect{A} = \bra{\psi}A\ket{\psi}` """ self._contract_premeasurement_network() ket = self._contracted_state_node bra = tn.conj(ket, name="Bra") all_device_wires = Wires(range(self.num_wires)) meas_device_wires = [] # For wires which are measured, add edges between # the ket node, the observable nodes, and the bra node for obs_node, wires in zip(obs_nodes, obs_wires): # translate to consecutive wire labels used by device device_wires = self.map_wires(wires) meas_device_wires.append(device_wires) for idx, l in enumerate(device_wires.labels): # Use convention that the indices of a tensor are ordered like # [output_idx1, output_idx2, ..., input_idx1, input_idx2, ...] output_idx = idx input_idx = len(device_wires) + idx tn.connect(obs_node[input_idx], ket[l]) # A|psi> tn.connect(bra[l], obs_node[output_idx]) # <psi|A meas_device_wires = Wires(meas_device_wires) # unmeasured wires are contracted directly between bra and ket unmeasured_device_wires = Wires.unique_wires( [all_device_wires, meas_device_wires]) for w in unmeasured_device_wires.labels: tn.connect(bra[w], ket[w]) # At this stage, all nodes are connected, and the contraction yields a # scalar value. ket_and_observable_node = ket for obs_node in obs_nodes: ket_and_observable_node = tn.contract_between( obs_node, ket_and_observable_node) return tn.contract_between(bra, ket_and_observable_node).tensor
def energy(self): """ Measure the energy expectation value by completing the network contraction. """ E = self.renvs[-1] self.psi.position(self.pos) for i in [self.pos + 1, self.pos]: nodes = [ E, self.psi.nodes[i], self.H.nodes[i], tn.conj(self.psi.nodes[i]) ] E = tn.ncon(nodes, [(1, 3, 5), (-1, 2, 1), (-2, 2, 4, 3), (-3, 4, 5)], backend=self.backend) E = tn.ncon([self.lenvs[-1], E], [(1, 2, 3), (1, 2, 3)]) return E.tensor.item()
def test_split_node_qr_unitarity_complex(backend): if backend == "pytorch": pytest.skip("Complex numbers currently not supported in PyTorch") if backend == "jax": pytest.skip("Complex QR crashes jax") a = tn.Node(np.random.rand(3, 3) + 1j * np.random.rand(3, 3), backend=backend) q, r = tn.split_node_qr(a, [a[0]], [a[1]]) q[1] | r[0] qbar = tn.conj(q) q[1] ^ qbar[1] u1 = q @ qbar qbar[0] ^ q[0] u2 = qbar @ q np.testing.assert_almost_equal(u1.tensor, np.eye(3)) np.testing.assert_almost_equal(u2.tensor, np.eye(3))
def _build_right_envs(self, rpos): R = tn.Node(np.array([[[1]]]), backend=self.backend, name='right_env_{}'.format(self._len)) renvs = [R] for i in reversed(range(rpos, self._len)): self.psi.position(i - 1) nodes = [ R, self.psi.nodes[i], self.H.nodes[i], tn.conj(self.psi.nodes[i]) ] R = tn.ncon(nodes, [(1, 3, 5), (-1, 2, 1), (-2, 2, 4, 3), (-3, 4, 5)], backend=self.backend) R.set_name('right_env_{}'.format(i)) renvs.append(R) return renvs
def ev(self, obs_nodes, wires): r"""Expectation value of observables on specified wires. Args: obs_nodes (Sequence[tn.Node]): the observables as tensornetwork Nodes wires (Sequence[Sequence[[int]]): measured subsystems for each observable Returns: float: expectation value :math:`\expect{A} = \bra{\psi}A\ket{\psi}` """ all_wires = tuple(w for w in range(self.num_wires)) ket = self._add_node(self._state, wires=all_wires, name="Ket") bra = self._add_node(tn.conj(ket), wires=all_wires, name="Bra") meas_wires = [] # We need to build up <psi|A|psi> step-by-step. # For wires which are measured, we need to connect edges between # bra, obs_node, and ket. # For wires which are not measured, we need to connect edges between # bra and ket. # We use the convention that the indices of a tensor are ordered like # [output_idx1, output_idx2, ..., input_idx1, input_idx2, ...] for obs_node, obs_wires in zip(obs_nodes, wires): meas_wires.extend(obs_wires) for idx, w in enumerate(obs_wires): output_idx = idx input_idx = len(obs_wires) + idx self._add_edge(obs_node, input_idx, ket, w) # A|psi> self._add_edge(bra, w, obs_node, output_idx) # <psi|A for w in set(all_wires) - set(meas_wires): self._add_edge(bra, w, ket, w) # |psi[w]|**2 # At this stage, all nodes are connected, and the contraction yields a # scalar value. contracted_ket = ket for obs_node in obs_nodes: contracted_ket = tn.contract_between(obs_node, contracted_ket) expval = tn.contract_between(bra, contracted_ket).tensor if np.abs(expval.imag) > tolerance: warnings.warn( "Nonvanishing imaginary part {} in expectation value.".format( expval.imag), RuntimeWarning, ) return expval.real
def _ev_exact(self, obs_nodes, wires): r"""Expectation value of observables on specified wires using an exact representation. Args: obs_nodes (Sequence[tn.Node]): the observables as TensorNetwork Nodes wires (Sequence[Sequence[int]]): measured subsystems for each observable Returns: complex: expectation value :math:`\expect{A} = \bra{\psi}A\ket{\psi}` """ self._contract_premeasurement_network() ket = self._contracted_state_node bra = tn.conj(ket, name="Bra") all_wires = tuple(range(self.num_wires)) meas_wires = [] # For wires which are measured, add edges between # the ket node, the observable nodes, and the bra node for obs_node, obs_wires in zip(obs_nodes, wires): meas_wires.extend(obs_wires) for idx, w in enumerate(obs_wires): # Use convention that the indices of a tensor are ordered like # [output_idx1, output_idx2, ..., input_idx1, input_idx2, ...] output_idx = idx input_idx = len(obs_wires) + idx tn.connect(obs_node[input_idx], ket[w]) # A|psi> tn.connect(bra[w], obs_node[output_idx]) # <psi|A # unmeasured wires are contracted directly between bra and ket for w in set(all_wires) - set(meas_wires): tn.connect(bra[w], ket[w]) # At this stage, all nodes are connected, and the contraction yields a # scalar value. ket_and_observable_node = ket for obs_node in obs_nodes: ket_and_observable_node = tn.contract_between(obs_node, ket_and_observable_node) return tn.contract_between(bra, ket_and_observable_node).tensor
def test_conj_of_node_without_backend_raises_error(): node = np.random.rand(3, 3, 3) with pytest.raises(AttributeError): tn.conj(node)
def regauge_B_symm_linear_problem_v1(B, AL, AR, p=0): Bn = tn.Node(B, axis_names=["p", "L", "R"]) ALn = tn.Node(AL, axis_names=["p", "L", "R"]) ARn = tn.Node(AR * np.exp(-1.j * p), axis_names=["p", "L", "R"]) D = B.shape[2] ALn_c = tn.conj(ALn) ALn["L"] ^ ALn_c["L"] ALn["R"] ^ ALn_c["R"] PLn = tn.contract_between(ALn, ALn_c, output_edge_order=[ALn_c["p"], ALn["p"]], axis_names=["p_in", "p_out"]) ARn_c = tn.conj(ARn) ARn["L"] ^ ARn_c["L"] ARn["R"] ^ ARn_c["R"] PRn = tn.contract_between(ARn, ARn_c, output_edge_order=[ARn_c["p"], ARn["p"]], axis_names=["p_in", "p_out"]) PLRn = tn.Node(PLn.get_tensor() + PRn.get_tensor(), axis_names=["p_in", "p_out"]) E = tn.CopyNode(2, D, dtype=B.dtype) PLRn["p_out"] ^ ALn_c["p"] MLn = tn.contract_between( PLRn, ALn_c, output_edge_order=[PLRn["p_in"], ALn_c["L"], ALn_c["R"]], axis_names=["p_in", "L_in", "L_out"]) MLn = tn.contract_between( MLn, E, output_edge_order=[MLn["L_out"], E[0], MLn["p_in"], MLn["L_in"], E[1]], axis_names=["L_out", "R_out", "p_in", "L_in", "R_in"], allow_outer_product=True) PLRn["p_out"] ^ ARn_c["p"] MRn = tn.contract_between( PLRn, ARn_c, output_edge_order=[PLRn["p_in"], ARn_c["L"], ARn_c["R"]], axis_names=["p_in", "R_out", "R_in"]) MRn = tn.contract_between( MRn, E, output_edge_order=[E[0], MRn["R_out"], MRn["p_in"], E[1], MRn["R_in"]], axis_names=["L_out", "R_out", "p_in", "L_in", "R_in"], allow_outer_product=True) MLRn = tn.Node(MLn.get_tensor() - MRn.get_tensor(), axis_names=["L_out", "R_out", "p_in", "L_in", "R_in"]) MLRn["p_in"] ^ Bn["p"] MLRn["L_in"] ^ Bn["L"] MLRn["R_in"] ^ Bn["R"] target_n = tn.contract_between( MLRn, Bn, output_edge_order=[MLRn["L_out"], MLRn["R_out"]]) target_vec = target_n.get_tensor().ravel() ARn["p"] ^ MLRn["p_in"] ARn["R"] ^ MLRn["R_in"] bigM_Rn = tn.contract_between(ARn, MLRn, output_edge_order=[ MLRn["L_out"], MLRn["R_out"], MLRn["L_in"], ARn["L"] ]) ALn["p"] ^ MLRn["p_in"] ALn["L"] ^ MLRn["L_in"] bigM_Ln = tn.contract_between(ALn, MLRn, output_edge_order=[ MLRn["L_out"], MLRn["R_out"], ALn["R"], MLRn["R_in"] ]) bigMn = tn.Node(bigM_Rn.get_tensor() - bigM_Ln.get_tensor(), axis_names=["L_out", "R_out", "L_in", "R_in"]) mat = bigMn.get_tensor().reshape((D**2, D**2)) return mat, target_vec
def binary_mera_energy(hamiltonian, state, isometry, disentangler): """Computes the energy using a layer of uniform binary MERA. Args: hamiltonian: The hamiltonian (rank-6 tensor) defined at the bottom of the MERA layer. state: The 3-site reduced state (rank-6 tensor) defined at the top of the MERA layer. isometry: The isometry tensor (rank 3) of the binary MERA. disentangler: The disentangler tensor (rank 4) of the binary MERA. Returns: The energy. """ backend = "jax" out = [] for dirn in ('left', 'right'): iso_l = tensornetwork.Node(isometry, backend=backend) iso_c = tensornetwork.Node(isometry, backend=backend) iso_r = tensornetwork.Node(isometry, backend=backend) iso_l_con = tensornetwork.conj(iso_l) iso_c_con = tensornetwork.conj(iso_c) iso_r_con = tensornetwork.conj(iso_r) op = tensornetwork.Node(hamiltonian, backend=backend) rho = tensornetwork.Node(state, backend=backend) un_l = tensornetwork.Node(disentangler, backend=backend) un_l_con = tensornetwork.conj(un_l) un_r = tensornetwork.Node(disentangler, backend=backend) un_r_con = tensornetwork.conj(un_r) tensornetwork.connect(iso_l[2], rho[0]) tensornetwork.connect(iso_c[2], rho[1]) tensornetwork.connect(iso_r[2], rho[2]) tensornetwork.connect(iso_l[0], iso_l_con[0]) tensornetwork.connect(iso_l[1], un_l[2]) tensornetwork.connect(iso_c[0], un_l[3]) tensornetwork.connect(iso_c[1], un_r[2]) tensornetwork.connect(iso_r[0], un_r[3]) tensornetwork.connect(iso_r[1], iso_r_con[1]) if dirn == 'right': tensornetwork.connect(un_l[0], un_l_con[0]) tensornetwork.connect(un_l[1], op[3]) tensornetwork.connect(un_r[0], op[4]) tensornetwork.connect(un_r[1], op[5]) tensornetwork.connect(op[0], un_l_con[1]) tensornetwork.connect(op[1], un_r_con[0]) tensornetwork.connect(op[2], un_r_con[1]) elif dirn == 'left': tensornetwork.connect(un_l[0], op[3]) tensornetwork.connect(un_l[1], op[4]) tensornetwork.connect(un_r[0], op[5]) tensornetwork.connect(un_r[1], un_r_con[1]) tensornetwork.connect(op[0], un_l_con[0]) tensornetwork.connect(op[1], un_l_con[1]) tensornetwork.connect(op[2], un_r_con[0]) tensornetwork.connect(un_l_con[2], iso_l_con[1]) tensornetwork.connect(un_l_con[3], iso_c_con[0]) tensornetwork.connect(un_r_con[2], iso_c_con[1]) tensornetwork.connect(un_r_con[3], iso_r_con[0]) tensornetwork.connect(iso_l_con[2], rho[3]) tensornetwork.connect(iso_c_con[2], rho[4]) tensornetwork.connect(iso_r_con[2], rho[5]) # FIXME: Check that this is giving us a good path! out.append( contractors.branch(tensornetwork.reachable(rho), nbranch=2).get_tensor()) return 0.5 * sum(out)
# solve order and contract network using opt_einsum t0 = time.time() T1 = oe.contract(*comb_list, [-1, -2, -3, -4, -5, -6], optimize='branch-all') print("opt_einsum: time to contract = ", time.time() - t0) """ For a final comparison, we demonstrate how the example network can be solved for the optimal order and contracted using the node/edge API with opt_einsum """ # define network nodes backend = "numpy" iso_l = tn.Node(w, backend=backend) iso_c = tn.Node(w, backend=backend) iso_r = tn.Node(w, backend=backend) iso_l_con = tn.conj(iso_l) iso_c_con = tn.conj(iso_c) iso_r_con = tn.conj(iso_r) op = tn.Node(ham, backend=backend) un_l = tn.Node(u, backend=backend) un_l_con = tn.conj(un_l) un_r = tn.Node(u, backend=backend) un_r_con = tn.conj(un_r) # define network edges tn.connect(iso_l[0], iso_l_con[0]) tn.connect(iso_l[1], un_l[2]) tn.connect(iso_c[0], un_l[3]) tn.connect(iso_c[1], un_r[2]) tn.connect(iso_r[0], un_r[3]) tn.connect(iso_r[1], iso_r_con[1])