OperatorSum.evaluate() adds a spurious zero-coefficient identity term for multi-term operators
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 1.1k
- Forks
- 455
- Avg merge
- 1d 22h
- Merged PRs (30d)
- 165
Description
Required prerequisites
- Consult the security policy. Not a security report.
- Read the documentation; this isn't addressed there.
- Searched the issue tracker; found nothing on this.
- PR attached with a failing test.
Describe the bug
OperatorSum.evaluate() (any of SpinOperator, BosonOperator, FermionOperator,
MatrixOperator) grows an extra, spurious zero-coefficient identity term every time it is
called on an operator with more than one term.
_sum_transformation (python/cudaq/operators/manipulation.py:154-188) seeds its accumulator
with arithmetics.evaluate(ScalarOperator.const(0)) and then adds every term to it, including
the first: evaluated = arithmetics.evaluate(ScalarOperator.const(0)); ...; evaluated = arithmetics.add(evaluated, evaluated_term). For the default evaluation arithmetics used by
.evaluate(), add is just op1 + op2, so the very first term is combined as 0 + term1.
The underlying C++ operator classes turn scalar + operator into an explicit zero-coefficient
identity term (degrees == []) instead of simplifying it away, so the result always carries
one more term than the input, and that extra term acts on no degrees of freedom at all.
This method is not just a display convenience - python/cudaq/dynamics/evolution.py:303 and
python/cudaq/dynamics/evolution.py:608 both call op.evaluate(**step_parameters) on every
user-supplied observable at every time step of cudaq.evolve(), so this fires on real
observables in the dynamics/evolve path, not just when a user calls .evaluate() directly.
Steps to reproduce the bug
from cudaq.operators import spin
s = spin.x(0) + spin.z(1)
print(s.term_count) # 2
ev = s.evaluate()
print(ev.term_count) # 3 - one term too many
print(ev) # (0+0i) + (1+0i) * X0 + (1+0i) * Z1
for term in ev:
print(term.degrees) # [], [0], [1] - the first term acts on nothing
Observed on the CUDA-Q 0.15.1 wheel (aca5853a7) and confirmed byte-identical on current
main for this file (git log aca5853a7..main -- python/cudaq/operators is empty):
2
3
(0+0i) + (1+0i) * X0 + (1+0i) * Z1
[]
[0]
[1]
Expected behavior
s.evaluate().term_count == s.term_count, and no term in the evaluated result should have an
empty degrees list. Numerically the extra term contributes exactly 0, so quantities computed
by simplification-tolerant consumers (e.g. cudaq.observe) come out correct regardless - but
any code that assumes every term of an evaluated operator acts on at least one degree of
freedom (e.g. term.degrees[0]) will hit an IndexError on the spurious term, and anything
iterating term_count terms gets one it never asked for.
Is this a regression? If it is, put the last known working version (or commit) here.
Not a regression - _sum_transformation and .evaluate() were introduced together in
da31e1b7a (#2817, "Migrating the Python operators to be C++ bindings", May 2025) and the
accumulator has never been touched since (only a copyright-year bump). No existing test checks
term_count or per-term degrees after .evaluate(); the existing coverage in
python/tests/operator/test_matrix_op.py::test_evaluation only compares to_matrix() output,
which is insensitive to an extra zero-coefficient term.
Environment
- CUDA-Q version: 0.15.1 (
aca5853a7) - Python version: 3.12.3
- Operating system: Ubuntu 24.04.4 LTS
Suggestions
Seed the accumulation with the first evaluated term instead of arithmetics.evaluate(ScalarOperator.const(0)),
falling back to that only when the operator has no terms at all. PR attached.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in python/cudaq/operators/manipulation.py:154-188, then review python/tests/operator/test_matrix_op.py::test_evaluation and the evaluation calls in python/cudaq/dynamics/evolution.py:303 and :608. Verify that multi-term SpinOperator, BosonOperator, FermionOperator, and MatrixOperator evaluations preserve term_count and contain no term with empty degrees; the existing matrix comparison should continue to pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- quantum-computing
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 85/100