Skip to content

Commit 114fe76

Browse files
clean up cons
1 parent f0131c4 commit 114fe76

7 files changed

Lines changed: 82 additions & 46 deletions

File tree

.agents/memory/workflow_and_quality.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ Use this file for changes that touch public API, tests, docs, or code quality.
4040
- Benchmark external libraries in isolated environments and match precision/settings before making performance claims.
4141
- Library plotting helpers should accept an optional `ax` and should not call `plt.show()` internally.
4242
- In read-only or sandboxed environments, redirect caches such as `NUMBA_CACHE_DIR`, `MPLCONFIGDIR`, and mypy's cache directory in the shell command or local harness used for validation, not in tracked source files, unless the repo explicitly needs that behavior.
43+
- If a test or import failure plausibly comes from sandbox restrictions or third-party runtime cache/process-pool setup, rerun the exact command with escalated permissions before attributing the failure to TensorCircuit logic; sandbox artifacts can mask the real failure mode.
4344
- If `cotengra` fails at import time with `ImportError: cannot import name 'get_namespace' from autoray`, treat it as an environment dependency mismatch and upgrade `autoray` before debugging TensorCircuit's contraction code.
4445
- On macOS sandboxed runs, `conda run -n <env> ...` can silently resolve to the base interpreter; verify `sys.executable` or call the env's Python directly when reproducing environment-sensitive issues.
4546
- Cotengra hyper-optimization that reaches `joblib`/`loky` process-pool setup can fail inside the sandbox with `PermissionError` from `os.sysconf("SC_SEM_NSEMS_MAX")`; for end-to-end benchmark validation, rerun the exact benchmark command with escalated permissions rather than changing TensorCircuit logic.

examples/cotengra_visualize_path.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def main():
2525

2626
print(f"Created a {n}-qubit circuit with {len(c._nodes)} gates.")
2727

28-
# 2. Extract algebraic contraction topology
28+
# 2. Extract a stable contraction topology description for cotengra
2929
# We use the internal `_nodes` which represents the state vector contraction
3030
info, _ = tc.cons.get_tn_info(c._nodes)
3131
inputs, output, size_dict = info

examples/ng_whitepaper/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# TensorCircuit-NG Whitepaper Numerical Demonstrations
2+
3+
This directory contains the core numerical demonstration scripts, benchmarks, and figures accompanying the TensorCircuit-NG whitepaper:
4+
5+
**Paper Link**: [TensorCircuit-NG: Next-Generation Tensor Network and Differentiable Quantum Simulation](https://arxiv.org/abs/2602.14167) (arXiv:2602.14167)
6+
7+
Each script is named according to the whitepaper section it belongs to and is fully executable, demonstrating the power, flexibility, and performance of the framework.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Reproduce Papers with TensorCircuit-NG
2+
3+
This directory contains high-fidelity, reproducible simulations of key quantum computing and quantum information papers using **TensorCircuit-NG**. Each subfolder represents a specific paper reproduction.
4+
5+
## Directory Structure
6+
7+
For any new paper reproduction, follow the standardized naming and folder structure:
8+
```
9+
examples/reproduce_papers/
10+
└── <YYYY>_<keywords>/ # e.g., 2026_diff_qec_surface/
11+
├── meta.yaml # Standard metadata describing the reproduction strategy
12+
├── main.py # Main executable reproduction script (JAX-native, JIT-friendly)
13+
└── outputs/ # Directory containing generated results and plots
14+
└── result.png # Reproduced figure/plot
15+
```
16+
17+
## Guidance for AI Agents
18+
19+
Refer to the complete instruction set in the `arxiv-reproduce` skill (`.agents/skills/arxiv-reproduce/SKILL.md`) for detailed specifications.

tensorcircuit/cons.py

Lines changed: 31 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def sorted_edges(edges: Iterator[tn.Edge]) -> List[tn.Edge]:
8080
# these above lines are just for mypy, it is not very good at evaluating runtime object
8181

8282

83+
def _set_global_contractor(contractor_fn: Callable[..., Any]) -> None:
84+
for module in sys.modules:
85+
if module.startswith(package_name):
86+
setattr(sys.modules[module], "contractor", contractor_fn)
87+
88+
8389
def set_tensornetwork_backend(
8490
backend: Optional[str] = None, set_global: bool = True
8591
) -> Any:
@@ -1008,9 +1014,7 @@ def runtime_nodes_capture(key: str = "nodes") -> Iterator[Any]:
10081014
except NodesReturn as e:
10091015
captured_value[key] = e.value
10101016
finally:
1011-
for module in sys.modules:
1012-
if module.startswith(package_name):
1013-
setattr(sys.modules[module], "contractor", old_contractor)
1017+
_set_global_contractor(old_contractor)
10141018

10151019

10161020
def custom(
@@ -1023,6 +1027,8 @@ def custom(
10231027
use_primitives: Optional[bool] = None,
10241028
**kws: Any,
10251029
) -> Any:
1030+
local_kws = dict(kws)
1031+
debug_level = local_kws.pop("debug_level", debug_level)
10261032
if len(nodes) < 5:
10271033
alg = opt_einsum.paths.optimal
10281034
# not good at minimize WRITE actually...
@@ -1033,19 +1039,18 @@ def custom(
10331039
ignore_edge_order,
10341040
debug_level=debug_level,
10351041
use_primitives=use_primitives,
1036-
**kws,
1042+
**local_kws,
10371043
)
10381044

10391045
total_size = None
10401046
has_hyperedges = any(isinstance(n, tn.CopyNode) for n in nodes)
1041-
if kws.get("preprocessing", None) and not has_hyperedges:
1047+
if local_kws.get("preprocessing", None) and not has_hyperedges:
10421048
# nodes = _full_light_cone_cancel(nodes)
10431049
nodes, total_size = _merge_single_gates(nodes)
10441050
if not isinstance(optimizer, list):
10451051
alg = partial(optimizer, memory_limit=memory_limit)
10461052
else:
10471053
alg = optimizer
1048-
debug_level = kws.get("debug_level", 0)
10491054
return _base(
10501055
nodes,
10511056
alg,
@@ -1054,7 +1059,7 @@ def custom(
10541059
total_size,
10551060
debug_level=debug_level,
10561061
use_primitives=use_primitives,
1057-
**kws,
1062+
**local_kws,
10581063
)
10591064

10601065

@@ -1068,42 +1073,27 @@ def custom_stateful(
10681073
use_primitives: Optional[bool] = None,
10691074
**kws: Any,
10701075
) -> Any:
1071-
if len(nodes) < 5:
1072-
alg = opt_einsum.paths.optimal
1073-
# dynamic_programming has a potential bug for outer product
1074-
# not good at minimize WRITE actually...
1075-
return _base(
1076-
nodes,
1077-
alg,
1078-
output_edge_order,
1079-
ignore_edge_order,
1080-
use_primitives=use_primitives,
1081-
)
1082-
1083-
total_size = None
1084-
has_hyperedges = any(isinstance(n, tn.CopyNode) for n in nodes)
1085-
if kws.get("preprocessing", None) and not has_hyperedges:
1086-
nodes, total_size = _merge_single_gates(nodes)
10871076
if opt_conf is None:
10881077
opt_conf = {}
10891078
opt = optimizer(**opt_conf) # reinitiate the optimizer each time
10901079
if kws.get("contraction_info", None):
10911080
opt = contraction_info_decorator(opt)
1092-
alg = partial(opt, memory_limit=memory_limit)
1093-
debug_level = kws.get("debug_level", 0)
1094-
1095-
return _base(
1081+
local_kws = dict(kws)
1082+
debug_level = local_kws.pop("debug_level", 0)
1083+
local_kws.pop("contraction_info", None)
1084+
return custom(
10961085
nodes,
1097-
alg,
1098-
output_edge_order,
1099-
ignore_edge_order,
1100-
total_size,
1086+
opt,
1087+
memory_limit=memory_limit,
1088+
output_edge_order=output_edge_order,
1089+
ignore_edge_order=ignore_edge_order,
11011090
debug_level=debug_level,
11021091
use_primitives=use_primitives,
1092+
**local_kws,
11031093
)
11041094

11051095

1106-
# only work for custom
1096+
# used by custom contractor variants
11071097
def contraction_info_decorator(algorithm: Callable[..., Any]) -> Callable[..., Any]:
11081098
"""Decorator to add contraction information logging to an optimizer.
11091099
@@ -1249,9 +1239,7 @@ def set_contractor(
12491239
**kws,
12501240
)
12511241
if set_global:
1252-
for module in sys.modules:
1253-
if module.startswith(package_name):
1254-
setattr(sys.modules[module], "contractor", cf)
1242+
_set_global_contractor(cf)
12551243
return cf
12561244

12571245

@@ -1273,11 +1261,10 @@ def wrapper(f: Callable[..., Any]) -> Callable[..., Any]:
12731261
def newf(*args: Any, **kws: Any) -> Any:
12741262
old_contractor = getattr(thismodule, "contractor")
12751263
set_contractor(*confargs, **confkws)
1276-
r = f(*args, **kws)
1277-
for module in sys.modules:
1278-
if module.startswith(package_name):
1279-
setattr(sys.modules[module], "contractor", old_contractor)
1280-
return r
1264+
try:
1265+
return f(*args, **kws)
1266+
finally:
1267+
_set_global_contractor(old_contractor)
12811268

12821269
return newf
12831270

@@ -1294,10 +1281,10 @@ def runtime_contractor(*confargs: Any, **confkws: Any) -> Iterator[Any]:
12941281
"""
12951282
old_contractor = getattr(thismodule, "contractor")
12961283
nc = set_contractor(*confargs, **confkws)
1297-
yield nc
1298-
for module in sys.modules:
1299-
if module.startswith(package_name):
1300-
setattr(sys.modules[module], "contractor", old_contractor)
1284+
try:
1285+
yield nc
1286+
finally:
1287+
_set_global_contractor(old_contractor)
13011288

13021289

13031290
def split_rules(

tests/test_circuit.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2126,3 +2126,24 @@ def test_strip_exponent_no_hyperedge(backend, reset_contractor):
21262126
res_node, exponent = result
21272127
np.testing.assert_allclose(tc.backend.numpy(res_node.tensor), 1.0, atol=1e-5)
21282128
np.testing.assert_allclose(exponent, 10.0, atol=1e-5)
2129+
2130+
2131+
@pytest.mark.parametrize("backend", [lf("npb"), lf("jaxb")])
2132+
def test_custom_stateful_strip_exponent(backend, reset_contractor):
2133+
tc.set_contractor(
2134+
"custom_stateful",
2135+
optimizer=oem.RandomGreedy,
2136+
max_repeats=1,
2137+
strip_exponent=True,
2138+
)
2139+
2140+
nodes = [
2141+
tn.Node(tc.backend.convert_to_tensor(10.0, tc.rdtypestr)) for _ in range(6)
2142+
]
2143+
2144+
result = tc.cons.contractor(nodes)
2145+
assert isinstance(result, tuple)
2146+
2147+
res_node, exponent = result
2148+
np.testing.assert_allclose(tc.backend.numpy(res_node.tensor), 1.0, atol=1e-5)
2149+
np.testing.assert_allclose(exponent, 6.0, atol=1e-5)

tests/test_quantum.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,8 @@ def test_qop2quimb(backend):
745745
ket_inds_mps = [f"k{i}" for i in range(nwires_mps)]
746746
vec_from_quimb = np.ravel(quimb_mps.to_dense(ket_inds_mps))
747747

748-
np.testing.assert_allclose(vec_from_qop, vec_from_quimb, atol=1e-5)
748+
atol = 5e-5 if tc.backend.name == "jax" else 1e-5
749+
np.testing.assert_allclose(vec_from_qop, vec_from_quimb, atol=atol)
749750

750751

751752
@pytest.mark.parametrize("backend", [lf("npb"), lf("tfb"), lf("jaxb")])

0 commit comments

Comments
 (0)