Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions include/numsim_cas/tensor/wrappers/permute_indices_wrapper.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,30 @@ class permute_indices_wrapper final
return m_indices;
}

// #342 — the permutation is part of the node's identity: two different
// permutations of the same tensor must not hash or compare equal.
void update_hash_value() const noexcept override {
base::m_hash_value = 0;
hash_combine(base::m_hash_value, base::get_id());
hash_combine(base::m_hash_value, this->expr().get().hash_value());
hash_combine(base::m_hash_value, m_indices);
}

friend bool operator==(permute_indices_wrapper const &lhs,
permute_indices_wrapper const &rhs) {
return lhs.m_indices == rhs.m_indices &&
static_cast<base const &>(lhs) == static_cast<base const &>(rhs);
}

friend bool operator<(permute_indices_wrapper const &lhs,
permute_indices_wrapper const &rhs) {
if (static_cast<base const &>(lhs) < static_cast<base const &>(rhs))
return true;
if (static_cast<base const &>(rhs) < static_cast<base const &>(lhs))
return false;
return lhs.m_indices < rhs.m_indices;
}

protected:
/**
* @brief Stores the permutation indices.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,36 @@ class tensor_inner_product_to_scalar final
return m_rhs_indices;
}

// #343 — the contraction sequences are part of the node's identity
// (mirrors inner_product_wrapper's #266 fix): A:B and A:B^T must not
// hash or compare equal.
void update_hash_value() const noexcept override {
base::m_hash_value = 0;
hash_combine(base::m_hash_value, base::get_id());
hash_combine(base::m_hash_value, base::expr_lhs().get().hash_value());
hash_combine(base::m_hash_value, base::expr_rhs().get().hash_value());
hash_combine(base::m_hash_value, m_lhs_indices);
hash_combine(base::m_hash_value, m_rhs_indices);
}

friend bool operator==(tensor_inner_product_to_scalar const &lhs,
tensor_inner_product_to_scalar const &rhs) {
return lhs.m_lhs_indices == rhs.m_lhs_indices &&
lhs.m_rhs_indices == rhs.m_rhs_indices &&
static_cast<base const &>(lhs) == static_cast<base const &>(rhs);
}

friend bool operator<(tensor_inner_product_to_scalar const &lhs,
tensor_inner_product_to_scalar const &rhs) {
if (static_cast<base const &>(lhs) < static_cast<base const &>(rhs))
return true;
if (static_cast<base const &>(rhs) < static_cast<base const &>(lhs))
return false;
if (lhs.m_lhs_indices != rhs.m_lhs_indices)
return lhs.m_lhs_indices < rhs.m_lhs_indices;
return lhs.m_rhs_indices < rhs.m_rhs_indices;
}

protected:
sequence m_lhs_indices;
sequence m_rhs_indices;
Expand Down
29 changes: 29 additions & 0 deletions tests/CoreBugFixTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,35 @@ TEST(PowDivisionConfusion, SignPullOutCanonicalizesNestedBase) {
EXPECT_EQ(to_string(pow(-pow(x, 2.0), 2.0) - pow(x, 4.0)), "0");
}

// #342 — permute_indices_wrapper identity must include the permutation.
TEST(IndexSequenceIdentity, PermutationsDistinguish) {
auto [T] = make_tensor_variable(std::tuple{"T", 3, 3});
auto p1 = permute_indices(T, sequence{2, 1, 3});
auto p2 = permute_indices(T, sequence{1, 3, 2});
EXPECT_FALSE(*p1 == *p2);
EXPECT_NE(p1.get().hash_value(), p2.get().hash_value());
EXPECT_NE(to_string(p1 - p2), "0{3}");
// identical permutation still cancels
auto q = permute_indices(T, sequence{2, 1, 3});
EXPECT_EQ(to_string(p1 - q), "0{3}");
}

// #343 — tensor_inner_product_to_scalar identity must include the
// contraction sequences (A:B is not A:B^T).
TEST(IndexSequenceIdentity, T2sContractionSequencesDistinguish) {
auto [A, B] =
make_tensor_variable(std::tuple{"A", 3, 2}, std::tuple{"B", 3, 2});
auto ab = dot_product(A, sequence{1, 2}, B, sequence{1, 2});
auto abt = dot_product(A, sequence{1, 2}, B, sequence{2, 1});
EXPECT_FALSE(*ab == *abt);
EXPECT_NE(ab.get().hash_value(), abt.get().hash_value());
EXPECT_NE(to_string(ab - abt), "0");
EXPECT_NE(to_string(ab + abt), to_string(2.0 * abt));
// identical sequences still cancel
auto ab2 = dot_product(A, sequence{1, 2}, B, sequence{1, 2});
EXPECT_EQ(to_string(ab - ab2), "0");
}

} // namespace numsim::cas

#endif // COREBUGFIXTEST_H
Loading