From 1457462a77d9eea27a88cb7fc45886279377d6b9 Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Mon, 10 Nov 2025 10:09:29 +0100 Subject: [PATCH 1/7] another mechanism for rules draft --- src/SymbolicUtils.jl | 2 + src/rule2.jl | 128 ++++++++++++++++++++++++++++++++++++ test/rule2.jl | 153 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 src/rule2.jl create mode 100644 test/rule2.jl diff --git a/src/SymbolicUtils.jl b/src/SymbolicUtils.jl index 89486e8c8..c4656bcb3 100644 --- a/src/SymbolicUtils.jl +++ b/src/SymbolicUtils.jl @@ -136,6 +136,8 @@ include("rule.jl") include("matchers.jl") include("rewriters.jl") +include("rule2.jl") + # Convert to an efficient multi-variate polynomial representation import DynamicPolynomials export expand diff --git a/src/rule2.jl b/src/rule2.jl new file mode 100644 index 000000000..cf58888af --- /dev/null +++ b/src/rule2.jl @@ -0,0 +1,128 @@ + +# function create_n_expressions(n::Int)::Nothing +# @syms x +# exps = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}[] +# for i in 1:n +# e = :(∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x)) => +# 1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2")) +# push!(exps, e) +# end +# end +# +# function create_n_rules(n::Int)::Nothing +# @syms x ∫(var1, var2) +# rules = Rule[] +# for i in 1:n +# r = @rule ∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x)) => +# 1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2") +# push!(rules, r) +# end +# end + +# empty base imuttable dict of the correct type + +const SymsType = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal} +const MatchDict = ImmutableDict{Symbol, SymsType} +const NO_MATCHES = MatchDict() # or {Symbol, Union{Symbol, Real}} ? +const FAIL_DICT = MatchDict(:_fail,0) + +function merge_immutable_dicts(d1::MatchDict, d2::MatchDict)::MatchDict + result = d1 + for (k, v) in d2 + result = Base.ImmutableDict(result, k, v) + end + return result +end + +@syms ∫(var1, var2) +""" +against is a quoted expression +to_check is a symbolic expression + +return value is the dictionary containing the bindings matches found. +1) if a mismatch is found, FAIL_DICT is returned. +2) if no mismatch is found but no matches either (for example in mathcing ^2), a NO_MATCHES is returned. +3) otherwise the dictionary of matches is returned that could look like: +Base.ImmutableDict{Symbol, SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}}(:x => a, :y => b) + +""" +function check_expr_r(to_check::SymsType, against::Expr)::MatchDict + # print("Checking "); show(to_check); print(" against "); show(against); println() + if against.head != :call + error("It happened") #it should never happen + end + # against is a slot or defslot + if against.head == :call && against.args[1] == :(~) + # TODO add predicates + return MatchDict(against.args[2], to_check) + end + # against is a call, check operation and arguments + !iscall(to_check) && return FAIL_DICT + # check operation + (operation(to_check) !== eval(against.args[1])) && return FAIL_DICT + # check arguments + arg_to_check = arguments(to_check) + arg_against = against.args[2:end] + (length(arg_to_check) != length(arg_against)) && return FAIL_DICT + + bind_args = NO_MATCHES + for (a, b) in zip(arg_to_check, arg_against) + bind_arg = check_expr_r(a, b) + if bind_arg===FAIL_DICT + return FAIL_DICT + elseif bind_arg!==NO_MATCHES + bind_args = merge_immutable_dicts(bind_args, bind_arg) + end + # if bind_arg===NO_MATCHES continue to next + end + return bind_args +end + +function check_expr_r(to_check::SymsType, against::Real)::MatchDict + # print("Checking "); show(to_check); print(" against the real "); show(against); println() + unw = unwrap_const(to_check) + # if we have literal numbers in the rule + if isa(unw, Real) + if unw!==against + return FAIL_DICT + end + return NO_MATCHES + end + # else always match + return MatchDict(to_check, against) +end + +""" + +""" +function rewrite(matches::MatchDict, rhs::Expr)::SymsType + if rhs.head != :call + error("It happened") #it should never happen + end + # rhs is a slot or defslot + if rhs.head == :call && rhs.args[1] == :(~) + var_name = rhs.args[2] + if haskey(matches, var_name) + return matches[var_name] + else + error("No match found for variable $(var_name)") #it should never happen + end + end + # rhs is a call, reconstruct it + op = eval(rhs.args[1]) + args = SymsType[] + for a in rhs.args[2:end] + push!(args, rewrite(matches, a)) + end + return op(args...) +end + +function rewrite(matches::MatchDict, rhs::Real)::SymsType + return rhs +end + +function rule2(rule::Pair{Expr, Expr}, exp::SymsType)::Union{SymsType, Nothing} + m = check_expr_r(exp, rule.first) + m===FAIL_DICT && return nothing + return rewrite(m, rule.second) +end diff --git a/test/rule2.jl b/test/rule2.jl new file mode 100644 index 000000000..3ef3d28f9 --- /dev/null +++ b/test/rule2.jl @@ -0,0 +1,153 @@ + + +function int_and_subst(expr::SymsType, var::SymsType, old::SymsType, new::SymsType, tag::String)::SymsType + print("int_and_subst called with expr: "); show(expr); println() + print(" var: "); show(var); println() + print(" old: "); show(old); println() + print(" new: "); show(new); println() + print(" tag: "); show(tag); println() + return expr #dummy +end + + +function generate_random_expression_r(depth::Int, syms::Vector{SymsType}, operations::Vector{Symbol})::SymsType + if depth == 0 + return syms[rand(1:length(syms))] + end + op = operations[rand(1:length(operations))] + if op in [:+, :-, :*, :/, :^] + left = generate_random_expression_r(depth - 1, syms, operations) + right = generate_random_expression_r(depth - 1, syms, operations) + return eval(op)(left, right) + elseif op in [:log, :sin, :cos, :exp] + arg = generate_random_expression_r(depth - 1, syms, operations) + return eval(op)(arg) + elseif op == :∫ + var = syms[rand(1:length(syms))] + integrand = generate_random_expression_r(depth - 1, syms, operations) + return ∫(integrand, var) + else + error("Unknown operation: $op") + end +end + +function generate_random_expression() + operations = [:+, :-, :*, :/, :^, :log, :sin, :cos, :exp, :∫] + syms = [a, b, c, d, e, f, g, h] + return generate_random_expression_r(3, syms, operations) +end + +# function generate_random_rule_r2(depth::Int, syms::Vector{SymsType}, operations::Vector{Symbol})::Expr +# if depth == 0 +# choosen = syms[rand(1:length(syms))] +# return :(~($choosen)) +# end +# op = operations[rand(1:length(operations))] +# if op in [:+, :-, :*, :/, :^] +# left = generate_random_rule_r2(depth - 1, syms, operations) +# right = generate_random_rule_r2(depth - 1, syms, operations) +# return Expr(:call, op, left, right) +# elseif op in [:log, :sin, :cos, :exp] +# arg = generate_random_rule_r(depth - 1, syms, operations) +# return Expr(:call, op, arg) +# elseif op == :∫ +# var = syms[rand(1:length(syms))] +# integrand = generate_random_rule_r2(depth - 1, syms, operations) +# return Expr(:call, :∫, integrand, Expr(:call, :~, var)) +# else +# error("Unknown operation: $op") +# end +# end +# +# function generate_random_rule2() +# operations = [:+, :-, :*, :/, :^, :log, :sin, :cos, :exp, :∫] +# syms = [a, b, c, d, e, f, g, h] +# lhs = generate_random_rule_r2(3, syms, operations) +# rhs = generate_random_rule_r2(3, syms, operations) +# return (lhs, rhs) +# end +# +# function generate_random_rule1() +# operations = [:+, :-, :*, :/, :^, :log, :sin, :cos, :exp, :∫] +# syms = [a, b, c, d, e, f, g, h] +# lhs = generate_random_rule_r2(3, syms, operations) +# rhs = generate_random_rule_r2(3, syms, operations) +# r = @rule lhs => rhs +# println("Generated random rule: "); show(r); println(typeof(r)) +# return r +# end + +function testrule1(n::Int, verbose::Bool=false) + @syms x ∫(var1, var2) a b c d e f g h + rules = SymbolicUtils.Rule[] + for i in 1:n + r = @rule ∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x)) => + 1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2") + push!(rules, r) + end + + # set random seed for reproducibility + Random.seed!(1234) + for i in 1:n + rex = generate_random_expression() + verbose && print("$i) checking against expression: ", rex) + result = rules[i](rex) + if result === nothing + verbose && println(" NO MATCH") + else + verbose && println(" YES MATCH: ", result) + end + end +end + + +function testrule2(n::Int, verbose::Bool=false) + @syms x ∫(var1, var2) a b c d e f g h + rules = Pair{Expr, Expr}[] + for i in 1:n + r = (:(∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x))) => + :(1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2"))) + push!(rules, r) + end + + # set random seed for reproducibility + Random.seed!(1234) + for i in 1:n + rex = generate_random_expression() + verbose && print("$i) checking against expression: ", rex) + result = SymbolicUtils.rule2(rules[i], rex) + if result === nothing + verbose && println(" NO MATCH") + else + verbose && println(" YES MATCH: ", result) + end + end +end + + + +""" Results on macbook air m1: +julia> @benchmark testrule2(\$1000) +BenchmarkTools.Trial: 244 samples with 1 evaluation per sample. + Range (min … max): 18.481 ms … 29.089 ms ┊ GC (min … max): 0.00% … 30.02% + Time (median): 19.456 ms ┊ GC (median): 0.00% + Time (mean ± σ): 20.564 ms ± 2.652 ms ┊ GC (mean ± σ): 6.09% ± 10.54% + + ▄▆█▇▄▁ + ▇█▇██████▁▄▆▆▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▄▆▆▄▁▆▄▁▄▁▁▄▄▁▄▆▆▄▄▁▇▆▇▆▄▇▆▁▆▄ ▆ + 18.5 ms Histogram: log(frequency) by time 28.2 ms < + + Memory estimate: 13.07 MiB, allocs estimate: 356839. + +julia> @benchmark testrule1(\$1000) +BenchmarkTools.Trial: 11 samples with 1 evaluation per sample. + Range (min … max): 446.396 ms … 472.119 ms ┊ GC (min … max): 0.00% … 5.67% + Time (median): 461.125 ms ┊ GC (median): 3.27% + Time (mean ± σ): 460.506 ms ± 7.303 ms ┊ GC (mean ± σ): 3.12% ± 1.73% + + ▁ ▁ █ █ ▁ ▁ ▁ ▁ ▁ + █▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁█▁█▁▁█▁▁▁█▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁█ ▁ + 446 ms Histogram: frequency by time 472 ms < + + Memory estimate: 110.40 MiB, allocs estimate: 3393493. +""" \ No newline at end of file From c0beb9ba858133df74654fdaf4fd154873f4505e Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Mon, 10 Nov 2025 22:56:35 +0100 Subject: [PATCH 2/7] added multiple slot in rule and predicates --- src/rule2.jl | 123 +++++++++++++++++++++------------------------------ 1 file changed, 51 insertions(+), 72 deletions(-) diff --git a/src/rule2.jl b/src/rule2.jl index cf58888af..f9c32e5e1 100644 --- a/src/rule2.jl +++ b/src/rule2.jl @@ -1,95 +1,74 @@ -# function create_n_expressions(n::Int)::Nothing -# @syms x -# exps = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}[] -# for i in 1:n -# e = :(∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x)) => -# 1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2")) -# push!(exps, e) -# end -# end -# -# function create_n_rules(n::Int)::Nothing -# @syms x ∫(var1, var2) -# rules = Rule[] -# for i in 1:n -# r = @rule ∫(((~f) + (~!g)*(~x))^(~!q)*((~!a) + (~!b)*log((~!c)*((~d) + (~!e)*(~x))^(~!n)))^(~!p),(~x)) => -# 1⨸(~e)*int_and_subst(((~f)*(~x)⨸(~d))^(~q)*((~a) + (~b)*log((~c)*(~x)^(~n)))^(~p), (~x), (~x), (~d) + (~e)*(~x), "3_3_2") -# push!(rules, r) -# end -# end - -# empty base imuttable dict of the correct type - +# empty Base.ImmutableDict of the correct type const SymsType = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal} const MatchDict = ImmutableDict{Symbol, SymsType} const NO_MATCHES = MatchDict() # or {Symbol, Union{Symbol, Real}} ? const FAIL_DICT = MatchDict(:_fail,0) -function merge_immutable_dicts(d1::MatchDict, d2::MatchDict)::MatchDict - result = d1 - for (k, v) in d2 - result = Base.ImmutableDict(result, k, v) - end - return result -end - -@syms ∫(var1, var2) """ -against is a quoted expression -to_check is a symbolic expression +data is a symbolic expression, we need to check if respects the rule +rule is a quoted expression, representing part of the rule +matches is the dictionary of the matches found so far -return value is the dictionary containing the bindings matches found. +return value is a ImmutableDict 1) if a mismatch is found, FAIL_DICT is returned. -2) if no mismatch is found but no matches either (for example in mathcing ^2), a NO_MATCHES is returned. -3) otherwise the dictionary of matches is returned that could look like: +2) if no mismatch is found but no new matches either (for example in mathcing ^2), the original matches is returned +3) otherwise the dictionary of old + new ones is returned that could look like: Base.ImmutableDict{Symbol, SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}}(:x => a, :y => b) - """ -function check_expr_r(to_check::SymsType, against::Expr)::MatchDict - # print("Checking "); show(to_check); print(" against "); show(against); println() - if against.head != :call - error("It happened") #it should never happen - end - # against is a slot or defslot - if against.head == :call && against.args[1] == :(~) - # TODO add predicates - return MatchDict(against.args[2], to_check) +# TODO matches does assigment or mutation? which is faster? +function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict)::MatchDict + # print("Checking "); show(data); print(" rule "); show(rule); println() + rule.head != :call && error("It happened") #it should never happen + # rule is a slot or defslot + if rule.head == :call && rule.args[1] == :(~) + if rule.args[2] in keys(matches) # if the slot has already been matched + # check if it mached the same symbolic expression + !isequal(matches[rule.args[2]],data) && return FAIL_DICT + return matches + else # if never been matched + # if there is a predicate + if isa(rule.args[2], Expr) + rule.args[2].head != :(::) && error("it happened") # it should never happen + # check it + pred = rule.args[2].args[2] + !eval(pred)(SymbolicUtils.unwrap_const(data)) && return FAIL_DICT + return Base.ImmutableDict(matches, rule.args[2].args[1], data) + end + # if no predicate add match + return Base.ImmutableDict(matches, rule.args[2], data) + end end - # against is a call, check operation and arguments - !iscall(to_check) && return FAIL_DICT - # check operation - (operation(to_check) !== eval(against.args[1])) && return FAIL_DICT - # check arguments - arg_to_check = arguments(to_check) - arg_against = against.args[2:end] - (length(arg_to_check) != length(arg_against)) && return FAIL_DICT - - bind_args = NO_MATCHES - for (a, b) in zip(arg_to_check, arg_against) - bind_arg = check_expr_r(a, b) - if bind_arg===FAIL_DICT + # rule is a call, check operation and arguments + # - check operation + !iscall(data) && return FAIL_DICT + (Symbol(operation(data)) !== rule.args[1]) && return FAIL_DICT + # - check arguments + arg_data = arguments(data); arg_rule = rule.args[2:end]; + (length(arg_data) != length(arg_rule)) && return FAIL_DICT + for (a, b) in zip(arg_data, arg_rule) + new_matches = check_expr_r(a, b, matches) + if new_matches===FAIL_DICT return FAIL_DICT - elseif bind_arg!==NO_MATCHES - bind_args = merge_immutable_dicts(bind_args, bind_arg) end - # if bind_arg===NO_MATCHES continue to next + # else the match has been added (or confirmed) + matches = new_matches end - return bind_args + return matches end -function check_expr_r(to_check::SymsType, against::Real)::MatchDict - # print("Checking "); show(to_check); print(" against the real "); show(against); println() - unw = unwrap_const(to_check) - # if we have literal numbers in the rule +# for when the rule contains a constant, a literal number +function check_expr_r(data::SymsType, rule::Real, matches::MatchDict)::MatchDict + # print("Checking "); show(data); print(" against the real "); show(rule); println() + unw = unwrap_const(data) if isa(unw, Real) - if unw!==against + if unw!==rule return FAIL_DICT end - return NO_MATCHES + return matches end - # else always match - return MatchDict(to_check, against) + # else always fail + return FAIL_DICT end """ @@ -122,7 +101,7 @@ function rewrite(matches::MatchDict, rhs::Real)::SymsType end function rule2(rule::Pair{Expr, Expr}, exp::SymsType)::Union{SymsType, Nothing} - m = check_expr_r(exp, rule.first) + m = check_expr_r(exp, rule.first, NO_MATCHES) m===FAIL_DICT && return nothing return rewrite(m, rule.second) end From c35d39e16ff1ef67d58694e8754b83a5714fb5ed Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Mon, 10 Nov 2025 23:56:27 +0100 Subject: [PATCH 3/7] added predicates test --- test/rule2.jl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/rule2.jl b/test/rule2.jl index 3ef3d28f9..5ee006a19 100644 --- a/test/rule2.jl +++ b/test/rule2.jl @@ -150,4 +150,10 @@ BenchmarkTools.Trial: 11 samples with 1 evaluation per sample. 446 ms Histogram: frequency by time 472 ms < Memory estimate: 110.40 MiB, allocs estimate: 3393493. -""" \ No newline at end of file +""" + +function testpredicates() + @syms ∫ a + SymbolicUtils.rule2(:(∫(1 / (~x)^(~m::iseven), ~x)) => :(log(~x)*~m), ∫(1/a^3,a))===nothing + SymbolicUtils.rule2(:(∫(1 / (~x)^(~m::iseven), ~x)) => :(log(~x)*~m), ∫(1/a^2,a))!==nothing +end \ No newline at end of file From 3294fbca9ab8e4979f42041704cfb943601017f0 Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Mon, 10 Nov 2025 23:56:41 +0100 Subject: [PATCH 4/7] added commutative checks for + and * --- src/rule2.jl | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/rule2.jl b/src/rule2.jl index f9c32e5e1..67dbf67b5 100644 --- a/src/rule2.jl +++ b/src/rule2.jl @@ -15,10 +15,11 @@ return value is a ImmutableDict 2) if no mismatch is found but no new matches either (for example in mathcing ^2), the original matches is returned 3) otherwise the dictionary of old + new ones is returned that could look like: Base.ImmutableDict{Symbol, SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal}}(:x => a, :y => b) + +TODO matches does assigment or mutation? which is faster? """ -# TODO matches does assigment or mutation? which is faster? function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict)::MatchDict - # print("Checking "); show(data); print(" rule "); show(rule); println() + # print("Checking "); show(data); print(" against "); show(rule); println(" and with ", matches) rule.head != :call && error("It happened") #it should never happen # rule is a slot or defslot if rule.head == :call && rule.args[1] == :(~) @@ -46,15 +47,34 @@ function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict)::MatchDict # - check arguments arg_data = arguments(data); arg_rule = rule.args[2:end]; (length(arg_data) != length(arg_rule)) && return FAIL_DICT - for (a, b) in zip(arg_data, arg_rule) - new_matches = check_expr_r(a, b, matches) - if new_matches===FAIL_DICT - return FAIL_DICT + # commutative checks + if (rule.args[1]===:+) || (rule.args[1]===:*) + for perm_arg_data in permutations(arg_data) # is the same if done on arg_rule right? + matches_this_perm = matches + goto_next_perm::Bool = false + for (a, b) in zip(perm_arg_data, arg_rule) + matches_this_perm = check_expr_r(a, b, matches_this_perm) + if matches_this_perm===FAIL_DICT + goto_next_perm = true + break + end + # else the match has been added (or confirmed) + end + !goto_next_perm && return matches_this_perm + # else try with next perm + end + # if all perm failed + return FAIL_DICT + else + for (a, b) in zip(arg_data, arg_rule) + matches = check_expr_r(a, b, matches) + if matches===FAIL_DICT + return FAIL_DICT + end + # else the match has been added (or confirmed) end - # else the match has been added (or confirmed) - matches = new_matches + return matches end - return matches end # for when the rule contains a constant, a literal number @@ -62,9 +82,7 @@ function check_expr_r(data::SymsType, rule::Real, matches::MatchDict)::MatchDict # print("Checking "); show(data); print(" against the real "); show(rule); println() unw = unwrap_const(data) if isa(unw, Real) - if unw!==rule - return FAIL_DICT - end + unw!==rule && return FAIL_DICT return matches end # else always fail @@ -72,7 +90,10 @@ function check_expr_r(data::SymsType, rule::Real, matches::MatchDict)::MatchDict end """ +matches is the dictionary +rhs is the expression to be rewritten into +TODO investigate foo in rhs not working """ function rewrite(matches::MatchDict, rhs::Expr)::SymsType if rhs.head != :call From 88b37be2cad857104e66b66ae484df3de94081a3 Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Tue, 11 Nov 2025 20:13:28 +0100 Subject: [PATCH 5/7] refactor --- src/rule2.jl | 57 ++++++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/rule2.jl b/src/rule2.jl index 67dbf67b5..639513402 100644 --- a/src/rule2.jl +++ b/src/rule2.jl @@ -1,4 +1,3 @@ - # empty Base.ImmutableDict of the correct type const SymsType = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal} const MatchDict = ImmutableDict{Symbol, SymsType} @@ -18,15 +17,15 @@ Base.ImmutableDict{Symbol, SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbo TODO matches does assigment or mutation? which is faster? """ -function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict)::MatchDict - # print("Checking "); show(data); print(" against "); show(rule); println(" and with ", matches) - rule.head != :call && error("It happened") #it should never happen +function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict) + print("Checking "); show(data); print(" against "); show(rule); println(" and with ", matches) + rule.head != :call && error("It happened, rule head is not a call") #it should never happen # rule is a slot or defslot if rule.head == :call && rule.args[1] == :(~) if rule.args[2] in keys(matches) # if the slot has already been matched # check if it mached the same symbolic expression - !isequal(matches[rule.args[2]],data) && return FAIL_DICT - return matches + !isequal(matches[rule.args[2]],data) && return FAIL_DICT::MatchDict + return matches::MatchDict else # if never been matched # if there is a predicate if isa(rule.args[2], Expr) @@ -34,47 +33,43 @@ function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict)::MatchDict # check it pred = rule.args[2].args[2] !eval(pred)(SymbolicUtils.unwrap_const(data)) && return FAIL_DICT - return Base.ImmutableDict(matches, rule.args[2].args[1], data) + return Base.ImmutableDict(matches, rule.args[2].args[1], data)::MatchDict end # if no predicate add match - return Base.ImmutableDict(matches, rule.args[2], data) + return Base.ImmutableDict(matches, rule.args[2], data)::MatchDict end end # rule is a call, check operation and arguments # - check operation - !iscall(data) && return FAIL_DICT - (Symbol(operation(data)) !== rule.args[1]) && return FAIL_DICT + !iscall(data) && return FAIL_DICT::MatchDict + (Symbol(operation(data)) !== rule.args[1]) && return FAIL_DICT::MatchDict # - check arguments arg_data = arguments(data); arg_rule = rule.args[2:end]; - (length(arg_data) != length(arg_rule)) && return FAIL_DICT + (length(arg_data) != length(arg_rule)) && return FAIL_DICT::MatchDict # commutative checks if (rule.args[1]===:+) || (rule.args[1]===:*) for perm_arg_data in permutations(arg_data) # is the same if done on arg_rule right? - matches_this_perm = matches - goto_next_perm::Bool = false - for (a, b) in zip(perm_arg_data, arg_rule) - matches_this_perm = check_expr_r(a, b, matches_this_perm) - if matches_this_perm===FAIL_DICT - goto_next_perm = true - break - end - # else the match has been added (or confirmed) - end - !goto_next_perm && return matches_this_perm + matches_this_perm = foo(perm_arg_data, arg_rule, matches) + matches_this_perm!==FAIL_DICT && return matches_this_perm::MatchDict # else try with next perm end # if all perm failed - return FAIL_DICT + return FAIL_DICT::MatchDict else - for (a, b) in zip(arg_data, arg_rule) - matches = check_expr_r(a, b, matches) - if matches===FAIL_DICT - return FAIL_DICT - end - # else the match has been added (or confirmed) - end - return matches + return foo(arg_data, arg_rule, matches)::MatchDict + end +end + +# TODO add types +function foo(arg_data, arg_rule, matches) + for (a, b) in zip(arg_data, arg_rule) + matches = check_expr_r(a, b, matches) + if matches===FAIL_DICT + return FAIL_DICT + end + # else the match has been added (or confirmed) end + return matches end # for when the rule contains a constant, a literal number From 7e92a563ebc18c83e680765edf2a219b007e31d3 Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Tue, 11 Nov 2025 22:03:18 +0100 Subject: [PATCH 6/7] defslot --- src/rule2.jl | 86 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/src/rule2.jl b/src/rule2.jl index 639513402..a755c32b7 100644 --- a/src/rule2.jl +++ b/src/rule2.jl @@ -3,6 +3,7 @@ const SymsType = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{ const MatchDict = ImmutableDict{Symbol, SymsType} const NO_MATCHES = MatchDict() # or {Symbol, Union{Symbol, Real}} ? const FAIL_DICT = MatchDict(:_fail,0) +const op_map = Dict(:+ => 0, :* => 1, :^ => 1) """ data is a symbolic expression, we need to check if respects the rule @@ -18,18 +19,17 @@ Base.ImmutableDict{Symbol, SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbo TODO matches does assigment or mutation? which is faster? """ function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict) - print("Checking "); show(data); print(" against "); show(rule); println(" and with ", matches) + # println("Checking ",data," against ",rule,", with matches: ",[m for m in matches]...) rule.head != :call && error("It happened, rule head is not a call") #it should never happen - # rule is a slot or defslot + # rule is a slot if rule.head == :call && rule.args[1] == :(~) if rule.args[2] in keys(matches) # if the slot has already been matched # check if it mached the same symbolic expression !isequal(matches[rule.args[2]],data) && return FAIL_DICT::MatchDict return matches::MatchDict else # if never been matched - # if there is a predicate + # if there is a predicate rule.args[2] is a expression with :: if isa(rule.args[2], Expr) - rule.args[2].head != :(::) && error("it happened") # it should never happen # check it pred = rule.args[2].args[2] !eval(pred)(SymbolicUtils.unwrap_const(data)) && return FAIL_DICT @@ -39,49 +39,69 @@ function check_expr_r(data::SymsType, rule::Expr, matches::MatchDict) return Base.ImmutableDict(matches, rule.args[2], data)::MatchDict end end - # rule is a call, check operation and arguments - # - check operation - !iscall(data) && return FAIL_DICT::MatchDict - (Symbol(operation(data)) !== rule.args[1]) && return FAIL_DICT::MatchDict - # - check arguments - arg_data = arguments(data); arg_rule = rule.args[2:end]; - (length(arg_data) != length(arg_rule)) && return FAIL_DICT::MatchDict - # commutative checks - if (rule.args[1]===:+) || (rule.args[1]===:*) - for perm_arg_data in permutations(arg_data) # is the same if done on arg_rule right? - matches_this_perm = foo(perm_arg_data, arg_rule, matches) - matches_this_perm!==FAIL_DICT && return matches_this_perm::MatchDict - # else try with next perm + # if there is a deflsot in the arguments + p=findfirst(a->isa(a, Expr) && a.args[1] == :~ && isa(a.args[2], Expr) && a.args[2].args[1] == :!,rule.args[2:end]) + if p!==nothing + # build rule expr without defslot and check it + if p==1 + newr = Expr(:call, rule.args[1], :(~$(rule.args[2].args[2].args[2])), rule.args[3]) + elseif p==2 + newr = Expr(:call, rule.args[1], rule.args[2], :(~$(rule.args[3].args[2].args[2]))) + else + error("defslot error")# it should never happen end - # if all perm failed - return FAIL_DICT::MatchDict + rv = check_expr_r(data, newr, matches) + rv!==FAIL_DICT && return rv::MatchDict + # if no normal match, check only the non-defslot part of the rule + rv = check_expr_r(data, rule.args[p==1 ? 3 : 2], matches) + # if yes match + rv!==FAIL_DICT && return Base.ImmutableDict(rv, rule.args[p+1].args[2].args[2], get(op_map, rule.args[1], -1))::MatchDict + return FAIL_DICT::MatchDict else - return foo(arg_data, arg_rule, matches)::MatchDict + # rule is a call, check operation and arguments + # - check operation + !iscall(data) && return FAIL_DICT::MatchDict + (Symbol(operation(data)) !== rule.args[1]) && return FAIL_DICT::MatchDict + # - check arguments + arg_data = arguments(data); arg_rule = rule.args[2:end]; + (length(arg_data) != length(arg_rule)) && return FAIL_DICT::MatchDict + if (rule.args[1]===:+) || (rule.args[1]===:*) + # commutative checks + for perm_arg_data in permutations(arg_data) # is the same if done on arg_rule right? + matches_this_perm = ceoaa(perm_arg_data, arg_rule, matches) + matches_this_perm!==FAIL_DICT && return matches_this_perm::MatchDict + # else try with next perm + end + # if all perm failed + return FAIL_DICT::MatchDict + else + # normal checks + return ceoaa(arg_data, arg_rule, matches)::MatchDict + end end end -# TODO add types -function foo(arg_data, arg_rule, matches) +# check expression of all arguments +function ceoaa(arg_data, arg_rule, matches::MatchDict) + println(typeof(arg_data), typeof(arg_rule)) for (a, b) in zip(arg_data, arg_rule) - matches = check_expr_r(a, b, matches) - if matches===FAIL_DICT - return FAIL_DICT - end - # else the match has been added (or confirmed) + matches = check_expr_r(a, b, matches) + matches===FAIL_DICT && return FAIL_DICT::MatchDict + # else the match has been added (or not added but confirmed) end - return matches + return matches::MatchDict end # for when the rule contains a constant, a literal number -function check_expr_r(data::SymsType, rule::Real, matches::MatchDict)::MatchDict - # print("Checking "); show(data); print(" against the real "); show(rule); println() +function check_expr_r(data::SymsType, rule::Real, matches::MatchDict) + # println("Checking ",data," against the real ",rule,", with matches: ",[m for m in matches]...) unw = unwrap_const(data) if isa(unw, Real) - unw!==rule && return FAIL_DICT - return matches + unw!==rule && return FAIL_DICT::MatchDict + return matches::MatchDict end # else always fail - return FAIL_DICT + return FAIL_DICT::MatchDict end """ From 366fd3ef7e78aa0ed250033406bac0b700b8b7ce Mon Sep 17 00:00:00 2001 From: Bumblebee00 Date: Tue, 11 Nov 2025 22:24:03 +0100 Subject: [PATCH 7/7] changed tyoe of syms --- src/rule2.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rule2.jl b/src/rule2.jl index a755c32b7..eb76c9015 100644 --- a/src/rule2.jl +++ b/src/rule2.jl @@ -1,5 +1,5 @@ # empty Base.ImmutableDict of the correct type -const SymsType = SymbolicUtils.BasicSymbolicImpl.var"typeof(BasicSymbolicImpl)"{SymReal} +const SymsType = BasicSymbolic{SymReal} const MatchDict = ImmutableDict{Symbol, SymsType} const NO_MATCHES = MatchDict() # or {Symbol, Union{Symbol, Real}} ? const FAIL_DICT = MatchDict(:_fail,0)