Description
What happened?
Description:
When using TensorNetwork.contract_ind
with inplace=False
(even with an empty **kwargs
), an inplace=False
keyword argument appears to be passed down to cotengra
's internal caching mechanism for array_contract_expression
. This inplace
key is then included in the arguments passed to cotengra
's _build_expression
function, which does not expect it, resulting in a TypeError
.
This issue prevents the use of TensorNetwork.contract_ind
for contracting indices after adding a gate tensor to a TensorNetwork
, a common operation in simulating quantum circuits or tensor network algorithms.
Environment:
- Python: 3.12.x
- Quimb: 1.11.0
- Cotengra: 0.7.4
- NumPy: 2.2.6 (though likely reproducible with other NumPy 2.x versions)
Minimal Reproducing Example (test_quimb_contract_ind_issue.py
):
import quimb as qu
import quimb.tensor as qtn
import numpy as np
import uuid
import cotengra as ctg
def main():
print(f"Quimb version: {qu.__version__}")
print(f"Cotengra version: {ctg.__version__}")
print(f"Numpy version: {np.__version__}")
# 1. Create a simple TensorNetwork (e.g., two tensors representing a product state)
T0_data = np.zeros(2, dtype=complex); T0_data[0] = 1.0
T0 = qtn.Tensor(T0_data, inds=('kS0',), tags={'S0'})
T1_data = np.zeros(2, dtype=complex); T1_data[0] = 1.0
T1 = qtn.Tensor(T1_data, inds=('kS1',), tags={'S1'})
initial_tn = qtn.TensorNetwork([T0, T1])
print("\nInitial TensorNetwork:")
print(initial_tn)
# 2. Define a 2-site gate (e.g., CNOT)
gate_CNOT_np = np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,0,1],
[0,0,1,0]
], dtype=np.complex128).reshape((2,2,2,2)) # kS0_new, kS1_new, kS0_old, kS1_old
gate_target_phys_indices = ['kS0', 'kS1']
gate_output_new_indices = [f"{pind}_new_{uuid.uuid4().hex[:4]}" for pind in gate_target_phys_indices]
gate_tensor_inds = tuple(gate_output_new_indices + gate_target_phys_indices)
gate_tensor = qtn.Tensor(
data=gate_CNOT_np,
inds=gate_tensor_inds,
tags={'GATE_OP'}
)
print(f"\nGate Tensor: {gate_tensor}")
# 3. Mimic gate application logic
tn_after_gate = initial_tn.copy()
tn_after_gate.add_tensor(gate_tensor, virtual=False)
print("\nTensorNetwork after adding gate tensor:")
print(tn_after_gate)
print(f"\nAttempting to contract indices: {gate_target_phys_indices}")
cotengra_options_for_contract = {}
optimize_path = 'auto'
try:
for old_phys_idx in gate_target_phys_indices:
print(f" Contracting index: {old_phys_idx} with optimize='{optimize_path}' and kwargs: {cotengra_options_for_contract}")
# This is the call that triggers the error
tn_after_gate = tn_after_gate.contract_ind(
old_phys_idx,
inplace=False,
optimize=optimize_path,
**cotengra_options_for_contract
)
print(f" TN after contracting {old_phys_idx}: {tn_after_gate}")
rename_map = {new_idx: old_idx for new_idx, old_idx in zip(gate_output_new_indices, gate_target_phys_indices)}
tn_after_gate.reindex_(rename_map, inplace=True)
print("\nFinal TensorNetwork after gate application and re-indexing:")
print(tn_after_gate)
print("\nMinimal test for contract_ind sequence completed.")
except Exception as e:
print(f"\nAn unexpected error occurred during contract_ind test: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()
Observed Output & Traceback:
Quimb version: 1.11.0
Cotengra version: 0.7.4
Numpy version: 2.2.6
Initial TensorNetwork:
TensorNetwork([
Tensor(shape=(2,), inds=('kS0',), tags=oset(['S0'])),
Tensor(shape=(2,), inds=('kS1',), tags=oset(['S1'])),
], tensors=2, indices=2)
Gate Tensor: Tensor(shape=(2, 2, 2, 2), inds=..., tags=oset(['GATE_OP']), ...)
TensorNetwork after adding gate tensor:
TensorNetwork([...], tensors=3, indices=4)
Attempting to contract indices: ['kS0', 'kS1']
Contracting index: kS0 with optimize='auto' and kwargs: {}
<...>\Lib\site-packages\cotengra\interface.py:781: UserWarning: Contraction cache disabled as one of the arguments is not hashable: {'strip_exponent': False, 'inplace': False}.
warnings.warn(
An unexpected error occurred during contract_ind test: _build_expression() got an unexpected keyword argument 'inplace'
Traceback (most recent call last):
File "<...>\Lib\site-packages\cotengra\interface.py", line 771, in array_contract_expression
expr = _CONTRACT_EXPR_CACHE[key]
~~~~~~~~~~~~~~~~~~~~^^^^^
KeyError: (...)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<...>\Lib\site-packages\cotengra\interface.py", line 774, in array_contract_expression
expr = _CONTRACT_EXPR_CACHE[key] = _build_expression(
^^^^^^^^^^^^^^^^^^
TypeError: _build_expression() got an unexpected keyword argument 'inplace'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "test_quimb_contract_ind_issue.py", line 67, in main # Line number might vary slightly
tn_after_gate = tn_after_gate.contract_ind(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<...>\Lib\site-packages\quimb\tensor\tensor_core.py", line 5967, in contract_ind
tnew = tensor_contract(
^^^^^^^^^^^^^^^^
File "<...>\Python\Python312\Lib\functools.py", line 912, in wrapper
return dispatch(args[0].__class__)(*args, **kw)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<...>\Lib\site-packages\quimb\tensor\tensor_core.py", line 309, in tensor_contract
data_out = array_contract(
^^^^^^^^^^^^^^^
File "<...>\Lib\site-packages\quimb\tensor\contraction.py", line 285, in array_contract
return ctg.array_contract(
^^^^^^^^^^^^^^^^^^^
File "<...>\Lib\site-packages\cotengra\interface.py", line 855, in array_contract
expr = array_contract_expression(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "<...>\Lib\site-packages\cotengra\interface.py", line 786, in array_contract_expression
expr = _build_expression(
^^^^^^^^^^^^^^^^^^
TypeError: _build_expression() got an unexpected keyword argument 'inplace'
Expected Behavior:
The contract_ind
method should successfully contract the specified index without cotengra
's _build_expression
receiving an inplace
argument. The inplace
argument of contract_ind
itself should only control whether the operation modifies the TensorNetwork
object in place or returns a new one.
Diagnosis:
The UserWarning
from cotengra
indicates that {'strip_exponent': False, 'inplace': False}
is part of the arguments it received and used for its cache key. This dictionary is then passed to _build_expression
, causing the TypeError
. Since the **kwargs
passed from the minimal example to contract_ind
was empty ({}
), the inplace: False
must have been introduced by quimb
's internal logic before calling cotengra
.
It seems that quimb
forwards its own inplace
parameter setting (or a default if not specified by the user at the contract_ind
level but handled internally) into the options passed to cotengra
, which cotengra
then tries to use for both caching and as direct arguments to _build_expression
.
Potential Solution/Suggestion for quimb
:
quimb
should ensure that its own method-specific arguments like inplace
are not included in the generic **kwargs
dictionary that is passed down to cotengra
for contraction pathfinding and execution if cotengra
's underlying functions do not expect them. The options dictionary passed to cotengra
should be filtered to only contain cotengra
-specific parameters.
Thank you for looking into this!
What did you expect to happen?
The contract_ind
method should successfully contract the specified index without cotengra
's _build_expression
receiving an inplace
argument. The inplace
argument of contract_ind
itself should only control whether the operation modifies the TensorNetwork
object in place or returns a new one.
Minimal Complete Verifiable Example
import quimb as qu
import quimb.tensor as qtn
import numpy as np
import uuid
import cotengra as ctg
def main():
print(f"Quimb version: {qu.__version__}")
print(f"Cotengra version: {ctg.__version__}")
print(f"Numpy version: {np.__version__}")
# 1. Create a simple TensorNetwork (e.g., two tensors representing a product state)
T0_data = np.zeros(2, dtype=complex); T0_data[0] = 1.0
T0 = qtn.Tensor(T0_data, inds=('kS0',), tags={'S0'})
T1_data = np.zeros(2, dtype=complex); T1_data[0] = 1.0
T1 = qtn.Tensor(T1_data, inds=('kS1',), tags={'S1'})
initial_tn = qtn.TensorNetwork([T0, T1])
print("\nInitial TensorNetwork:")
print(initial_tn)
# 2. Define a 2-site gate (e.g., CNOT)
gate_CNOT_np = np.array([
[1,0,0,0],
[0,1,0,0],
[0,0,0,1],
[0,0,1,0]
], dtype=np.complex128).reshape((2,2,2,2)) # kS0_new, kS1_new, kS0_old, kS1_old
gate_target_phys_indices = ['kS0', 'kS1']
gate_output_new_indices = [f"{pind}_new_{uuid.uuid4().hex[:4]}" for pind in gate_target_phys_indices]
gate_tensor_inds = tuple(gate_output_new_indices + gate_target_phys_indices)
gate_tensor = qtn.Tensor(
data=gate_CNOT_np,
inds=gate_tensor_inds,
tags={'GATE_OP'}
)
print(f"\nGate Tensor: {gate_tensor}")
# 3. Mimic gate application logic
tn_after_gate = initial_tn.copy()
tn_after_gate.add_tensor(gate_tensor, virtual=False)
print("\nTensorNetwork after adding gate tensor:")
print(tn_after_gate)
print(f"\nAttempting to contract indices: {gate_target_phys_indices}")
cotengra_options_for_contract = {}
optimize_path = 'auto'
try:
for old_phys_idx in gate_target_phys_indices:
print(f" Contracting index: {old_phys_idx} with optimize='{optimize_path}' and kwargs: {cotengra_options_for_contract}")
# This is the call that triggers the error
tn_after_gate = tn_after_gate.contract_ind(
old_phys_idx,
inplace=False,
optimize=optimize_path,
**cotengra_options_for_contract
)
print(f" TN after contracting {old_phys_idx}: {tn_after_gate}")
rename_map = {new_idx: old_idx for new_idx, old_idx in zip(gate_output_new_indices, gate_target_phys_indices)}
tn_after_gate.reindex_(rename_map, inplace=True)
print("\nFinal TensorNetwork after gate application and re-indexing:")
print(tn_after_gate)
print("\nMinimal test for contract_ind sequence completed.")
except Exception as e:
print(f"\nAn unexpected error occurred during contract_ind test: {e}")
import traceback
traceback.print_exc()
if __name__ == '__main__':
main()
Relevant log output
python test_quimb_contract_ind_issue.py
C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\hyperoptimizers\hyper.py:36: UserWarning: Couldn't import `kahypar` - skipping from default hyper optimizer and using basic `labels` method instead.
warnings.warn(
Quimb version: 1.11.0
Cotengra version: 0.7.4
Numpy version: 2.2.6
Initial TensorNetwork:
TensorNetwork([
Tensor(shape=(2,), inds=('kS0',), tags=oset(['S0'])),
Tensor(shape=(2,), inds=('kS1',), tags=oset(['S1'])),
], tensors=2, indices=2)
Tensor(shape=(2,), inds=('kS0',), tags=oset(['S0']), backend='numpy', dtype='complex128')
Tensor(shape=(2,), inds=('kS1',), tags=oset(['S1']), backend='numpy', dtype='complex128')
Gate Tensor: Tensor(shape=(2, 2, 2, 2), inds=('kS0_new_cad5', 'kS1_new_3b41', 'kS0', 'kS1'), tags=oset(['GATE_OP']), backend='numpy', dtype='complex128')
TensorNetwork after adding gate tensor:
TensorNetwork([
Tensor(shape=(2,), inds=('kS0',), tags=oset(['S0'])),
Tensor(shape=(2,), inds=('kS1',), tags=oset(['S1'])),
Tensor(shape=(2, 2, 2, 2), inds=('kS0_new_cad5', 'kS1_new_3b41', 'kS0', 'kS1'), tags=oset(['GATE_OP'])),
], tensors=3, indices=4)
Attempting to contract indices: ['kS0', 'kS1']
Contracting index: kS0 with optimize='auto' and kwargs: {}
C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\interface.py:781: UserWarning: Contraction cache disabled as one of the arguments is not hashable: {'strip_exponent': False, 'inplace': False}.
warnings.warn(
An unexpected error occurred during contract_ind test: _build_expression() got an unexpected keyword argument 'inplace'
Traceback (most recent call last):
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\interface.py", line 771, in array_contract_expression
expr = _CONTRACT_EXPR_CACHE[key]
~~~~~~~~~~~~~~~~~~~~^^^^^
KeyError: (-7830817418705795007, 2)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\interface.py", line 774, in array_contract_expression
expr = _CONTRACT_EXPR_CACHE[key] = _build_expression(
^^^^^^^^^^^^^^^^^^
TypeError: _build_expression() got an unexpected keyword argument 'inplace'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\test_quimb_contract_ind_issue.py", line 82, in main
tn_after_gate = tn_after_gate.contract_ind(
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\quimb\tensor\tensor_core.py", line 5967, in contract_ind
tnew = tensor_contract(
^^^^^^^^^^^^^^^^
File "C:\Users\quant\AppData\Local\Programs\Python\Python312\Lib\functools.py", line 912, in wrapper
return dispatch(args[0].__class__)(*args, **kw)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\quimb\tensor\tensor_core.py", line 309, in tensor_contract
data_out = array_contract(
^^^^^^^^^^^^^^^
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\quimb\tensor\contraction.py", line 285, in array_contract
return ctg.array_contract(
^^^^^^^^^^^^^^^^^^^
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\interface.py", line 855, in array_contract
expr = array_contract_expression(
^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\quant\Documents\GitHub\SC-Automaton-Theory\.venv\Lib\site-packages\cotengra\interface.py", line 786, in array_contract_expression
expr = _build_expression(
^^^^^^^^^^^^^^^^^^
TypeError: _build_expression() got an unexpected keyword argument 'inplace'
Anything else we need to know?
No response
Environment
- Python: 3.12.x
- Quimb: 1.11.0
- Cotengra: 0.7.4
- NumPy: 2.2.6 (though likely reproducible with other NumPy 2.x versions)