Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Divide and conquer #134

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open

Divide and conquer #134

wants to merge 21 commits into from

Conversation

pwochner
Copy link
Contributor

Description

This PR includes a basic implementation of divide and conquer functions.

Main additions:

divide_and_conquer: Main function implementing the divide-and-conquer search procedure.

divide: Breaks down a problem specification into individual subproblems, each consisting of a single input-output example.

decide: Decides if a partial program should be kept. Programs are kept if they satisfy at least one subproblem.

conquer: Combines solutions to subproblems into a global solution program using a decision tree. Helper functions for decision tree:

  • get_predicates: Generates predicates for feature tests in the decision tree (BFS iterator with provided start symbol).
  • get_features: Constructs feature matrix by evaluating every IOExample inputs on all predicates.
  • get_labels: Extracts labels for each subproblem. First partial program that satisfied the subproblem is used as label.
  • construct_final_program: Recursively constructs the final program from the decision tree.

Copy link

codecov bot commented Feb 11, 2025

Codecov Report

Attention: Patch coverage is 96.77419% with 4 lines in your changes missing coverage. Please review.

Project coverage is 75.96%. Comparing base (8fe642a) to head (1f1fe81).
Report is 6 commits behind head on master.

Files with missing lines Patch % Lines
ext/DivideAndConquerExt/conquer.jl 95.65% 3 Missing ⚠️
ext/DivideAndConquerExt/DivideAndConquerExt.jl 96.29% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #134      +/-   ##
==========================================
+ Coverage   71.26%   75.96%   +4.70%     
==========================================
  Files          20       23       +3     
  Lines         689      774      +85     
==========================================
+ Hits          491      588      +97     
+ Misses        198      186      -12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ReubenJ ReubenJ self-requested a review February 19, 2025 08:57
Copy link
Member

@ReubenJ ReubenJ left a comment

Choose a reason for hiding this comment

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

Nice work! I've added a few suggestions and questions.

Project.toml Outdated
@@ -5,6 +5,8 @@ version = "0.4.1"

[deps]
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
DecisionTree = "7806a523-6efd-50cb-b5f6-3fa6f1930dbb"
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering whether it'd be nice to follow #124 and make an extension so we don't add DecisionTree as a hard dependency of HerbSearch. The reasoning for doing that with Clingo_jll in #124 is that the package is only used for one specific functionality that not everyone is going to use (grammar refactoring). I'm thinking the divide and conquer strategy is another such case.

Comment on lines 3 to 9
struct ConditionalIfElseError <: Exception
msg::String
end

function Base.showerror(io::IO, e::ConditionalIfElseError)
print(io, e.msg)
end
Copy link
Member

Choose a reason for hiding this comment

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

Is it necessary to introduce a custom error type if we don't do anything special when printing it? I could see this being useful if you add some context to the error that shows the rule in the grammar that it was looking for (:($sym_bool ? $sym_start : $sym_start)).

# Turn dic into vector since we cannot guarantee order when iterating over dict.
ioexamples_solutions =
[
(example, [idx for idx in vec]) for (prob, vec) in problems_to_solutions for
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
(example, [idx for idx in vec]) for (prob, vec) in problems_to_solutions for
(example, vec) for (prob, vec) in problems_to_solutions for

Comment on lines 37 to 38
problems_to_solutions::Dict{Problem{Vector{T}}, Vector{Int}},
solutions::Vector{RuleNode},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
problems_to_solutions::Dict{Problem{Vector{T}}, Vector{Int}},
solutions::Vector{RuleNode},
problems_to_solutions::AbstractDict{Problem{<:AbstractVector{T}}, <:AbstractVector{Int}},
solutions::Vector{RuleNode},

try to keep types abstract so the users don't have to convert to specific Vector/Dict types in case they're using something other than the defaults.

sym_start::Symbol,
sym_constraint::Symbol,
symboltable::SymbolTable,
)::RuleNode where T <: IOExample
Copy link
Member

Choose a reason for hiding this comment

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

Since this type parameter is just used once, it can be removed and the <: IOExample bit can be moved up to the type parameter of problems_to_solutions.

Comment on lines 95 to 96
# We get the first grammar rule that has the specified `sym_constraint` and add constraint to grammar.
clearconstraints!(grammar)
Copy link
Member

Choose a reason for hiding this comment

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

First, copy the grammar here. Otherwise, the divide and conquer process will modify the grammar, and I guess that would be surprising to a user.

"""
function get_predicates(grammar::AbstractGrammar,
sym_bool::Symbol,
sym_constraint::Symbol,
Copy link
Member

Choose a reason for hiding this comment

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

I think if you switch the signature to this, you can remove the second definition of get_predicates. The bodies of the methods are the same—right? Sorry, didn't look closely enough.

Either way, can the logic that follows addconstraint! be rewritten into a single method? This way we don't have to change something twice if we need to make changes to it later on.

Comment on lines 99 to 112
domain = DomainRuleNode(grammar, rules)
addconstraint!(grammar, ContainsSubtree(domain))

iterator = BFSIterator(grammar, sym_bool)
predicates = Vector{RuleNode}()

for (i, candidate_program) ∈ enumerate(iterator)
candidate_program = freeze_state(candidate_program)
push!(predicates, candidate_program)
if i >= n_predicates
break
end
end
return predicates
Copy link
Member

Choose a reason for hiding this comment

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

You can replace these lines with a call to the other signature of get_predicates below.

predicate.
"""
function get_features(
ioexamples_solutions::Vector{Tuple{T, Vector{Int}}},
Copy link
Member

Choose a reason for hiding this comment

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

Do we need the solutions too here? Might be cleaner to pass AbstractVector{<:IOExample} directly.

@test decide(problem2, expr, symboltable) == false
end

@testset verbose = true "conquer" begin
Copy link
Member

Choose a reason for hiding this comment

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

Maybe I'm missing it, but you never call the conquer function directly here, right? It looks like the tests dissect the internal logic of the function, but if the function definition is changed, these tests might not catch it. Can you add a test to check it directly?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants