Skip to content

Commit 2a50821

Browse files
Fix: Avoid cotengra bug for empty contraction paths and add tests
- Handle len(raw_tensors) == 0 by returning a scalar identity tensor. - Handle len(raw_tensors) == 1 by using be.einsum directly. - Guard against IndexError when accessing nodes[0].backend. - Ensure exponent is initialized for these edge cases. - Add edge case tests in tests/test_hyperedge.py for 0 and 1 tensor cases. Co-authored-by: refraction-ray <35157286+refraction-ray@users.noreply.github.com>
1 parent b93811c commit 2a50821

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

tensorcircuit/cons.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -587,9 +587,16 @@ def _algebraic_base_contraction(
587587

588588
raw_tensors, input_sets, output_set, size_dict = _extract_topology(nodes)
589589
# Use the backend of the first node
590-
be = nodes[0].backend
590+
if len(nodes) > 0:
591+
be = nodes[0].backend
592+
else:
593+
be = get_backend(get_default_backend())
591594

592-
if len(raw_tensors) == 1:
595+
if len(raw_tensors) == 0:
596+
# Avoid cotengra bug for empty contraction paths
597+
final_raw_tensor = be.ones([])
598+
exponent = 0.0
599+
elif len(raw_tensors) == 1:
593600
# Avoid cotengra bug for empty contraction paths
594601
final_raw_tensor = be.einsum(input_sets[0] + "->" + output_set, *raw_tensors)
595602
exponent = 0.0

tests/test_hyperedge.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,3 +545,24 @@ def test_qir_fallback(contractor_setup, backend):
545545
qir = c.to_qir()
546546
c2 = tc.Circuit.from_qir(qir, circuit_params={"nqubits": n})
547547
np.testing.assert_allclose(c.state(), c2.state(), atol=1e-5)
548+
549+
@pytest.mark.parametrize("contractor_setup", [("cotengra", {"use_primitives": True})], indirect=True)
550+
def test_algebraic_contraction_edge_cases(contractor_setup, backend_setup):
551+
from tensorcircuit.cons import _algebraic_base_contraction
552+
import opt_einsum
553+
554+
# 0 nodes case
555+
res0 = _algebraic_base_contraction([], opt_einsum.paths.greedy)
556+
np.testing.assert_allclose(tc.backend.numpy(res0.tensor), 1.0)
557+
558+
# 1 node case
559+
a = tn.Node(tc.backend.convert_to_tensor(np.array([1.0, 2.0])))
560+
res1 = _algebraic_base_contraction([a], opt_einsum.paths.greedy)
561+
np.testing.assert_allclose(tc.backend.numpy(res1.tensor), np.array([1.0, 2.0]))
562+
563+
# 1 node with self-loop (trace)
564+
# _extract_topology handles traces by mapping them to symbols
565+
b = tn.Node(tc.backend.convert_to_tensor(np.eye(2)))
566+
b[0] ^ b[1]
567+
res1_trace = _algebraic_base_contraction([b], opt_einsum.paths.greedy)
568+
np.testing.assert_allclose(tc.backend.numpy(res1_trace.tensor), 2.0)

0 commit comments

Comments
 (0)