From d50294b632670d4f515377c21d7faf53787d3e84 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Mon, 14 Oct 2024 17:20:35 -0400 Subject: [PATCH 01/19] Add WeakValueDicts --- Project.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Project.toml b/Project.toml index c2e992d3..db03a879 100644 --- a/Project.toml +++ b/Project.toml @@ -26,6 +26,7 @@ SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5" TermInterface = "8ea1fca8-c5ef-4a55-8b96-4e9afe9c9a3c" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" Unityper = "a7c27f48-0311-42f6-a7f8-2c11e75eb415" +WeakValueDicts = "897b6980-f191-5a31-bcb0-bf3c4585e0c1" [weakdeps] LabelledArrays = "2ee39098-c373-598a-b85f-a56591580800" @@ -57,6 +58,7 @@ SymbolicIndexingInterface = "0.3" TermInterface = "2.0" TimerOutputs = "0.5" Unityper = "0.1.2" +WeakValueDicts = "0.1.0" julia = "1.3" [extras] From 7a3d8baa9bca14ccc6c816b3a5db248aa40bf1d7 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 16 Oct 2024 14:57:09 -0400 Subject: [PATCH 02/19] Import `WeakValueDict` --- src/SymbolicUtils.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SymbolicUtils.jl b/src/SymbolicUtils.jl index acf5c0cf..fb13f50b 100644 --- a/src/SymbolicUtils.jl +++ b/src/SymbolicUtils.jl @@ -20,6 +20,7 @@ import TermInterface: iscall, isexpr, head, children, operation, arguments, metadata, maketerm, sorted_arguments # For ReverseDiffExt import ArrayInterface +using WeakValueDicts: WeakValueDict Base.@deprecate istree iscall export istree, operation, arguments, sorted_arguments, iscall From e9e702b5e17f2239878384eceb373a8cf77fdad0 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 16 Oct 2024 14:58:46 -0400 Subject: [PATCH 03/19] Construct SymbolicUtils internal `WeakValueDict` --- src/types.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/types.jl b/src/types.jl index 683f58d4..f300649d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -77,6 +77,8 @@ function exprtype(x::BasicSymbolic) end end +wvd = WeakValueDict{UInt, BasicSymbolic}() + # Same but different error messages @noinline error_on_type() = error("Internal error: unreachable reached!") @noinline error_sym() = error("Sym doesn't have a operation or arguments!") From 62403b99454cf7909c3ee5bd81ce77a3a000a349 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 16 Oct 2024 15:00:13 -0400 Subject: [PATCH 04/19] Change `BasicSymbolic` types to `mutable` due to Julia finalizer to support `WeakValueDict` --- src/types.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/types.jl b/src/types.jl index f300649d..711b14e5 100644 --- a/src/types.jl +++ b/src/types.jl @@ -23,38 +23,38 @@ const EMPTY_DICT = sdict() const EMPTY_DICT_T = typeof(EMPTY_DICT) @compactify show_methods=false begin - @abstract struct BasicSymbolic{T} <: Symbolic{T} + @abstract mutable struct BasicSymbolic{T} <: Symbolic{T} metadata::Metadata = NO_METADATA end - struct Sym{T} <: BasicSymbolic{T} + mutable struct Sym{T} <: BasicSymbolic{T} name::Symbol = :OOF end - struct Term{T} <: BasicSymbolic{T} + mutable struct Term{T} <: BasicSymbolic{T} f::Any = identity # base/num if Pow; issorted if Add/Dict arguments::Vector{Any} = EMPTY_ARGS hash::RefValue{UInt} = EMPTY_HASH end - struct Mul{T} <: BasicSymbolic{T} + mutable struct Mul{T} <: BasicSymbolic{T} coeff::Any = 0 # exp/den if Pow dict::EMPTY_DICT_T = EMPTY_DICT hash::RefValue{UInt} = EMPTY_HASH arguments::Vector{Any} = EMPTY_ARGS issorted::RefValue{Bool} = NOT_SORTED end - struct Add{T} <: BasicSymbolic{T} + mutable struct Add{T} <: BasicSymbolic{T} coeff::Any = 0 # exp/den if Pow dict::EMPTY_DICT_T = EMPTY_DICT hash::RefValue{UInt} = EMPTY_HASH arguments::Vector{Any} = EMPTY_ARGS issorted::RefValue{Bool} = NOT_SORTED end - struct Div{T} <: BasicSymbolic{T} + mutable struct Div{T} <: BasicSymbolic{T} num::Any = 1 den::Any = 1 simplified::Bool = false arguments::Vector{Any} = EMPTY_ARGS end - struct Pow{T} <: BasicSymbolic{T} + mutable struct Pow{T} <: BasicSymbolic{T} base::Any = 1 exp::Any = 1 arguments::Vector{Any} = EMPTY_ARGS From 7e93ca4b91b28e47ffd6ed9d1159097e676fe2e8 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 16 Oct 2024 17:21:44 -0400 Subject: [PATCH 05/19] Define hash extension function for incorporating symtype --- src/types.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/types.jl b/src/types.jl index 711b14e5..adda3a68 100644 --- a/src/types.jl +++ b/src/types.jl @@ -309,6 +309,11 @@ function Base.hash(s::BasicSymbolic, salt::UInt)::UInt end end +hash2(s::BasicSymbolic) = hash2(s, zero(UInt)) +function hash2(s::BasicSymbolic{T}, salt::UInt)::UInt where {T} + hash(T, hash(s, salt)) +end + ### ### Constructors ### From 9be3d0368acf6640ab3c689038273437f60a8f00 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 16 Oct 2024 17:25:30 -0400 Subject: [PATCH 06/19] Hash consing for `Sym` --- src/types.jl | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/types.jl b/src/types.jl index adda3a68..06647bcc 100644 --- a/src/types.jl +++ b/src/types.jl @@ -318,8 +318,13 @@ end ### Constructors ### -function Sym{T}(name::Symbol; kw...) where T - Sym{T}(; name=name, kw...) +function Sym{T}(name::Symbol; metadata = NO_METADATA, kw...) where {T} + if metadata==NO_METADATA + s = Sym{T}(; name, kw...) + get!(wvd, hash2(s), s) + else + Sym{T}(; name, metadata, kw...) + end end function Term{T}(f, args; kw...) where T From f1a9a933ff54a06da4f6351a8da21e46cc6c2e71 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Fri, 18 Oct 2024 17:42:24 -0400 Subject: [PATCH 07/19] Test hash consing for `Sym` with different symtypes --- test/hash_consing.jl | 17 +++++++++++++++++ test/runtests.jl | 1 + 2 files changed, 18 insertions(+) create mode 100644 test/hash_consing.jl diff --git a/test/hash_consing.jl b/test/hash_consing.jl new file mode 100644 index 00000000..a59ba8ff --- /dev/null +++ b/test/hash_consing.jl @@ -0,0 +1,17 @@ +using SymbolicUtils, Test + +@testset "Sym" begin + x1 = only(@syms x) + x2 = only(@syms x) + @test x1 === x2 + x3 = only(@syms x::Float64) + @test x1 !== x3 + x4 = only(@syms x::Float64) + @test x1 !== x4 + @test x3 === x4 + x5 = only(@syms x::Int) + x6 = only(@syms x::Int) + @test x1 !== x5 + @test x3 !== x5 + @test x5 === x6 +end diff --git a/test/runtests.jl b/test/runtests.jl index 6899c184..9ea8354a 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,5 +16,6 @@ using Pkg, Test, SafeTestsets # Disabled until https://github.com/JuliaMath/SpecialFunctions.jl/issues/446 is fixed @safetestset "Fuzz" begin include("fuzz.jl") end @safetestset "Adjoints" begin include("adjoints.jl") end + @safetestset "Hash Consing" begin include("hash_consing.jl") end end end From 70de918f4cc111148414cd81a270d45eb5ed1063 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Fri, 25 Oct 2024 15:31:00 -0400 Subject: [PATCH 08/19] Feat: Incorporate `metadata` into `BasicSymbolic` hash computation --- src/types.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 06647bcc..cb0855c1 100644 --- a/src/types.jl +++ b/src/types.jl @@ -311,7 +311,7 @@ end hash2(s::BasicSymbolic) = hash2(s, zero(UInt)) function hash2(s::BasicSymbolic{T}, salt::UInt)::UInt where {T} - hash(T, hash(s, salt)) + hash(metadata(s), hash(T, hash(s, salt))) end ### From 2dac2a3bc3440f9c96166160a3fff49c90e1c523 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Fri, 25 Oct 2024 16:39:13 -0400 Subject: [PATCH 09/19] Apply hash consing also when there is metadata --- src/types.jl | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/types.jl b/src/types.jl index cb0855c1..010840d7 100644 --- a/src/types.jl +++ b/src/types.jl @@ -318,13 +318,9 @@ end ### Constructors ### -function Sym{T}(name::Symbol; metadata = NO_METADATA, kw...) where {T} - if metadata==NO_METADATA - s = Sym{T}(; name, kw...) - get!(wvd, hash2(s), s) - else - Sym{T}(; name, metadata, kw...) - end +function Sym{T}(name::Symbol; kw...) where {T} + s = Sym{T}(; name, kw...) + get!(wvd, hash2(s), s) end function Term{T}(f, args; kw...) where T From c2d85c30e9480fba9836f78a8c458291fd9ea88a Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Fri, 25 Oct 2024 17:13:35 -0400 Subject: [PATCH 10/19] Create flyweight factory for `BasicSymbolic` --- src/types.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 010840d7..2aca744b 100644 --- a/src/types.jl +++ b/src/types.jl @@ -318,9 +318,13 @@ end ### Constructors ### +function BasicSymbolic(s::BasicSymbolic)::BasicSymbolic + get!(wvd, hash2(s), s) +end + function Sym{T}(name::Symbol; kw...) where {T} s = Sym{T}(; name, kw...) - get!(wvd, hash2(s), s) + BasicSymbolic(s) end function Term{T}(f, args; kw...) where T From 89572909a92143fee9bb9531ef2540473051916f Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 11:49:45 -0500 Subject: [PATCH 11/19] Add `isequal2` function for checking metadata comparison --- src/types.jl | 20 ++++++++++++++++++++ test/basics.jl | 9 ++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 2aca744b..01cfef07 100644 --- a/src/types.jl +++ b/src/types.jl @@ -267,6 +267,26 @@ function _isequal(a, b, E) end end +""" +$(TYPEDSIGNATURES) + +Checks for equality between two `BasicSymbolic` objects, considering both their +values and metadata. + +The default `Base.isequal` function for `BasicSymbolic` only compares their expressions +and ignores metadata. This does not help deal with hash collisions when metadata is +relevant for distinguishing expressions, particularly in hashing contexts. This function +provides a stricter equality check that includes metadata comparison, preventing +such collisions. + +Modifying `Base.isequal` directly breaks numerous tests in `SymbolicUtils.jl` and +downstream packages like `ModelingToolkit.jl`, hence the need for this separate +function. +""" +function isequal2(a::BasicSymbolic, b::BasicSymbolic)::Bool + isequal(a, b) && isequal(metadata(a), metadata(b)) +end + Base.one( s::Symbolic) = one( symtype(s)) Base.zero(s::Symbolic) = zero(symtype(s)) diff --git a/test/basics.jl b/test/basics.jl index 1402f9ac..93dfbd42 100644 --- a/test/basics.jl +++ b/test/basics.jl @@ -1,4 +1,4 @@ -using SymbolicUtils: Symbolic, Sym, FnType, Term, Add, Mul, Pow, symtype, operation, arguments, issym, isterm, BasicSymbolic, term +using SymbolicUtils: Symbolic, Sym, FnType, Term, Add, Mul, Pow, symtype, operation, arguments, issym, isterm, BasicSymbolic, term, isequal2 using SymbolicUtils using IfElse: ifelse using Setfield @@ -336,6 +336,13 @@ end @test !isequal(a, missing) @test !isequal(missing, b) + + a1 = setmetadata(a, Ctx1, "meta_1") + a2 = setmetadata(a, Ctx1, "meta_1") + a3 = setmetadata(a, Ctx2, "meta_2") + @test !isequal2(a, a1) + @test isequal2(a1, a2) + @test !isequal2(a1, a3) end @testset "subtyping" begin From 27798568b68aaad46a6e46e37934b653cd9df008 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:12:17 -0500 Subject: [PATCH 12/19] Handle hash collision with customized `isequal2` --- src/types.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 01cfef07..429dc07d 100644 --- a/src/types.jl +++ b/src/types.jl @@ -339,7 +339,13 @@ end ### function BasicSymbolic(s::BasicSymbolic)::BasicSymbolic - get!(wvd, hash2(s), s) + h = hash2(s) + t = get!(wvd, h, s) + if t === s || isequal2(t, s) + return t + else + return s + end end function Sym{T}(name::Symbol; kw...) where {T} From d36198f57ce192b1169dac67a1e1d867a36f898a Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:12:46 -0500 Subject: [PATCH 13/19] Call outer constructor for `Sym` in `ConstructionBase.setproperties` --- src/types.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 429dc07d..91cb5c8f 100644 --- a/src/types.jl +++ b/src/types.jl @@ -94,7 +94,10 @@ const SIMPLIFIED = 0x01 << 0 function ConstructionBase.setproperties(obj::BasicSymbolic{T}, patch::NamedTuple)::BasicSymbolic{T} where T nt = getproperties(obj) nt_new = merge(nt, patch) - Unityper.rt_constructor(obj){T}(;nt_new...) + @compactified obj::BasicSymbolic begin + Sym => Sym{T}(nt_new.name; nt_new...) + _ => Unityper.rt_constructor(obj){T}(;nt_new...) + end end ### From cf937b0ac65c2e08a4067e2f6545ecd5a50b1bc2 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:14:09 -0500 Subject: [PATCH 14/19] Test hash consing for `Sym` with metadata --- test/hash_consing.jl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/hash_consing.jl b/test/hash_consing.jl index a59ba8ff..aaf97997 100644 --- a/test/hash_consing.jl +++ b/test/hash_consing.jl @@ -1,5 +1,8 @@ using SymbolicUtils, Test +struct Ctx1 end +struct Ctx2 end + @testset "Sym" begin x1 = only(@syms x) x2 = only(@syms x) @@ -14,4 +17,10 @@ using SymbolicUtils, Test @test x1 !== x5 @test x3 !== x5 @test x5 === x6 + + xm1 = setmetadata(x1, Ctx1, "meta_1") + xm2 = setmetadata(x1, Ctx1, "meta_1") + @test xm1 === xm2 + xm3 = setmetadata(x1, Ctx2, "meta_2") + @test xm1 !== xm3 end From 765293a5843bb36f2e5332662a1fd157a4b96733 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:34:35 -0500 Subject: [PATCH 15/19] Add docstring for the flyweight factory function --- src/types.jl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/types.jl b/src/types.jl index 91cb5c8f..3e273d27 100644 --- a/src/types.jl +++ b/src/types.jl @@ -341,6 +341,27 @@ end ### Constructors ### +""" +$(TYPEDSIGNATURES) + +Implements hash consing (flyweight design pattern) for `BasicSymbolic` objects. + +This function checks if an equivalent `BasicSymbolic` object already exists. It uses a +custom hash function (`hash2`) incorporating metadata and symtypes to search for existing +objects in a `WeakValueDict` (`wvd`). Due to the possibility of hash collisions (where +different objects produce the same hash), a custom equality check (`isequal2`) which +includes metadata comparison, is used to confirm the equivalence of objects with matching +hashes. If an equivalent object is found, the existing object is returned; otherwise, the +input `s` is returned. This reduces memory usage, improves compilation time for runtime +code generation, and supports built-in common subexpression elimination, particularly when +working with symbolic objects with metadata. + +Using a `WeakValueDict` ensures that only weak references to `BasicSymbolic` objects are +stored, allowing objects that are no longer strongly referenced to be garbage collected. +Custom functions `hash2` and `isequal2` are used instead of `Base.hash` and `Base.isequal` +to accommodate metadata without disrupting existing tests reliant on the original behavior +of those functions. +""" function BasicSymbolic(s::BasicSymbolic)::BasicSymbolic h = hash2(s) t = get!(wvd, h, s) From 84a05960c0a005964c336ada232403f319543f71 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:51:28 -0500 Subject: [PATCH 16/19] Add docstring for `hash2` --- src/types.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/types.jl b/src/types.jl index 3e273d27..fa35d2e1 100644 --- a/src/types.jl +++ b/src/types.jl @@ -332,6 +332,18 @@ function Base.hash(s::BasicSymbolic, salt::UInt)::UInt end end +""" +$(TYPEDSIGNATURES) + +Calculates a hash value for a `BasicSymbolic` object, incorporating both its metadata and +symtype. + +This function provides an alternative hashing strategy to `Base.hash` for `BasicSymbolic` +objects. Unlike `Base.hash`, which only considers the expression structure, `hash2` also +includes the metadata and symtype in the hash calculation. This can be beneficial for hash +consing, allowing for more effective deduplication of symbolically equivalent expressions +with different metadata or symtypes. +""" hash2(s::BasicSymbolic) = hash2(s, zero(UInt)) function hash2(s::BasicSymbolic{T}, salt::UInt)::UInt where {T} hash(metadata(s), hash(T, hash(s, salt))) From 187ce4538f9a14b7917ac84d343270ed0b430446 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Tue, 5 Nov 2024 12:56:19 -0500 Subject: [PATCH 17/19] Add comment explaining why calling outer constructor in `setproperties` --- src/types.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types.jl b/src/types.jl index fa35d2e1..831154bd 100644 --- a/src/types.jl +++ b/src/types.jl @@ -94,6 +94,7 @@ const SIMPLIFIED = 0x01 << 0 function ConstructionBase.setproperties(obj::BasicSymbolic{T}, patch::NamedTuple)::BasicSymbolic{T} where T nt = getproperties(obj) nt_new = merge(nt, patch) + # Call outer constructor because hash consing cannot be applied in inner constructor @compactified obj::BasicSymbolic begin Sym => Sym{T}(nt_new.name; nt_new...) _ => Unityper.rt_constructor(obj){T}(;nt_new...) From 55ca2ec991503f213f44810730b5a07098827a56 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Wed, 6 Nov 2024 08:13:50 -0500 Subject: [PATCH 18/19] Refactor: Make `wvd` a constant global --- src/types.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 831154bd..a7dc9b3e 100644 --- a/src/types.jl +++ b/src/types.jl @@ -77,7 +77,7 @@ function exprtype(x::BasicSymbolic) end end -wvd = WeakValueDict{UInt, BasicSymbolic}() +const wvd = WeakValueDict{UInt, BasicSymbolic}() # Same but different error messages @noinline error_on_type() = error("Internal error: unreachable reached!") From 13b642b5944e1bcebc2223c42f74e02f45a444e3 Mon Sep 17 00:00:00 2001 From: "Bowen S. Zhu" Date: Thu, 7 Nov 2024 09:28:08 -0500 Subject: [PATCH 19/19] Rename the `isequal2` function to `isequal_with_metadata` for clarity. --- src/types.jl | 22 +++++++++++----------- test/basics.jl | 8 ++++---- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/types.jl b/src/types.jl index a7dc9b3e..8a8c7361 100644 --- a/src/types.jl +++ b/src/types.jl @@ -287,7 +287,7 @@ Modifying `Base.isequal` directly breaks numerous tests in `SymbolicUtils.jl` an downstream packages like `ModelingToolkit.jl`, hence the need for this separate function. """ -function isequal2(a::BasicSymbolic, b::BasicSymbolic)::Bool +function isequal_with_metadata(a::BasicSymbolic, b::BasicSymbolic)::Bool isequal(a, b) && isequal(metadata(a), metadata(b)) end @@ -362,23 +362,23 @@ Implements hash consing (flyweight design pattern) for `BasicSymbolic` objects. This function checks if an equivalent `BasicSymbolic` object already exists. It uses a custom hash function (`hash2`) incorporating metadata and symtypes to search for existing objects in a `WeakValueDict` (`wvd`). Due to the possibility of hash collisions (where -different objects produce the same hash), a custom equality check (`isequal2`) which -includes metadata comparison, is used to confirm the equivalence of objects with matching -hashes. If an equivalent object is found, the existing object is returned; otherwise, the -input `s` is returned. This reduces memory usage, improves compilation time for runtime -code generation, and supports built-in common subexpression elimination, particularly when -working with symbolic objects with metadata. +different objects produce the same hash), a custom equality check (`isequal_with_metadata`) +which includes metadata comparison, is used to confirm the equivalence of objects with +matching hashes. If an equivalent object is found, the existing object is returned; +otherwise, the input `s` is returned. This reduces memory usage, improves compilation time +for runtime code generation, and supports built-in common subexpression elimination, +particularly when working with symbolic objects with metadata. Using a `WeakValueDict` ensures that only weak references to `BasicSymbolic` objects are stored, allowing objects that are no longer strongly referenced to be garbage collected. -Custom functions `hash2` and `isequal2` are used instead of `Base.hash` and `Base.isequal` -to accommodate metadata without disrupting existing tests reliant on the original behavior -of those functions. +Custom functions `hash2` and `isequal_with_metadata` are used instead of `Base.hash` and +`Base.isequal` to accommodate metadata without disrupting existing tests reliant on the +original behavior of those functions. """ function BasicSymbolic(s::BasicSymbolic)::BasicSymbolic h = hash2(s) t = get!(wvd, h, s) - if t === s || isequal2(t, s) + if t === s || isequal_with_metadata(t, s) return t else return s diff --git a/test/basics.jl b/test/basics.jl index 93dfbd42..a5f0b514 100644 --- a/test/basics.jl +++ b/test/basics.jl @@ -1,4 +1,4 @@ -using SymbolicUtils: Symbolic, Sym, FnType, Term, Add, Mul, Pow, symtype, operation, arguments, issym, isterm, BasicSymbolic, term, isequal2 +using SymbolicUtils: Symbolic, Sym, FnType, Term, Add, Mul, Pow, symtype, operation, arguments, issym, isterm, BasicSymbolic, term, isequal_with_metadata using SymbolicUtils using IfElse: ifelse using Setfield @@ -340,9 +340,9 @@ end a1 = setmetadata(a, Ctx1, "meta_1") a2 = setmetadata(a, Ctx1, "meta_1") a3 = setmetadata(a, Ctx2, "meta_2") - @test !isequal2(a, a1) - @test isequal2(a1, a2) - @test !isequal2(a1, a3) + @test !isequal_with_metadata(a, a1) + @test isequal_with_metadata(a1, a2) + @test !isequal_with_metadata(a1, a3) end @testset "subtyping" begin