Skip to content

Note: Inserting a new SYMENGINE_ENUM may cause the test to fail #2133

@Xingye-Dujing

Description

@Xingye-Dujing

Description

When a new type is inserted in the middle of the type enumeration, the type_code_ of all subsequent types increases by a fixed offset. Since the hash of every expression includes its type_code_ as the initial seed, like this:

hash_t seed = this->get_type_code();
hash_combine(seed, *a_);
hash_combine(seed, *b_);

However, because hash_combine uses a nonlinear mixing function, such as:

seed ^= static_cast<hash_t>(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);

Even a uniform shift in the initial seed (i.e., type_code_) does not preserve the relative ordering of final hash values. Two expressions A and B that previously satisfied hash(A) < hash(B) may end up with hash(A) > hash(B) after the insertion—despite both their type codes being increased by the same amount.

This nonlinearity means that the iteration order in std::set<RCP<...>, RCPBasicKeyLess> can change unpredictably, breaking tests or logic that rely on deterministic expression ordering.

For example

Inserting SYMENGINE_INTEGRAL between SYMENGINE_DERIVATIVE and SYMENGINE_SUBS will cause the test_printing.cpp to fail.

SYMENGINE_ENUM(SYMENGINE_DERIVATIVE, Derivative)
SYMENGINE_ENUM(SYMENGINE_INTEGRAL, Integral) 
SYMENGINE_ENUM(SYMENGINE_SUBS, Subs)
FAILED:
  CHECK( s == reinterpret_cast<const char *>(u8"-1 \u2260 x \u2227 x < 0 \u2227 x < 2") )
with expansion:
  "-1 ≠ x ∧ x < 2 ∧ x < 0"
  ==
  "-1 ≠ x ∧ x < 0 ∧ x < 2"

FAILED:
  CHECK( s == reinterpret_cast<const char *>(u8"-1 \u2260 x \u2228 x < 0 \u2228 x < 2") )
with expansion:
  "-1 ≠ x ∨ x < 2 ∨ x < 0"
  ==
  "-1 ≠ x ∨ x < 0 ∨ x < 2"

FAILED:
  CHECK( s == reinterpret_cast<const char *>(u8"-1 \u2260 x \u22BB x < 0 \u22BB x < 2") )
with expansion:
  "-1 ≠ x ⊻ x < 2 ⊻ x < 0"
  ==
  "-1 ≠ x ⊻ x < 0 ⊻ x < 2"

Solution

We try to add the new SYMENGINE_ENUM at the end of the type_codes.inc file. Or after testing, it was found that inserting it in the middle could also pass all the tests.

Specifically, if we want to add SYMENGINE_INTEGRAL. Ensure that all tests are passed, and place it together with SYMENGINE_DERIVATIVE. We can put them all at the end of the type_codes.inc file. After I try, it is good.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions