Skip to content

Commit

Permalink
Merge pull request #69 from Herb-AI/bug/propagate-on-filled-holes
Browse files Browse the repository at this point in the history
Fix propagate on filled holes
  • Loading branch information
ReubenJ authored Jan 28, 2025
2 parents 29db752 + 9bf2bd7 commit b7ca7be
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 4 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "HerbConstraints"
uuid = "1fa96474-3206-4513-b4fa-23913f296dfc"
authors = ["Jaap de Jong <[email protected]>"]
version = "0.2.4"
version = "0.2.5"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
Expand Down
16 changes: 14 additions & 2 deletions src/lessthanorequal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,25 @@ function make_less_than_or_equal!(
@assert isfeasible(solver)
@match (isfilled(hole1), isfilled(hole2)) begin
(true, true) => begin
#(RuleNode, RuleNode)
#(RuleNode | Hole [domain size == 1], RuleNode | Hole [domain size == 1])
if get_rule(hole1) < get_rule(hole2)
return LessThanOrEqualSuccessLessThan()
elseif get_rule(hole1) > get_rule(hole2)
return LessThanOrEqualHardFail()
end
return make_less_than_or_equal!(solver, hole1.children, hole2.children, guards)

# domain of hole must have been 1 because it `isfilled`
# but still has no children, so there's nothing more that
# can be deduced before the hole is simplified
# ideally, these holes should be simplified before making
# it to the lessthanorequal step.
if hole1 isa Hole && !isempty(get_children(hole2))
return LessThanOrEqualSoftFail(hole1)
elseif hole2 isa Hole && !isempty(get_children(hole1))
return LessThanOrEqualSoftFail(hole2)
end

return make_less_than_or_equal!(solver, get_children(hole1), get_children(hole2), guards)
end
(true, false) => begin
#(RuleNode, AbstractHole)
Expand Down
14 changes: 13 additions & 1 deletion src/patternmatch.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,23 @@ The '(RuleNode, AbstractHole)' case could still include two nodes of type `Abstr
"""
function pattern_match(h1::Union{RuleNode, AbstractHole}, h2::Union{RuleNode, AbstractHole}, vars::Dict{Symbol, AbstractRuleNode})::PatternMatchResult
@match (isfilled(h1), isfilled(h2)) begin
#(RuleNode, RuleNode)
#(RuleNode | Hole [domain size == 1], RuleNode | Hole [domain size == 1])
(true, true) => begin
if get_rule(h1) get_rule(h2)
return PatternMatchHardFail()
end

# domain of hole must have been 1 because it `isfilled`
# but still has no children, so there's nothing more that
# can be deduced before the hole is simplified
# ideally, these holes should be simplified before making
# it to the pattern matching step.
if h1 isa Hole && !isempty(get_children(h2))
return PatternMatchSoftFail(h1)
elseif h2 isa Hole && !isempty(get_children(h1))
return PatternMatchSoftFail(h2)
end

return pattern_match(get_children(h1), get_children(h2), vars)
end

Expand Down
3 changes: 3 additions & 0 deletions src/solver/generic_solver/treemanipulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ function simplify_hole!(solver::GenericSolver, path::Vector{Int})

#the hole will be simplified and replaced with a `new_node`
if !isnothing(new_node)
# Ideally, we should try to simplify holes with domain size of 1 here
# before substituting. This would remove some duplicated logic when calling
# pattern_match and lessthanorequal
substitute!(solver, path, new_node, is_domain_increasing=false)
for i 1:length(new_node.children)
# try to simplify the new children
Expand Down
34 changes: 34 additions & 0 deletions test/test_lessthanorequal.jl
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,40 @@ using HerbCore, HerbGrammar
@test HerbConstraints.make_less_than_or_equal!(solver, left, right) isa HerbConstraints.LessThanOrEqualSoftFail
end

@testset "left hole softfails chemistry grammar" begin
grammar = @csgrammar begin
Species = "A" #1
Species = "B" #2

SpeciesList = Species #3
SpeciesList = SpeciesList + SpeciesList #4
Reaction = SpeciesList --> SpeciesList #5

ReactionList = Reaction #6
ReactionList = ReactionList + ReactionList #7
end

left = Hole(BitVector((0, 0, 0, 0, 1, 0, 0)))
right = Hole(BitVector((0, 0, 0, 0, 1, 0, 0)))

solver = GenericSolver(grammar, :ReactionList)
tree = RuleNode(7, [
RuleNode(6, [
left
]),
RuleNode(6, [right])
])

leftpath = get_path(tree, left)
rightpath = get_path(tree, right)
new_state!(solver, tree)
left = get_node_at_location(solver, leftpath) #leftnode might have been simplified by `new_state!`
left = get_node_at_location(solver, rightpath) #rightnode might have been simplified by `new_state!`

@test HerbConstraints.make_less_than_or_equal!(solver, left, right) isa HerbConstraints.LessThanOrEqualSoftFail
@test HerbConstraints.make_less_than_or_equal!(solver, right, left) isa HerbConstraints.LessThanOrEqualSoftFail
end

@testset "left hole gets filled once, two holes remain" begin
left = Hole(BitVector((0, 0, 1, 1)))
right = RuleNode(3, [RuleNode(2), RuleNode(2)])
Expand Down
7 changes: 7 additions & 0 deletions test/test_pattern_match.jl
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,13 @@ using HerbGrammar
@test pattern_match(rn2, mn) isa HerbConstraints.PatternMatchSoftFail
end

@testset "PatternMatchSoftFail, 1 hole with a domain of 1, matching rulenode" begin
rn = Hole([0, 0, 0, 1])
mn = RuleNode(4, [VarNode(:a), VarNode(:a)])
@test pattern_match(rn, mn) isa HerbConstraints.PatternMatchSoftFail
@test pattern_match(mn, rn) isa HerbConstraints.PatternMatchSoftFail
end

@testset "PatternMatchSoftFail, 2 holes with valid domains" begin
rn = RuleNode(4, [Hole(BitVector((1, 1, 1, 1, 1, 1))), Hole(BitVector((1, 1, 1, 1, 1, 1)))])
mn = RuleNode(4, [RuleNode(1), RuleNode(1)])
Expand Down

2 comments on commit b7ca7be

@ReubenJ
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/123853

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.2.5 -m "<description of version>" b7ca7be544271934009a03393ace90bc3aabfdb5
git push origin v0.2.5

Please sign in to comment.