Skip to content

Clean up expression implementations - #589

Open
Krzmbrzl wants to merge 21 commits into
ValeevGroup:masterfrom
Krzmbrzl:expr-impl-cleanup
Open

Clean up expression implementations#589
Krzmbrzl wants to merge 21 commits into
ValeevGroup:masterfrom
Krzmbrzl:expr-impl-cleanup

Conversation

@Krzmbrzl

Copy link
Copy Markdown
Collaborator

See individual commits for what changed

They aren't universally supported by all expression subclasses.
Therefore, them being defined in the general Expr API doesn't make too
much sense. It's better to be notified of a missing operator via a
compiler error than via a runtime exception.
This avoids situations in which important functions are not implemented
for a given expression type (as was the case with
NormalOperatorSequence). Thus, this gives a compiler-enforced guarantee
that these functions will not just remain at the (useless) base
implementations that just throw.
Comment thread SeQuant/core/expressions/expr.hpp Outdated
Comment thread SeQuant/core/expressions/product.hpp Outdated
@evaleev

evaleev commented Aug 13, 2026

Copy link
Copy Markdown
Member

Review

WARNING this Claude-generated, reviewed by me; some accompanying fixes are in #590

Reviewed the full diff (18 files) against master, plus surrounding context in expr.hpp, expr_ptr.hpp, product.hpp, sum.hpp, op.hpp, wick.impl.hpp and tensor_network/v1.cpp. Line references are as of ab3531c.

The risky mechanical rewrites all check out:

  • prefactor.as<Product>() *= *factor (wick.impl.hpp:834) — prefactor is ex<CProduct>(...), and CProduct does not override type_id(), so is<Product>() holds and Product::operator*= is reached exactly as before.
  • canon_byproduct.as<Constant>() *= *bp (tensor_network/v1.cpp:461) — canon_byproduct is initialized ex<Constant>(1) at line 62 and never rebound.
  • The as<Sum>() +=/-= and as<Product>() *= rewrites in expr_ptr.cpp are all inside is<Sum>()/is<Product>() guards, and neither CProduct nor NCProduct ever overrode those operators, so the devirtualization is behavior-preserving.
  • operator==(const ExprPtr&, const ExprPtr&) keeps its hidden-friend declaration at expr_ptr.hpp:87, so moving the definition into expr_ptr.cpp does not silently fall back to shared_ptr pointer comparison. This was the main thing I wanted to rule out.
  • Every Expr subclass implements the now-pure clone()/adjoint()/type_id()/static_equal().
  • The NormalOperator::labels() explicit-specialization declarations are correctly placed after the class template and before any use that would trigger implicit instantiation — a genuine ill-formed-NDR fix.

Findings below. The NormalOperatorSequence::static_equal self-comparison I hit while reviewing this is pre-existing rather than yours, so it is split out into #590 against master; worth noting here only because adding clone() at op.hpp:1071 is what first lets those objects into Sum/Product and hence into simplify/canonicalization.


1. Variable::conjugate() does not reset the memoized hash — variable.cpp:50

void Variable::conjugate() { conjugated_ = !conjugated_; }

memoizing_hash() (line 22-25) folds conjugated_ into the hash and asserts memoized-vs-recomputed consistency:

auto v = ex<Variable>(L"x");
v->hash_value();   // memoizes
v->adjoint();      // -> conjugate(), flips conjugated_, no reset
v->hash_value();   // SEQUANT_ASSERT fires in Debug; stale hash in Release

Variable::set_label (line 45-47) and the sibling Power::conjugate() (power.cpp:44) both reset, so this is inconsistent within the same layer. Moved code rather than new breakage, but this PR is the natural place to fix it.

2. Constant::operator*=, +=, -= do not reset the memoized hash — constant.cpp:24, :33, :42

All three mutate value_ and return, while Constant::memoizing_hash() (line 55) asserts the memoized hash still matches value_. Constant::adjoint() (line 19) does reset, so the file is self-inconsistent.

Reachable through Sum::append, which folds constants in place:

Sum s;
s.append(ex<Constant>(1));
s.hash_value();          // Sum::memoizing_hash -> hash::range over dereferenced
                         // summands, memoizing the Constant summand's own hash
s.append(ex<Constant>(2));  // sum.hpp:135 -> summands_[i].as<Constant>() += *summand
                            // mutates the resident Constant 1 -> 3 in place
s.hash_value();          // assert fires in Debug; stale hash silently used in Release

Note Sum::append resets the Sum's hash, not the folded-into Constant's.

3. Expr::not_implemented() is declared but no longer defined — expr.hpp:485

Exception not_implemented(const char *fn) const;

The definition was deleted from expr.cpp along with the throwing default operators, and git grep finds no other definition in the tree. It links today only because nothing calls it. Any future use — in-class, or from a derived class in another TU, since the name is still visible in the inherited context — becomes an undefined reference at link time rather than a compile error. Suggest deleting the declaration.

4. clone() and adjoint() docs still describe the removed throwing default — expr.hpp:82, :244

/// @note - must be overridden in the derived class.
///       - the default implementation throws an exception
virtual ExprPtr clone() const = 0;
...
/// @note base implementation throws, must be reimplemented in the derived class
virtual void adjoint() = 0;

Both are = 0 now. Since the point of the PR is that a missing override is a compile error rather than a runtime throw, these comments currently assert the opposite of the new contract.

5. Product::operator*= is still virtual with nothing to override — product.hpp:364

The Expr base declaration was removed in this PR and nothing derives-and-overrides it. Sum::operator+=/-=, Constant::operator*=/+=/-= and Power::operator*= all correctly dropped virtual in the same change, so this reads as an oversight; it leaves a vestigial vtable slot that suggests base-level dispatch which no longer exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants