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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions