Fix #348: discriminate symbols across domains in expression identity - #442
Merged
Merged
Conversation
Node ids index each domain's own type list, so a scalar and a tensor symbol of the same name tie on id and on the name-only hash. Comparison then downcast to the wrong type: abort in Debug, SIGSEGV in Release. - expression::operator==/< compare the dynamic type once id and hash tie, before the same-type downcast. - symbol_base::operator== compares the name, operator< tiebreaks on it, so per-char hash collisions no longer merge distinct symbols. - relation::operator< orders by its operands rather than their hashes, so colliding relations stay distinct members of relation::set. Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
…sert - Drop the relation ordering comment that still described hash ordering. - EvaluatorKeepsBothDomainBindings now asserts both entries coexist under evaluator_base's keying, not just that the scalar value survived. - tensor_scalar_mul::like_term_of asserts the downcast like n_ary_tree does. Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #348.
A scalar symbol
xand a tensor symbolxshareid()(both index 0 of their own domain's type list) and hash (symbol_base::update_hash_valuecovers only the name).expression::operator==/operator<therefore fell through to the same-type downcast invisitable_impl, casting atensorto ascalar.Reproduced at 05f3c3a, worse than the issue reported:
assert(dynamic_cast<Derived const *>(&rhs) != nullptr)→ abort, core dumpedstd::stringout of atensor's layoutFix
expression::operator==/operator<(src/numsim_cas/core/expression.cpp): compare the dynamic type onceid()and hash tie, before the downcast.operator<orders unequal types withtype_info::before. Within a domain, equalid()already implies equal type, so this changes nothing there.RTTI is safe here: the library builds as a static archive and its typeinfo carries ordinary mangled names, so libstdc++ compares them with
strcmp— deterministic, and correct even across a DSO boundary. There is no-fvisibility=hidden, and Windows setsWINDOWS_EXPORT_ALL_SYMBOLS.symbol_base::operator==comparesm_name;operator<uses it as the tiebreak (include/numsim_cas/core/symbol_base.h). Two names whose per-char hashes collide no longer merge.relation::operator<(include/numsim_cas/core/assumptions.h) orders by the operands themselves instead of their hashes, so colliding relations stay distinct inrelation::set.Not changed: the hash discriminator
The issue also proposes mixing a domain tag into
symbol_base::update_hash_value. I implemented it and measured: 43 existing tests fail, all print-order flips (y*pow(x,2)*z*Xvspow(x,2)*y*z*X) — same factors, different hash-driven order. Symbol hashes driveoperator<, which drives n-ary print order, and many tests pin that output.It is also not needed for correctness: the hash is a fast-reject and an ordering key, not an identity. With the type discrimination above, a hash tie between a scalar and a tensor symbol is a benign collision —
std::maporders throughoperator<, which is now total and UB-free. I left the hash alone rather than rewrite 43 expected-output strings; changing print order deserves its own decision.Tests
SymbolIdentity.SameNameAcrossDomainsIsDistinct— cross-domain==is false and the order is total.SymbolIdentity.EvaluatorKeepsBothDomainBindings—evaluator_basekeys onestd::map<expression_holder<expression>, std::any>, so both domains share it; binding a scalarxand a tensorxmust keep both values.AssumptionFixture.RelationSetKeepsDistinctOperands—relation<>has no users anywhere, so itsoperator<was never instantiated; this test compiles the changed code and pins that distinct relations stay distinct. It cannot demonstrate the collision fix (no way to force a hash collision through the public API).Negative control (revert
expression.cpponly, keep the tests):SameNameAcrossDomainsIsDistinctequals_same_typeEvaluatorKeepsBothDomainBindingsless_than_same_typeBoth pass with the fix.
Suite: Debug 2349/2349, Release 2349/2349 (gcc-14,
-Werrorminus the pedantic / deprecated-declarations warnings already onmain). clang-format-18 clean.Not fixed (reported)
n_ary_tree::like_term_ofandtensor_scalar_mul::like_term_ofuse the same "id + hash tie ⇒ downcast" pattern. Not reachable cross-domain today — I probedscalar_addvstensor_add: ids (4 vs 1) and hashes both differ, andlike_term_ofis only called from per-domain n-ary merging. Left alone becausen_ary_tree.his being edited by Fix #369: drop noexcept from allocating paths #436 and there is no repro to verify against.dim/rank, soA(3,2)andA(2,2)are the same key. Same name-only-identity family, tracked separately in Tensor symbols with the same name but different dim/rank are one key — evaluator returns the wrong tensor #443 — closing Scalar and tensor symbols with the same name collide (name-only hash, per-domain ids): assert abort in debug, UB with NDEBUG #348 does not cover it.Overlap
Touches
symbol_base.h(#431 adds the hash reset inupdate_hash_value) and the comparison operators (#436 removes theirnoexcept). Hunks are kept minimal and disjoint from both.