Skip to content

Commit 0e8ecbc

Browse files
fix: fix hashconsing not comparing metadata of symbolics inside metadata
1 parent 982e6b1 commit 0e8ecbc

File tree

2 files changed

+68
-2
lines changed

2 files changed

+68
-2
lines changed

src/types.jl

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,62 @@ downstream packages like `ModelingToolkit.jl`, hence the need for this separate
293293
function.
294294
"""
295295
function isequal_with_metadata(a::BasicSymbolic, b::BasicSymbolic)::Bool
296-
isequal(a, b) && isequal(metadata(a), metadata(b))
296+
isequal(a, b) && isequal_with_metadata(metadata(a), metadata(b))
297+
end
298+
299+
"""
300+
$(TYPEDSIGNATURES)
301+
302+
Compare the metadata of two `BasicSymbolic`s to ensure it is equal, recursively calling
303+
`isequal_with_metadata` to ensure symbolic variables in the metadata also have equal
304+
metadata.
305+
"""
306+
function isequal_with_metadata(a::Union{AbstractDict, NamedTuple}, b::Union{AbstractDict, NamedTuple})
307+
typeof(a) == typeof(b) || return false
308+
length(a) == length(b) || return false
309+
310+
for (k, v) in pairs(a)
311+
haskey(b, k) || return false
312+
isequal_with_metadata(v, b[k]) || return false
313+
end
314+
315+
for (k, v) in pairs(b)
316+
haskey(a, k) || return false
317+
isequal_with_metadata(v, a[k]) || return false
318+
end
319+
320+
return true
321+
end
322+
323+
"""
324+
$(TYPEDSIGNATURES)
325+
326+
Fallback method which uses `isequal`.
327+
"""
328+
isequal_with_metadata(a, b) = isequal(a, b)
329+
330+
"""
331+
$(TYPEDSIGNATURES)
332+
333+
Specialized methods to check if two ranges are equal without comparing each element.
334+
"""
335+
isequal_with_metadata(a::AbstractRange, b::AbstractRange) = isequal(a, b)
336+
337+
"""
338+
$(TYPEDSIGNATURES)
339+
340+
Check if two arrays/tuples are equal by calling `isequal_with_metadata` on each element.
341+
This is to ensure true equality of any symbolic elements, if present.
342+
"""
343+
function isequal_with_metadata(a::Union{AbstractArray, Tuple}, b::Union{AbstractArray, Tuple})
344+
typeof(a) == typeof(b) || return false
345+
if a isa AbstractArray
346+
size(a) == size(b) || return false
347+
end # otherwise they're tuples and type equality also checks length equality
348+
for (x, y) in zip(a, b)
349+
isequal_with_metadata(x, y) || return false
350+
end
351+
return true
297352
end
298353

299354
Base.one( s::Symbolic) = one( symtype(s))

test/hash_consing.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using SymbolicUtils, Test
2-
using SymbolicUtils: Term, Add, Mul, Div, Pow, hash2
2+
using SymbolicUtils: Term, Add, Mul, Div, Pow, hash2, metadata
33

44
struct Ctx1 end
55
struct Ctx2 end
@@ -108,3 +108,14 @@ end
108108
@test hash2(f, u0) != hash2(r, u0)
109109
@test f + a !== r + a
110110
end
111+
112+
@testset "Symbolics in metadata" begin
113+
@syms a b
114+
a1 = setmetadata(a, Int, b)
115+
b1 = setmetadata(b, Int, 3)
116+
a2 = setmetadata(a, Int, b1)
117+
@test a1 !== a2
118+
@test !SymbolicUtils.isequal_with_metadata(a1, a2)
119+
@test metadata(metadata(a1)[Int]) === nothing
120+
@test metadata(metadata(a2)[Int])[Int] == 3
121+
end

0 commit comments

Comments
 (0)