From 62785965c0d0872aa3e9d8a57f0321c8db826dee Mon Sep 17 00:00:00 2001 From: Jaap de Jong Date: Fri, 30 Jun 2023 14:05:40 +0200 Subject: [PATCH 01/75] Add main index file --- docs/documentation.jl | 21 +++++++++++++++++++++ docs/src/HerbConstraints.md | 15 +++++++++++++++ docs/src/HerbGrammar.md | 15 +++++++++++++++ docs/src/HerbSearch.md | 0 docs/src/assets/logo.svg | 1 + docs/src/index.md | 8 ++++++++ 6 files changed, 60 insertions(+) create mode 100644 docs/documentation.jl create mode 100644 docs/src/HerbConstraints.md create mode 100644 docs/src/HerbGrammar.md create mode 100644 docs/src/HerbSearch.md create mode 100644 docs/src/assets/logo.svg create mode 100644 docs/src/index.md diff --git a/docs/documentation.jl b/docs/documentation.jl new file mode 100644 index 0000000..7bf9397 --- /dev/null +++ b/docs/documentation.jl @@ -0,0 +1,21 @@ +using Documenter + +include("../src/Herb.jl") + +using .Herb + +using .Herb.HerbConstraints +using .Herb.HerbSearch +using .Herb.HerbGrammar +using .Herb.HerbData +using .Herb.HerbEvaluation + +makedocs( + sitename="Herb.jl", + pages = [ + "HerbGrammar.jl" => "HerbGrammar.md", + "HerbSearch.jl" => "HerbSearch.md", + "HerbConstraints.jl" => "HerbConstraints.md" + ] +) + diff --git a/docs/src/HerbConstraints.md b/docs/src/HerbConstraints.md new file mode 100644 index 0000000..328b6f8 --- /dev/null +++ b/docs/src/HerbConstraints.md @@ -0,0 +1,15 @@ +# HerbConstraints.jl Documentation + +```@meta +CurrentModule=HerbConstraints +``` + +```@autodocs +Modules = [HerbConstraints] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/HerbGrammar.md b/docs/src/HerbGrammar.md new file mode 100644 index 0000000..0984cfc --- /dev/null +++ b/docs/src/HerbGrammar.md @@ -0,0 +1,15 @@ +# HerbGrammar.jl Documentation + +```@meta +CurrentModule=HerbGrammar +``` + +```@autodocs +Modules = [HerbGrammar] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/HerbSearch.md b/docs/src/HerbSearch.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg new file mode 100644 index 0000000..4f084f8 --- /dev/null +++ b/docs/src/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..ed761fb --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,8 @@ +# Herb.jl Documentation + +```@meta +CurrentModule=Herb +``` + +```@contents +``` From 01af3b15e5e8d0e2f7200966171532dcb8cd2c69 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Mon, 4 Sep 2023 09:50:28 +0200 Subject: [PATCH 02/75] update documentation deps --- docs/documentation.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/documentation.jl b/docs/documentation.jl index 7bf9397..852fd1d 100644 --- a/docs/documentation.jl +++ b/docs/documentation.jl @@ -4,11 +4,11 @@ include("../src/Herb.jl") using .Herb -using .Herb.HerbConstraints -using .Herb.HerbSearch -using .Herb.HerbGrammar -using .Herb.HerbData -using .Herb.HerbEvaluation +using HerbConstraints +using HerbSearch +using HerbGrammar +using HerbData +using HerbEvaluation makedocs( sitename="Herb.jl", From 1cf808c8bbbcd3a6d9dd9587e791ffb131f42e8b Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 06:42:23 +0200 Subject: [PATCH 03/75] Update docs deployment --- docs/Project.toml | 9 +++++++++ docs/README.md | 6 ++++++ docs/documentation.jl | 21 --------------------- docs/make.jl | 36 ++++++++++++++++++++++++++++++++++++ docs/src/HerbCore.md | 15 +++++++++++++++ docs/src/HerbData.md | 15 +++++++++++++++ docs/src/HerbEvaluation.md | 15 +++++++++++++++ docs/src/index.md | 15 +++++++++++++-- 8 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 docs/Project.toml create mode 100644 docs/README.md delete mode 100644 docs/documentation.jl create mode 100644 docs/make.jl create mode 100644 docs/src/HerbCore.md create mode 100644 docs/src/HerbData.md create mode 100644 docs/src/HerbEvaluation.md diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..b3ee46d --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,9 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" +HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" +HerbEvaluation = "eb1bf938-813d-4942-ac0f-b4657a683e76" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..9dfd983 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,6 @@ +# Documentation of Herb.jl + +## Compiling the documentation: + +1. Update the respective dependencies by either updating them over the REPL or switching to their respective dev mode +2. Run `julia --project=. make.jl` to compile the entire documentation. diff --git a/docs/documentation.jl b/docs/documentation.jl deleted file mode 100644 index 852fd1d..0000000 --- a/docs/documentation.jl +++ /dev/null @@ -1,21 +0,0 @@ -using Documenter - -include("../src/Herb.jl") - -using .Herb - -using HerbConstraints -using HerbSearch -using HerbGrammar -using HerbData -using HerbEvaluation - -makedocs( - sitename="Herb.jl", - pages = [ - "HerbGrammar.jl" => "HerbGrammar.md", - "HerbSearch.jl" => "HerbSearch.md", - "HerbConstraints.jl" => "HerbConstraints.md" - ] -) - diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..f6cdfdd --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,36 @@ +using Documenter + +include("../src/Herb.jl") + +using .Herb + +using HerbConstraints +using HerbSearch +using HerbGrammar +using HerbData +using HerbEvaluation +using HerbCore + +makedocs(; + modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbEvaluation, HerbCore], + authors="SEBs", + sitename="Herb.jl", + repo="https://github.com/Herb-AI/Herb.jl", + pages = [ + "Home" => "index.md", + "Sub-Modules" => [ + "HerbGrammar.jl" => "HerbGrammar.md", + "HerbSearch.jl" => "HerbSearch.md", + "HerbConstraints.jl" => "HerbConstraints.md", + "HerbCore.jl" => "HerbData.md", + "HerbEvaluation.jl" => "HerbEvaluation.md", + "HerbData.jl" => "HerbData.md", + ], + ], +) + +deploydocs(; + repo="github.com/Herb-AI/Herb.jl.git", + devbranch="documentation", +) + diff --git a/docs/src/HerbCore.md b/docs/src/HerbCore.md new file mode 100644 index 0000000..328b6f8 --- /dev/null +++ b/docs/src/HerbCore.md @@ -0,0 +1,15 @@ +# HerbConstraints.jl Documentation + +```@meta +CurrentModule=HerbConstraints +``` + +```@autodocs +Modules = [HerbConstraints] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/HerbData.md b/docs/src/HerbData.md new file mode 100644 index 0000000..328b6f8 --- /dev/null +++ b/docs/src/HerbData.md @@ -0,0 +1,15 @@ +# HerbConstraints.jl Documentation + +```@meta +CurrentModule=HerbConstraints +``` + +```@autodocs +Modules = [HerbConstraints] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/HerbEvaluation.md b/docs/src/HerbEvaluation.md new file mode 100644 index 0000000..328b6f8 --- /dev/null +++ b/docs/src/HerbEvaluation.md @@ -0,0 +1,15 @@ +# HerbConstraints.jl Documentation + +```@meta +CurrentModule=HerbConstraints +``` + +```@autodocs +Modules = [HerbConstraints] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/index.md b/docs/src/index.md index ed761fb..bbdd81d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,8 +1,19 @@ -# Herb.jl Documentation - ```@meta CurrentModule=Herb ``` +# Herb.jl Documentation + +This is a test. + ```@contents ``` + +## Index + +This is the index. + +```@index +``` + + From 73243b9e2fbaf8d13b1abe5ab8219f4a7884b1f9 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 06:59:27 +0200 Subject: [PATCH 04/75] Add Documentation deployment --- .github/workflows/documentation.yml | 26 ++++++++++++++++++++++++++ docs/Project.toml | 3 +++ 2 files changed, 29 insertions(+) create mode 100644 .github/workflows/documentation.yml diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..dc56f76 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,26 @@ +name: Documentation + +on: + push: + branches: + - documentation # update to match your development branch (master, main, dev, trunk, ...) + tags: '*' + pull_request: + +jobs: + build: + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: '1.6' + - name: Install dependencies + run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + - name: Build and deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} # If authenticating with SSH deploy key + run: julia --project=docs/ docs/make.jl diff --git a/docs/Project.toml b/docs/Project.toml index b3ee46d..6d10934 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -7,3 +7,6 @@ HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" HerbEvaluation = "eb1bf938-813d-4942-ac0f-b4657a683e76" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" + +[compat] +Documenter = "0.27" From 73bdc411871ec4d06d190392fdea75f0b7aa29cc Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 07:03:31 +0200 Subject: [PATCH 05/75] Ignore docs/Manifest.toml --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f3cf632..8bba82b 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ deps/src/ # Build artifacts for creating documentation generated by the Documenter package docs/build/ docs/site/ +docs/Manifest.toml # File generated by Pkg, the package manager, based on a corresponding Project.toml # It records a fixed state of all packages used by the project. As such, it should not be From a1098344c10e8f911e6767f7ecc75777c961cf55 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 07:07:06 +0200 Subject: [PATCH 06/75] Use different Julia version for deployment --- .github/workflows/documentation.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index dc56f76..c18c2f2 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@v1 with: - version: '1.6' + version: '1.8' - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy From fd11bc3985eb52877c51d407b68c1508ce12bff4 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 07:51:44 +0200 Subject: [PATCH 07/75] Update devurl for dos --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index f6cdfdd..dd96c93 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -32,5 +32,6 @@ makedocs(; deploydocs(; repo="github.com/Herb-AI/Herb.jl.git", devbranch="documentation", + devurl="dev", ) From a8983f0237f1b3e3b52114c68eaeee27866e3122 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 08:05:36 +0200 Subject: [PATCH 08/75] Update docs for contribution guide --- docs/README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 9dfd983..e52025b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,6 +1,15 @@ # Documentation of Herb.jl -## Compiling the documentation: +This documentation was created using [Documenter.jl](https://documenter.juliadocs.org/stable/man/guide/). -1. Update the respective dependencies by either updating them over the REPL or switching to their respective dev mode -2. Run `julia --project=. make.jl` to compile the entire documentation. +## Writing documentation + +The majority of the documentation of automatically generated from the respective docstrings of each sub-package. +There are some pages that were added manually and can be edited likewise. New pages can 1. easily be written in markdown and 2. added to the site-tree by editing `docs/make.jl`. + +## Compiling the documentation +Compiling is automatically triggered whenever pushing to this branch. If you want to run the documentation locally run `julia --project=. make.jl` in this directory. + +## Help! + +If help is needed reach out to [THinnerichs](https://github.com/thinnerichs). From 0f45d098e80527fc91a2fdb9a13971670f5810a3 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 10:47:38 +0200 Subject: [PATCH 09/75] Update landing page and started guide --- docs/make.jl | 19 +++++--- .../index.md} | 2 +- docs/src/{HerbCore.md => HerbCore/index.md} | 6 +-- docs/src/{HerbData.md => HerbData/index.md} | 6 +-- docs/src/HerbEvaluation/index.md | 15 +++++++ .../{HerbGrammar.md => HerbGrammar/index.md} | 2 +- docs/src/HerbSearch.md | 0 .../index.md} | 6 +-- docs/src/concepts.md | 2 + docs/src/get_started.md | 22 +++++++++ docs/src/index.md | 45 ++++++++++++++++++- docs/src/install.md | 34 ++++++++++++++ 12 files changed, 139 insertions(+), 20 deletions(-) rename docs/src/{HerbConstraints.md => HerbConstraints/index.md} (71%) rename docs/src/{HerbCore.md => HerbCore/index.md} (52%) rename docs/src/{HerbData.md => HerbData/index.md} (52%) create mode 100644 docs/src/HerbEvaluation/index.md rename docs/src/{HerbGrammar.md => HerbGrammar/index.md} (73%) delete mode 100644 docs/src/HerbSearch.md rename docs/src/{HerbEvaluation.md => HerbSearch/index.md} (50%) create mode 100644 docs/src/concepts.md create mode 100644 docs/src/get_started.md create mode 100644 docs/src/install.md diff --git a/docs/make.jl b/docs/make.jl index dd96c93..343e041 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -17,14 +17,19 @@ makedocs(; sitename="Herb.jl", repo="https://github.com/Herb-AI/Herb.jl", pages = [ - "Home" => "index.md", + "Basics" => [ + "index.md", + "install.md", + "get_started.md", + "concepts.md" + ], "Sub-Modules" => [ - "HerbGrammar.jl" => "HerbGrammar.md", - "HerbSearch.jl" => "HerbSearch.md", - "HerbConstraints.jl" => "HerbConstraints.md", - "HerbCore.jl" => "HerbData.md", - "HerbEvaluation.jl" => "HerbEvaluation.md", - "HerbData.jl" => "HerbData.md", + "HerbGrammar.jl" => "HerbGrammar/index.md", + "HerbSearch.jl" => "HerbSearch/index.md", + "HerbConstraints.jl" => "HerbConstraints/index.md", + "HerbCore.jl" => "HerbData/index.md", + "HerbEvaluation.jl" => "HerbEvaluation/index.md", + "HerbData.jl" => "HerbData/index.md", ], ], ) diff --git a/docs/src/HerbConstraints.md b/docs/src/HerbConstraints/index.md similarity index 71% rename from docs/src/HerbConstraints.md rename to docs/src/HerbConstraints/index.md index 328b6f8..345fe5d 100644 --- a/docs/src/HerbConstraints.md +++ b/docs/src/HerbConstraints/index.md @@ -1,4 +1,4 @@ -# HerbConstraints.jl Documentation +# [HerbConstraints.jl Documentation](@id HerbConstraints_docs) ```@meta CurrentModule=HerbConstraints diff --git a/docs/src/HerbCore.md b/docs/src/HerbCore/index.md similarity index 52% rename from docs/src/HerbCore.md rename to docs/src/HerbCore/index.md index 328b6f8..d1b647f 100644 --- a/docs/src/HerbCore.md +++ b/docs/src/HerbCore/index.md @@ -1,11 +1,11 @@ -# HerbConstraints.jl Documentation +# [HerbCore.jl Documentation](@id HerbCore_docs) ```@meta -CurrentModule=HerbConstraints +CurrentModule=HerbCore ``` ```@autodocs -Modules = [HerbConstraints] +Modules = [HerbCore] Order = [:type, :const, :macro, :function] ``` diff --git a/docs/src/HerbData.md b/docs/src/HerbData/index.md similarity index 52% rename from docs/src/HerbData.md rename to docs/src/HerbData/index.md index 328b6f8..bbb3577 100644 --- a/docs/src/HerbData.md +++ b/docs/src/HerbData/index.md @@ -1,11 +1,11 @@ -# HerbConstraints.jl Documentation +# [HerbData.jl Documentation](@id HerbData_docs) ```@meta -CurrentModule=HerbConstraints +CurrentModule=HerbData ``` ```@autodocs -Modules = [HerbConstraints] +Modules = [HerbData] Order = [:type, :const, :macro, :function] ``` diff --git a/docs/src/HerbEvaluation/index.md b/docs/src/HerbEvaluation/index.md new file mode 100644 index 0000000..5fd47b1 --- /dev/null +++ b/docs/src/HerbEvaluation/index.md @@ -0,0 +1,15 @@ +# [HerbEvaluation.jl Documentation](@id HerbEvaluation_docs) + +```@meta +CurrentModule=HerbEvaluation +``` + +```@autodocs +Modules = [HerbEvaluation] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/HerbGrammar.md b/docs/src/HerbGrammar/index.md similarity index 73% rename from docs/src/HerbGrammar.md rename to docs/src/HerbGrammar/index.md index 0984cfc..25611ed 100644 --- a/docs/src/HerbGrammar.md +++ b/docs/src/HerbGrammar/index.md @@ -1,4 +1,4 @@ -# HerbGrammar.jl Documentation +# [HerbGrammar.jl Documentation](@id HerbGrammar_docs) ```@meta CurrentModule=HerbGrammar diff --git a/docs/src/HerbSearch.md b/docs/src/HerbSearch.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/src/HerbEvaluation.md b/docs/src/HerbSearch/index.md similarity index 50% rename from docs/src/HerbEvaluation.md rename to docs/src/HerbSearch/index.md index 328b6f8..17f7b30 100644 --- a/docs/src/HerbEvaluation.md +++ b/docs/src/HerbSearch/index.md @@ -1,11 +1,11 @@ -# HerbConstraints.jl Documentation +# [HerbSearch.jl Documentation](@id HerbSearch_docs) ```@meta -CurrentModule=HerbConstraints +CurrentModule=HerbSearch ``` ```@autodocs -Modules = [HerbConstraints] +Modules = [HerbSearch] Order = [:type, :const, :macro, :function] ``` diff --git a/docs/src/concepts.md b/docs/src/concepts.md new file mode 100644 index 0000000..015079e --- /dev/null +++ b/docs/src/concepts.md @@ -0,0 +1,2 @@ +# Architecture and core concepts + diff --git a/docs/src/get_started.md b/docs/src/get_started.md new file mode 100644 index 0000000..ae7b35f --- /dev/null +++ b/docs/src/get_started.md @@ -0,0 +1,22 @@ +# Getting Started + +You can either paste this code into the Julia REPL or into a seperate file, e.g. `get_started.jl`. + +```julia +using HerbSearch, HerbData, HerbInterpret + +# define our very simple context-free grammar +# Can add and multiply an input variable x or the integers 1,2. +g = @cfgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number +end + +problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) +solution = search(g₁, problem, :Number, max_depth=3) + +test_with_input(SymbolTable(g₁), solution, Dict(:x => 6)) +``` + diff --git a/docs/src/index.md b/docs/src/index.md index bbdd81d..573ee47 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -2,9 +2,50 @@ CurrentModule=Herb ``` -# Herb.jl Documentation +# [Herb.jl](https://github.com/Herb-AI/Herb.jl) +*A library for defining and efficiently solving program synthesis tasks in Julia.* + +## Why Herb.jl? + +When writing research software we almost always investigate highly specific properties or algorithms of our domain, leading to us building the tools from scratch over and over again. The very same holds for the field of program synthesis: Tools are hard to run, benchmarks are hard to get and prepare, and its hard to adapt our existing code to a novel idea. + +Herb.jl will take care of this for you and helps you defining, solving and extending your program synthesis problems. + +Herb.jl provides... +- a unified and universal framework for program synthesis +- `Herb.jl` allows you to describe all sorts of program synthesis problems using context-free grammars +- a number of state-of-the-art benchmarks and solvers already implemented and usable out-of-the-box + +Herb.jl's sub-packages provide fast and easily extendable implementations of +- various static and dynamic search strategies, +- learning search strategies, sampling techniques and more, +- constraint formulation and propagation, +- easy grammar formulation and usage, +- wide-range of usable program interpreters and languages + the possibility to use your own, and +- efficient data formulation. + +## Why Julia? + + +## Sub-Modules + +Herb's functionality is distributed among several sub-packages: +- [HerbCore.jl](@ref HerbCore_docs): The core of Herb.jl defining core concepts to avoid circular dependencies. +- [HerbGrammar.jl](@ref HerbGrammar_docs): +- [HerbData.jl](@ref HerbData_docs): +- [HerbEvaluation.jl](@ref HerbEvaluation_docs): +- [HerbSearch.jl](@ref HerbSearch_docs): +- [HerbConstraints.jl](@ref HerbConstraints_docs): + + +## Basics: + +```@contents +Pages = [ "def_pomdp.md", "interfaces.md"] +Depth = 3 +``` + -This is a test. ```@contents ``` diff --git a/docs/src/install.md b/docs/src/install.md new file mode 100644 index 0000000..0140497 --- /dev/null +++ b/docs/src/install.md @@ -0,0 +1,34 @@ +# Installation Guide + +Before installing Herb.jl, ensure that you have a running Julia distribution installed (Julia version 1.7 and above were tested). + +Thanks to Julia's package management, installing Herb.jl is very straighforward. +Activate the default Julia REPL using + +```shell +julia +``` + +or from within one of your projects using + +```shell +julia --project=. +``` + +From the Julia REPL run +```julia +] +add Herb +``` + +or instead running + +```julia +import Pkg +Pkg.add("Herb") +``` + +which will both install all dependencies automatically. + +And just like this you are done! Welcome to Herb.jl! + From 404b159bb655d55a4e0676f9c8053ff57f3c1ec8 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 5 Sep 2023 12:30:52 +0200 Subject: [PATCH 10/75] Update Basics section --- docs/src/index.md | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 573ee47..24027f9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -38,23 +38,13 @@ Herb's functionality is distributed among several sub-packages: - [HerbConstraints.jl](@ref HerbConstraints_docs): -## Basics: +## Basics ```@contents -Pages = [ "def_pomdp.md", "interfaces.md"] -Depth = 3 +Pages = ["install.md", "get_started.md", "concepts.md"] ``` - +## Advanced content ```@contents ``` - -## Index - -This is the index. - -```@index -``` - - From 87c34dfe34a4b6d72532c812dbe108f60cd3581a Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 21 Sep 2023 13:45:50 +0200 Subject: [PATCH 11/75] Update getting started and install --- docs/src/get_started.md | 58 ++++++++++++++++++++++++++++++++++++++--- docs/src/install.md | 5 ++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index ae7b35f..5cc672e 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -1,6 +1,56 @@ # Getting Started -You can either paste this code into the Julia REPL or into a seperate file, e.g. `get_started.jl`. +You can either paste this code into the Julia REPL or into a seperate file, e.g. `get_started.jl` followed by `julia get_started.jl`. + +To begin, we need to import all needed packages using + +```julia +using HerbGrammar, HerbData, HerbSearch, HerbInterpret +``` + +To define a program synthesis problem, we need a grammar and specification. + +First, the grammar can be constructed using the `@cfgrammar` macro included in `HerbGrammar`. Here we describe a simple integer arithmetic example, that can add and multiply an input variable `x` or the integers `1,2`, using + + +```julia +g = @cfgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number +end +``` + +Second, the problem specification can be provided using e.g. input/output examples using `HerbData`. Inputs are provided as a `Dict` assigning values to variables, and outputs as arbitrary values. The problem itself is then a list of `IOExample`s using + +```julia +problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) +``` + +The problem is given now, let us search for a solution with `HerbSearch`. For now we will just use the default parameters searching for a satisfying program over the grammar, given the problem and a starting symbol using + +```julia +solution = search(g, problem, :Number, max_depth=3) +println(solution) +``` + +There are various ways to adapt the search technique to your needs. Please have a look at the [`search`][@ref] documentation. + +Eventually, we want to test our solution on some other inputs using `HerbInterpret`. We transform our grammar `g` to a Julia expression with `Symboltable(g)`, add our solution and the input, assigning the value `6` to the variable `x`. + +```julia +output = test_with_input(SymbolTable(g), solution, Dict(:x => 6)) +println(output) +``` + +Just like that we tackled (almost) all modules of Herb.jl. + +## Where to go from here? + +See our other tutorials! + +## The full code example ```julia using HerbSearch, HerbData, HerbInterpret @@ -15,8 +65,10 @@ g = @cfgrammar begin end problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) -solution = search(g₁, problem, :Number, max_depth=3) +solution = search(g, problem, :Number, max_depth=3) -test_with_input(SymbolTable(g₁), solution, Dict(:x => 6)) +test_with_input(SymbolTable(g), solution, Dict(:x => 6)) ``` + + diff --git a/docs/src/install.md b/docs/src/install.md index 0140497..20f723a 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -30,5 +30,10 @@ Pkg.add("Herb") which will both install all dependencies automatically. +For later convenience we can also add the respective dependencies to our project, so that we do not have to write `Herb.HerbGrammar` every time. +```julia +] add HerbConstraints HerbCore HerbData HerbInterpret HerbGrammar HerbSearch +``` + And just like this you are done! Welcome to Herb.jl! From 262d9140960bcc111a35f15129222a33c88411b7 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 21 Sep 2023 13:51:06 +0200 Subject: [PATCH 12/75] Update getting started and install --- docs/src/get_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index 5cc672e..320b842 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -35,7 +35,7 @@ solution = search(g, problem, :Number, max_depth=3) println(solution) ``` -There are various ways to adapt the search technique to your needs. Please have a look at the [`search`][@ref] documentation. +There are various ways to adapt the search technique to your needs. Please have a look at the [`search`](@ref) documentation. Eventually, we want to test our solution on some other inputs using `HerbInterpret`. We transform our grammar `g` to a Julia expression with `Symboltable(g)`, add our solution and the input, assigning the value `6` to the variable `x`. From 6aa014adc112555eeb611749445ba2b2606d3db3 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 28 Sep 2023 17:33:41 +0200 Subject: [PATCH 13/75] HerbEvaluation -> HerbInterpret --- docs/Project.toml | 2 +- docs/make.jl | 10 ++++------ docs/src/HerbEvaluation/index.md | 15 --------------- docs/src/HerbInterpret/index.md | 15 +++++++++++++++ docs/src/index.md | 2 +- 5 files changed, 21 insertions(+), 23 deletions(-) delete mode 100644 docs/src/HerbEvaluation/index.md create mode 100644 docs/src/HerbInterpret/index.md diff --git a/docs/Project.toml b/docs/Project.toml index 6d10934..ccfa3e6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,8 +4,8 @@ DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" -HerbEvaluation = "eb1bf938-813d-4942-ac0f-b4657a683e76" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" [compat] diff --git a/docs/make.jl b/docs/make.jl index 343e041..3d03691 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,18 +1,16 @@ using Documenter -include("../src/Herb.jl") - -using .Herb +using Herb using HerbConstraints using HerbSearch using HerbGrammar using HerbData -using HerbEvaluation +using HerbInterpret using HerbCore makedocs(; - modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbEvaluation, HerbCore], + modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbInterpret, HerbCore], authors="SEBs", sitename="Herb.jl", repo="https://github.com/Herb-AI/Herb.jl", @@ -28,7 +26,7 @@ makedocs(; "HerbSearch.jl" => "HerbSearch/index.md", "HerbConstraints.jl" => "HerbConstraints/index.md", "HerbCore.jl" => "HerbData/index.md", - "HerbEvaluation.jl" => "HerbEvaluation/index.md", + "HerbInterpret.jl" => "HerbInterpret/index.md", "HerbData.jl" => "HerbData/index.md", ], ], diff --git a/docs/src/HerbEvaluation/index.md b/docs/src/HerbEvaluation/index.md deleted file mode 100644 index 5fd47b1..0000000 --- a/docs/src/HerbEvaluation/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# [HerbEvaluation.jl Documentation](@id HerbEvaluation_docs) - -```@meta -CurrentModule=HerbEvaluation -``` - -```@autodocs -Modules = [HerbEvaluation] -Order = [:type, :const, :macro, :function] -``` - -## Index - -```@index -``` diff --git a/docs/src/HerbInterpret/index.md b/docs/src/HerbInterpret/index.md new file mode 100644 index 0000000..52bb521 --- /dev/null +++ b/docs/src/HerbInterpret/index.md @@ -0,0 +1,15 @@ +# [HerbInterpret.jl Documentation](@id HerbInterpret_docs) + +```@meta +CurrentModule=HerbInterpret +``` + +```@autodocs +Modules = [HerbInterpret] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` diff --git a/docs/src/index.md b/docs/src/index.md index 24027f9..1343902 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -33,7 +33,7 @@ Herb's functionality is distributed among several sub-packages: - [HerbCore.jl](@ref HerbCore_docs): The core of Herb.jl defining core concepts to avoid circular dependencies. - [HerbGrammar.jl](@ref HerbGrammar_docs): - [HerbData.jl](@ref HerbData_docs): -- [HerbEvaluation.jl](@ref HerbEvaluation_docs): +- [HerbInterpret.jl](@ref HerbInterpret_docs): - [HerbSearch.jl](@ref HerbSearch_docs): - [HerbConstraints.jl](@ref HerbConstraints_docs): From ab89d2d1fe18558de40da73297ce5d342dab1151 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Sun, 29 Oct 2023 11:18:15 +0100 Subject: [PATCH 14/75] Update logo --- docs/src/assets/herb.png | Bin 0 -> 74388 bytes docs/src/assets/herb.svg | 927 ++++++++++++++++++++++++++++++++++ docs/src/assets/herb_blue.png | Bin 0 -> 82507 bytes docs/src/assets/herb_logo.png | Bin 0 -> 147634 bytes docs/src/assets/logo.png | Bin 0 -> 77914 bytes docs/src/assets/logo.svg | 4 +- docs/src/assets/logo_old.svg | 1 + 7 files changed, 931 insertions(+), 1 deletion(-) create mode 100644 docs/src/assets/herb.png create mode 100644 docs/src/assets/herb.svg create mode 100644 docs/src/assets/herb_blue.png create mode 100644 docs/src/assets/herb_logo.png create mode 100644 docs/src/assets/logo.png create mode 100644 docs/src/assets/logo_old.svg diff --git a/docs/src/assets/herb.png b/docs/src/assets/herb.png new file mode 100644 index 0000000000000000000000000000000000000000..0268ea7cf4e4d9927471e0127cac26f18da80b0b GIT binary patch literal 74388 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 z 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 literal 0 HcmV?d00001 diff --git a/docs/src/assets/herb.svg b/docs/src/assets/herb.svg new file mode 100644 index 0000000..8908de8 --- /dev/null +++ b/docs/src/assets/herb.svg @@ -0,0 +1,927 @@ + + + + diff --git a/docs/src/assets/herb_blue.png b/docs/src/assets/herb_blue.png new file mode 100644 index 0000000000000000000000000000000000000000..e8782ac9bb4e27ada9dcaa2fc98c4be402240dff GIT binary patch literal 82507 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 TajJ%BsBgUAl$89t!`1%-&&6rD literal 0 HcmV?d00001 diff --git a/docs/src/assets/herb_logo.png b/docs/src/assets/herb_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d9f5a5c59107c89a6af8c89ec2914af1f476693b GIT binary patch literal 147634 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

VR7UJB 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

FmeoNxQr(7t)D-YCe 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%0Ao6c`Nw%

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 Dn>R^0 literal 0 HcmV?d00001 diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b9032240ec97039d70d58951042a3486e21d084a GIT binary patch literal 77914 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⋼(>Y!_x;n$It7En3V6rP&k%sfNw5wL&fJa_ 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

\ No newline at end of file diff --git a/docs/src/tutorials/working_with_interpreters.jl b/docs/src/tutorials/working_with_interpreters.jl index 815eef2..66767a4 100644 --- a/docs/src/tutorials/working_with_interpreters.jl +++ b/docs/src/tutorials/working_with_interpreters.jl @@ -5,8 +5,10 @@ using Markdown using InteractiveUtils # ╔═╡ e0a7076c-9345-40ef-a26e-99e8bad31463 -using HerbGrammar -using HerbInterpret +begin + using HerbGrammar + using HerbInterpret +end # ╔═╡ 55719688-3940-11ef-1f29-f51dea064ff3 md"# Using the Julia interpreter @@ -102,21 +104,32 @@ The idea behind this programming language is that the program specifies a set of We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program: - As `RuleNode`s only store indices + interpret(prog::AbstractRuleNode, grammar::ContextSensitiveGrammar, state::RobotState)::RobotState + + As `RuleNode`s only store indices of derivation rules from the grammar, not the functions themselves, we will first pull the function call associated with every derivation rule. In Julia, this is indicated by the top-level symbol of the rules. For example, the top-level symbol for the derivation rule 6 is `:moveRight`; for rule 12, that is `:IF`. " # ╔═╡ c99860ae-644d-4b72-b176-413095cd9b2c -# ╔═╡ 4a733a80-e98b-4f87-adf4-f72a16afd0c1 +# ╔═╡ 0be36600-9528-46b6-96e6-515e2c338f57 +# ╔═╡ 7f960487-8268-4b45-8bed-4e073fbbbb4e + + +# ╔═╡ 4a733a80-e98b-4f87-adf4-f72a16afd0c1 +md"" + # ╔═╡ b55fa52d-75cc-4311-8387-1e6c2682282f # ╔═╡ 204e7a43-ebc7-4caf-af46-7502be14a314 +# ╔═╡ 767cfc7d-de5a-4e86-9198-b99db4760634 + + # ╔═╡ bd2b8c2d-c9a4-40b5-afcd-214daad9eccd @@ -131,34 +144,624 @@ The idea behind this programming language is that the program specifies a set of # ╔═╡ 1f700607-3cdf-43bf-91f2-72de3c9abc85 md" -The remaining functions follow a similar idea. (You can see the full implementation of this interpreter at [this link]())." - -# ╔═╡ 5f56cdd8-7ede-44e9-b322-36ac7ebb537b - +The remaining functions follow a similar idea. (You can see the full implementation of this interpreter [here](https://github.com/Herb-AI/HerbBenchmarks.jl/blob/new-robots/src/data/Robots_2020/robots_primitives.jl))." + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" + +[compat] +HerbGrammar = "~0.3.0" +HerbInterpret = "~0.1.3" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.9.1" +manifest_format = "2.0" +project_hash = "3d463631c2622b04eb5c06a35c60329ce68a7044" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.0.2+0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.2" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbCore]] +git-tree-sha1 = "923877c2715b8166d7ba9f9be2136d70eed87725" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.3.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "b4cbf9712dbb3ab281ff4ed517d3cd6bc12f3078" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.3.0" + +[[deps.HerbInterpret]] +deps = ["HerbCore", "HerbGrammar", "HerbSpecification"] +git-tree-sha1 = "9e19b4ee5f29eb8bb9b1049524728b38e878eca2" +uuid = "5bbddadd-02c5-4713-84b8-97364418cca7" +version = "0.1.3" + +[[deps.HerbSpecification]] +git-tree-sha1 = "5385b81e40c3cd62aeea591319896148036863c9" +uuid = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +version = "0.1.0" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.Inflate]] +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.5" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.3" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "7.84.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.10.2+0" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2022.10.11" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.21+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+0" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.9.0" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA", "Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.7" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.9.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "5.10.1+6" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.13.1+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.41+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.17.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+0" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+0" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.48.0+0" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+0" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" # ╔═╡ Cell order: # ╠═e0a7076c-9345-40ef-a26e-99e8bad31463 -# ╠═55719688-3940-11ef-1f29-f51dea064ff3 +# ╟─55719688-3940-11ef-1f29-f51dea064ff3 # ╠═39eaa982-ba88-49b9-ad52-076a169d0439 # ╟─2478d5a4-a11e-42aa-87dd-d97a3fa5d378 # ╠═1e15898e-568c-4211-ba00-27de61806aeb # ╟─d43a2094-b215-4d6c-b6d8-8d32fe8898d6 # ╠═a77621ce-1749-4e16-b3dc-f5312cc5ee73 -# ╠═48997ff5-a492-4bfd-8c64-d935433228c0 +# ╟─48997ff5-a492-4bfd-8c64-d935433228c0 # ╠═3e4f7ed5-bfba-4aa0-9f43-2978a9082054 -# ╠═2a6f6456-2937-4520-8427-8d7595076ec5 +# ╟─2a6f6456-2937-4520-8427-8d7595076ec5 # ╠═c0dcdbc8-2355-4f4d-85c2-ec37bfeab226 -# ╠═b3ac6903-8513-40cc-91ee-8ae7beb08d1d -# ╠═f765b471-74f8-4e17-8e1e-e556d88eb84b +# ╟─b3ac6903-8513-40cc-91ee-8ae7beb08d1d +# ╟─f765b471-74f8-4e17-8e1e-e556d88eb84b # ╠═1b251d0f-3a77-494f-a359-d8dc33ad5d44 -# ╠═aff77be9-365f-4672-bbd4-07f23528e32e +# ╟─aff77be9-365f-4672-bbd4-07f23528e32e # ╠═c99860ae-644d-4b72-b176-413095cd9b2c +# ╠═0be36600-9528-46b6-96e6-515e2c338f57 +# ╠═7f960487-8268-4b45-8bed-4e073fbbbb4e # ╠═4a733a80-e98b-4f87-adf4-f72a16afd0c1 # ╠═b55fa52d-75cc-4311-8387-1e6c2682282f # ╠═204e7a43-ebc7-4caf-af46-7502be14a314 +# ╠═767cfc7d-de5a-4e86-9198-b99db4760634 # ╠═bd2b8c2d-c9a4-40b5-afcd-214daad9eccd # ╠═f7202e23-56e8-4611-9cde-633156e7e4da # ╠═9db3b242-a14b-4932-80cd-75c54a21946b # ╠═ceb78d01-8e80-4356-95ad-bac889bc3fc1 # ╠═1f700607-3cdf-43bf-91f2-72de3c9abc85 -# ╠═5f56cdd8-7ede-44e9-b322-36ac7ebb537b +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From 63b163ee173944a8acf241a854c344969f0b0990 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 4 Jul 2024 10:42:23 +0200 Subject: [PATCH 49/75] rename constraints guide, add to make.jl --- docs/make.jl | 3 +- docs/src/tutorials/constraints.ipynb | 932 --------------------------- docs/src/tutorials/constraints.md | 738 --------------------- 3 files changed, 2 insertions(+), 1671 deletions(-) delete mode 100644 docs/src/tutorials/constraints.ipynb delete mode 100644 docs/src/tutorials/constraints.md diff --git a/docs/make.jl b/docs/make.jl index e89727b..d8bcbc5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -24,7 +24,8 @@ makedocs( "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.html" + "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", + "Working with custom interpreters" => "tutorials/working_with_interpreters.html" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", diff --git a/docs/src/tutorials/constraints.ipynb b/docs/src/tutorials/constraints.ipynb deleted file mode 100644 index 624f169..0000000 --- a/docs/src/tutorials/constraints.ipynb +++ /dev/null @@ -1,932 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Getting started with HerbConstraints\n", - "\n", - "When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup\n", - "\n", - "For this tutorial, we need to import the following modules of the Herb.jl framework:\n", - "\n", - "* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s\n", - "* `HerbGrammar` to define the grammar\n", - "* `HerbConstraints` to define the constraints\n", - "* `HerbSearch` to execute a constrained enumeration\n", - "\n", - "We will also redefine the simple arithmetic grammar from the previous tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 1\n", - "2: Int = x\n", - "3: Int = -Int\n", - "4: Int = Int + Int\n", - "5: Int = Int * Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "using HerbCore, HerbGrammar, HerbConstraints, HerbSearch\n", - "\n", - "grammar = @cfgrammar begin\n", - " Int = 1\n", - " Int = x\n", - " Int = - Int\n", - " Int = Int + Int\n", - " Int = Int * Int\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Working with constraints\n", - "\n", - "To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes).\n", - "\n", - "(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar)" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n", - "1 + 1\n" - ] - } - ], - "source": [ - "clearconstraints!(grammar)\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints.\n", - "\n", - "To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids:\n", - "\n", - "* `-(-1)`\n", - "* `-(-X)`\n", - "* `-(-(1 + 1))`\n", - "* `1 + -(-(1 + 1))`\n", - "* etc" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "x * 1\n", - "x * x\n", - "x + x\n", - "x + 1\n", - "1 + 1\n", - "1 + x\n" - ] - } - ], - "source": [ - "one = 1\n", - "x = 2\n", - "minus = 3\n", - "plus = 4\n", - "times = 5\n", - "\n", - "addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A\n", - "addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A)\n", - "\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Forbidden Constraint\n", - "\n", - "The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types:\n", - "* `RuleNode(1)`. Matches exactly the given rule.\n", - "* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5.\n", - "* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "154\n", - "106\n" - ] - } - ], - "source": [ - "#this constraint forbids A+A and A*A\n", - "constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)]))\n", - "\n", - "# Without this constraint, we encounter 154 programs\n", - "clearconstraints!(grammar)\n", - "iter = BFSIterator(grammar, :Int, max_size=5)\n", - "println(length(iter))\n", - "\n", - "# With this constraint, we encounter 106 programs\n", - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, constraint)\n", - "iter = BFSIterator(grammar, :Int, max_size=5)\n", - "println(length(iter))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Contains Constraint\n", - "\n", - "The `Contains` constraint enforces that a given rule appears in the program tree at least once. \n", - "\n", - "In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x\n", - "-x\n", - "-(-x)\n", - "1x\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n" - ] - } - ], - "source": [ - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Contains Subtree Constraint\n", - "\n", - "Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x * x\n", - "-(x * x)\n" - ] - } - ], - "source": [ - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree\n", - "iter = BFSIterator(grammar, :Int, max_size=4)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Ordered Constraint\n", - "\n", - "The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants.\n", - "\n", - "To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree.\n", - "\n", - "In the upcoming example we will set up a template tree representing `a+b` and `a*b`.\n", - "Then, we will impose an ordering `a<=b` on all the subtrees that match the template.\n", - "\n", - "The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x + x\n", - "1 + x\n", - "1 + 1\n" - ] - } - ], - "source": [ - "clearconstraints!(grammar)\n", - "\n", - "template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)])\n", - "order = [:a, :b]\n", - "\n", - "addconstraint!(grammar, Ordered(template_tree, order))\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Forbidden Sequence Constraint\n", - "\n", - "The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. \n", - "\n", - "An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. \n", - "\n", - "Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence.\n", - "\n", - "This constraint will **forbid** the following programs:\n", - "\n", - "* x + 1\n", - "* x + -1\n", - "* x + -(-1)\n", - "* x + (x + 1)\n", - "* x * (x + 1)\n", - "\n", - "But it will **allow** the following program (as * disrupts the sequence):\n", - "\n", - "* x + (x * 1)\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x + x\n" - ] - } - ], - "source": [ - "constraint = ForbiddenSequence([plus, one], ignore_if=[times])\n", - "addconstraint!(grammar, constraint)\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Custom Constraint\n", - "\n", - "To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`.\n", - "\n", - "A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location.\n", - "\n", - "A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. \n", - "\n", - "Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function.\n", - "\n", - "(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.on_new_node" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\"\"\"\n", - "Forbids the consecutive application of the specified rule.\n", - "For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row.\n", - "\"\"\"\n", - "struct ForbidConsecutive <: AbstractGrammarConstraint\n", - " rule::Int\n", - "end\n", - "\n", - "\"\"\"\n", - "Post a local constraint on each new node that appears in the tree\n", - "\"\"\"\n", - "function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int})\n", - " HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path:\n", - "\n", - "* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})`\n", - "* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)`\n", - "* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree)\n", - "\n", - "In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver:\n", - "\n", - "* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators.\n", - "* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation.\n", - "* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint.\n", - "\n", - "The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions:\n", - "\n", - "* `get_tree(solver)` returns the root node of the current (partial) program tree\n", - "* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants.\n", - "* `get_path(solver, node)` returns the path at which the node is located.\n", - "* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations).\n", - "* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2).\n", - "\n", - "To get information about a node, we can use the following getter functions:\n", - "\n", - "* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1.\n", - "* `get_rule(node)`. Get the rule of a filled node.\n", - "* `get_children(node)`. Get the children of a node.\n", - "* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain.\n", - "\n", - "Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.propagate!" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\"\"\"\n", - "Forbids the consecutive application of the specified rule at path `path`.\n", - "\"\"\"\n", - "struct LocalForbidConsecutive <: AbstractLocalConstraint\n", - " path::Vector{Int}\n", - " rule::Int\n", - "end\n", - "\n", - "\"\"\"\n", - "Propagates the constraints by preventing a consecutive application of the specified rule.\n", - "\"\"\"\n", - "function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive)\n", - " node = get_node_at_location(solver, constraint.path)\n", - " if isfilled(node)\n", - " if get_rule(node) == constraint.rule\n", - " #the specified rule is used, make sure the rule will not be used by any of the children\n", - " for (i, child) ∈ enumerate(get_children(node))\n", - " if isfilled(child)\n", - " if get_rule(child) == constraint.rule\n", - " #the specified rule was used twice in a row, which is violating the constraint\n", - " set_infeasible!(solver)\n", - " return\n", - " end\n", - " elseif child.domain[constraint.rule]\n", - " child_path = push!(copy(constraint.path), i)\n", - " remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child\n", - " end\n", - " end\n", - " end\n", - " elseif node.domain[constraint.rule]\n", - " #our node is a hole with the specified rule in its domain\n", - " #we will now check if any of the children already uses the specified rule\n", - " softfail = false\n", - " for (i, child) ∈ enumerate(get_children(node))\n", - " if isfilled(child)\n", - " if get_rule(child) == constraint.rule\n", - " #the child holds the specified rule, so the parent cannot have this rule\n", - " remove!(solver, constraint.path, constraint.rule)\n", - " end\n", - " elseif child.domain[constraint.rule]\n", - " #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail.\n", - " softfail = true\n", - " end\n", - " end\n", - " if softfail\n", - " #we cannot deactivate the constraint, because it needs to be repropagated\n", - " return\n", - " end\n", - " end\n", - "\n", - " #the constraint is satisfied and can be deactivated\n", - " HerbConstraints.deactivate!(solver, constraint)\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation.\n", - "\n", - "Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation.\n", - "\n", - "In our case, we want to repropagate if either:\n", - "* a tree manipulation occured at the `constraint.path`\n", - "* a tree manipulation occured at the child of the `constraint.path`" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.shouldschedule" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "\n", - "\"\"\"\n", - "Gets called whenever an tree manipulation occurs at the given `path`.\n", - "Returns true iff the `constraint` should be rescheduled for propagation.\n", - "\"\"\"\n", - "function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool\n", - " return (path == constraint.path) || (path == constraint.path[1:end-1])\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint." - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "1x\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n", - "1 + 1\n", - "1 * -1\n", - "1 * -x\n", - "x * -x\n", - "x * -1\n", - "x + -1\n", - "x + -x\n", - "1 + -x\n", - "-(1 * 1)\n", - "1 + -1\n", - "-(1x)\n", - "-(x * x)\n", - "-(x * 1)\n", - "-((x + 1))\n", - "-((x + x))\n", - "-1 * 1\n", - "-((1 + x))\n", - "-1 * x\n", - "-((1 + 1))\n", - "-x * x\n", - "-x * 1\n", - "-x + 1\n", - "-x + x\n", - "-1 + x\n", - "-1 + 1\n", - "(1 + 1) * 1\n", - "-(1 * -1)\n", - "(1 + 1) * x\n", - "-(1 * -x)\n", - "(1 + x) * x\n", - "-(x * -x)\n", - "(1 + x) * 1\n", - "-(x * -1)\n", - "(x + x) * 1\n", - "-((x + -1))\n", - "(x + x) * x\n", - "-((x + -x))\n", - "(x + 1) * x\n", - "-((1 + -x))\n", - "(x + 1) * 1\n", - "-((1 + -1))\n", - "x * 1 + 1\n", - "x * 1 + x\n", - "x * x + x\n", - "x * x + 1\n", - "1x + 1\n", - "1x + x\n", - "-(-1 * 1)\n", - "1 * 1 + x\n", - "-(-1 * x)\n", - "1 * 1 + 1\n", - "-(-x * x)\n", - "-(-x * 1)\n", - "-((-x + 1))\n", - "1 * (1 + 1)\n", - "-((-x + x))\n", - "1 * (1 + x)\n", - "-((-1 + x))\n", - "1 * (x + x)\n", - "-((-1 + 1))\n", - "1 * (x + 1)\n", - "x * (x + 1)\n", - "x * (x + x)\n", - "x * (1 + x)\n", - "x * (1 + 1)\n", - "-1 * -1\n", - "x + 1 * 1\n", - "-1 * -x\n", - "x + 1x\n", - "-x * -x\n", - "x + x * x\n", - "-x * -1\n", - "x + x * 1\n", - "-x + -1\n", - "1 + x * 1\n", - "-x + -x\n", - "1 + x * x\n", - "-1 + -x\n", - "1 + 1x\n", - "-1 + -1\n", - "1 + 1 * 1\n", - "-1 * (1 + 1)\n", - "1 * (1 + -1)\n", - "-1 * (1 + x)\n", - "1 * (1 + -x)\n", - "-1 * (x + x)\n", - "1 * (x + -x)\n", - "-1 * (x + 1)\n", - "1 * (x + -1)\n", - "-x * (x + 1)\n", - "x * (x + -1)\n", - "-x * (x + x)\n", - "x * (x + -x)\n", - "-x * (1 + x)\n", - "x * (1 + -x)\n", - "-x * (1 + 1)\n", - "x * (1 + -1)\n", - "-x + 1 * 1\n", - "x + 1 * -1\n", - "-x + 1x\n", - "x + 1 * -x\n", - "-x + x * x\n", - "x + x * -x\n", - "-x + x * 1\n", - "x + x * -1\n", - "-1 + x * 1\n", - "1 + x * -1\n", - "-1 + x * x\n", - "1 + x * -x\n", - "-1 + 1x\n", - "1 + 1 * -x\n", - "-1 + 1 * 1\n", - "1 + 1 * -1\n", - "1 * -(1 * 1)\n", - "(1 + -1) * 1\n", - "1 * -(1x)\n", - "(1 + -1) * x\n", - "1 * -(x * x)\n", - "(1 + -x) * x\n", - "1 * -(x * 1)\n", - "(1 + -x) * 1\n", - "1 * -((x + 1))\n", - "(x + -x) * 1\n", - "1 * -((x + x))\n", - "(x + -x) * x\n", - "1 * -((1 + x))\n", - "(x + -1) * x\n", - "1 * -((1 + 1))\n", - "(x + -1) * 1\n", - "x * -((1 + 1))\n", - "x * -1 + 1\n", - "x * -((1 + x))\n", - "x * -1 + x\n", - "x * -((x + x))\n", - "x * -x + x\n", - "x * -((x + 1))\n", - "x * -x + 1\n", - "x * -(x * 1)\n", - "1 * -x + 1\n", - "x * -(x * x)\n", - "1 * -x + x\n", - "x * -(1x)\n", - "1 * -1 + x\n", - "x * -(1 * 1)\n", - "1 * -1 + 1\n", - "x + -(1 * 1)\n", - "x + -(1x)\n", - "1 * (-1 + 1)\n", - "x + -(x * x)\n", - "1 * (-1 + x)\n", - "x + -(x * 1)\n", - "1 * (-x + x)\n", - "x + -((x + 1))\n", - "1 * (-x + 1)\n", - "x + -((x + x))\n", - "x * (-x + 1)\n", - "x + -((1 + x))\n", - "x * (-x + x)\n", - "x + -((1 + 1))\n", - "x * (-1 + x)\n", - "1 + -((1 + 1))\n", - "x * (-1 + 1)\n", - "1 + -((1 + x))\n", - "x + -1 * 1\n", - "1 + -((x + x))\n", - "x + -1 * x\n", - "1 + -((x + 1))\n", - "x + -x * x\n", - "1 + -(x * 1)\n", - "x + -x * 1\n", - "1 + -(x * x)\n", - "1 + -x * 1\n", - "1 + -(1x)\n", - "1 + -x * x\n", - "1 + -(1 * 1)\n", - "1 + -1 * x\n", - "1 + -1 * 1\n", - "-(1 * 1) * 1\n", - "-(1 * 1) * x\n", - "-((1 + 1) * 1)\n", - "-(1x) * x\n", - "-((1 + 1) * x)\n", - "-(1x) * 1\n", - "-((1 + x) * x)\n", - "-(x * x) * 1\n", - "-((1 + x) * 1)\n", - "-(x * x) * x\n", - "-((x + x) * 1)\n", - "-(x * 1) * x\n", - "-((x + x) * x)\n", - "-(x * 1) * 1\n", - "-((x + 1) * x)\n", - "-((x + 1)) * 1\n", - "-((x + 1) * 1)\n", - "-((x + 1)) * x\n", - "-((x * 1 + 1))\n", - "-((x + x)) * x\n", - "-((x * 1 + x))\n", - "-((x + x)) * 1\n", - "-((x * x + x))\n", - "-((1 + x)) * 1\n", - "-((x * x + 1))\n", - "-((1 + x)) * x\n", - "-((1x + 1))\n", - "-((1 + 1)) * x\n", - "-((1x + x))\n", - "-((1 + 1)) * 1\n", - "-((1 * 1 + x))\n", - "-((1 + 1)) + 1\n", - "-((1 * 1 + 1))\n", - "-((1 + 1)) + x\n", - "-((1 + x)) + x\n", - "-(-1 * -1)\n", - "-((1 + x)) + 1\n", - "-(-1 * -x)\n", - "-((x + x)) + 1\n", - "-(-x * -x)\n", - "-((x + x)) + x\n", - "-(-x * -1)\n", - "-((x + 1)) + x\n", - "-((-x + -1))\n", - "-((x + 1)) + 1\n", - "-((-x + -x))\n", - "-(x * 1) + 1\n", - "-((-1 + -x))\n", - "-(x * 1) + x\n", - "-((-1 + -1))\n", - "-(x * x) + x\n", - "-(x * x) + 1\n", - "(-1 + 1) * 1\n", - "-(1x) + 1\n", - "(-1 + 1) * x\n", - "-(1x) + x\n", - "(-1 + x) * x\n", - "-(1 * 1) + x\n", - "(-1 + x) * 1\n", - "-(1 * 1) + 1\n", - "(-x + x) * 1\n", - "(-x + x) * x\n", - "(1 + 1) * -1\n", - "(-x + 1) * x\n", - "(1 + 1) * -x\n", - "(-x + 1) * 1\n", - "(1 + x) * -x\n", - "-x * 1 + 1\n", - "(1 + x) * -1\n", - "-x * 1 + x\n", - "(x + x) * -1\n", - "-x * x + x\n", - "(x + x) * -x\n", - "-x * x + 1\n", - "(x + 1) * -x\n", - "-1 * x + 1\n", - "(x + 1) * -1\n", - "-1 * x + x\n", - "x * 1 + -1\n", - "-1 * 1 + x\n", - "x * 1 + -x\n", - "-1 * 1 + 1\n", - "x * x + -x\n", - "x * x + -1\n", - "-(1 * (1 + 1))\n", - "1x + -1\n", - "-(1 * (1 + x))\n", - "1x + -x\n", - "-(1 * (x + x))\n", - "1 * 1 + -x\n", - "-(1 * (x + 1))\n", - "1 * 1 + -1\n", - "-(x * (x + 1))\n", - "-(x * (x + x))\n", - "-(x * (1 + x))\n", - "-(x * (1 + 1))\n", - "-((x + 1 * 1))\n", - "-((x + 1x))\n", - "-((x + x * x))\n", - "-((x + x * 1))\n", - "-((1 + x * 1))\n", - "-((1 + x * x))\n", - "-((1 + 1x))\n", - "-((1 + 1 * 1))\n" - ] - } - ], - "source": [ - "clearconstraints!(grammar)\n", - "\n", - "addconstraint!(grammar, ForbidConsecutive(minus))\n", - "addconstraint!(grammar, ForbidConsecutive(plus))\n", - "addconstraint!(grammar, ForbidConsecutive(times))\n", - "\n", - "iter = BFSIterator(grammar, :Int, max_size=6)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.8.5", - "language": "julia", - "name": "julia-1.8" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.8.5" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/src/tutorials/constraints.md b/docs/src/tutorials/constraints.md deleted file mode 100644 index 08d9265..0000000 --- a/docs/src/tutorials/constraints.md +++ /dev/null @@ -1,738 +0,0 @@ -# Getting started with HerbConstraints - -When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space. - -### Setup - -For this tutorial, we need to import the following modules of the Herb.jl framework: - -* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s -* `HerbGrammar` to define the grammar -* `HerbConstraints` to define the constraints -* `HerbSearch` to execute a constrained enumeration - -We will also redefine the simple arithmetic grammar from the previous tutorial. - - -```julia -using HerbCore, HerbGrammar, HerbConstraints, HerbSearch - -grammar = @cfgrammar begin - Int = 1 - Int = x - Int = - Int - Int = Int + Int - Int = Int * Int -end -``` - - - 1: Int = 1 - 2: Int = x - 3: Int = -Int - 4: Int = Int + Int - 5: Int = Int * Int - - - -### Working with constraints - -To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes). - -(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar) - - -```julia -clearconstraints!(grammar) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x * 1 - x + 1 - x + x - 1 + x - 1 + 1 - - -Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. - -To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids: - -* `-(-1)` -* `-(-X)` -* `-(-(1 + 1))` -* `1 + -(-(1 + 1))` -* etc - - -```julia -one = 1 -x = 2 -minus = 3 -plus = 4 -times = 5 - -addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A -addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A) - -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - 1 - x - -1 - -x - x * 1 - x * x - x + x - x + 1 - 1 + 1 - 1 + x - - -### Forbidden Constraint - -The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types: -* `RuleNode(1)`. Matches exactly the given rule. -* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5. -* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same. - - -```julia -#this constraint forbids A+A and A*A -constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)])) - -# Without this constraint, we encounter 154 programs -clearconstraints!(grammar) -iter = BFSIterator(grammar, :Int, max_size=5) -println(length(iter)) - -# With this constraint, we encounter 106 programs -clearconstraints!(grammar) -addconstraint!(grammar, constraint) -iter = BFSIterator(grammar, :Int, max_size=5) -println(length(iter)) - -``` - - 154 - 106 - - -### Contains Constraint - -The `Contains` constraint enforces that a given rule appears in the program tree at least once. - -In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant. - - -```julia -clearconstraints!(grammar) -addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - x - -x - -(-x) - 1x - x * x - x * 1 - x + 1 - x + x - 1 + x - - -### Contains Subtree Constraint - -Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once. - - -```julia -clearconstraints!(grammar) -addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree -iter = BFSIterator(grammar, :Int, max_size=4) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - x * x - -(x * x) - - -### Ordered Constraint - -The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants. - -To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree. - -In the upcoming example we will set up a template tree representing `a+b` and `a*b`. -Then, we will impose an ordering `a<=b` on all the subtrees that match the template. - -The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`. - - - -```julia -clearconstraints!(grammar) - -template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)]) -order = [:a, :b] - -addconstraint!(grammar, Ordered(template_tree, order)) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x + x - 1 + x - 1 + 1 - - -### Forbidden Sequence Constraint - -The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. - -An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. - -Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence. - -This constraint will **forbid** the following programs: - -* x + 1 -* x + -1 -* x + -(-1) -* x + (x + 1) -* x * (x + 1) - -But it will **allow** the following program (as * disrupts the sequence): - -* x + (x * 1) - - - -```julia -constraint = ForbiddenSequence([plus, one], ignore_if=[times]) -addconstraint!(grammar, constraint) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x + x - - -### Custom Constraint - -To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`. - -A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location. - -A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies. - -Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. - -Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function. - -(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace) - - -```julia -""" -Forbids the consecutive application of the specified rule. -For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row. -""" -struct ForbidConsecutive <: AbstractGrammarConstraint - rule::Int -end - -""" -Post a local constraint on each new node that appears in the tree -""" -function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int}) - HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule)) -end -``` - - - HerbConstraints.on_new_node - - -Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path: - -* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})` -* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)` -* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree) - -In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver: - -* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators. -* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation. -* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint. - -The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions: - -* `get_tree(solver)` returns the root node of the current (partial) program tree -* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants. -* `get_path(solver, node)` returns the path at which the node is located. -* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations). -* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2). - -To get information about a node, we can use the following getter functions: - -* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1. -* `get_rule(node)`. Get the rule of a filled node. -* `get_children(node)`. Get the children of a node. -* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain. - -Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match. - - - -```julia -""" -Forbids the consecutive application of the specified rule at path `path`. -""" -struct LocalForbidConsecutive <: AbstractLocalConstraint - path::Vector{Int} - rule::Int -end - -""" -Propagates the constraints by preventing a consecutive application of the specified rule. -""" -function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive) - node = get_node_at_location(solver, constraint.path) - if isfilled(node) - if get_rule(node) == constraint.rule - #the specified rule is used, make sure the rule will not be used by any of the children - for (i, child) ∈ enumerate(get_children(node)) - if isfilled(child) - if get_rule(child) == constraint.rule - #the specified rule was used twice in a row, which is violating the constraint - set_infeasible!(solver) - return - end - elseif child.domain[constraint.rule] - child_path = push!(copy(constraint.path), i) - remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child - end - end - end - elseif node.domain[constraint.rule] - #our node is a hole with the specified rule in its domain - #we will now check if any of the children already uses the specified rule - softfail = false - for (i, child) ∈ enumerate(get_children(node)) - if isfilled(child) - if get_rule(child) == constraint.rule - #the child holds the specified rule, so the parent cannot have this rule - remove!(solver, constraint.path, constraint.rule) - end - elseif child.domain[constraint.rule] - #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail. - softfail = true - end - end - if softfail - #we cannot deactivate the constraint, because it needs to be repropagated - return - end - end - - #the constraint is satisfied and can be deactivated - HerbConstraints.deactivate!(solver, constraint) -end -``` - - - HerbConstraints.propagate! - - -Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation. - -Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation. - -In our case, we want to repropagate if either: -* a tree manipulation occured at the `constraint.path` -* a tree manipulation occured at the child of the `constraint.path` - - -```julia - -""" -Gets called whenever an tree manipulation occurs at the given `path`. -Returns true iff the `constraint` should be rescheduled for propagation. -""" -function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool - return (path == constraint.path) || (path == constraint.path[1:end-1]) -end - -``` - - - HerbConstraints.shouldschedule - - -With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint. - - -```julia -clearconstraints!(grammar) - -addconstraint!(grammar, ForbidConsecutive(minus)) -addconstraint!(grammar, ForbidConsecutive(plus)) -addconstraint!(grammar, ForbidConsecutive(times)) - -iter = BFSIterator(grammar, :Int, max_size=6) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - 1 - x - -1 - -x - 1 * 1 - 1x - x * x - x * 1 - x + 1 - x + x - 1 + x - 1 + 1 - 1 * -1 - 1 * -x - x * -x - x * -1 - x + -1 - x + -x - 1 + -x - -(1 * 1) - 1 + -1 - -(1x) - -(x * x) - -(x * 1) - -((x + 1)) - -((x + x)) - -1 * 1 - -((1 + x)) - -1 * x - -((1 + 1)) - -x * x - -x * 1 - -x + 1 - -x + x - -1 + x - -1 + 1 - (1 + 1) * 1 - -(1 * -1) - (1 + 1) * x - -(1 * -x) - (1 + x) * x - -(x * -x) - (1 + x) * 1 - -(x * -1) - (x + x) * 1 - -((x + -1)) - (x + x) * x - -((x + -x)) - (x + 1) * x - -((1 + -x)) - (x + 1) * 1 - -((1 + -1)) - x * 1 + 1 - x * 1 + x - x * x + x - x * x + 1 - 1x + 1 - 1x + x - -(-1 * 1) - 1 * 1 + x - -(-1 * x) - 1 * 1 + 1 - -(-x * x) - -(-x * 1) - -((-x + 1)) - 1 * (1 + 1) - -((-x + x)) - 1 * (1 + x) - -((-1 + x)) - 1 * (x + x) - -((-1 + 1)) - 1 * (x + 1) - x * (x + 1) - x * (x + x) - x * (1 + x) - x * (1 + 1) - -1 * -1 - x + 1 * 1 - -1 * -x - x + 1x - -x * -x - x + x * x - -x * -1 - x + x * 1 - -x + -1 - 1 + x * 1 - -x + -x - 1 + x * x - -1 + -x - 1 + 1x - -1 + -1 - 1 + 1 * 1 - -1 * (1 + 1) - 1 * (1 + -1) - -1 * (1 + x) - 1 * (1 + -x) - -1 * (x + x) - 1 * (x + -x) - -1 * (x + 1) - 1 * (x + -1) - -x * (x + 1) - x * (x + -1) - -x * (x + x) - x * (x + -x) - -x * (1 + x) - x * (1 + -x) - -x * (1 + 1) - x * (1 + -1) - -x + 1 * 1 - x + 1 * -1 - -x + 1x - x + 1 * -x - -x + x * x - x + x * -x - -x + x * 1 - x + x * -1 - -1 + x * 1 - 1 + x * -1 - -1 + x * x - 1 + x * -x - -1 + 1x - 1 + 1 * -x - -1 + 1 * 1 - 1 + 1 * -1 - 1 * -(1 * 1) - (1 + -1) * 1 - 1 * -(1x) - (1 + -1) * x - 1 * -(x * x) - (1 + -x) * x - 1 * -(x * 1) - (1 + -x) * 1 - 1 * -((x + 1)) - (x + -x) * 1 - 1 * -((x + x)) - (x + -x) * x - 1 * -((1 + x)) - (x + -1) * x - 1 * -((1 + 1)) - (x + -1) * 1 - x * -((1 + 1)) - x * -1 + 1 - x * -((1 + x)) - x * -1 + x - x * -((x + x)) - x * -x + x - x * -((x + 1)) - x * -x + 1 - x * -(x * 1) - 1 * -x + 1 - x * -(x * x) - 1 * -x + x - x * -(1x) - 1 * -1 + x - x * -(1 * 1) - 1 * -1 + 1 - x + -(1 * 1) - x + -(1x) - 1 * (-1 + 1) - x + -(x * x) - 1 * (-1 + x) - x + -(x * 1) - 1 * (-x + x) - x + -((x + 1)) - 1 * (-x + 1) - x + -((x + x)) - x * (-x + 1) - x + -((1 + x)) - x * (-x + x) - x + -((1 + 1)) - x * (-1 + x) - 1 + -((1 + 1)) - x * (-1 + 1) - 1 + -((1 + x)) - x + -1 * 1 - 1 + -((x + x)) - x + -1 * x - 1 + -((x + 1)) - x + -x * x - 1 + -(x * 1) - x + -x * 1 - 1 + -(x * x) - 1 + -x * 1 - 1 + -(1x) - 1 + -x * x - 1 + -(1 * 1) - 1 + -1 * x - 1 + -1 * 1 - -(1 * 1) * 1 - -(1 * 1) * x - -((1 + 1) * 1) - -(1x) * x - -((1 + 1) * x) - -(1x) * 1 - -((1 + x) * x) - -(x * x) * 1 - -((1 + x) * 1) - -(x * x) * x - -((x + x) * 1) - -(x * 1) * x - -((x + x) * x) - -(x * 1) * 1 - -((x + 1) * x) - -((x + 1)) * 1 - -((x + 1) * 1) - -((x + 1)) * x - -((x * 1 + 1)) - -((x + x)) * x - -((x * 1 + x)) - -((x + x)) * 1 - -((x * x + x)) - -((1 + x)) * 1 - -((x * x + 1)) - -((1 + x)) * x - -((1x + 1)) - -((1 + 1)) * x - -((1x + x)) - -((1 + 1)) * 1 - -((1 * 1 + x)) - -((1 + 1)) + 1 - -((1 * 1 + 1)) - -((1 + 1)) + x - -((1 + x)) + x - -(-1 * -1) - -((1 + x)) + 1 - -(-1 * -x) - -((x + x)) + 1 - -(-x * -x) - -((x + x)) + x - -(-x * -1) - -((x + 1)) + x - -((-x + -1)) - -((x + 1)) + 1 - -((-x + -x)) - -(x * 1) + 1 - -((-1 + -x)) - -(x * 1) + x - -((-1 + -1)) - -(x * x) + x - -(x * x) + 1 - (-1 + 1) * 1 - -(1x) + 1 - (-1 + 1) * x - -(1x) + x - (-1 + x) * x - -(1 * 1) + x - (-1 + x) * 1 - -(1 * 1) + 1 - (-x + x) * 1 - (-x + x) * x - (1 + 1) * -1 - (-x + 1) * x - (1 + 1) * -x - (-x + 1) * 1 - (1 + x) * -x - -x * 1 + 1 - (1 + x) * -1 - -x * 1 + x - (x + x) * -1 - -x * x + x - (x + x) * -x - -x * x + 1 - (x + 1) * -x - -1 * x + 1 - (x + 1) * -1 - -1 * x + x - x * 1 + -1 - -1 * 1 + x - x * 1 + -x - -1 * 1 + 1 - x * x + -x - x * x + -1 - -(1 * (1 + 1)) - 1x + -1 - -(1 * (1 + x)) - 1x + -x - -(1 * (x + x)) - 1 * 1 + -x - -(1 * (x + 1)) - 1 * 1 + -1 - -(x * (x + 1)) - -(x * (x + x)) - -(x * (1 + x)) - -(x * (1 + 1)) - -((x + 1 * 1)) - -((x + 1x)) - -((x + x * x)) - -((x + x * 1)) - -((1 + x * 1)) - -((1 + x * x)) - -((1 + 1x)) - -((1 + 1 * 1)) - From 331a048cc2d1752b4262ad52be88e807a78a5065 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 4 Jul 2024 10:42:40 +0200 Subject: [PATCH 50/75] Rename constraints guide --- .../getting_started_with_constraints.ipynb | 932 ++++++++++++++++++ .../getting_started_with_constraints.md | 738 ++++++++++++++ 2 files changed, 1670 insertions(+) create mode 100644 docs/src/tutorials/getting_started_with_constraints.ipynb create mode 100644 docs/src/tutorials/getting_started_with_constraints.md diff --git a/docs/src/tutorials/getting_started_with_constraints.ipynb b/docs/src/tutorials/getting_started_with_constraints.ipynb new file mode 100644 index 0000000..624f169 --- /dev/null +++ b/docs/src/tutorials/getting_started_with_constraints.ipynb @@ -0,0 +1,932 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Getting started with HerbConstraints\n", + "\n", + "When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup\n", + "\n", + "For this tutorial, we need to import the following modules of the Herb.jl framework:\n", + "\n", + "* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s\n", + "* `HerbGrammar` to define the grammar\n", + "* `HerbConstraints` to define the constraints\n", + "* `HerbSearch` to execute a constrained enumeration\n", + "\n", + "We will also redefine the simple arithmetic grammar from the previous tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 1\n", + "2: Int = x\n", + "3: Int = -Int\n", + "4: Int = Int + Int\n", + "5: Int = Int * Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "using HerbCore, HerbGrammar, HerbConstraints, HerbSearch\n", + "\n", + "grammar = @cfgrammar begin\n", + " Int = 1\n", + " Int = x\n", + " Int = - Int\n", + " Int = Int + Int\n", + " Int = Int * Int\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working with constraints\n", + "\n", + "To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes).\n", + "\n", + "(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n", + "1 + 1\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints.\n", + "\n", + "To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids:\n", + "\n", + "* `-(-1)`\n", + "* `-(-X)`\n", + "* `-(-(1 + 1))`\n", + "* `1 + -(-(1 + 1))`\n", + "* etc" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "x * 1\n", + "x * x\n", + "x + x\n", + "x + 1\n", + "1 + 1\n", + "1 + x\n" + ] + } + ], + "source": [ + "one = 1\n", + "x = 2\n", + "minus = 3\n", + "plus = 4\n", + "times = 5\n", + "\n", + "addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A\n", + "addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A)\n", + "\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forbidden Constraint\n", + "\n", + "The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types:\n", + "* `RuleNode(1)`. Matches exactly the given rule.\n", + "* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5.\n", + "* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "154\n", + "106\n" + ] + } + ], + "source": [ + "#this constraint forbids A+A and A*A\n", + "constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)]))\n", + "\n", + "# Without this constraint, we encounter 154 programs\n", + "clearconstraints!(grammar)\n", + "iter = BFSIterator(grammar, :Int, max_size=5)\n", + "println(length(iter))\n", + "\n", + "# With this constraint, we encounter 106 programs\n", + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, constraint)\n", + "iter = BFSIterator(grammar, :Int, max_size=5)\n", + "println(length(iter))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contains Constraint\n", + "\n", + "The `Contains` constraint enforces that a given rule appears in the program tree at least once. \n", + "\n", + "In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x\n", + "-x\n", + "-(-x)\n", + "1x\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contains Subtree Constraint\n", + "\n", + "Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x * x\n", + "-(x * x)\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree\n", + "iter = BFSIterator(grammar, :Int, max_size=4)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ordered Constraint\n", + "\n", + "The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants.\n", + "\n", + "To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree.\n", + "\n", + "In the upcoming example we will set up a template tree representing `a+b` and `a*b`.\n", + "Then, we will impose an ordering `a<=b` on all the subtrees that match the template.\n", + "\n", + "The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x + x\n", + "1 + x\n", + "1 + 1\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "\n", + "template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)])\n", + "order = [:a, :b]\n", + "\n", + "addconstraint!(grammar, Ordered(template_tree, order))\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forbidden Sequence Constraint\n", + "\n", + "The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. \n", + "\n", + "An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. \n", + "\n", + "Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence.\n", + "\n", + "This constraint will **forbid** the following programs:\n", + "\n", + "* x + 1\n", + "* x + -1\n", + "* x + -(-1)\n", + "* x + (x + 1)\n", + "* x * (x + 1)\n", + "\n", + "But it will **allow** the following program (as * disrupts the sequence):\n", + "\n", + "* x + (x * 1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x + x\n" + ] + } + ], + "source": [ + "constraint = ForbiddenSequence([plus, one], ignore_if=[times])\n", + "addconstraint!(grammar, constraint)\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Constraint\n", + "\n", + "To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`.\n", + "\n", + "A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location.\n", + "\n", + "A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. \n", + "\n", + "Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function.\n", + "\n", + "(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.on_new_node" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "Forbids the consecutive application of the specified rule.\n", + "For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row.\n", + "\"\"\"\n", + "struct ForbidConsecutive <: AbstractGrammarConstraint\n", + " rule::Int\n", + "end\n", + "\n", + "\"\"\"\n", + "Post a local constraint on each new node that appears in the tree\n", + "\"\"\"\n", + "function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int})\n", + " HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path:\n", + "\n", + "* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})`\n", + "* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)`\n", + "* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree)\n", + "\n", + "In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver:\n", + "\n", + "* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators.\n", + "* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation.\n", + "* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint.\n", + "\n", + "The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions:\n", + "\n", + "* `get_tree(solver)` returns the root node of the current (partial) program tree\n", + "* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants.\n", + "* `get_path(solver, node)` returns the path at which the node is located.\n", + "* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations).\n", + "* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2).\n", + "\n", + "To get information about a node, we can use the following getter functions:\n", + "\n", + "* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1.\n", + "* `get_rule(node)`. Get the rule of a filled node.\n", + "* `get_children(node)`. Get the children of a node.\n", + "* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain.\n", + "\n", + "Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.propagate!" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "Forbids the consecutive application of the specified rule at path `path`.\n", + "\"\"\"\n", + "struct LocalForbidConsecutive <: AbstractLocalConstraint\n", + " path::Vector{Int}\n", + " rule::Int\n", + "end\n", + "\n", + "\"\"\"\n", + "Propagates the constraints by preventing a consecutive application of the specified rule.\n", + "\"\"\"\n", + "function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive)\n", + " node = get_node_at_location(solver, constraint.path)\n", + " if isfilled(node)\n", + " if get_rule(node) == constraint.rule\n", + " #the specified rule is used, make sure the rule will not be used by any of the children\n", + " for (i, child) ∈ enumerate(get_children(node))\n", + " if isfilled(child)\n", + " if get_rule(child) == constraint.rule\n", + " #the specified rule was used twice in a row, which is violating the constraint\n", + " set_infeasible!(solver)\n", + " return\n", + " end\n", + " elseif child.domain[constraint.rule]\n", + " child_path = push!(copy(constraint.path), i)\n", + " remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child\n", + " end\n", + " end\n", + " end\n", + " elseif node.domain[constraint.rule]\n", + " #our node is a hole with the specified rule in its domain\n", + " #we will now check if any of the children already uses the specified rule\n", + " softfail = false\n", + " for (i, child) ∈ enumerate(get_children(node))\n", + " if isfilled(child)\n", + " if get_rule(child) == constraint.rule\n", + " #the child holds the specified rule, so the parent cannot have this rule\n", + " remove!(solver, constraint.path, constraint.rule)\n", + " end\n", + " elseif child.domain[constraint.rule]\n", + " #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail.\n", + " softfail = true\n", + " end\n", + " end\n", + " if softfail\n", + " #we cannot deactivate the constraint, because it needs to be repropagated\n", + " return\n", + " end\n", + " end\n", + "\n", + " #the constraint is satisfied and can be deactivated\n", + " HerbConstraints.deactivate!(solver, constraint)\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation.\n", + "\n", + "Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation.\n", + "\n", + "In our case, we want to repropagate if either:\n", + "* a tree manipulation occured at the `constraint.path`\n", + "* a tree manipulation occured at the child of the `constraint.path`" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.shouldschedule" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "\"\"\"\n", + "Gets called whenever an tree manipulation occurs at the given `path`.\n", + "Returns true iff the `constraint` should be rescheduled for propagation.\n", + "\"\"\"\n", + "function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool\n", + " return (path == constraint.path) || (path == constraint.path[1:end-1])\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "1x\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n", + "1 + 1\n", + "1 * -1\n", + "1 * -x\n", + "x * -x\n", + "x * -1\n", + "x + -1\n", + "x + -x\n", + "1 + -x\n", + "-(1 * 1)\n", + "1 + -1\n", + "-(1x)\n", + "-(x * x)\n", + "-(x * 1)\n", + "-((x + 1))\n", + "-((x + x))\n", + "-1 * 1\n", + "-((1 + x))\n", + "-1 * x\n", + "-((1 + 1))\n", + "-x * x\n", + "-x * 1\n", + "-x + 1\n", + "-x + x\n", + "-1 + x\n", + "-1 + 1\n", + "(1 + 1) * 1\n", + "-(1 * -1)\n", + "(1 + 1) * x\n", + "-(1 * -x)\n", + "(1 + x) * x\n", + "-(x * -x)\n", + "(1 + x) * 1\n", + "-(x * -1)\n", + "(x + x) * 1\n", + "-((x + -1))\n", + "(x + x) * x\n", + "-((x + -x))\n", + "(x + 1) * x\n", + "-((1 + -x))\n", + "(x + 1) * 1\n", + "-((1 + -1))\n", + "x * 1 + 1\n", + "x * 1 + x\n", + "x * x + x\n", + "x * x + 1\n", + "1x + 1\n", + "1x + x\n", + "-(-1 * 1)\n", + "1 * 1 + x\n", + "-(-1 * x)\n", + "1 * 1 + 1\n", + "-(-x * x)\n", + "-(-x * 1)\n", + "-((-x + 1))\n", + "1 * (1 + 1)\n", + "-((-x + x))\n", + "1 * (1 + x)\n", + "-((-1 + x))\n", + "1 * (x + x)\n", + "-((-1 + 1))\n", + "1 * (x + 1)\n", + "x * (x + 1)\n", + "x * (x + x)\n", + "x * (1 + x)\n", + "x * (1 + 1)\n", + "-1 * -1\n", + "x + 1 * 1\n", + "-1 * -x\n", + "x + 1x\n", + "-x * -x\n", + "x + x * x\n", + "-x * -1\n", + "x + x * 1\n", + "-x + -1\n", + "1 + x * 1\n", + "-x + -x\n", + "1 + x * x\n", + "-1 + -x\n", + "1 + 1x\n", + "-1 + -1\n", + "1 + 1 * 1\n", + "-1 * (1 + 1)\n", + "1 * (1 + -1)\n", + "-1 * (1 + x)\n", + "1 * (1 + -x)\n", + "-1 * (x + x)\n", + "1 * (x + -x)\n", + "-1 * (x + 1)\n", + "1 * (x + -1)\n", + "-x * (x + 1)\n", + "x * (x + -1)\n", + "-x * (x + x)\n", + "x * (x + -x)\n", + "-x * (1 + x)\n", + "x * (1 + -x)\n", + "-x * (1 + 1)\n", + "x * (1 + -1)\n", + "-x + 1 * 1\n", + "x + 1 * -1\n", + "-x + 1x\n", + "x + 1 * -x\n", + "-x + x * x\n", + "x + x * -x\n", + "-x + x * 1\n", + "x + x * -1\n", + "-1 + x * 1\n", + "1 + x * -1\n", + "-1 + x * x\n", + "1 + x * -x\n", + "-1 + 1x\n", + "1 + 1 * -x\n", + "-1 + 1 * 1\n", + "1 + 1 * -1\n", + "1 * -(1 * 1)\n", + "(1 + -1) * 1\n", + "1 * -(1x)\n", + "(1 + -1) * x\n", + "1 * -(x * x)\n", + "(1 + -x) * x\n", + "1 * -(x * 1)\n", + "(1 + -x) * 1\n", + "1 * -((x + 1))\n", + "(x + -x) * 1\n", + "1 * -((x + x))\n", + "(x + -x) * x\n", + "1 * -((1 + x))\n", + "(x + -1) * x\n", + "1 * -((1 + 1))\n", + "(x + -1) * 1\n", + "x * -((1 + 1))\n", + "x * -1 + 1\n", + "x * -((1 + x))\n", + "x * -1 + x\n", + "x * -((x + x))\n", + "x * -x + x\n", + "x * -((x + 1))\n", + "x * -x + 1\n", + "x * -(x * 1)\n", + "1 * -x + 1\n", + "x * -(x * x)\n", + "1 * -x + x\n", + "x * -(1x)\n", + "1 * -1 + x\n", + "x * -(1 * 1)\n", + "1 * -1 + 1\n", + "x + -(1 * 1)\n", + "x + -(1x)\n", + "1 * (-1 + 1)\n", + "x + -(x * x)\n", + "1 * (-1 + x)\n", + "x + -(x * 1)\n", + "1 * (-x + x)\n", + "x + -((x + 1))\n", + "1 * (-x + 1)\n", + "x + -((x + x))\n", + "x * (-x + 1)\n", + "x + -((1 + x))\n", + "x * (-x + x)\n", + "x + -((1 + 1))\n", + "x * (-1 + x)\n", + "1 + -((1 + 1))\n", + "x * (-1 + 1)\n", + "1 + -((1 + x))\n", + "x + -1 * 1\n", + "1 + -((x + x))\n", + "x + -1 * x\n", + "1 + -((x + 1))\n", + "x + -x * x\n", + "1 + -(x * 1)\n", + "x + -x * 1\n", + "1 + -(x * x)\n", + "1 + -x * 1\n", + "1 + -(1x)\n", + "1 + -x * x\n", + "1 + -(1 * 1)\n", + "1 + -1 * x\n", + "1 + -1 * 1\n", + "-(1 * 1) * 1\n", + "-(1 * 1) * x\n", + "-((1 + 1) * 1)\n", + "-(1x) * x\n", + "-((1 + 1) * x)\n", + "-(1x) * 1\n", + "-((1 + x) * x)\n", + "-(x * x) * 1\n", + "-((1 + x) * 1)\n", + "-(x * x) * x\n", + "-((x + x) * 1)\n", + "-(x * 1) * x\n", + "-((x + x) * x)\n", + "-(x * 1) * 1\n", + "-((x + 1) * x)\n", + "-((x + 1)) * 1\n", + "-((x + 1) * 1)\n", + "-((x + 1)) * x\n", + "-((x * 1 + 1))\n", + "-((x + x)) * x\n", + "-((x * 1 + x))\n", + "-((x + x)) * 1\n", + "-((x * x + x))\n", + "-((1 + x)) * 1\n", + "-((x * x + 1))\n", + "-((1 + x)) * x\n", + "-((1x + 1))\n", + "-((1 + 1)) * x\n", + "-((1x + x))\n", + "-((1 + 1)) * 1\n", + "-((1 * 1 + x))\n", + "-((1 + 1)) + 1\n", + "-((1 * 1 + 1))\n", + "-((1 + 1)) + x\n", + "-((1 + x)) + x\n", + "-(-1 * -1)\n", + "-((1 + x)) + 1\n", + "-(-1 * -x)\n", + "-((x + x)) + 1\n", + "-(-x * -x)\n", + "-((x + x)) + x\n", + "-(-x * -1)\n", + "-((x + 1)) + x\n", + "-((-x + -1))\n", + "-((x + 1)) + 1\n", + "-((-x + -x))\n", + "-(x * 1) + 1\n", + "-((-1 + -x))\n", + "-(x * 1) + x\n", + "-((-1 + -1))\n", + "-(x * x) + x\n", + "-(x * x) + 1\n", + "(-1 + 1) * 1\n", + "-(1x) + 1\n", + "(-1 + 1) * x\n", + "-(1x) + x\n", + "(-1 + x) * x\n", + "-(1 * 1) + x\n", + "(-1 + x) * 1\n", + "-(1 * 1) + 1\n", + "(-x + x) * 1\n", + "(-x + x) * x\n", + "(1 + 1) * -1\n", + "(-x + 1) * x\n", + "(1 + 1) * -x\n", + "(-x + 1) * 1\n", + "(1 + x) * -x\n", + "-x * 1 + 1\n", + "(1 + x) * -1\n", + "-x * 1 + x\n", + "(x + x) * -1\n", + "-x * x + x\n", + "(x + x) * -x\n", + "-x * x + 1\n", + "(x + 1) * -x\n", + "-1 * x + 1\n", + "(x + 1) * -1\n", + "-1 * x + x\n", + "x * 1 + -1\n", + "-1 * 1 + x\n", + "x * 1 + -x\n", + "-1 * 1 + 1\n", + "x * x + -x\n", + "x * x + -1\n", + "-(1 * (1 + 1))\n", + "1x + -1\n", + "-(1 * (1 + x))\n", + "1x + -x\n", + "-(1 * (x + x))\n", + "1 * 1 + -x\n", + "-(1 * (x + 1))\n", + "1 * 1 + -1\n", + "-(x * (x + 1))\n", + "-(x * (x + x))\n", + "-(x * (1 + x))\n", + "-(x * (1 + 1))\n", + "-((x + 1 * 1))\n", + "-((x + 1x))\n", + "-((x + x * x))\n", + "-((x + x * 1))\n", + "-((1 + x * 1))\n", + "-((1 + x * x))\n", + "-((1 + 1x))\n", + "-((1 + 1 * 1))\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "\n", + "addconstraint!(grammar, ForbidConsecutive(minus))\n", + "addconstraint!(grammar, ForbidConsecutive(plus))\n", + "addconstraint!(grammar, ForbidConsecutive(times))\n", + "\n", + "iter = BFSIterator(grammar, :Int, max_size=6)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.8.5", + "language": "julia", + "name": "julia-1.8" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/src/tutorials/getting_started_with_constraints.md b/docs/src/tutorials/getting_started_with_constraints.md new file mode 100644 index 0000000..08d9265 --- /dev/null +++ b/docs/src/tutorials/getting_started_with_constraints.md @@ -0,0 +1,738 @@ +# Getting started with HerbConstraints + +When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space. + +### Setup + +For this tutorial, we need to import the following modules of the Herb.jl framework: + +* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s +* `HerbGrammar` to define the grammar +* `HerbConstraints` to define the constraints +* `HerbSearch` to execute a constrained enumeration + +We will also redefine the simple arithmetic grammar from the previous tutorial. + + +```julia +using HerbCore, HerbGrammar, HerbConstraints, HerbSearch + +grammar = @cfgrammar begin + Int = 1 + Int = x + Int = - Int + Int = Int + Int + Int = Int * Int +end +``` + + + 1: Int = 1 + 2: Int = x + 3: Int = -Int + 4: Int = Int + Int + 5: Int = Int * Int + + + +### Working with constraints + +To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes). + +(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar) + + +```julia +clearconstraints!(grammar) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x * 1 + x + 1 + x + x + 1 + x + 1 + 1 + + +Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. + +To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids: + +* `-(-1)` +* `-(-X)` +* `-(-(1 + 1))` +* `1 + -(-(1 + 1))` +* etc + + +```julia +one = 1 +x = 2 +minus = 3 +plus = 4 +times = 5 + +addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A +addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A) + +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + 1 + x + -1 + -x + x * 1 + x * x + x + x + x + 1 + 1 + 1 + 1 + x + + +### Forbidden Constraint + +The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types: +* `RuleNode(1)`. Matches exactly the given rule. +* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5. +* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same. + + +```julia +#this constraint forbids A+A and A*A +constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)])) + +# Without this constraint, we encounter 154 programs +clearconstraints!(grammar) +iter = BFSIterator(grammar, :Int, max_size=5) +println(length(iter)) + +# With this constraint, we encounter 106 programs +clearconstraints!(grammar) +addconstraint!(grammar, constraint) +iter = BFSIterator(grammar, :Int, max_size=5) +println(length(iter)) + +``` + + 154 + 106 + + +### Contains Constraint + +The `Contains` constraint enforces that a given rule appears in the program tree at least once. + +In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant. + + +```julia +clearconstraints!(grammar) +addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + x + -x + -(-x) + 1x + x * x + x * 1 + x + 1 + x + x + 1 + x + + +### Contains Subtree Constraint + +Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once. + + +```julia +clearconstraints!(grammar) +addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree +iter = BFSIterator(grammar, :Int, max_size=4) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + x * x + -(x * x) + + +### Ordered Constraint + +The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants. + +To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree. + +In the upcoming example we will set up a template tree representing `a+b` and `a*b`. +Then, we will impose an ordering `a<=b` on all the subtrees that match the template. + +The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`. + + + +```julia +clearconstraints!(grammar) + +template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)]) +order = [:a, :b] + +addconstraint!(grammar, Ordered(template_tree, order)) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x + x + 1 + x + 1 + 1 + + +### Forbidden Sequence Constraint + +The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. + +An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. + +Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence. + +This constraint will **forbid** the following programs: + +* x + 1 +* x + -1 +* x + -(-1) +* x + (x + 1) +* x * (x + 1) + +But it will **allow** the following program (as * disrupts the sequence): + +* x + (x * 1) + + + +```julia +constraint = ForbiddenSequence([plus, one], ignore_if=[times]) +addconstraint!(grammar, constraint) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x + x + + +### Custom Constraint + +To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`. + +A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location. + +A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies. + +Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. + +Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function. + +(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace) + + +```julia +""" +Forbids the consecutive application of the specified rule. +For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row. +""" +struct ForbidConsecutive <: AbstractGrammarConstraint + rule::Int +end + +""" +Post a local constraint on each new node that appears in the tree +""" +function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int}) + HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule)) +end +``` + + + HerbConstraints.on_new_node + + +Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path: + +* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})` +* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)` +* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree) + +In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver: + +* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators. +* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation. +* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint. + +The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions: + +* `get_tree(solver)` returns the root node of the current (partial) program tree +* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants. +* `get_path(solver, node)` returns the path at which the node is located. +* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations). +* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2). + +To get information about a node, we can use the following getter functions: + +* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1. +* `get_rule(node)`. Get the rule of a filled node. +* `get_children(node)`. Get the children of a node. +* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain. + +Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match. + + + +```julia +""" +Forbids the consecutive application of the specified rule at path `path`. +""" +struct LocalForbidConsecutive <: AbstractLocalConstraint + path::Vector{Int} + rule::Int +end + +""" +Propagates the constraints by preventing a consecutive application of the specified rule. +""" +function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive) + node = get_node_at_location(solver, constraint.path) + if isfilled(node) + if get_rule(node) == constraint.rule + #the specified rule is used, make sure the rule will not be used by any of the children + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the specified rule was used twice in a row, which is violating the constraint + set_infeasible!(solver) + return + end + elseif child.domain[constraint.rule] + child_path = push!(copy(constraint.path), i) + remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child + end + end + end + elseif node.domain[constraint.rule] + #our node is a hole with the specified rule in its domain + #we will now check if any of the children already uses the specified rule + softfail = false + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the child holds the specified rule, so the parent cannot have this rule + remove!(solver, constraint.path, constraint.rule) + end + elseif child.domain[constraint.rule] + #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail. + softfail = true + end + end + if softfail + #we cannot deactivate the constraint, because it needs to be repropagated + return + end + end + + #the constraint is satisfied and can be deactivated + HerbConstraints.deactivate!(solver, constraint) +end +``` + + + HerbConstraints.propagate! + + +Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation. + +Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation. + +In our case, we want to repropagate if either: +* a tree manipulation occured at the `constraint.path` +* a tree manipulation occured at the child of the `constraint.path` + + +```julia + +""" +Gets called whenever an tree manipulation occurs at the given `path`. +Returns true iff the `constraint` should be rescheduled for propagation. +""" +function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool + return (path == constraint.path) || (path == constraint.path[1:end-1]) +end + +``` + + + HerbConstraints.shouldschedule + + +With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint. + + +```julia +clearconstraints!(grammar) + +addconstraint!(grammar, ForbidConsecutive(minus)) +addconstraint!(grammar, ForbidConsecutive(plus)) +addconstraint!(grammar, ForbidConsecutive(times)) + +iter = BFSIterator(grammar, :Int, max_size=6) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + 1 + x + -1 + -x + 1 * 1 + 1x + x * x + x * 1 + x + 1 + x + x + 1 + x + 1 + 1 + 1 * -1 + 1 * -x + x * -x + x * -1 + x + -1 + x + -x + 1 + -x + -(1 * 1) + 1 + -1 + -(1x) + -(x * x) + -(x * 1) + -((x + 1)) + -((x + x)) + -1 * 1 + -((1 + x)) + -1 * x + -((1 + 1)) + -x * x + -x * 1 + -x + 1 + -x + x + -1 + x + -1 + 1 + (1 + 1) * 1 + -(1 * -1) + (1 + 1) * x + -(1 * -x) + (1 + x) * x + -(x * -x) + (1 + x) * 1 + -(x * -1) + (x + x) * 1 + -((x + -1)) + (x + x) * x + -((x + -x)) + (x + 1) * x + -((1 + -x)) + (x + 1) * 1 + -((1 + -1)) + x * 1 + 1 + x * 1 + x + x * x + x + x * x + 1 + 1x + 1 + 1x + x + -(-1 * 1) + 1 * 1 + x + -(-1 * x) + 1 * 1 + 1 + -(-x * x) + -(-x * 1) + -((-x + 1)) + 1 * (1 + 1) + -((-x + x)) + 1 * (1 + x) + -((-1 + x)) + 1 * (x + x) + -((-1 + 1)) + 1 * (x + 1) + x * (x + 1) + x * (x + x) + x * (1 + x) + x * (1 + 1) + -1 * -1 + x + 1 * 1 + -1 * -x + x + 1x + -x * -x + x + x * x + -x * -1 + x + x * 1 + -x + -1 + 1 + x * 1 + -x + -x + 1 + x * x + -1 + -x + 1 + 1x + -1 + -1 + 1 + 1 * 1 + -1 * (1 + 1) + 1 * (1 + -1) + -1 * (1 + x) + 1 * (1 + -x) + -1 * (x + x) + 1 * (x + -x) + -1 * (x + 1) + 1 * (x + -1) + -x * (x + 1) + x * (x + -1) + -x * (x + x) + x * (x + -x) + -x * (1 + x) + x * (1 + -x) + -x * (1 + 1) + x * (1 + -1) + -x + 1 * 1 + x + 1 * -1 + -x + 1x + x + 1 * -x + -x + x * x + x + x * -x + -x + x * 1 + x + x * -1 + -1 + x * 1 + 1 + x * -1 + -1 + x * x + 1 + x * -x + -1 + 1x + 1 + 1 * -x + -1 + 1 * 1 + 1 + 1 * -1 + 1 * -(1 * 1) + (1 + -1) * 1 + 1 * -(1x) + (1 + -1) * x + 1 * -(x * x) + (1 + -x) * x + 1 * -(x * 1) + (1 + -x) * 1 + 1 * -((x + 1)) + (x + -x) * 1 + 1 * -((x + x)) + (x + -x) * x + 1 * -((1 + x)) + (x + -1) * x + 1 * -((1 + 1)) + (x + -1) * 1 + x * -((1 + 1)) + x * -1 + 1 + x * -((1 + x)) + x * -1 + x + x * -((x + x)) + x * -x + x + x * -((x + 1)) + x * -x + 1 + x * -(x * 1) + 1 * -x + 1 + x * -(x * x) + 1 * -x + x + x * -(1x) + 1 * -1 + x + x * -(1 * 1) + 1 * -1 + 1 + x + -(1 * 1) + x + -(1x) + 1 * (-1 + 1) + x + -(x * x) + 1 * (-1 + x) + x + -(x * 1) + 1 * (-x + x) + x + -((x + 1)) + 1 * (-x + 1) + x + -((x + x)) + x * (-x + 1) + x + -((1 + x)) + x * (-x + x) + x + -((1 + 1)) + x * (-1 + x) + 1 + -((1 + 1)) + x * (-1 + 1) + 1 + -((1 + x)) + x + -1 * 1 + 1 + -((x + x)) + x + -1 * x + 1 + -((x + 1)) + x + -x * x + 1 + -(x * 1) + x + -x * 1 + 1 + -(x * x) + 1 + -x * 1 + 1 + -(1x) + 1 + -x * x + 1 + -(1 * 1) + 1 + -1 * x + 1 + -1 * 1 + -(1 * 1) * 1 + -(1 * 1) * x + -((1 + 1) * 1) + -(1x) * x + -((1 + 1) * x) + -(1x) * 1 + -((1 + x) * x) + -(x * x) * 1 + -((1 + x) * 1) + -(x * x) * x + -((x + x) * 1) + -(x * 1) * x + -((x + x) * x) + -(x * 1) * 1 + -((x + 1) * x) + -((x + 1)) * 1 + -((x + 1) * 1) + -((x + 1)) * x + -((x * 1 + 1)) + -((x + x)) * x + -((x * 1 + x)) + -((x + x)) * 1 + -((x * x + x)) + -((1 + x)) * 1 + -((x * x + 1)) + -((1 + x)) * x + -((1x + 1)) + -((1 + 1)) * x + -((1x + x)) + -((1 + 1)) * 1 + -((1 * 1 + x)) + -((1 + 1)) + 1 + -((1 * 1 + 1)) + -((1 + 1)) + x + -((1 + x)) + x + -(-1 * -1) + -((1 + x)) + 1 + -(-1 * -x) + -((x + x)) + 1 + -(-x * -x) + -((x + x)) + x + -(-x * -1) + -((x + 1)) + x + -((-x + -1)) + -((x + 1)) + 1 + -((-x + -x)) + -(x * 1) + 1 + -((-1 + -x)) + -(x * 1) + x + -((-1 + -1)) + -(x * x) + x + -(x * x) + 1 + (-1 + 1) * 1 + -(1x) + 1 + (-1 + 1) * x + -(1x) + x + (-1 + x) * x + -(1 * 1) + x + (-1 + x) * 1 + -(1 * 1) + 1 + (-x + x) * 1 + (-x + x) * x + (1 + 1) * -1 + (-x + 1) * x + (1 + 1) * -x + (-x + 1) * 1 + (1 + x) * -x + -x * 1 + 1 + (1 + x) * -1 + -x * 1 + x + (x + x) * -1 + -x * x + x + (x + x) * -x + -x * x + 1 + (x + 1) * -x + -1 * x + 1 + (x + 1) * -1 + -1 * x + x + x * 1 + -1 + -1 * 1 + x + x * 1 + -x + -1 * 1 + 1 + x * x + -x + x * x + -1 + -(1 * (1 + 1)) + 1x + -1 + -(1 * (1 + x)) + 1x + -x + -(1 * (x + x)) + 1 * 1 + -x + -(1 * (x + 1)) + 1 * 1 + -1 + -(x * (x + 1)) + -(x * (x + x)) + -(x * (1 + x)) + -(x * (1 + 1)) + -((x + 1 * 1)) + -((x + 1x)) + -((x + x * x)) + -((x + x * 1)) + -((1 + x * 1)) + -((1 + x * x)) + -((1 + 1x)) + -((1 + 1 * 1)) + From ccfec46f4851c31dde0b701b08e5702605d668aa Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Thu, 4 Jul 2024 16:44:22 +0200 Subject: [PATCH 51/75] added automatic ipynb to md conversion for tutorials. Updated grammar and basic serach tutorials --- docs/Project.toml | 1 + docs/make.jl | 12 ++ docs/src/tutorials/defining_grammars.ipynb | 134 +++++++++--------- docs/src/tutorials/defining_grammars.md | 26 ++-- .../tutorials/getting_started_with_herb.ipynb | 116 ++++++++++----- .../tutorials/getting_started_with_herb.md | 74 ++++++---- 6 files changed, 220 insertions(+), 143 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 2fdf779..06eab32 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -8,6 +8,7 @@ HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" [compat] Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index d8bcbc5..2e860ae 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -9,6 +9,18 @@ using HerbInterpret using HerbCore using HerbSpecification +# Use jupyter.nbconver to convert notebooks to markdown +using PyCall +jupyter = pyimport("jupyterlab") +nbconvert = pyimport("nbconvert") +all_notebooks = readdir("src/tutorials/") +for file in all_notebooks + if occursin("ipynb", file) + path = pwd() * "/src/tutorials/" * file + run(`jupyter nbconvert --to markdown $path`) + end +end + makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", diff --git a/docs/src/tutorials/defining_grammars.ipynb b/docs/src/tutorials/defining_grammars.ipynb index c0828fe..c31b791 100644 --- a/docs/src/tutorials/defining_grammars.ipynb +++ b/docs/src/tutorials/defining_grammars.ipynb @@ -4,7 +4,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Defining Grammars in Herb.jl\n", + "# Defining Grammars in Herb.jl using HerbGrammar\n", "\n", "The program space in Herb.jl is defined using a grammar. \n", "This notebook demonstrates how such a grammar can be created. \n", @@ -15,17 +15,17 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Setup" + "### Setup\n", + "First, we import the necessary Herb packages." ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 73, "metadata": {}, "outputs": [], "source": [ - "include(\"../../src/Herb.jl\") # this can be deleted when we have modules\n", - "using ..Herb" + "using HerbGrammar, HerbConstraints" ] }, { @@ -39,12 +39,12 @@ "The grammar is defined using the `@cfgrammar` macro. \n", "This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. \n", "Macro's are executed during compilation.\n", - "If you want to load a grammar during execution, have a look at the `Herb.HerbGrammar.expr2cfgrammar` function." + "If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2cfgrammar` function." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 74, "metadata": {}, "outputs": [ { @@ -62,7 +62,7 @@ } ], "source": [ - "g₁ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₁ = HerbGrammar.@cfgrammar begin\n", " Int = 1\n", " Int = 2\n", " Int = 3\n", @@ -81,7 +81,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 75, "metadata": {}, "outputs": [ { @@ -106,7 +106,7 @@ } ], "source": [ - "g₂ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₂ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -122,7 +122,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 76, "metadata": {}, "outputs": [ { @@ -142,7 +142,7 @@ } ], "source": [ - "g₃ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₃ = HerbGrammar.@cfgrammar begin\n", " Int = |([0, 2, 4, 6, 8])\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -158,7 +158,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 77, "metadata": {}, "outputs": [ { @@ -184,7 +184,7 @@ } ], "source": [ - "g₄ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₄ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -203,7 +203,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 78, "metadata": {}, "outputs": [ { @@ -232,7 +232,7 @@ "source": [ "f(a) = a + 1\n", "\n", - "g₅ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₅ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -251,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 79, "metadata": {}, "outputs": [ { @@ -279,7 +279,7 @@ "source": [ "×(a, b) = a * b\n", "\n", - "g₆ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₆ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", " Int = a\n", " Int = Int + Int\n", @@ -304,7 +304,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 80, "metadata": {}, "outputs": [ { @@ -331,7 +331,7 @@ } ], "source": [ - "g₇ = Herb.HerbGrammar.@cfgrammar begin\n", + "g₇ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", " Int = Int + Int\n", " Int = Int * Int\n", @@ -359,7 +359,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 81, "metadata": {}, "outputs": [ { @@ -373,12 +373,12 @@ } ], "source": [ - "Herb.HerbGrammar.isterminal(g₇, 1)" + "HerbGrammar.isterminal(g₇, 1)" ] }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 82, "metadata": {}, "outputs": [ { @@ -392,7 +392,7 @@ } ], "source": [ - "Herb.HerbGrammar.isterminal(g₇, 11)" + "HerbGrammar.isterminal(g₇, 11)" ] }, { @@ -406,7 +406,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 83, "metadata": {}, "outputs": [ { @@ -420,7 +420,7 @@ } ], "source": [ - "Herb.HerbGrammar.return_type(g₇, 11)" + "HerbGrammar.return_type(g₇, 11)" ] }, { @@ -435,7 +435,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 84, "metadata": {}, "outputs": [ { @@ -451,12 +451,12 @@ } ], "source": [ - "Herb.HerbGrammar.child_types(g₇, 11)" + "HerbGrammar.child_types(g₇, 11)" ] }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 85, "metadata": {}, "outputs": [ { @@ -470,7 +470,7 @@ } ], "source": [ - "Herb.HerbGrammar.nchildren(g₇, 11)" + "HerbGrammar.nchildren(g₇, 11)" ] }, { @@ -484,7 +484,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 86, "metadata": {}, "outputs": [ { @@ -499,7 +499,7 @@ } ], "source": [ - "Herb.HerbGrammar.nonterminals(g₇)" + "HerbGrammar.nonterminals(g₇)" ] }, { @@ -518,7 +518,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 87, "metadata": {}, "outputs": [ { @@ -545,7 +545,7 @@ } ], "source": [ - "Herb.HerbGrammar.add_rule!(g₇, :(Int = Int - Int))" + "HerbGrammar.add_rule!(g₇, :(Int = Int - Int))" ] }, { @@ -567,7 +567,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 88, "metadata": {}, "outputs": [ { @@ -594,12 +594,12 @@ } ], "source": [ - "Herb.HerbGrammar.remove_rule!(g₇, 11)" + "HerbGrammar.remove_rule!(g₇, 11)" ] }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 89, "metadata": {}, "outputs": [ { @@ -625,7 +625,7 @@ } ], "source": [ - "Herb.HerbGrammar.cleanup_removed_rules!(g₇)" + "HerbGrammar.cleanup_removed_rules!(g₇)" ] }, { @@ -641,7 +641,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 90, "metadata": {}, "outputs": [ { @@ -667,7 +667,7 @@ } ], "source": [ - "g₈ = Herb.HerbGrammar.@csgrammar begin\n", + "g₈ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = Int + Int\n", " Int = Int * Int\n", @@ -693,14 +693,14 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 91, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "1-element Vector{Main.Herb.HerbCore.Constraint}:\n", - " Main.Herb.HerbConstraints.ComesAfter(1, [9])" + "1-element Vector{HerbCore.Constraint}:\n", + " ComesAfter(1, [9])" ] }, "metadata": {}, @@ -708,7 +708,7 @@ } ], "source": [ - "Herb.HerbGrammar.addconstraint!(g₈, Herb.HerbConstraints.ComesAfter(1, [9]))" + "HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9]))" ] }, { @@ -724,7 +724,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": 92, "metadata": {}, "outputs": [ { @@ -746,30 +746,30 @@ "text": [ "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", "│ Uniform distribution is assumed.\n", - "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n" + "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n" ] } ], "source": [ - "g₉ = Herb.HerbGrammar.@pcfgrammar begin\n", + "g₉ = HerbGrammar.@pcfgrammar begin\n", " 0.4 : Int = |(0:9)\n", " 0.2 : Int = Int + Int\n", " 0.1 : Int = Int * Int\n", @@ -777,7 +777,7 @@ "end\n", "\n", "for r ∈ 1:length(g₃.rules)\n", - " p = Herb.HerbGrammar.probability(g₈, r)\n", + " p = HerbGrammar.probability(g₈, r)\n", "\n", " println(\"$p : $r\")\n", "end" @@ -811,16 +811,16 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": 93, "metadata": {}, "outputs": [], "source": [ - "Herb.HerbGrammar.store_cfg(\"demo.txt\", g₇)" + "HerbGrammar.store_cfg(\"demo.txt\", g₇)" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": 94, "metadata": { "scrolled": false }, @@ -848,7 +848,7 @@ } ], "source": [ - "Herb.HerbGrammar.read_cfg(\"demo.txt\")" + "HerbGrammar.read_cfg(\"demo.txt\")" ] }, { @@ -865,7 +865,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": 95, "metadata": {}, "outputs": [ { @@ -884,7 +884,7 @@ "11: Int = Int + Int\n", "12: Int = Int * Int\n", "13: Int = x\n", - ", Main.Herb.HerbCore.Constraint[Main.Herb.HerbConstraints.ComesAfter(1, [9])])" + ", HerbCore.Constraint[ComesAfter(1, [9])])" ] }, "metadata": {}, @@ -892,13 +892,13 @@ } ], "source": [ - "Herb.HerbGrammar.store_csg(\"demo.grammar\", \"demo.constraints\", g₈)\n", + "HerbGrammar.store_csg(\"demo.grammar\", \"demo.constraints\", g₈)\n", "g₈, g₈.constraints" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": 96, "metadata": {}, "outputs": [ { @@ -917,7 +917,7 @@ "11: Int = Int + Int\n", "12: Int = Int * Int\n", "13: Int = x\n", - ", Main.Herb.HerbCore.Constraint[Main.Herb.HerbConstraints.ComesAfter(1, [9])])" + ", HerbCore.Constraint[ComesAfter(1, [9])])" ] }, "metadata": {}, @@ -925,22 +925,22 @@ } ], "source": [ - "g₉ = Herb.HerbGrammar.read_csg(\"demo.grammar\", \"demo.constraints\")\n", + "g₉ = HerbGrammar.read_csg(\"demo.grammar\", \"demo.constraints\")\n", "g₉, g₉.constraints" ] } ], "metadata": { "kernelspec": { - "display_name": "Julia 1.8.5", + "display_name": "Julia 1.9.4", "language": "julia", - "name": "julia-1.8" + "name": "julia-1.9" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.8.5" + "version": "1.9.4" } }, "nbformat": 4, diff --git a/docs/src/tutorials/defining_grammars.md b/docs/src/tutorials/defining_grammars.md index c8e2548..6223b12 100644 --- a/docs/src/tutorials/defining_grammars.md +++ b/docs/src/tutorials/defining_grammars.md @@ -1,10 +1,12 @@ -# Defining Grammars in Herb.jl using `HerbGrammar` +# Defining Grammars in Herb.jl using HerbGrammar The program space in Herb.jl is defined using a grammar. This notebook demonstrates how such a grammar can be created. There are multiple kinds of grammars, but they can all be defined in a very similar way. ### Setup +First, we import the necessary Herb packages. + ```julia using HerbGrammar, HerbConstraints @@ -435,8 +437,8 @@ HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9])) ``` - 1-element Vector{Main.HerbCore.Constraint}: - Main.HerbConstraints.ComesAfter(1, [9]) + 1-element Vector{HerbCore.Constraint}: + ComesAfter(1, [9]) ### Probabilistic grammars @@ -472,25 +474,25 @@ end ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 ┌ Warning: Requesting probability in a non-probabilistic grammar. │ Uniform distribution is assumed. - └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 The numbers before each rule represent the probability assigned to that rule. @@ -561,7 +563,7 @@ g₈, g₈.constraints 11: Int = Int + Int 12: Int = Int * Int 13: Int = x - , Main.HerbCore.Constraint[Main.HerbConstraints.ComesAfter(1, [9])]) + , HerbCore.Constraint[ComesAfter(1, [9])]) @@ -584,5 +586,5 @@ g₉, g₉.constraints 11: Int = Int + Int 12: Int = Int * Int 13: Int = x - , Main.HerbCore.Constraint[Main.HerbConstraints.ComesAfter(1, [9])]) + , HerbCore.Constraint[ComesAfter(1, [9])]) diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb index 4d3f2fc..b64ece4 100644 --- a/docs/src/tutorials/getting_started_with_herb.ipynb +++ b/docs/src/tutorials/getting_started_with_herb.ipynb @@ -22,12 +22,11 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 52, "metadata": {}, "outputs": [], "source": [ - "include(\"../../src/Herb.jl\") # these are only until we get modules going\n", - "using ..Herb" + "using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints" ] }, { @@ -37,9 +36,9 @@ "source": [ "### Defining the program space\n", "\n", - "Next, we start by creating a grammar. We define a context-free grammar which is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). \n", + "Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). \n", "\n", - "Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see `example2_defining_grammars.ipynb`.\n", + "Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md).\n", "\n", "For now, we specify a simple grammar for dealing with integers and explain all the rules individually:\n", "\n", @@ -54,7 +53,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 53, "metadata": {}, "outputs": [ { @@ -81,7 +80,7 @@ } ], "source": [ - "g = Herb.HerbGrammar.@cfgrammar begin\n", + "g = HerbGrammar.@cfgrammar begin\n", " Real = |(0:9)\n", " Real = x\n", " Real = Real + Real\n", @@ -116,18 +115,18 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "5-element Vector{Main.Herb.HerbSpecification.IOExample}:\n", - " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", - " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", - " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", - " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", - " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)" + "5-element Vector{IOExample}:\n", + " IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", + " IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", + " IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", + " IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", + " IOExample(Dict{Symbol, Any}(:x => 5), 20)" ] }, "metadata": {}, @@ -136,7 +135,7 @@ ], "source": [ "# Create input-output examples\n", - "examples = [Herb.HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" + "examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" ] }, { @@ -151,13 +150,13 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Main.Herb.HerbSpecification.Problem(Main.Herb.HerbSpecification.Example[Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)], \"example\")" + "Problem{Vector{IOExample}}(\"example\", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)])" ] }, "metadata": {}, @@ -165,7 +164,7 @@ } ], "source": [ - "problem = Herb.HerbSpecification.Problem(examples, \"example\")" + "problem = HerbSpecification.Problem(\"example\", examples)" ] }, { @@ -181,24 +180,40 @@ "Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. \n", "This program satisfies all our examples, but we don't expect it to generalize very well.\n", "\n", + "To search through a program space, we first need to define a [`HerbSearch.ProgramIterator`](@ref), which can be instantiated with different iterators, for now we use a simple [`HerbSearch.BFSIterator`](@ref). For more advanced search methods check out our tutorial on [advanced search](.advanced_search.md). For more information about iterators, check out our tutorial on [working with interpreters](.working_with_interpreters.md). \n", + "\n", "In general, we assume that a smaller program is more general than a larger program. \n", "Therefore, we search for the smallest program in our grammar that satisfies our examples. \n", "This can be done using a breadth-first search over the program/search space.\n", "\n", "This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. \n", "\n", - "So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`." + "So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. " ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - ":(x * 3 + 5)" + "BFSIterator(1: Real = 0\n", + "2: Real = 1\n", + "3: Real = 2\n", + "4: Real = 3\n", + "5: Real = 4\n", + "6: Real = 5\n", + "7: Real = 6\n", + "8: Real = 7\n", + "9: Real = 8\n", + "10: Real = 9\n", + "11: Real = x\n", + "12: Real = Real + Real\n", + "13: Real = Real - Real\n", + "14: Real = Real * Real\n", + ", :Real, 9223372036854775807, 9223372036854775807, 9223372036854775807, 9223372036854775807)" ] }, "metadata": {}, @@ -206,21 +221,35 @@ } ], "source": [ - "Herb.HerbSearch.search(g, problem, :Real)" + "iterator = BFSIterator(g, :Real)" ] }, { - "attachments": {}, - "cell_type": "markdown", + "cell_type": "code", + "execution_count": 57, "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(12{14{11,4}6}, optimal_program)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "As you can see, the search procedure found the correct program!" + "synth(problem, iterator)" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, - "source": [] + "source": [ + "As you can see, the search procedure found the correct program!" + ] }, { "attachments": {}, @@ -231,27 +260,28 @@ "\n", "In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values.\n", "\n", - "We first define a new problem to test with, we are looking for the programs that can compute the value`167`. We immediately pass the examples to the problem and then set up the new search.\n", + "We first define a new problem to test with, we are looking for the programs that can compute the value `167`. We immediately pass the examples to the problem and then set up the new search.\n", "\n", "Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 58, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "nothing" + "(14{7,14{5,8}}, optimal_program)" ] } ], "source": [ - "problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5], \"example2\")\n", - "expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180)\n", + "problem = HerbSpecification.Problem(\"example2\", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5])\n", + "iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30, max_time=180)\n", + "expr = HerbSearch.synth(problem, iterator)\n", "print(expr)" ] }, @@ -260,34 +290,42 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? If you want you can try below.\n", + "We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? You can try below, using the same iterator.\n", "\n", "In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy!" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 59, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(12{14{7,14{10,4}}6}, optimal_program)" + ] + } + ], "source": [ - "problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5], \"example2\")\n", - "expr = Herb.HerbSearch.search(g, problem, :Real)\n", + "problem = HerbSpecification.Problem(\"example3\", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5])\n", + "expr = HerbSearch.synth(problem, iterator)\n", "print(expr)" ] } ], "metadata": { "kernelspec": { - "display_name": "Julia 1.8.5", + "display_name": "Julia 1.9.4", "language": "julia", - "name": "julia-1.8" + "name": "julia-1.9" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.8.5" + "version": "1.9.4" } }, "nbformat": 4, diff --git a/docs/src/tutorials/getting_started_with_herb.md b/docs/src/tutorials/getting_started_with_herb.md index 591549e..a4651f8 100644 --- a/docs/src/tutorials/getting_started_with_herb.md +++ b/docs/src/tutorials/getting_started_with_herb.md @@ -8,14 +8,14 @@ First, we start with the setup. We need to access to all the function in the Her ```julia -using Herb +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints ``` ### Defining the program space -Next, we start by creating a grammar. We define a context-free grammar which is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). +Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). -Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see `example2_defining_grammars.ipynb`. +Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md). For now, we specify a simple grammar for dealing with integers and explain all the rules individually: @@ -29,7 +29,7 @@ If you run this cell, you can see all the rules rolled out. ```julia -g = Herb.HerbGrammar.@cfgrammar begin +g = HerbGrammar.@cfgrammar begin Real = |(0:9) Real = x Real = Real + Real @@ -71,16 +71,16 @@ In the cell below we automatically generate some examples for `x` assigning valu ```julia # Create input-output examples -examples = [Herb.HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] +examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] ``` - 5-element Vector{Main.Herb.HerbSpecification.IOExample}: - Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8) - Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11) - Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14) - Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17) - Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20) + 5-element Vector{IOExample}: + IOExample(Dict{Symbol, Any}(:x => 1), 8) + IOExample(Dict{Symbol, Any}(:x => 2), 11) + IOExample(Dict{Symbol, Any}(:x => 3), 14) + IOExample(Dict{Symbol, Any}(:x => 4), 17) + IOExample(Dict{Symbol, Any}(:x => 5), 20) Now that we have some input-output examples, we can define the problem. @@ -89,11 +89,11 @@ For now, this is irrelevant, and you can give the program any name you like. ```julia -problem = Herb.HerbSpecification.Problem(examples, "example") +problem = HerbSpecification.Problem("example", examples) ``` - Main.Herb.HerbSpecification.Problem(Main.Herb.HerbSpecification.Example[Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)], "example") + Problem{Vector{IOExample}}("example", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)]) ### Searching @@ -104,51 +104,75 @@ Of course, our problem is underdefined as there might be multiple programs that Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. This program satisfies all our examples, but we don't expect it to generalize very well. +To search through a program space, we first need to define a [`HerbSearch.ProgramIterator`](@ref), which can be instantiated with different iterators, for now we use a simple [`HerbSearch.BFSIterator`](@ref). For more advanced search methods check out our tutorial on [advanced search](.advanced_search.md). For more information about iterators, check out our tutorial on [working with interpreters](.working_with_interpreters.md). + In general, we assume that a smaller program is more general than a larger program. Therefore, we search for the smallest program in our grammar that satisfies our examples. This can be done using a breadth-first search over the program/search space. This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. -So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. +So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. ```julia -Herb.HerbSearch.search(g, problem, :Real) +iterator = BFSIterator(g, :Real) ``` - :(x * 3 + 5) + BFSIterator(1: Real = 0 + 2: Real = 1 + 3: Real = 2 + 4: Real = 3 + 5: Real = 4 + 6: Real = 5 + 7: Real = 6 + 8: Real = 7 + 9: Real = 8 + 10: Real = 9 + 11: Real = x + 12: Real = Real + Real + 13: Real = Real - Real + 14: Real = Real * Real + , :Real, 9223372036854775807, 9223372036854775807, 9223372036854775807, 9223372036854775807) + + +```julia +synth(problem, iterator) +``` -As you can see, the search procedure found the correct program! + (12{14{11,4}6}, optimal_program) +As you can see, the search procedure found the correct program! + ### Defining the search procedure In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values. -We first define a new problem to test with, we are looking for the programs that can compute the value`167`. We immediately pass the examples to the problem and then set up the new search. +We first define a new problem to test with, we are looking for the programs that can compute the value `167`. We immediately pass the examples to the problem and then set up the new search. Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search. ```julia -problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5], "example2") -expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180) +problem = HerbSpecification.Problem("example2", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5]) +iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30, max_time=180) +expr = HerbSearch.synth(problem, iterator) print(expr) ``` - nothing + (14{7,14{5,8}}, optimal_program) -We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? If you want you can try below. +We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? You can try below, using the same iterator. In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy! ```julia -problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5], "example2") -expr = Herb.HerbSearch.search(g, problem, :Real) -println(expr) +problem = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5]) +expr = HerbSearch.synth(problem, iterator) +print(expr) ``` From 4fab0ccd7386c4b4ea8d8ecc2981c4e66a3e2cd7 Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Thu, 4 Jul 2024 16:53:02 +0200 Subject: [PATCH 52/75] added markdown for interpreters tutorial --- docs/src/tutorials/defining_grammars.ipynb | 548 +++--------------- .../getting_started_with_constraints.ipynb | 505 +--------------- .../getting_started_with_constraints.md | 18 +- .../tutorials/getting_started_with_herb.ipynb | 128 +--- .../tutorials/getting_started_with_herb.md | 2 + .../tutorials/working_with_interpreters.ipynb | 271 +++++++++ .../tutorials/working_with_interpreters.md | 141 +++++ 7 files changed, 532 insertions(+), 1081 deletions(-) create mode 100644 docs/src/tutorials/working_with_interpreters.ipynb create mode 100644 docs/src/tutorials/working_with_interpreters.md diff --git a/docs/src/tutorials/defining_grammars.ipynb b/docs/src/tutorials/defining_grammars.ipynb index c31b791..8bbfb79 100644 --- a/docs/src/tutorials/defining_grammars.ipynb +++ b/docs/src/tutorials/defining_grammars.ipynb @@ -21,7 +21,7 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -44,23 +44,9 @@ }, { "cell_type": "code", - "execution_count": 74, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 1\n", - "2: Int = 2\n", - "3: Int = 3\n", - "4: Int = Int * Int\n", - "5: Int = Int + Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₁ = HerbGrammar.@cfgrammar begin\n", " Int = 1\n", @@ -81,30 +67,9 @@ }, { "cell_type": "code", - "execution_count": 75, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int * Int\n", - "12: Int = Int + Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₂ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", @@ -122,25 +87,9 @@ }, { "cell_type": "code", - "execution_count": 76, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 2\n", - "3: Int = 4\n", - "4: Int = 6\n", - "5: Int = 8\n", - "6: Int = Int * Int\n", - "7: Int = Int + Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₃ = HerbGrammar.@cfgrammar begin\n", " Int = |([0, 2, 4, 6, 8])\n", @@ -158,31 +107,9 @@ }, { "cell_type": "code", - "execution_count": 77, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int * Int\n", - "12: Int = Int + Int\n", - "13: Int = x\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₄ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", @@ -203,32 +130,9 @@ }, { "cell_type": "code", - "execution_count": 78, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int * Int\n", - "12: Int = Int + Int\n", - "13: Int = f(Int)\n", - "14: Int = x\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "f(a) = a + 1\n", "\n", @@ -251,31 +155,9 @@ }, { "cell_type": "code", - "execution_count": 79, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = a\n", - "12: Int = Int + Int\n", - "13: Int = Int × Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "×(a, b) = a * b\n", "\n", @@ -304,32 +186,9 @@ }, { "cell_type": "code", - "execution_count": 80, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "13-element Vector{Tuple{Int64, Any}}:\n", - " (1, 0)\n", - " (2, 1)\n", - " (3, 2)\n", - " (4, 3)\n", - " (5, 4)\n", - " (6, 5)\n", - " (7, 6)\n", - " (8, 7)\n", - " (9, 8)\n", - " (10, 9)\n", - " (11, :(Int + Int))\n", - " (12, :(Int * Int))\n", - " (13, :x)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₇ = HerbGrammar.@cfgrammar begin\n", " Int = |(0:9)\n", @@ -359,38 +218,18 @@ }, { "cell_type": "code", - "execution_count": 81, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "true" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.isterminal(g₇, 1)" ] }, { "cell_type": "code", - "execution_count": 82, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "false" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.isterminal(g₇, 11)" ] @@ -406,19 +245,9 @@ }, { "cell_type": "code", - "execution_count": 83, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - ":Int" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.return_type(g₇, 11)" ] @@ -435,40 +264,18 @@ }, { "cell_type": "code", - "execution_count": 84, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2-element Vector{Symbol}:\n", - " :Int\n", - " :Int" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.child_types(g₇, 11)" ] }, { "cell_type": "code", - "execution_count": 85, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "2" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.nchildren(g₇, 11)" ] @@ -484,20 +291,9 @@ }, { "cell_type": "code", - "execution_count": 86, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1-element Vector{Symbol}:\n", - " :Int" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.nonterminals(g₇)" ] @@ -518,32 +314,9 @@ }, { "cell_type": "code", - "execution_count": 87, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int + Int\n", - "12: Int = Int * Int\n", - "13: Int = x\n", - "14: Int = Int - Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.add_rule!(g₇, :(Int = Int - Int))" ] @@ -567,63 +340,18 @@ }, { "cell_type": "code", - "execution_count": 88, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: nothing = nothing\n", - "12: Int = Int * Int\n", - "13: Int = x\n", - "14: Int = Int - Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.remove_rule!(g₇, 11)" ] }, { "cell_type": "code", - "execution_count": 89, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int * Int\n", - "12: Int = x\n", - "13: Int = Int - Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.cleanup_removed_rules!(g₇)" ] @@ -641,31 +369,9 @@ }, { "cell_type": "code", - "execution_count": 90, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int + Int\n", - "12: Int = Int * Int\n", - "13: Int = x\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₈ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", @@ -693,20 +399,9 @@ }, { "cell_type": "code", - "execution_count": 91, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1-element Vector{HerbCore.Constraint}:\n", - " ComesAfter(1, [9])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9]))" ] @@ -724,50 +419,9 @@ }, { "cell_type": "code", - "execution_count": 92, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.07692307692307693 : 1\n", - "0.07692307692307693 : 2\n", - "0.07692307692307693 : 3\n", - "0.07692307692307693 : 4\n", - "0.07692307692307693 : 5\n", - "0.07692307692307693 : 6\n", - "0.07692307692307693 : 7\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n", - "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", - "│ Uniform distribution is assumed.\n", - "└ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155\n" - ] - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₉ = HerbGrammar.@pcfgrammar begin\n", " 0.4 : Int = |(0:9)\n", @@ -811,7 +465,7 @@ }, { "cell_type": "code", - "execution_count": 93, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -820,33 +474,11 @@ }, { "cell_type": "code", - "execution_count": 94, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int * Int\n", - "12: Int = x\n", - "13: Int = Int - Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "HerbGrammar.read_cfg(\"demo.txt\")" ] @@ -865,32 +497,9 @@ }, { "cell_type": "code", - "execution_count": 95, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int + Int\n", - "12: Int = Int * Int\n", - "13: Int = x\n", - ", HerbCore.Constraint[ComesAfter(1, [9])])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "HerbGrammar.store_csg(\"demo.grammar\", \"demo.constraints\", g₈)\n", "g₈, g₈.constraints" @@ -898,32 +507,9 @@ }, { "cell_type": "code", - "execution_count": 96, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(1: Int = 0\n", - "2: Int = 1\n", - "3: Int = 2\n", - "4: Int = 3\n", - "5: Int = 4\n", - "6: Int = 5\n", - "7: Int = 6\n", - "8: Int = 7\n", - "9: Int = 8\n", - "10: Int = 9\n", - "11: Int = Int + Int\n", - "12: Int = Int * Int\n", - "13: Int = x\n", - ", HerbCore.Constraint[ComesAfter(1, [9])])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "execution_count": null, + "metadata": {}, + "outputs": [], "source": [ "g₉ = HerbGrammar.read_csg(\"demo.grammar\", \"demo.constraints\")\n", "g₉, g₉.constraints" diff --git a/docs/src/tutorials/getting_started_with_constraints.ipynb b/docs/src/tutorials/getting_started_with_constraints.ipynb index 624f169..2bb63b3 100644 --- a/docs/src/tutorials/getting_started_with_constraints.ipynb +++ b/docs/src/tutorials/getting_started_with_constraints.ipynb @@ -27,23 +27,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = 1\n", - "2: Int = x\n", - "3: Int = -Int\n", - "4: Int = Int + Int\n", - "5: Int = Int * Int\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "using HerbCore, HerbGrammar, HerbConstraints, HerbSearch\n", "\n", @@ -70,30 +56,9 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n", - "1 + 1\n" - ] - } - ], + "outputs": [], "source": [ "clearconstraints!(grammar)\n", "iter = BFSIterator(grammar, :Int, max_size=3)\n", @@ -120,26 +85,9 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "x * 1\n", - "x * x\n", - "x + x\n", - "x + 1\n", - "1 + 1\n", - "1 + x\n" - ] - } - ], + "outputs": [], "source": [ "one = 1\n", "x = 2\n", @@ -171,18 +119,9 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "154\n", - "106\n" - ] - } - ], + "outputs": [], "source": [ "#this constraint forbids A+A and A*A\n", "constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)]))\n", @@ -212,25 +151,9 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x\n", - "-x\n", - "-(-x)\n", - "1x\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n" - ] - } - ], + "outputs": [], "source": [ "clearconstraints!(grammar)\n", "addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program\n", @@ -252,18 +175,9 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "x * x\n", - "-(x * x)\n" - ] - } - ], + "outputs": [], "source": [ "clearconstraints!(grammar)\n", "addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree\n", @@ -292,28 +206,9 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x + x\n", - "1 + x\n", - "1 + 1\n" - ] - } - ], + "outputs": [], "source": [ "clearconstraints!(grammar)\n", "\n", @@ -355,26 +250,9 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "-(-1)\n", - "1x\n", - "-(-x)\n", - "x * x\n", - "x + x\n" - ] - } - ], + "outputs": [], "source": [ "constraint = ForbiddenSequence([plus, one], ignore_if=[times])\n", "addconstraint!(grammar, constraint)\n", @@ -411,19 +289,9 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.on_new_node" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "\"\"\"\n", "Forbids the consecutive application of the specified rule.\n", @@ -480,19 +348,9 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.propagate!" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "\"\"\"\n", "Forbids the consecutive application of the specified rule at path `path`.\n", @@ -564,19 +422,9 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "HerbConstraints.shouldschedule" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "\n", "\"\"\"\n", @@ -597,308 +445,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "1\n", - "x\n", - "-1\n", - "-x\n", - "1 * 1\n", - "1x\n", - "x * x\n", - "x * 1\n", - "x + 1\n", - "x + x\n", - "1 + x\n", - "1 + 1\n", - "1 * -1\n", - "1 * -x\n", - "x * -x\n", - "x * -1\n", - "x + -1\n", - "x + -x\n", - "1 + -x\n", - "-(1 * 1)\n", - "1 + -1\n", - "-(1x)\n", - "-(x * x)\n", - "-(x * 1)\n", - "-((x + 1))\n", - "-((x + x))\n", - "-1 * 1\n", - "-((1 + x))\n", - "-1 * x\n", - "-((1 + 1))\n", - "-x * x\n", - "-x * 1\n", - "-x + 1\n", - "-x + x\n", - "-1 + x\n", - "-1 + 1\n", - "(1 + 1) * 1\n", - "-(1 * -1)\n", - "(1 + 1) * x\n", - "-(1 * -x)\n", - "(1 + x) * x\n", - "-(x * -x)\n", - "(1 + x) * 1\n", - "-(x * -1)\n", - "(x + x) * 1\n", - "-((x + -1))\n", - "(x + x) * x\n", - "-((x + -x))\n", - "(x + 1) * x\n", - "-((1 + -x))\n", - "(x + 1) * 1\n", - "-((1 + -1))\n", - "x * 1 + 1\n", - "x * 1 + x\n", - "x * x + x\n", - "x * x + 1\n", - "1x + 1\n", - "1x + x\n", - "-(-1 * 1)\n", - "1 * 1 + x\n", - "-(-1 * x)\n", - "1 * 1 + 1\n", - "-(-x * x)\n", - "-(-x * 1)\n", - "-((-x + 1))\n", - "1 * (1 + 1)\n", - "-((-x + x))\n", - "1 * (1 + x)\n", - "-((-1 + x))\n", - "1 * (x + x)\n", - "-((-1 + 1))\n", - "1 * (x + 1)\n", - "x * (x + 1)\n", - "x * (x + x)\n", - "x * (1 + x)\n", - "x * (1 + 1)\n", - "-1 * -1\n", - "x + 1 * 1\n", - "-1 * -x\n", - "x + 1x\n", - "-x * -x\n", - "x + x * x\n", - "-x * -1\n", - "x + x * 1\n", - "-x + -1\n", - "1 + x * 1\n", - "-x + -x\n", - "1 + x * x\n", - "-1 + -x\n", - "1 + 1x\n", - "-1 + -1\n", - "1 + 1 * 1\n", - "-1 * (1 + 1)\n", - "1 * (1 + -1)\n", - "-1 * (1 + x)\n", - "1 * (1 + -x)\n", - "-1 * (x + x)\n", - "1 * (x + -x)\n", - "-1 * (x + 1)\n", - "1 * (x + -1)\n", - "-x * (x + 1)\n", - "x * (x + -1)\n", - "-x * (x + x)\n", - "x * (x + -x)\n", - "-x * (1 + x)\n", - "x * (1 + -x)\n", - "-x * (1 + 1)\n", - "x * (1 + -1)\n", - "-x + 1 * 1\n", - "x + 1 * -1\n", - "-x + 1x\n", - "x + 1 * -x\n", - "-x + x * x\n", - "x + x * -x\n", - "-x + x * 1\n", - "x + x * -1\n", - "-1 + x * 1\n", - "1 + x * -1\n", - "-1 + x * x\n", - "1 + x * -x\n", - "-1 + 1x\n", - "1 + 1 * -x\n", - "-1 + 1 * 1\n", - "1 + 1 * -1\n", - "1 * -(1 * 1)\n", - "(1 + -1) * 1\n", - "1 * -(1x)\n", - "(1 + -1) * x\n", - "1 * -(x * x)\n", - "(1 + -x) * x\n", - "1 * -(x * 1)\n", - "(1 + -x) * 1\n", - "1 * -((x + 1))\n", - "(x + -x) * 1\n", - "1 * -((x + x))\n", - "(x + -x) * x\n", - "1 * -((1 + x))\n", - "(x + -1) * x\n", - "1 * -((1 + 1))\n", - "(x + -1) * 1\n", - "x * -((1 + 1))\n", - "x * -1 + 1\n", - "x * -((1 + x))\n", - "x * -1 + x\n", - "x * -((x + x))\n", - "x * -x + x\n", - "x * -((x + 1))\n", - "x * -x + 1\n", - "x * -(x * 1)\n", - "1 * -x + 1\n", - "x * -(x * x)\n", - "1 * -x + x\n", - "x * -(1x)\n", - "1 * -1 + x\n", - "x * -(1 * 1)\n", - "1 * -1 + 1\n", - "x + -(1 * 1)\n", - "x + -(1x)\n", - "1 * (-1 + 1)\n", - "x + -(x * x)\n", - "1 * (-1 + x)\n", - "x + -(x * 1)\n", - "1 * (-x + x)\n", - "x + -((x + 1))\n", - "1 * (-x + 1)\n", - "x + -((x + x))\n", - "x * (-x + 1)\n", - "x + -((1 + x))\n", - "x * (-x + x)\n", - "x + -((1 + 1))\n", - "x * (-1 + x)\n", - "1 + -((1 + 1))\n", - "x * (-1 + 1)\n", - "1 + -((1 + x))\n", - "x + -1 * 1\n", - "1 + -((x + x))\n", - "x + -1 * x\n", - "1 + -((x + 1))\n", - "x + -x * x\n", - "1 + -(x * 1)\n", - "x + -x * 1\n", - "1 + -(x * x)\n", - "1 + -x * 1\n", - "1 + -(1x)\n", - "1 + -x * x\n", - "1 + -(1 * 1)\n", - "1 + -1 * x\n", - "1 + -1 * 1\n", - "-(1 * 1) * 1\n", - "-(1 * 1) * x\n", - "-((1 + 1) * 1)\n", - "-(1x) * x\n", - "-((1 + 1) * x)\n", - "-(1x) * 1\n", - "-((1 + x) * x)\n", - "-(x * x) * 1\n", - "-((1 + x) * 1)\n", - "-(x * x) * x\n", - "-((x + x) * 1)\n", - "-(x * 1) * x\n", - "-((x + x) * x)\n", - "-(x * 1) * 1\n", - "-((x + 1) * x)\n", - "-((x + 1)) * 1\n", - "-((x + 1) * 1)\n", - "-((x + 1)) * x\n", - "-((x * 1 + 1))\n", - "-((x + x)) * x\n", - "-((x * 1 + x))\n", - "-((x + x)) * 1\n", - "-((x * x + x))\n", - "-((1 + x)) * 1\n", - "-((x * x + 1))\n", - "-((1 + x)) * x\n", - "-((1x + 1))\n", - "-((1 + 1)) * x\n", - "-((1x + x))\n", - "-((1 + 1)) * 1\n", - "-((1 * 1 + x))\n", - "-((1 + 1)) + 1\n", - "-((1 * 1 + 1))\n", - "-((1 + 1)) + x\n", - "-((1 + x)) + x\n", - "-(-1 * -1)\n", - "-((1 + x)) + 1\n", - "-(-1 * -x)\n", - "-((x + x)) + 1\n", - "-(-x * -x)\n", - "-((x + x)) + x\n", - "-(-x * -1)\n", - "-((x + 1)) + x\n", - "-((-x + -1))\n", - "-((x + 1)) + 1\n", - "-((-x + -x))\n", - "-(x * 1) + 1\n", - "-((-1 + -x))\n", - "-(x * 1) + x\n", - "-((-1 + -1))\n", - "-(x * x) + x\n", - "-(x * x) + 1\n", - "(-1 + 1) * 1\n", - "-(1x) + 1\n", - "(-1 + 1) * x\n", - "-(1x) + x\n", - "(-1 + x) * x\n", - "-(1 * 1) + x\n", - "(-1 + x) * 1\n", - "-(1 * 1) + 1\n", - "(-x + x) * 1\n", - "(-x + x) * x\n", - "(1 + 1) * -1\n", - "(-x + 1) * x\n", - "(1 + 1) * -x\n", - "(-x + 1) * 1\n", - "(1 + x) * -x\n", - "-x * 1 + 1\n", - "(1 + x) * -1\n", - "-x * 1 + x\n", - "(x + x) * -1\n", - "-x * x + x\n", - "(x + x) * -x\n", - "-x * x + 1\n", - "(x + 1) * -x\n", - "-1 * x + 1\n", - "(x + 1) * -1\n", - "-1 * x + x\n", - "x * 1 + -1\n", - "-1 * 1 + x\n", - "x * 1 + -x\n", - "-1 * 1 + 1\n", - "x * x + -x\n", - "x * x + -1\n", - "-(1 * (1 + 1))\n", - "1x + -1\n", - "-(1 * (1 + x))\n", - "1x + -x\n", - "-(1 * (x + x))\n", - "1 * 1 + -x\n", - "-(1 * (x + 1))\n", - "1 * 1 + -1\n", - "-(x * (x + 1))\n", - "-(x * (x + x))\n", - "-(x * (1 + x))\n", - "-(x * (1 + 1))\n", - "-((x + 1 * 1))\n", - "-((x + 1x))\n", - "-((x + x * x))\n", - "-((x + x * 1))\n", - "-((1 + x * 1))\n", - "-((1 + x * x))\n", - "-((1 + 1x))\n", - "-((1 + 1 * 1))\n" - ] - } - ], + "outputs": [], "source": [ "clearconstraints!(grammar)\n", "\n", @@ -916,15 +465,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.8.5", + "display_name": "Julia 1.9.4", "language": "julia", - "name": "julia-1.8" + "name": "julia-1.9" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.8.5" + "version": "1.9.4" } }, "nbformat": 4, diff --git a/docs/src/tutorials/getting_started_with_constraints.md b/docs/src/tutorials/getting_started_with_constraints.md index 08d9265..7c95e90 100644 --- a/docs/src/tutorials/getting_started_with_constraints.md +++ b/docs/src/tutorials/getting_started_with_constraints.md @@ -32,7 +32,7 @@ end 3: Int = -Int 4: Int = Int + Int 5: Int = Int * Int - + ### Working with constraints @@ -66,7 +66,7 @@ end x + x 1 + x 1 + 1 - + Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. @@ -106,7 +106,7 @@ end x + 1 1 + 1 1 + x - + ### Forbidden Constraint @@ -135,7 +135,7 @@ println(length(iter)) 154 106 - + ### Contains Constraint @@ -163,7 +163,7 @@ end x + 1 x + x 1 + x - + ### Contains Subtree Constraint @@ -182,7 +182,7 @@ end x * x -(x * x) - + ### Ordered Constraint @@ -224,7 +224,7 @@ end x + x 1 + x 1 + 1 - + ### Forbidden Sequence Constraint @@ -269,7 +269,7 @@ end -(-x) x * x x + x - + ### Custom Constraint @@ -735,4 +735,4 @@ end -((1 + x * x)) -((1 + 1x)) -((1 + 1 * 1)) - + diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb index b64ece4..45abcbb 100644 --- a/docs/src/tutorials/getting_started_with_herb.ipynb +++ b/docs/src/tutorials/getting_started_with_herb.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -53,32 +53,9 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Real = 0\n", - "2: Real = 1\n", - "3: Real = 2\n", - "4: Real = 3\n", - "5: Real = 4\n", - "6: Real = 5\n", - "7: Real = 6\n", - "8: Real = 7\n", - "9: Real = 8\n", - "10: Real = 9\n", - "11: Real = x\n", - "12: Real = Real + Real\n", - "13: Real = Real - Real\n", - "14: Real = Real * Real\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "g = HerbGrammar.@cfgrammar begin\n", " Real = |(0:9)\n", @@ -115,24 +92,9 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5-element Vector{IOExample}:\n", - " IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", - " IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", - " IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", - " IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", - " IOExample(Dict{Symbol, Any}(:x => 5), 20)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "# Create input-output examples\n", "examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" @@ -150,19 +112,9 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Problem{Vector{IOExample}}(\"example\", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)])" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "problem = HerbSpecification.Problem(\"example\", examples)" ] @@ -193,52 +145,18 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "BFSIterator(1: Real = 0\n", - "2: Real = 1\n", - "3: Real = 2\n", - "4: Real = 3\n", - "5: Real = 4\n", - "6: Real = 5\n", - "7: Real = 6\n", - "8: Real = 7\n", - "9: Real = 8\n", - "10: Real = 9\n", - "11: Real = x\n", - "12: Real = Real + Real\n", - "13: Real = Real - Real\n", - "14: Real = Real * Real\n", - ", :Real, 9223372036854775807, 9223372036854775807, 9223372036854775807, 9223372036854775807)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "iterator = BFSIterator(g, :Real)" ] }, { "cell_type": "code", - "execution_count": 57, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(12{14{11,4}6}, optimal_program)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "synth(problem, iterator)" ] @@ -267,17 +185,9 @@ }, { "cell_type": "code", - "execution_count": 58, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(14{7,14{5,8}}, optimal_program)" - ] - } - ], + "outputs": [], "source": [ "problem = HerbSpecification.Problem(\"example2\", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5])\n", "iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30, max_time=180)\n", @@ -297,17 +207,9 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(12{14{7,14{10,4}}6}, optimal_program)" - ] - } - ], + "outputs": [], "source": [ "problem = HerbSpecification.Problem(\"example3\", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5])\n", "expr = HerbSearch.synth(problem, iterator)\n", diff --git a/docs/src/tutorials/getting_started_with_herb.md b/docs/src/tutorials/getting_started_with_herb.md index a4651f8..3467ee9 100644 --- a/docs/src/tutorials/getting_started_with_herb.md +++ b/docs/src/tutorials/getting_started_with_herb.md @@ -176,3 +176,5 @@ problem = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dic expr = HerbSearch.synth(problem, iterator) print(expr) ``` + + (12{14{7,14{10,4}}6}, optimal_program) diff --git a/docs/src/tutorials/working_with_interpreters.ipynb b/docs/src/tutorials/working_with_interpreters.ipynb new file mode 100644 index 0000000..ce05015 --- /dev/null +++ b/docs/src/tutorials/working_with_interpreters.ipynb @@ -0,0 +1,271 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using the Julia interpreter\n", + "\n", + "To know how good a candidate program is, program synthesisers execute them. The easiest way to execute a program is to rely on Julia itself. To leverage the Julia interpreter, you only have to ensure that your programs are valid Julia expressions. \n", + "\n", + "First, let's import the necessary packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "using HerbGrammar, HerbInterpret" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "Now, assume the following grammar." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Number = 1\n", + "2: Number = 2\n", + "3: Number = x\n", + "4: Number = Number + Number\n", + "5: Number = Number * Number\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g = @cfgrammar begin\n", + " Number = |(1:2)\n", + " Number = x\n", + " Number = Number + Number\n", + " Number = Number * Number\n", + " end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's construct a program `x+3`, which would correspond to the following `RuleNode` representation" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4{3,1}" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "myprog = RuleNode(4,[RuleNode(3),RuleNode(1)])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To run this program, we have to convert it into a Julia expression, which we can do in the following way:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ":(x + 1)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "myprog_julia = rulenode2expr(myprog, g)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we have a valid Julia expression, but we are still missing one key ingredient: we have to inform the interpreter about the special symbols. In our case, these are `:x` and `:+`. To do so, we need to create a symbol table, which is nothing more than a dictionary mapping symbols to their values:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Dict{Symbol, Any} with 2 entries:\n", + " :+ => +\n", + " :x => 2" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "symboltable = Dict{Symbol,Any}(:x => 2, :+ => +)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can execute our program through the defaul interpreter available in `HerbInterpret`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "3" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "interpret(symboltable, myprog_julia)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And that's it!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Defining a custom interpreter\n", + "\n", + "A disadvantage of the default Julia interpreter is that it needs to traverse abstract syntax tree twice -- once to convert it into a Julia expression, and the second time to execute that expression. Program execution is regularly the most consuming part of the entire pipeline and, by eliminating one of these steps, we can cut the runtime in half.\n", + "\n", + "We can define an interpreter that works directly over `RuleNode`s. \n", + "Consider the scenario in which we want to write programs for robot navigation: imagine a 2D world in which the robot can move around and pick up a ball. The programs we could write direct the robot to go up, down, left, and right. For convenience, the programming language also offers conditionals and loops:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grammar_robots = @csgrammar begin\n", + " Start = Sequence #1\n", + "\n", + " Sequence = Operation #2\n", + " Sequence = (Operation; Sequence) #3\n", + " Operation = Transformation #4\n", + " Operation = ControlStatement #5\n", + "\n", + " Transformation = moveRight() | moveDown() | moveLeft() | moveUp() | drop() | grab() #6\n", + " ControlStatement = IF(Condition, Sequence, Sequence) #12\n", + " ControlStatement = WHILE(Condition, Sequence) #13\n", + "\n", + " Condition = atTop() | atBottom() | atLeft() | atRight() | notAtTop() | notAtBottom() | notAtLeft() | notAtRight() #14\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs:\n", + " - `moveRight(); moveLeft(); drop()`\n", + " - WHILE(notAtTop(), moveUp())\n", + "\n", + "The idea behind this programming language is that the program specifies a set of transformations over a state of the robot world. Thus, a program can only be executed over a particular state. In this case, the state represents the size of the 2D world, the current position of a robot, the current position of a ball, and whether the robot is currently holding a ball. The execution of a particular instruction acts as a state transformation: each instruction takes a state as an input, transforms it, and passes it to the subsequent instruction. For example, execution of the program `moveRight(); moveLeft(); drop()` would proceed as:\n", + " 1. take an input state, \n", + " 2. pass it to the `moveRight()` instruction,\n", + " 3. pass the output of `moveRight()` to `moveLeft()` instructions,\n", + " 4. pass the output of `moveLeft()` to `drop()`,\n", + " 5. return the output of `drop()`.\n", + "\n", + "\n", + " The following is only one possible way to implement a custom interpreter, but it demonstrates a general template that can always be followed.\n", + "\n", + " We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program:\n", + "\n", + " interpret(prog::AbstractRuleNode, grammar::ContextSensitiveGrammar, state::RobotState)::RobotState\n", + "\n", + " As `RuleNode`s only store indices of derivation rules from the grammar, not the functions themselves, we will first pull the function call associated with every derivation rule. In Julia, this is indicated by the top-level symbol of the rules. For example, the top-level symbol for the derivation rule 6 is `:moveRight`; for rule 12, that is `:IF`." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The remaining functions follow a similar idea. (You can see the full implementation of this interpreter [here](https://github.com/Herb-AI/HerbBenchmarks.jl/blob/new-robots/src/data/Robots_2020/robots_primitives.jl))." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "PLUTO_PROJECT_TOML_CONTENTS = \"\"\"\n", + "[deps]\n", + "HerbGrammar = \"4ef9e186-2fe5-4b24-8de7-9f7291f24af7\"\n", + "HerbInterpret = \"5bbddadd-02c5-4713-84b8-97364418cca7\"\n", + "\n", + "[compat]\n", + "HerbGrammar = \"~0.3.0\"\n", + "HerbInterpret = \"~0.1.3\"\n", + "\"\"\"" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.9.4", + "language": "julia", + "name": "julia-1.9" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.9.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/src/tutorials/working_with_interpreters.md b/docs/src/tutorials/working_with_interpreters.md new file mode 100644 index 0000000..d1cc62e --- /dev/null +++ b/docs/src/tutorials/working_with_interpreters.md @@ -0,0 +1,141 @@ +# Using the Julia interpreter + +To know how good a candidate program is, program synthesisers execute them. The easiest way to execute a program is to rely on Julia itself. To leverage the Julia interpreter, you only have to ensure that your programs are valid Julia expressions. + +First, let's import the necessary packages + + +```julia +using HerbGrammar, HerbInterpret +``` + + +Now, assume the following grammar. + + +```julia +g = @cfgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number + end +``` + + + 1: Number = 1 + 2: Number = 2 + 3: Number = x + 4: Number = Number + Number + 5: Number = Number * Number + + + +Let's construct a program `x+3`, which would correspond to the following `RuleNode` representation + + +```julia +myprog = RuleNode(4,[RuleNode(3),RuleNode(1)]) +``` + + + 4{3,1} + + +To run this program, we have to convert it into a Julia expression, which we can do in the following way: + + +```julia +myprog_julia = rulenode2expr(myprog, g) +``` + + + :(x + 1) + + +Now we have a valid Julia expression, but we are still missing one key ingredient: we have to inform the interpreter about the special symbols. In our case, these are `:x` and `:+`. To do so, we need to create a symbol table, which is nothing more than a dictionary mapping symbols to their values: + + +```julia +symboltable = Dict{Symbol,Any}(:x => 2, :+ => +) +``` + + + Dict{Symbol, Any} with 2 entries: + :+ => + + :x => 2 + + +Now we can execute our program through the defaul interpreter available in `HerbInterpret`: + + + +```julia +interpret(symboltable, myprog_julia) +``` + + + 3 + + +And that's it! + +# Defining a custom interpreter + +A disadvantage of the default Julia interpreter is that it needs to traverse abstract syntax tree twice -- once to convert it into a Julia expression, and the second time to execute that expression. Program execution is regularly the most consuming part of the entire pipeline and, by eliminating one of these steps, we can cut the runtime in half. + +We can define an interpreter that works directly over `RuleNode`s. +Consider the scenario in which we want to write programs for robot navigation: imagine a 2D world in which the robot can move around and pick up a ball. The programs we could write direct the robot to go up, down, left, and right. For convenience, the programming language also offers conditionals and loops: + + +```julia +grammar_robots = @csgrammar begin + Start = Sequence #1 + + Sequence = Operation #2 + Sequence = (Operation; Sequence) #3 + Operation = Transformation #4 + Operation = ControlStatement #5 + + Transformation = moveRight() | moveDown() | moveLeft() | moveUp() | drop() | grab() #6 + ControlStatement = IF(Condition, Sequence, Sequence) #12 + ControlStatement = WHILE(Condition, Sequence) #13 + + Condition = atTop() | atBottom() | atLeft() | atRight() | notAtTop() | notAtBottom() | notAtLeft() | notAtRight() #14 +end +``` + +This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs: + - `moveRight(); moveLeft(); drop()` + - WHILE(notAtTop(), moveUp()) + +The idea behind this programming language is that the program specifies a set of transformations over a state of the robot world. Thus, a program can only be executed over a particular state. In this case, the state represents the size of the 2D world, the current position of a robot, the current position of a ball, and whether the robot is currently holding a ball. The execution of a particular instruction acts as a state transformation: each instruction takes a state as an input, transforms it, and passes it to the subsequent instruction. For example, execution of the program `moveRight(); moveLeft(); drop()` would proceed as: + 1. take an input state, + 2. pass it to the `moveRight()` instruction, + 3. pass the output of `moveRight()` to `moveLeft()` instructions, + 4. pass the output of `moveLeft()` to `drop()`, + 5. return the output of `drop()`. + + + The following is only one possible way to implement a custom interpreter, but it demonstrates a general template that can always be followed. + + We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program: + + interpret(prog::AbstractRuleNode, grammar::ContextSensitiveGrammar, state::RobotState)::RobotState + + As `RuleNode`s only store indices of derivation rules from the grammar, not the functions themselves, we will first pull the function call associated with every derivation rule. In Julia, this is indicated by the top-level symbol of the rules. For example, the top-level symbol for the derivation rule 6 is `:moveRight`; for rule 12, that is `:IF`. + +The remaining functions follow a similar idea. (You can see the full implementation of this interpreter [here](https://github.com/Herb-AI/HerbBenchmarks.jl/blob/new-robots/src/data/Robots_2020/robots_primitives.jl)). + + +```julia +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" + +[compat] +HerbGrammar = "~0.3.0" +HerbInterpret = "~0.1.3" +""" +``` From b8b65cd172b071713d88670d7b08282209193f78 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Jul 2024 20:01:07 +0200 Subject: [PATCH 53/75] Fixing docs build --- .github/workflows/documentation.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 268c42f..dd33522 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -14,6 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: 3.10 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install jupyterlab nbconvert - uses: julia-actions/setup-julia@v1 with: version: '1' From 22c4fb0b6ffb152e491782fcdf7873b45fc4c033 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Jul 2024 20:11:53 +0200 Subject: [PATCH 54/75] Fixing docs build --- .github/workflows/documentation.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index dd33522..c9ba3d5 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -15,13 +15,13 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python 3.10 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: - python-version: 3.10 + python-version: "3.10" - name: Install dependencies run: | python -m pip install --upgrade pip - pip install jupyterlab nbconvert + python -m pip install --upgrade jupyterlab nbconvert - uses: julia-actions/setup-julia@v1 with: version: '1' From f7a0384dcb2e119f5fdfa94fbd509594f6698e99 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Jul 2024 20:43:48 +0200 Subject: [PATCH 55/75] Fix makepath in conversion --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 2e860ae..055ff62 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,7 +13,7 @@ using HerbSpecification using PyCall jupyter = pyimport("jupyterlab") nbconvert = pyimport("nbconvert") -all_notebooks = readdir("src/tutorials/") +all_notebooks = readdir(pwd()*"src/tutorials/") for file in all_notebooks if occursin("ipynb", file) path = pwd() * "/src/tutorials/" * file From b869d14122ac82a51bc3645a8357f29b8747233b Mon Sep 17 00:00:00 2001 From: Sebastijan Date: Fri, 5 Jul 2024 21:27:27 +0200 Subject: [PATCH 56/75] added more information --- docs/src/tutorials/TopDown.md | 102 +++++++++++++++++++++++++++++++--- 1 file changed, 94 insertions(+), 8 deletions(-) diff --git a/docs/src/tutorials/TopDown.md b/docs/src/tutorials/TopDown.md index ed3e84c..629b24f 100644 --- a/docs/src/tutorials/TopDown.md +++ b/docs/src/tutorials/TopDown.md @@ -1,9 +1,56 @@ -# Top Down Iterator +# Building Herb Iterators -## Base Iterator +The core building block in Herb is a program iterator. +A program iterator represents a walk through the program space; different iterators provide different ways of iterating through program space. +From the program synthesis point of view, program iterators actaully represent program spaces. -This function describes the iteration for a Top Down Iterator. A priority queue is created to determine the order. The solver is checked for the constraints, if it violates the constraints, it is considered infeasible, and if it is feasible, it is added to the queue. This function then returns the next complete tree, that is a tree without holes. -This function creates a priority queue, and is therefore called once during the initialisation. + +### Iterator hierarchy + +Program iterators are organised in a hierarchy. +The top-level abstract type is `ProgramIterator`. +At the next level of the hierarchy lie commonly used search families: + - `TopDownIterator` for top-down traversals + - `StochasticSearachIterator` for traversals with stochastic search + - `BottomUpIterator` for bottom-up search + + +Stochastic search further provides specific iterators: + - `MHSearchIterator` for program traversal with Metropolis-Hastings algorithm + - `VLNSearchIterator` for traversals with Very Large Neighbourhood Search + - `SASearchIterator` for Simulated Annealing + + We provide generic and customiseable implementations of each of these iterators, so that users can easily tweak them by through multiple dispatch. Keep reading! + + +### Iterator design + +Program iterators follow the standard Julia `Iterator` interface. +That is, every iterator should implement two functions: + - `iterate(<:ProgramIterator)::(RuleNode,Any)` to get the first program. The function takes a program iterator as an input, returning the first program and a state (which can be anything) + - `iterate(<:ProgramIterator,Any)::(RuleNode,Any)` to get the consequtive programs. The function takes the progrma iterator and the state from the previous iteration, and return the next program and the next state. + + + + + + + +## Top Down iterator + +We illustarate how to build iterators with a Top Down iterator. +The top Down iterator is build as a best-first iterator: it maintains a priority queue of programs and always pops the first element of the queue. +The iterator is customiseable through the following functions: +- priority_function: dictating the order of programs in the priority queue +- derivation_heuristic: dictating in which order to explore the derivations rules within a single hole +- hole_heuristic: dictating which hole to expand next + + + + + + +The first call to `iterate(iter::TopDownIterator)`: ``` julia function Base.iterate(iter::TopDownIterator) @@ -19,10 +66,11 @@ function Base.iterate(iter::TopDownIterator) end ``` -## Base Iterator With a Given Priority Queue +The first call steps everything up: it initiates the priority queue, the constraint solver (more on that later), and return the first program. +The function `_find_next_complete_tree(iter.solver, pq, iter)` does a lot of heavy lifting here; we will cover it later, but the only important thing is that it finds the next complete program in the priority queue (because, in case of top down enumeration, the queue also contains partial programs which we only want to expand, but not return to the user). + -This function describes the iteration for a Top Down Iterator, and a priority queue is given as an argument. This function then returns the next complete tree, that is a tree without holes. -After a priority queue is created, this is the function that will be called. +The subsequent call to `iterate(iter::TopDownIterator, pq::DataStructures.PriorityQueue)` are quite simple: all that is needed is to find the next complete program in the priority queue: ``` julia function Base.iterate(iter::TopDownIterator, pq::DataStructures.PriorityQueue) @@ -30,7 +78,45 @@ function Base.iterate(iter::TopDownIterator, pq::DataStructures.PriorityQueue) end ``` -# Find Next Complete Tree / Program +# Modifying the provided iterator + +If you would like to, for example, modify the priority function, you don't have to implement the iterator from scratch. +You simply need to create a new type and inherit from the `TopDownIterator`: + +`abstract type MyTopDown <: TopDownIterator end`. + +What is left is to implement the priority function, multiple-dispatching it over the new type. +For example, to do a random order: + +```julia +function priority_function( + ::MyTopDown, + ::AbstractGrammar, + ::AbstractRuleNode, + ::Union{Real, Tuple{Vararg{Real}}}, + ::Bool +) + Random.rand(); +end +``` + + +# A note on data structures + +As you have probably noticed, the priority queue some strange data structures: `SolverState` and `UniformIterator`; the top down iterator never puts `RuleNode`s into the queue. +In fact, the iterator never directly manipulates `RuleNode`s itself, but that is rather delegated to the constraint solver. +The constraint solver will do a lot of work to reduce the number of programs we have to consider. +The `SolverState` and `UniformIterator` are specialised data structure to improve the efficiency and memory usage. + +Herb uses a data structure of `UniformTrees` to represent all programs with an AST of the same shape, where each node has the same type. the `UniformIterator` is an iterator efficiently iterating over that structure. + +The `SolverState` represents non-uniform trees -- ASTs whose shape we haven't compeltely determined yet. `SolverState` is used as an intermediate representation betfore we reach `UniformTree`s on which partial constraint propagation is done. + +In principle, you should never construct ASTs yourself directly; you should leave that to the constraint solver. + + + +# Extra: Find Next Complete Tree / Program This function pops an element from the priority queue whilst it is not empty, and then checks what kind of iterator it is. From d214fb48cd0c8629a7c0fac071ff1af212d54491 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Mon, 8 Jul 2024 10:05:32 +0200 Subject: [PATCH 57/75] Fix makepath in notebook conversion 2 --- docs/make.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 055ff62..e87a26d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -13,10 +13,10 @@ using HerbSpecification using PyCall jupyter = pyimport("jupyterlab") nbconvert = pyimport("nbconvert") -all_notebooks = readdir(pwd()*"src/tutorials/") +all_notebooks = readdir("docs/src/tutorials/") for file in all_notebooks if occursin("ipynb", file) - path = pwd() * "/src/tutorials/" * file + path = "docs/src/tutorials/" * file run(`jupyter nbconvert --to markdown $path`) end end From 8d3f6d07af88a9eb3c12d29f708d96237e965a74 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Mon, 8 Jul 2024 10:14:21 +0200 Subject: [PATCH 58/75] Fix make file to use compiled files --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index e87a26d..5c6c794 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -37,7 +37,7 @@ makedocs( "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.html" + "Working with custom interpreters" => "tutorials/working_with_interpreters.md" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", From 8985a95f85724a86eb8f39315c3a4ed9d66c43a9 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 9 Jul 2024 12:25:37 +0200 Subject: [PATCH 59/75] Embed html of interpreter tutorial in md file. Increase html size. --- docs/make.jl | 18 +++++++------- .../tutorials/working_with_interpreters.md | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 docs/src/tutorials/working_with_interpreters.md diff --git a/docs/make.jl b/docs/make.jl index d8bcbc5..031014a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,7 +4,7 @@ using Herb using HerbConstraints using HerbSearch -using HerbGrammar +using HerbGrammar using HerbInterpret using HerbCore using HerbSpecification @@ -13,19 +13,19 @@ makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", sitename="Herb.jl", - pages = [ + pages=[ "Basics" => [ "index.md", "install.md", "get_started.md", "concepts.md" - ], + ], "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.html" + "Working with custom interpreters" => "tutorials/working_with_interpreters.md" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", @@ -34,12 +34,14 @@ makedocs( "HerbInterpret.jl" => "HerbInterpret/index.md", "HerbConstraints.jl" => "HerbConstraints/index.md", "HerbSearch.jl" => "HerbSearch/index.md", - ], + ], ], - format = Documenter.HTML( - sidebar_sitename = false + format=Documenter.HTML( + sidebar_sitename=false, + size_threshold=512000, # Set to a higher value + size_threshold_warn=102400 ), - warnonly = [:missing_docs, :cross_references, :doctest] + warnonly=[:missing_docs, :cross_references, :doctest] ) deploydocs(; diff --git a/docs/src/tutorials/working_with_interpreters.md b/docs/src/tutorials/working_with_interpreters.md new file mode 100644 index 0000000..3d9aab5 --- /dev/null +++ b/docs/src/tutorials/working_with_interpreters.md @@ -0,0 +1,24 @@ + + + Your HTML Content + + + + + + + + + +
+ + \ No newline at end of file From 3addeef63bd1d76352657e88c1b6f7e0557da30b Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 9 Jul 2024 12:37:12 +0200 Subject: [PATCH 60/75] Revert "Embed html of interpreter tutorial in md file. Increase html size." This reverts commit 8985a95f85724a86eb8f39315c3a4ed9d66c43a9. --- docs/make.jl | 18 +++++++------- .../tutorials/working_with_interpreters.md | 24 ------------------- 2 files changed, 8 insertions(+), 34 deletions(-) delete mode 100644 docs/src/tutorials/working_with_interpreters.md diff --git a/docs/make.jl b/docs/make.jl index 031014a..d8bcbc5 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,7 +4,7 @@ using Herb using HerbConstraints using HerbSearch -using HerbGrammar +using HerbGrammar using HerbInterpret using HerbCore using HerbSpecification @@ -13,19 +13,19 @@ makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", sitename="Herb.jl", - pages=[ + pages = [ "Basics" => [ "index.md", "install.md", "get_started.md", "concepts.md" - ], + ], "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.md" + "Working with custom interpreters" => "tutorials/working_with_interpreters.html" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", @@ -34,14 +34,12 @@ makedocs( "HerbInterpret.jl" => "HerbInterpret/index.md", "HerbConstraints.jl" => "HerbConstraints/index.md", "HerbSearch.jl" => "HerbSearch/index.md", - ], + ], ], - format=Documenter.HTML( - sidebar_sitename=false, - size_threshold=512000, # Set to a higher value - size_threshold_warn=102400 + format = Documenter.HTML( + sidebar_sitename = false ), - warnonly=[:missing_docs, :cross_references, :doctest] + warnonly = [:missing_docs, :cross_references, :doctest] ) deploydocs(; diff --git a/docs/src/tutorials/working_with_interpreters.md b/docs/src/tutorials/working_with_interpreters.md deleted file mode 100644 index 3d9aab5..0000000 --- a/docs/src/tutorials/working_with_interpreters.md +++ /dev/null @@ -1,24 +0,0 @@ - - - Your HTML Content - - - - - - - - - -
- - \ No newline at end of file From d132fc20952bc3de619c721df628bfd045136251 Mon Sep 17 00:00:00 2001 From: pwochner <78024695+pwochner@users.noreply.github.com> Date: Tue, 9 Jul 2024 12:54:27 +0200 Subject: [PATCH 61/75] Update make.jl Add missing comma in tutorials list --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 51e5ecd..0cf4b3c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -35,7 +35,7 @@ makedocs( "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", - "Advanced Search Procedures" => "tutorials/advanced_search.md" + "Advanced Search Procedures" => "tutorials/advanced_search.md", "Top Down Iterator" => "tutorials/TopDown.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", "Working with custom interpreters" => "tutorials/working_with_interpreters.md" From c970f0f9358134c590da8d4412048340a7bf9f83 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 9 Jul 2024 13:09:56 +0200 Subject: [PATCH 62/75] Increase size threshold for html. --- docs/make.jl | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index d8bcbc5..3d4b03e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -4,7 +4,7 @@ using Herb using HerbConstraints using HerbSearch -using HerbGrammar +using HerbGrammar using HerbInterpret using HerbCore using HerbSpecification @@ -13,19 +13,19 @@ makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", sitename="Herb.jl", - pages = [ + pages=[ "Basics" => [ "index.md", "install.md", "get_started.md", "concepts.md" - ], + ], "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.html" + # "Working with custom interpreters" => "tutorials/working_with_interpreters.html" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", @@ -34,12 +34,13 @@ makedocs( "HerbInterpret.jl" => "HerbInterpret/index.md", "HerbConstraints.jl" => "HerbConstraints/index.md", "HerbSearch.jl" => "HerbSearch/index.md", - ], + ], ], - format = Documenter.HTML( - sidebar_sitename = false + format=Documenter.HTML( + sidebar_sitename=false, + size_threshold=512000, ), - warnonly = [:missing_docs, :cross_references, :doctest] + warnonly=[:missing_docs, :cross_references, :doctest] ) deploydocs(; From 0b8edc99a1be39876ebb14345c353f71bc687c94 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Mon, 22 Jul 2024 16:39:33 +0200 Subject: [PATCH 63/75] Bug fix in tutorial 'A more verbose getting started' --- ...rees.ipynb => abstract_syntax_trees.ipynb} | 0 docs/src/tutorials/abstract_syntax_trees.md | 435 ++++++++++++++++++ .../tutorials/getting_started_with_herb.ipynb | 149 +++++- 3 files changed, 565 insertions(+), 19 deletions(-) rename docs/src/tutorials/{syntax-trees.ipynb => abstract_syntax_trees.ipynb} (100%) create mode 100644 docs/src/tutorials/abstract_syntax_trees.md diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/abstract_syntax_trees.ipynb similarity index 100% rename from docs/src/tutorials/syntax-trees.ipynb rename to docs/src/tutorials/abstract_syntax_trees.ipynb diff --git a/docs/src/tutorials/abstract_syntax_trees.md b/docs/src/tutorials/abstract_syntax_trees.md new file mode 100644 index 0000000..85b1e31 --- /dev/null +++ b/docs/src/tutorials/abstract_syntax_trees.md @@ -0,0 +1,435 @@ +# Herb tutorial: Abstract syntax trees + +In this tutorial, you will learn + +- How to represent a computer program as an abstract syntax tree in Herb. +- How to replace parts of the tree to modify the program. + +## Abstract syntax trees + +The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammar, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. + +In the context of program synthesis, ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found. + +Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. +An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. + +## A simple example program + +We first consider the simple program 5*(x+3). We will define a grammar that is sufficient to represent this program and use it to construct a AST for our program. + +### Define the grammar + + +```julia +using HerbCore, HerbGrammar, HerbInterpret + +grammar = @csgrammar begin + Number = |(0:9) + Number = x + Number = Number + Number + Number = Number * Number +end + +``` + + + 1: Number = 0 + 2: Number = 1 + 3: Number = 2 + 4: Number = 3 + 5: Number = 4 + 6: Number = 5 + 7: Number = 6 + 8: Number = 7 + 9: Number = 8 + 10: Number = 9 + 11: Number = x + 12: Number = Number + Number + 13: Number = Number * Number + + + +### Construct the syntax tree + +The AST of this program is shown in the diagram below. The number in each node refers to the index of the corresponding rule in our grammar. + +```mermaid + flowchart + id1((13)) --- + id2((6)) + id1 --- id3((12)) + id4((11)) + id5((4)) + id3 --- id4 + id3 --- id5 +``` + +In `Herb.jl`, the `HerbCore.RuleNode` is used to represent both an individual node, but also entire ASTs or sub-trees. This is achieved by nesting instances of `RuleNode`. A `RuleNode` can be instantiated by providing the index of the grammar rule that the node represents and a vector of child nodes. + + +```julia +syntaxtree = RuleNode(13, [RuleNode(6), RuleNode(12, [RuleNode(11), RuleNode(4)])]) +``` + + + 13{6,12{11,4}} + + +We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.execute_on_input`. + + +```julia +program = rulenode2expr(syntaxtree, grammar) +println(program) +``` + + 5 * (x + 3) + + + +```julia +# test solution on inputs +output = execute_on_input(grammar, syntaxtree, Dict(:x => 10)) +``` + + + 65 + + +## Another example: FizzBuzz + +Let's look at a more interesting example. +The program `fizbuzz()` is based on the popular _FizzBuzz_ problem. Given an integer number, the program simply returns a `String` of that number, but replace numbers divisible by 3 with `"Fizz"`, numbers divisible by 5 with `"Buzz"`, and number divisible by both 3 and 5 with `"FizzBuzz"`. + + +```julia +function fizzbuzz(x) + if x % 5 == 0 && x % 3 == 0 + return "FizzBuzz" + else + if x % 3 == 0 + return "Fizz" + else + if x % 5 == 0 + return "Buzz" + else + return string(x) + end + end + end +end +``` + + + fizzbuzz (generic function with 1 method) + + +### Define the grammar + +Let's define a grammar with all the rules that we need. + + +```julia +grammar_fizzbuzz = @csgrammar begin + Int = input1 + Int = 0 | 3 | 5 + String = "Fizz" | "Buzz" | "FizzBuzz" + String = string(Int) + Return = String + Int = Int % Int + Bool = Int == Int + Int = Bool ? Int : Int + Bool = Bool && Bool +end +``` + + + 1: Int = input1 + 2: Int = 0 + 3: Int = 3 + 4: Int = 5 + 5: String = Fizz + 6: String = Buzz + 7: String = FizzBuzz + 8: String = string(Int) + 9: Return = String + 10: Int = Int % Int + 11: Bool = Int == Int + 12: Int = if Bool + Int + else + Int + end + 13: Bool = Bool && Bool + + + +### Construct the syntax tree + +Given the grammar, the AST of `fizzbuzz()` looks like this: + +```mermaid +flowchart + id1((12)) --- id21((13)) + id1--- id22((9)) + id1--- id23((12)) + + id21 --- id31((11)) + id21 --- id32((11)) + + id31 --- id41((10)) + id31 --- id42((2)) + + id41 --- id51((1)) + id41 --- id52((4)) + + id32 --- id43((10)) + id32 --- id44((2)) + + id43 --- id53((1)) + id43 --- id54((3)) + + id22 --- id33((7)) + id23 --- id34((11)) + + id34 --- id45((10)) + id34 --- id46((2)) + + id45 --- id55((1)) + id45 --- id56((3)) + + id23 --- id35((9)) + id35 --- id47((5)) + + id23 --- id36((12)) + id36 --- id48((11)) + id48 --- id57((10)) + id57 --- id61((1)) + id57 --- id62((4)) + id48 --- id58((2)) + + id36 --- id49((9)) + id49 --- id59((6)) + + id36 --- id410((9)) + id410 --- id510((8)) + id510 --- id63((1)) + + + +``` + +As before, we use nest instanced of `RuleNode` to implement the AST. + + +```julia +fizzbuzz_syntaxtree = RuleNode(12, [ + RuleNode(13, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(4) + ]), + RuleNode(2) + ]), + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(3) + ]), + RuleNode(2) + ]) + ]), + RuleNode(9, [ + RuleNode(7) + + ]), + RuleNode(12, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(3), + ]), + RuleNode(2) + ]), + RuleNode(9, [ + RuleNode(5) + ]), + RuleNode(12, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(4) + ]), + RuleNode(2) + ]), + RuleNode(9, [ + RuleNode(6) + ]), + RuleNode(9, [ + RuleNode(8, [ + RuleNode(1) + ]) + ]) + ]) + ]) + ]) +``` + + + 12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}} + + +And we check our syntax tree is correct: + + +```julia +program = rulenode2expr(fizzbuzz_syntaxtree, grammar_fizzbuzz) +println(program) +``` + + if input1 % 5 == 0 && input1 % 3 == 0 + "FizzBuzz" + else + if input1 % 3 == 0 + "Fizz" + else + if input1 % 5 == 0 + "Buzz" + else + string(input1) + end + end + end + + + +```julia +# test solution on inputs +input = [Dict(:input1 => 3), Dict(:input1 => 5), Dict(:input1 =>15), Dict(:input1 => 22)] +output = execute_on_input(grammar_fizzbuzz, fizzbuzz_syntaxtree, input) +``` + + + 4-element Vector{Any}: + "Fizz" + "Buzz" + "FizzBuzz" + "22" + + +### Modify the AST/program + +There are several ways to modify an AST and hence, a program. You can + +- directly replace a node with `HerbCore.swap_node()` +- insert a rule node with `insert!` + +Let's modify our example such that if the input number is divisible by 3, the program returns "Buzz" instead of "Fizz". +We use `swap_node()` to replace the node of the AST that corresponds to rule 5 in the grammar (`String = Fizz`) with rule 6 (`String = Buzz`). To do so, `swap_node()` needs the tree that contains the node we want to modify, the new node we want to replace the node with, and the path to that node. + +Note that `swap_node()` modifies the tree, hence we make a deep copy of it first. + + +```julia +modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) +newnode = RuleNode(6) +path = [3, 2, 1] +swap_node(modified_fizzbuzz_syntaxtree, newnode, path) +``` + + + 6, + + +Let's confirm that we modified the AST, and hence the program, correctly: + + +```julia +program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz) +println(program) +``` + + if input1 % 5 == 0 && input1 % 3 == 0 + "FizzBuzz" + else + if input1 % 3 == 0 + "Buzz" + else + if input1 % 5 == 0 + "Buzz" + else + string(input1) + end + end + end + + + +```julia +# test solution on same inputs as before +output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input) +``` + + + 4-element Vector{Any}: + "Buzz" + "Buzz" + "FizzBuzz" + "22" + + +An alternative way to modify the AST is by using `insert!()`. This requires to provide the location of the node that we want to as `NodeLoc`. `NodeLoc` points to a node in the tree and consists of the parent and the child index of the node. +Again, we make a deep copy of the original AST first. + + +```julia +modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) +# get the node we want to modify and instantiate a NodeLoc from it. +node = get_node_at_location(modified_fizzbuzz_syntaxtree, [3, 2, 1]) +nodeloc = NodeLoc(node, 0) +# replace the node +insert!(node, nodeloc, newnode) +``` + + + 6, + + +Again, we check that we modified the program as intended: + + +```julia +program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz) +println(program) +``` + + if input1 % 5 == 0 && input1 % 3 == 0 + "FizzBuzz" + else + if input1 % 3 == 0 + "Buzz" + else + if input1 % 5 == 0 + "Buzz" + else + string(input1) + end + end + end + + + +```julia +# test on same inputs as before +output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input) +``` + + + 4-element Vector{Any}: + "Buzz" + "Buzz" + "FizzBuzz" + "22" + diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb index 45abcbb..f9690e0 100644 --- a/docs/src/tutorials/getting_started_with_herb.ipynb +++ b/docs/src/tutorials/getting_started_with_herb.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -53,9 +53,33 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1: Real = 0\n", + "2: Real = 1\n", + "3: Real = 2\n", + "4: Real = 3\n", + "5: Real = 4\n", + "6: Real = 5\n", + "7: Real = 6\n", + "8: Real = 7\n", + "9: Real = 8\n", + "10: Real = 9\n", + "11: Real = x\n", + "12: Real = Real + Real\n", + "13: Real = Real - Real\n", + "14: Real = Real * Real\n" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "g = HerbGrammar.@cfgrammar begin\n", " Real = |(0:9)\n", @@ -92,9 +116,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "5-element Vector{IOExample}:\n", + " IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", + " IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", + " IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", + " IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", + " IOExample(Dict{Symbol, Any}(:x => 5), 20)" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Create input-output examples\n", "examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" @@ -112,9 +152,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "Problem{Vector{IOExample}}(\"example\", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "problem = HerbSpecification.Problem(\"example\", examples)" ] @@ -145,18 +196,54 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "BFSIterator(GenericSolver(1: Real = 0\n", + "2: Real = 1\n", + "3: Real = 2\n", + "4: Real = 3\n", + "5: Real = 4\n", + "6: Real = 5\n", + "7: Real = 6\n", + "8: Real = 7\n", + "9: Real = 8\n", + "10: Real = 9\n", + "11: Real = x\n", + "12: Real = Real + Real\n", + "13: Real = Real - Real\n", + "14: Real = Real * Real\n", + ", SolverState(hole[Bool[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],, Set{AbstractLocalConstraint}(), true), DataStructures.PriorityQueue{AbstractLocalConstraint, Int64, Base.Order.ForwardOrdering}(), nothing, true, false, 9223372036854775807, 9223372036854775807))" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "iterator = BFSIterator(g, :Real)" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(12{14{4,11}6}, optimal_program)" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "synth(problem, iterator)" ] @@ -185,12 +272,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(14{14{4,9}8}, optimal_program)" + ] + } + ], "source": [ "problem = HerbSpecification.Problem(\"example2\", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5])\n", - "iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30, max_time=180)\n", + "iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30)\n", "expr = HerbSearch.synth(problem, iterator)\n", "print(expr)" ] @@ -207,9 +302,25 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[33m\u001b[1m┌ \u001b[22m\u001b[39m\u001b[33m\u001b[1mWarning: \u001b[22m\u001b[39mUniformSolver is iterating over more than 1000000 solutions...\n", + "\u001b[33m\u001b[1m└ \u001b[22m\u001b[39m\u001b[90m@ HerbSearch ~/.julia/packages/HerbSearch/PIeQW/src/uniform_iterator.jl:96\u001b[39m\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "(12{13{6,1}14{14{3,10}10}}, optimal_program)" + ] + } + ], "source": [ "problem = HerbSpecification.Problem(\"example3\", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5])\n", "expr = HerbSearch.synth(problem, iterator)\n", @@ -219,15 +330,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.9.4", + "display_name": "Julia 1.10.3", "language": "julia", - "name": "julia-1.9" + "name": "julia-1.10" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.9.4" + "version": "1.10.3" } }, "nbformat": 4, From fd5d48cf42af26ac10e195e8c8524b2a28fafda4 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 23 Jul 2024 08:05:02 +0200 Subject: [PATCH 64/75] Convert tutorials Verbose getting started and Getting started with Constraints into pluto notebooks. --- docs/make.jl | 33 +- .../getting_started_with_constraints.html | 17 + .../getting_started_with_constraints.jl | 1098 +++++++++++++++++ .../tutorials/getting_started_with_herb.html | 17 + .../tutorials/getting_started_with_herb.jl | 843 +++++++++++++ 5 files changed, 1998 insertions(+), 10 deletions(-) create mode 100644 docs/src/tutorials/getting_started_with_constraints.html create mode 100644 docs/src/tutorials/getting_started_with_constraints.jl create mode 100644 docs/src/tutorials/getting_started_with_herb.html create mode 100644 docs/src/tutorials/getting_started_with_herb.jl diff --git a/docs/make.jl b/docs/make.jl index b5c74d2..402bc80 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,5 +1,7 @@ using Documenter +using Pluto, PlutoSliderServer + using Herb using HerbConstraints @@ -9,18 +11,28 @@ using HerbInterpret using HerbCore using HerbSpecification -# Use jupyter.nbconver to convert notebooks to markdown -using PyCall -jupyter = pyimport("jupyterlab") -nbconvert = pyimport("nbconvert") -all_notebooks = readdir("docs/src/tutorials/") -for file in all_notebooks - if occursin("ipynb", file) - path = "docs/src/tutorials/" * file - run(`jupyter nbconvert --to markdown $path`) +# Create md file for tutorial that embeds html file +basedir = joinpath(@__DIR__, "src", "tutorials") +html_files = filter!(f -> occursin(r"\.html$", f), readdir(basedir)) # assumes all html file in directory are tutorials + +for f in html_files + html_path = joinpath(basedir, f) + filename = replace(f, ".html" => "") + md_path = joinpath(basedir, "$filename.md") + content = """ + # Tutorial $filename + + + """ + open(md_path, "w") do file + write( + file, + content + ) end end + makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", @@ -38,7 +50,8 @@ makedocs( "Advanced Search Procedures" => "tutorials/advanced_search.md", "Top Down Iterator" => "tutorials/TopDown.md", "Getting started with Constraints" => "tutorials/getting_started_with_constraints.md", - "Working with custom interpreters" => "tutorials/working_with_interpreters.md" + "Working with custom interpreters" => "tutorials/working_with_interpreters.md", + "Abstract Syntax Trees" => "tutorials/abstract_syntax_trees.md", ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", diff --git a/docs/src/tutorials/getting_started_with_constraints.html b/docs/src/tutorials/getting_started_with_constraints.html new file mode 100644 index 0000000..a16e59f --- /dev/null +++ b/docs/src/tutorials/getting_started_with_constraints.html @@ -0,0 +1,17 @@ + + + + + + + +
\ No newline at end of file diff --git a/docs/src/tutorials/getting_started_with_constraints.jl b/docs/src/tutorials/getting_started_with_constraints.jl new file mode 100644 index 0000000..b359985 --- /dev/null +++ b/docs/src/tutorials/getting_started_with_constraints.jl @@ -0,0 +1,1098 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ c5cb3782-c9af-4cf5-9e28-e1892c6442a2 +using HerbCore, HerbGrammar, HerbConstraints, HerbSearch + +# ╔═╡ c5509a19-43bc-44d3-baa9-9af83717b6e6 +md""" +# Getting started with HerbConstraints + +When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space. +""" + +# ╔═╡ 864bc658-9612-4439-9024-74668ba3f971 +md""" +### Setup + +For this tutorial, we need to import the following modules of the Herb.jl framework: + +* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s +* `HerbGrammar` to define the grammar +* `HerbConstraints` to define the constraints +* `HerbSearch` to execute a constrained enumeration + +We will also redefine the simple arithmetic grammar from the previous tutorial. +""" + +# ╔═╡ aacedef3-08d6-4bc0-a2c3-dd9a9aac340d +grammar = @cfgrammar begin + Int = 1 + Int = x + Int = - Int + Int = Int + Int + Int = Int * Int +end + +# ╔═╡ 1ff2dd58-01df-4cab-93d9-aabe1d07cc85 +md""" +### Working with constraints + +To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes). + +(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar) +""" + +# ╔═╡ 62e41031-6866-46fc-b160-fb2f81f75c04 +begin + clearconstraints!(grammar) + iter_1 = BFSIterator(grammar, :Int, max_size=3) + + for program ∈ iter_1 + println(rulenode2expr(program, grammar)) + end + +end + +# ╔═╡ 76478bc7-e1b0-44f7-b1e0-95573db9f0e3 +md""" +Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. + +To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids: + +* `-(-1)` +* `-(-X)` +* `-(-(1 + 1))` +* `1 + -(-(1 + 1))` +* etc +""" + +# ╔═╡ 4d94057f-0b69-4d7d-8bde-900e6eb3e53f +begin + one = 1 + x = 2 + minus = 3 + plus = 4 + times = 5 + + addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A + addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A) + + iter_2 = BFSIterator(grammar, :Int, max_size=3) + + for program ∈ iter_2 + println(rulenode2expr(program, grammar)) + end +end + +# ╔═╡ 1f0ba3c1-d160-44f5-85c4-4089cbd6f284 +md""" +### Forbidden Constraint + +The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types: +* `RuleNode(1)`. Matches exactly the given rule. +* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5. +* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same. +""" + +# ╔═╡ 030a150e-84ac-4aa6-9a08-5639538fb981 +begin + #this constraint forbids A+A and A*A + constraint_1 = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)])) + + # Without this constraint, we encounter 154 programs + clearconstraints!(grammar) + iter_3 = BFSIterator(grammar, :Int, max_size=5) + println(length(iter_3)) + + # With this constraint, we encounter 106 programs + clearconstraints!(grammar) + addconstraint!(grammar, constraint_1) + iter_4 = BFSIterator(grammar, :Int, max_size=5) + println(length(iter_4)) + +end + +# ╔═╡ 4e1e76b3-b6b2-4a1d-a749-9226a283522d +md""" +### Contains Constraint + +The `Contains` constraint enforces that a given rule appears in the program tree at least once. + +In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant. +""" + +# ╔═╡ 92eb7f60-88df-4212-a50b-b31bf386c720 +begin + clearconstraints!(grammar) + addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program + iter_5 = BFSIterator(grammar, :Int, max_size=3) + + for program ∈ iter_5 + println(rulenode2expr(program, grammar)) + end +end + +# ╔═╡ 6c6becfb-1a7f-403a-9b04-6c4334c9f489 +md""" +### Contains Subtree Constraint + +Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once. +""" + +# ╔═╡ ada60cc5-7aa1-41c4-828b-3d73c29fd087 +begin + clearconstraints!(grammar) + addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree + iter_6 = BFSIterator(grammar, :Int, max_size=4) + + for program ∈ iter_6 + println(rulenode2expr(program, grammar)) + end +end + +# ╔═╡ 3f42462a-0be0-42bd-a428-a666769054dd +md""" +### Ordered Constraint + +The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants. + +To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree. + +In the upcoming example we will set up a template tree representing `a+b` and `a*b`. +Then, we will impose an ordering `a<=b` on all the subtrees that match the template. + +The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`. + +""" + +# ╔═╡ 4e76bb87-c429-4547-accc-e304d4220f2d +begin + clearconstraints!(grammar) + + template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)]) + order = [:a, :b] + + addconstraint!(grammar, Ordered(template_tree, order)) + iter_7 = BFSIterator(grammar, :Int, max_size=3) + + for program ∈ iter_7 + println(rulenode2expr(program, grammar)) + end + +end + +# ╔═╡ 00f62b26-a15d-4525-bef0-857d37bb0d85 +md""" +### Forbidden Sequence Constraint + +The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. + +An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. + +Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence. + +This constraint will **forbid** the following programs: + +* x + 1 +* x + -1 +* x + -(-1) +* x + (x + 1) +* x * (x + 1) + +But it will **allow** the following program (as * disrupts the sequence): + +* x + (x * 1) + +""" + +# ╔═╡ 19d0a61b-46a3-4d53-b1d2-2b0e2650b56a +begin + constraint_2 = ForbiddenSequence([plus, one], ignore_if=[times]) + addconstraint!(grammar, constraint_2) + iter_8 = BFSIterator(grammar, :Int, max_size=3) + + for program ∈ iter_8 + println(rulenode2expr(program, grammar)) + end + +end + +# ╔═╡ 2bea7a99-c46a-4200-9ba8-1227a5806f2f +md""" +### Custom Constraint + +To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`. + +A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location. + +A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies. +""" + +# ╔═╡ f4c15b60-5d45-4b75-9100-a7be2969d7ca +md""" +Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. + +Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function. + +(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace) +""" + +# ╔═╡ bacc917b-2706-412d-9b85-deb4b6685323 +md""" +Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path: + +* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})` +* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)` +* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree) + +In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver: + +* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators. +* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation. +* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint. + +The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions: + +* `get_tree(solver)` returns the root node of the current (partial) program tree +* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants. +* `get_path(solver, node)` returns the path at which the node is located. +* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations). +* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2). + +To get information about a node, we can use the following getter functions: + +* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1. +* `get_rule(node)`. Get the rule of a filled node. +* `get_children(node)`. Get the children of a node. +* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain. + +Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match. + +""" + +# ╔═╡ fef62621-716a-4f8a-85b4-d92e48b30bc6 +begin + """ + Forbids the consecutive application of the specified rule at path `path`. + """ + struct LocalForbidConsecutive <: AbstractLocalConstraint + path::Vector{Int} + rule::Int + end + + """ + Propagates the constraints by preventing a consecutive application of the specified rule. + """ + function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive) + node = get_node_at_location(solver, constraint.path) + if isfilled(node) + if get_rule(node) == constraint.rule + #the specified rule is used, make sure the rule will not be used by any of the children + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the specified rule was used twice in a row, which is violating the constraint + set_infeasible!(solver) + return + end + elseif child.domain[constraint.rule] + child_path = push!(copy(constraint.path), i) + remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child + end + end + end + elseif node.domain[constraint.rule] + #our node is a hole with the specified rule in its domain + #we will now check if any of the children already uses the specified rule + softfail = false + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the child holds the specified rule, so the parent cannot have this rule + remove!(solver, constraint.path, constraint.rule) + end + elseif child.domain[constraint.rule] + #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail. + softfail = true + end + end + if softfail + #we cannot deactivate the constraint, because it needs to be repropagated + return + end + end + + #the constraint is satisfied and can be deactivated + HerbConstraints.deactivate!(solver, constraint) + end +end + +# ╔═╡ b00b039b-63ee-40d0-8f4b-d25539e596d5 +begin + """ + Forbids the consecutive application of the specified rule. + For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row. + """ + struct ForbidConsecutive <: AbstractGrammarConstraint + rule::Int + end + + """ + Post a local constraint on each new node that appears in the tree + """ + function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int}) + HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule)) + end +end + +# ╔═╡ e40e09fc-c697-4c83-90eb-2b758254128e +md""" +Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation. + +Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation. + +In our case, we want to repropagate if either: +* a tree manipulation occured at the `constraint.path` +* a tree manipulation occured at the child of the `constraint.path` +""" + +# ╔═╡ 4e0989a8-7e63-45eb-80c9-a3a2f97c357c + +""" +Gets called whenever an tree manipulation occurs at the given `path`. +Returns true iff the `constraint` should be rescheduled for propagation. +""" +function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool + return (path == constraint.path) || (path == constraint.path[1:end-1]) +end + + +# ╔═╡ ce9ae2a2-f3e3-4693-9e6d-60e91128de34 +md""" +With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint. +""" + +# ╔═╡ 89c165c6-3e04-4887-924f-364b25b21bcd +begin + clearconstraints!(grammar) + + addconstraint!(grammar, ForbidConsecutive(minus)) + addconstraint!(grammar, ForbidConsecutive(plus)) + addconstraint!(grammar, ForbidConsecutive(times)) + + iter = BFSIterator(grammar, :Int, max_size=6) + + for program ∈ iter + println(rulenode2expr(program, grammar)) + end +end + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" + +[compat] +HerbConstraints = "~0.2.2" +HerbCore = "~0.3.0" +HerbGrammar = "~0.3.0" +HerbSearch = "~0.3.0" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "f550d76e5fc2109cce82f8527132b9929cc7271f" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "4f2b57488ac7ee16124396de4f2bbdd51b2602ad" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.0" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbConstraints]] +deps = ["DataStructures", "HerbCore", "HerbGrammar", "MLStyle"] +git-tree-sha1 = "2e54da1d19119847b242d1ceda212b180cca36a9" +uuid = "1fa96474-3206-4513-b4fa-23913f296dfc" +version = "0.2.2" + +[[deps.HerbCore]] +git-tree-sha1 = "923877c2715b8166d7ba9f9be2136d70eed87725" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.3.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "b4cbf9712dbb3ab281ff4ed517d3cd6bc12f3078" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.3.0" + +[[deps.HerbInterpret]] +deps = ["HerbCore", "HerbGrammar", "HerbSpecification"] +git-tree-sha1 = "9e19b4ee5f29eb8bb9b1049524728b38e878eca2" +uuid = "5bbddadd-02c5-4713-84b8-97364418cca7" +version = "0.1.3" + +[[deps.HerbSearch]] +deps = ["DataStructures", "HerbConstraints", "HerbCore", "HerbGrammar", "HerbInterpret", "HerbSpecification", "Logging", "MLStyle", "Random", "StatsBase"] +git-tree-sha1 = "472e3f427c148f334dde3837b0bb1549897ed00a" +uuid = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +version = "0.3.0" + +[[deps.HerbSpecification]] +git-tree-sha1 = "5385b81e40c3cd62aeea591319896148036863c9" +uuid = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +version = "0.1.0" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.Inflate]] +git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.4" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.27" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MLStyle]] +git-tree-sha1 = "bc38dff0548128765760c79eb7388a4b37fae2c8" +uuid = "d8e11817-5142-5d16-987a-aa16d5891078" +version = "0.4.17" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.4" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.3" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.12.7+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.15.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" + +# ╔═╡ Cell order: +# ╟─c5509a19-43bc-44d3-baa9-9af83717b6e6 +# ╟─864bc658-9612-4439-9024-74668ba3f971 +# ╠═c5cb3782-c9af-4cf5-9e28-e1892c6442a2 +# ╠═aacedef3-08d6-4bc0-a2c3-dd9a9aac340d +# ╟─1ff2dd58-01df-4cab-93d9-aabe1d07cc85 +# ╠═62e41031-6866-46fc-b160-fb2f81f75c04 +# ╟─76478bc7-e1b0-44f7-b1e0-95573db9f0e3 +# ╠═4d94057f-0b69-4d7d-8bde-900e6eb3e53f +# ╟─1f0ba3c1-d160-44f5-85c4-4089cbd6f284 +# ╠═030a150e-84ac-4aa6-9a08-5639538fb981 +# ╟─4e1e76b3-b6b2-4a1d-a749-9226a283522d +# ╠═92eb7f60-88df-4212-a50b-b31bf386c720 +# ╟─6c6becfb-1a7f-403a-9b04-6c4334c9f489 +# ╠═ada60cc5-7aa1-41c4-828b-3d73c29fd087 +# ╟─3f42462a-0be0-42bd-a428-a666769054dd +# ╠═4e76bb87-c429-4547-accc-e304d4220f2d +# ╟─00f62b26-a15d-4525-bef0-857d37bb0d85 +# ╠═19d0a61b-46a3-4d53-b1d2-2b0e2650b56a +# ╟─2bea7a99-c46a-4200-9ba8-1227a5806f2f +# ╟─f4c15b60-5d45-4b75-9100-a7be2969d7ca +# ╠═b00b039b-63ee-40d0-8f4b-d25539e596d5 +# ╟─bacc917b-2706-412d-9b85-deb4b6685323 +# ╠═fef62621-716a-4f8a-85b4-d92e48b30bc6 +# ╟─e40e09fc-c697-4c83-90eb-2b758254128e +# ╠═4e0989a8-7e63-45eb-80c9-a3a2f97c357c +# ╟─ce9ae2a2-f3e3-4693-9e6d-60e91128de34 +# ╠═89c165c6-3e04-4887-924f-364b25b21bcd +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 diff --git a/docs/src/tutorials/getting_started_with_herb.html b/docs/src/tutorials/getting_started_with_herb.html new file mode 100644 index 0000000..990d2a6 --- /dev/null +++ b/docs/src/tutorials/getting_started_with_herb.html @@ -0,0 +1,17 @@ + + + + + + + +
\ No newline at end of file diff --git a/docs/src/tutorials/getting_started_with_herb.jl b/docs/src/tutorials/getting_started_with_herb.jl new file mode 100644 index 0000000..83adbd8 --- /dev/null +++ b/docs/src/tutorials/getting_started_with_herb.jl @@ -0,0 +1,843 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ 1defafc5-ce65-42f0-90cd-de9e8895ec90 +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints + +# ╔═╡ adabb7f1-7952-43c4-88a9-54225b40aaf0 +md""" +# Search + +This notebook describes how you can search a program space as defined by a grammar. +Specifically, we will look at example-based search, where the goal is to find a program that is able to transform the inputs of every example to the corresponding output. +""" + +# ╔═╡ 841f097d-a389-4dd2-9ad3-1a2292568634 +md""" +### Setup +First, we start with the setup. We need to access to all the function in the Herb.jl framework. +""" + +# ╔═╡ db7fe47b-ab3e-4705-b6ac-2733b9e81434 +md""" +### Defining the program space + +Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). + +Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md). + +For now, we specify a simple grammar for dealing with integers and explain all the rules individually: + +1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer. +2. Then, we can also use the variable `x` to hold an integer. +3. The third rule determines we can add two integers. +4. The fourth rule determines we can subtract an integer from another. +5. Finally, we also allow the multiplication of two integers. + +If you run this cell, you can see all the rules rolled out. +""" + +# ╔═╡ 763b378b-66f9-481e-a3da-ca37825eb255 +g = HerbGrammar.@cfgrammar begin + Real = |(0:9) + Real = x + Real = Real + Real + Real = Real - Real + Real = Real * Real +end + +# ╔═╡ 6d01dfe8-9048-4696-916c-b33fbc97268b +md""" +### Defining the problem +""" + +# ╔═╡ 56a63f9e-b484-4d85-af3e-de2cc4476e09 +md""" +As mentioned before, we are looking at example-based search. +This means that the problem is defined by a set of input-output examples. +A single example hence consists of an input and an output. +The input is defined as a dictionary, with a value assigned to each variable in the grammar. +It is important to write the variable name as a `Symbol` instead of a string. +A `Symbol` in Julia is written with a colon prefix, i.e. `:x`. +The output of the input-output example is just a single value for this specific grammar, but could possibly relate to e.g. arrays of values, too. + +In the cell below we automatically generate some examples for `x` assigning values `1-5`. +""" + +# ╔═╡ 8bf48b7a-0ff5-4015-81d3-ed2eeeceff1c +# Create input-output examples +examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] + +# ╔═╡ 2baa7f33-c86d-40e2-9253-720ec19e4c43 +md""" +Now that we have some input-output examples, we can define the problem. +Next to the examples, a problem also contains a name meant to link to the file path, which can be used to keep track of current examples. +For now, this is irrelevant, and you can give the program any name you like. +""" + +# ╔═╡ 059306d1-a45a-4727-ab01-1b5b80187999 +problem_1 = HerbSpecification.Problem("example", examples) + +# ╔═╡ 0f090666-9007-417e-a801-8231fffa19f3 +md""" +### Searching + +Now that we have defined the search space and the goal of the search, we can start the search. + +Of course, our problem is underdefined as there might be multiple programs that satisfy our examples. +Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. +This program satisfies all our examples, but we don't expect it to generalize very well. + +To search through a program space, we first need to define a [`HerbSearch.ProgramIterator`](@ref), which can be instantiated with different iterators, for now we use a simple [`HerbSearch.BFSIterator`](@ref). For more advanced search methods check out our tutorial on [advanced search](.advanced_search.md). For more information about iterators, check out our tutorial on [working with interpreters](.working_with_interpreters.md). + +In general, we assume that a smaller program is more general than a larger program. +Therefore, we search for the smallest program in our grammar that satisfies our examples. +This can be done using a breadth-first search over the program/search space. + +This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. + +So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. +""" + +# ╔═╡ d553f37b-bc8a-4426-a98b-fb195ed994d9 +iterator_1 = BFSIterator(g, :Real) + +# ╔═╡ e1910236-9783-4989-a014-c3f7ccdf33d3 +synth(problem_1, iterator_1) + +# ╔═╡ 4c9f6236-2a84-4e76-86ab-c1fd1c7a1ba1 +md""" +As you can see, the search procedure found the correct program! +""" + +# ╔═╡ 8f87eed7-dfa1-47e4-a9b3-8e2a8a966207 +md""" +### Defining the search procedure + +In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values. + +We first define a new problem to test with, we are looking for the programs that can compute the value `167`. We immediately pass the examples to the problem and then set up the new search. + +Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search. +""" + +# ╔═╡ cdab3f55-37e4-4aee-bae1-14d3475cbdcd +begin + problem_2 = HerbSpecification.Problem("example2", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5]) + iterator_2 = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30) + expr_2 = HerbSearch.synth(problem_2, iterator_2) + print(expr_2) +end + +# ╔═╡ 5ad86beb-eb25-4bae-b0c2-a33d1a38581a +md""" +We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? You can try below, using the same iterator. + +In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy! +""" + +# ╔═╡ c06d09a5-138a-4821-8a60-074fa7ec026d +begin + problem_3 = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5]) + expr_3 = HerbSearch.synth(problem_3, iterator_2) + print(expr_3) +end + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" +HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" + +[compat] +HerbConstraints = "~0.2.2" +HerbGrammar = "~0.3.0" +HerbInterpret = "~0.1.3" +HerbSearch = "~0.3.0" +HerbSpecification = "~0.1.0" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "ec293deacc6b3fb0969b68da9eec7f6d9738e0e4" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "4f2b57488ac7ee16124396de4f2bbdd51b2602ad" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.0" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbConstraints]] +deps = ["DataStructures", "HerbCore", "HerbGrammar", "MLStyle"] +git-tree-sha1 = "2e54da1d19119847b242d1ceda212b180cca36a9" +uuid = "1fa96474-3206-4513-b4fa-23913f296dfc" +version = "0.2.2" + +[[deps.HerbCore]] +git-tree-sha1 = "923877c2715b8166d7ba9f9be2136d70eed87725" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.3.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "b4cbf9712dbb3ab281ff4ed517d3cd6bc12f3078" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.3.0" + +[[deps.HerbInterpret]] +deps = ["HerbCore", "HerbGrammar", "HerbSpecification"] +git-tree-sha1 = "9e19b4ee5f29eb8bb9b1049524728b38e878eca2" +uuid = "5bbddadd-02c5-4713-84b8-97364418cca7" +version = "0.1.3" + +[[deps.HerbSearch]] +deps = ["DataStructures", "HerbConstraints", "HerbCore", "HerbGrammar", "HerbInterpret", "HerbSpecification", "Logging", "MLStyle", "Random", "StatsBase"] +git-tree-sha1 = "472e3f427c148f334dde3837b0bb1549897ed00a" +uuid = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +version = "0.3.0" + +[[deps.HerbSpecification]] +git-tree-sha1 = "5385b81e40c3cd62aeea591319896148036863c9" +uuid = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +version = "0.1.0" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.Inflate]] +git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.4" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.27" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MLStyle]] +git-tree-sha1 = "bc38dff0548128765760c79eb7388a4b37fae2c8" +uuid = "d8e11817-5142-5d16-987a-aa16d5891078" +version = "0.4.17" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.4" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.3" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.12.7+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.15.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" + +# ╔═╡ Cell order: +# ╟─adabb7f1-7952-43c4-88a9-54225b40aaf0 +# ╟─841f097d-a389-4dd2-9ad3-1a2292568634 +# ╠═1defafc5-ce65-42f0-90cd-de9e8895ec90 +# ╟─db7fe47b-ab3e-4705-b6ac-2733b9e81434 +# ╠═763b378b-66f9-481e-a3da-ca37825eb255 +# ╟─6d01dfe8-9048-4696-916c-b33fbc97268b +# ╟─56a63f9e-b484-4d85-af3e-de2cc4476e09 +# ╠═8bf48b7a-0ff5-4015-81d3-ed2eeeceff1c +# ╟─2baa7f33-c86d-40e2-9253-720ec19e4c43 +# ╠═059306d1-a45a-4727-ab01-1b5b80187999 +# ╟─0f090666-9007-417e-a801-8231fffa19f3 +# ╠═d553f37b-bc8a-4426-a98b-fb195ed994d9 +# ╠═e1910236-9783-4989-a014-c3f7ccdf33d3 +# ╟─4c9f6236-2a84-4e76-86ab-c1fd1c7a1ba1 +# ╟─8f87eed7-dfa1-47e4-a9b3-8e2a8a966207 +# ╠═cdab3f55-37e4-4aee-bae1-14d3475cbdcd +# ╟─5ad86beb-eb25-4bae-b0c2-a33d1a38581a +# ╠═c06d09a5-138a-4821-8a60-074fa7ec026d +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From 115187a9c144045c7e0c11be517bef849bcf1868 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 23 Jul 2024 08:07:11 +0200 Subject: [PATCH 65/75] Pluot notebook for AST tutorial --- docs/src/tutorials/abstract_syntax_trees.jl | 1051 +++++++++++++++++++ 1 file changed, 1051 insertions(+) create mode 100644 docs/src/tutorials/abstract_syntax_trees.jl diff --git a/docs/src/tutorials/abstract_syntax_trees.jl b/docs/src/tutorials/abstract_syntax_trees.jl new file mode 100644 index 0000000..07f8c7b --- /dev/null +++ b/docs/src/tutorials/abstract_syntax_trees.jl @@ -0,0 +1,1051 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ c784cbc3-19fc-45c9-b344-db10cf7a81fa +begin + using PlutoUI + + using HerbCore + using HerbGrammar + using HerbInterpret +end + +# ╔═╡ 65fbf850-74ae-4ea4-85f0-683095c73fba +md""" +# Herb tutorial: Abstract syntax trees""" + +# ╔═╡ 2493e9db-8b8e-4ef6-8379-f50ad26aab88 +md""" +In this tutorial, you will learn + +- How to represent a computer program as an abstract syntax tree in Herb. +- How to replace parts of the tree to modify the program.""" + +# ╔═╡ 8ff96964-e39e-4762-ae03-e9166e163fca +md""" +## Abstract syntax trees + +The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammar, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. + +In the context of program synthesis, ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found. + +Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. +An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. """ + +# ╔═╡ bff155ab-ff0e-452b-a8f5-fe744e41a30f +md""" +## A simple example program + +We first consider the simple program 5*(x+3). We will define a grammar that is sufficient to represent this program and use it to construct a AST for our program.""" + +# ╔═╡ caa3446e-c5df-4dac-905a-20515f681074 +md""" +### Define the grammar""" + +# ╔═╡ 9f54f013-e8b9-4e0d-8bac-9867f5d1a393 +grammar = @csgrammar begin + Number = |(0:9) + Number = x + Number = Number + Number + Number = Number * Number + end + +# ╔═╡ 46fbbe87-ee6e-4874-9708-20a42347ff18 +md""" +### Construct the syntax tree""" + +# ╔═╡ 5dc6be9c-e4d9-4fdb-90bf-f4c59bb66a70 +md""" +The AST of this program is shown in the diagram below. The number in each node refers to the index of the corresponding rule in our grammar. """ + +# ╔═╡ 64c2a6ce-5e3b-413b-bcb7-84936137439f +md""" +```mermaid + flowchart + id1((13)) --- + id2((6)) + id1 --- id3((12)) + id4((11)) + id5((4)) + id3 --- id4 + id3 --- id5 +``` +""" + +# ╔═╡ 64f6f1e3-5cbc-4e37-a806-d4fd45c20855 +md""" +```mermaid + flowchart + id1((13)) --- + id2((6)) + id1 --- id3((12)) + id4((11)) + id5((4)) + id3 --- id4 + id3 --- id5 +```""" + +# ╔═╡ 29b37a82-d022-453e-bf65-672aa94e4c87 +md""" +In `Herb.jl`, the `HerbCore.RuleNode` is used to represent both an individual node, but also entire ASTs or sub-trees. This is achieved by nesting instances of `RuleNode`. A `RuleNode` can be instantiated by providing the index of the grammar rule that the node represents and a vector of child nodes. """ + +# ╔═╡ 822d9601-284d-4d30-9551-605684f83d90 +syntaxtree = RuleNode(13, [RuleNode(6), RuleNode(12, [RuleNode(11), RuleNode(4)])]) + +# ╔═╡ 351210d1-20b6-4695-b9fe-f1136d4447d5 +md""" +We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.execute_on_input`.""" + +# ╔═╡ dc882fd5-d0fd-4a7d-8d5b-40516f3a3bcb +rulenode2expr(syntaxtree, grammar) + +# ╔═╡ cbfaa1b6-f3c5-490a-9e54-006262d0c727 +# test solution on inputs +execute_on_input(grammar, syntaxtree, Dict(:x => 10)) + +# ╔═╡ 6e018fd3-7626-48b2-b56a-240ae62a1ac4 +md""" +## Another example: FizzBuzz + +Let's look at a more interesting example. +The program `fizbuzz()` is based on the popular _FizzBuzz_ problem. Given an integer number, the program simply returns a `String` of that number, but replace numbers divisible by 3 with `\"Fizz\"`, numbers divisible by 5 with `\"Buzz\"`, and number divisible by both 3 and 5 with `\"FizzBuzz\"`.""" + +# ╔═╡ 3fd0895e-7f1f-4ecd-855f-95c69a466dde +function fizzbuzz(x) + if x % 5 == 0 && x % 3 == 0 + return "FizzBuzz" + else + if x % 3 == 0 + return "Fizz" + else + if x % 5 == 0 + return "Buzz" + else + return string(x) + end + end + end +end + +# ╔═╡ b302e44c-29a9-4851-bb11-e95c0dfbacdb +md""" +### Define the grammar + +Let's define a grammar with all the rules that we need.""" + +# ╔═╡ 59444d63-8b2a-4b76-9af3-b92b4abd4a98 +grammar_fizzbuzz = @csgrammar begin + Int = input1 + Int = 0 | 3 | 5 + String = "Fizz" | "Buzz" | "FizzBuzz" + String = string(Int) + Return = String + Int = Int % Int + Bool = Int == Int + Int = Bool ? Int : Int + Bool = Bool && Bool +end + +# ╔═╡ e389cf25-5f0b-4d94-bab0-7cf85ee0e6e0 +md""" +### Construct the syntax tree""" + +# ╔═╡ 8fa5cbbc-ad25-42ff-9ec8-284590fe1084 +md""" +Given the grammar, the AST of `fizzbuzz()` looks like this:""" + +# ╔═╡ 6a663bce-155b-4c0d-94ec-7dc5fbba348a +md""" +```mermaid +flowchart + id1((12)) --- id21((13)) + id1--- id22((9)) + id1--- id23((12)) + + id21 --- id31((11)) + id21 --- id32((11)) + + id31 --- id41((10)) + id31 --- id42((2)) + + id41 --- id51((1)) + id41 --- id52((4)) + + id32 --- id43((10)) + id32 --- id44((2)) + + id43 --- id53((1)) + id43 --- id54((3)) + + id22 --- id33((7)) + id23 --- id34((11)) + + id34 --- id45((10)) + id34 --- id46((2)) + + id45 --- id55((1)) + id45 --- id56((3)) + + id23 --- id35((9)) + id35 --- id47((5)) + + id23 --- id36((12)) + id36 --- id48((11)) + id48 --- id57((10)) + id57 --- id61((1)) + id57 --- id62((4)) + id48 --- id58((2)) + + id36 --- id49((9)) + id49 --- id59((6)) + + id36 --- id410((9)) + id410 --- id510((8)) + id510 --- id63((1)) + + + +```""" + +# ╔═╡ d9272c48-a7da-4ca0-af15-98c0fe4a3f24 +md""" +As before, we use nest instanced of `RuleNode` to implement the AST.""" + +# ╔═╡ 6a268dbb-e884-4b1f-b0c3-4ae1d36064a3 +fizzbuzz_syntaxtree = RuleNode(12, [ + RuleNode(13, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(4) + ]), + RuleNode(2) + ]), + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(3) + ]), + RuleNode(2) + ]) + ]), + RuleNode(9, [ + RuleNode(7) + + ]), + RuleNode(12, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(3), + ]), + RuleNode(2) + ]), + RuleNode(9, [ + RuleNode(5) + ]), + RuleNode(12, [ + RuleNode(11, [ + RuleNode(10, [ + RuleNode(1), + RuleNode(4) + ]), + RuleNode(2) + ]), + RuleNode(9, [ + RuleNode(6) + ]), + RuleNode(9, [ + RuleNode(8, [ + RuleNode(1) + ]) + ]) + ]) + ]) + ]) + +# ╔═╡ 61b27735-25ba-4995-b506-7982db8c50b5 +md""" +And we check our syntax tree is correct:""" + +# ╔═╡ 3692d164-4deb-4da2-834c-fc2eb8ac3fa0 +rulenode2expr(fizzbuzz_syntaxtree, grammar_fizzbuzz) + +# ╔═╡ 0d5aaa64-ae46-4b88-ac05-df8910da1648 +begin + # test solution on inputs + input = [Dict(:input1 => 3), Dict(:input1 => 5), Dict(:input1 =>15), Dict(:input1 => 22)] + output1 = execute_on_input(grammar_fizzbuzz, fizzbuzz_syntaxtree, input) + output1 +end + +# ╔═╡ c9a57153-96f8-4bcb-905b-dc46bc1f7765 +md""" +### Modify the AST/program + +There are several ways to modify an AST and hence, a program. You can + +- directly replace a node with `HerbCore.swap_node()` +- insert a rule node with `insert!` + +Let's modify our example such that if the input number is divisible by 3, the program returns \"Buzz\" instead of \"Fizz\". +We use `swap_node()` to replace the node of the AST that corresponds to rule 5 in the grammar (`String = Fizz`) with rule 6 (`String = Buzz`). To do so, `swap_node()` needs the tree that contains the node we want to modify, the new node we want to replace the node with, and the path to that node. + +Note that `swap_node()` modifies the tree, hence we make a deep copy of it first.""" + +# ╔═╡ 6e0bed41-6f06-47e2-a659-ec61fa9c0d40 +begin + modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) + newnode = RuleNode(6) + path = [3, 2, 1] + swap_node(modified_fizzbuzz_syntaxtree, newnode, path) + rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz) +end + +# ╔═╡ f9e4ec58-ded3-4bf8-9207-a05596d15586 +md""" +Let's confirm that we modified the AST, and hence the program, correctly:""" + +# ╔═╡ d9543762-e438-492c-a0b1-632c4c25c58b +# test solution on same inputs as before +execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input) + +# ╔═╡ 44cc6617-9262-44a5-8cec-90713819d03a +md""" +An alternative way to modify the AST is by using `insert!()`. This requires to provide the location of the node that we want to as `NodeLoc`. `NodeLoc` points to a node in the tree and consists of the parent and the child index of the node. +Again, we make a deep copy of the original AST first.""" + +# ╔═╡ 0b595e4a-c6a3-4986-b311-f09bb53ee189 +begin + anothermodified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) + # get the node we want to modify and instantiate a NodeLoc from it. + node = get_node_at_location(anothermodified_fizzbuzz_syntaxtree, [3, 2, 1]) + nodeloc = NodeLoc(node, 0) + # replace the node + insert!(node, nodeloc, newnode) + rulenode2expr(anothermodified_fizzbuzz_syntaxtree, grammar_fizzbuzz) +end + +# ╔═╡ e40403aa-cb27-4bfb-b581-1795fc1cce41 +md""" +Again, we check that we modified the program as intended:""" + +# ╔═╡ 8847f8b8-bb52-4a95-86f1-6483e5e0ab85 +# test on same inputs as before +execute_on_input(grammar_fizzbuzz, anothermodified_fizzbuzz_syntaxtree, input) + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" + +[compat] +HerbCore = "~0.2.0" +HerbGrammar = "~0.2.1" +HerbInterpret = "~0.1.2" +PlutoUI = "~0.7.59" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "0ce6f08e629fb0ab6945e23b691d12a7e72d4c7a" + +[[deps.AbstractPlutoDingetjes]] +deps = ["Pkg"] +git-tree-sha1 = "6e1d2a35f2f90a4bc7c2ed98079b2ba09c35b83a" +uuid = "6e696c72-6542-2067-7265-42206c756150" +version = "1.3.2" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.5" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.5" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "4f2b57488ac7ee16124396de4f2bbdd51b2602ad" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.0" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbCore]] +git-tree-sha1 = "f3312458fa882d4adaeecadf8f4b5721ec6e3322" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.2.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "aa70e06c5cec398294cc65c16e18f2ebdc3a7348" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.2.1" + +[[deps.HerbInterpret]] +deps = ["HerbCore", "HerbGrammar", "HerbSpecification"] +git-tree-sha1 = "3723e5ace26a2f2cd342b28689dea543491687c6" +uuid = "5bbddadd-02c5-4713-84b8-97364418cca7" +version = "0.1.2" + +[[deps.HerbSpecification]] +git-tree-sha1 = "5385b81e40c3cd62aeea591319896148036863c9" +uuid = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +version = "0.1.0" + +[[deps.Hyperscript]] +deps = ["Test"] +git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4" +uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91" +version = "0.0.5" + +[[deps.HypertextLiteral]] +deps = ["Tricks"] +git-tree-sha1 = "7134810b1afce04bbc1045ca1985fbe81ce17653" +uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +version = "0.9.5" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "8b72179abc660bfab5e28472e019392b97d0985c" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.4" + +[[deps.Inflate]] +git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.4" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MIMEs]] +git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb" +uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65" +version = "0.1.4" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.PlutoUI]] +deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"] +git-tree-sha1 = "ab55ee1510ad2af0ff674dbcced5e94921f867a9" +uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +version = "0.7.59" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.4" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.Tricks]] +git-tree-sha1 = "eae1bb484cd63b36999ee58be2de6c178105112f" +uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775" +version = "0.1.8" + +[[deps.URIs]] +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.12.7+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.15.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" + +# ╔═╡ Cell order: +# ╟─65fbf850-74ae-4ea4-85f0-683095c73fba +# ╟─2493e9db-8b8e-4ef6-8379-f50ad26aab88 +# ╟─8ff96964-e39e-4762-ae03-e9166e163fca +# ╟─bff155ab-ff0e-452b-a8f5-fe744e41a30f +# ╟─caa3446e-c5df-4dac-905a-20515f681074 +# ╠═c784cbc3-19fc-45c9-b344-db10cf7a81fa +# ╠═9f54f013-e8b9-4e0d-8bac-9867f5d1a393 +# ╟─46fbbe87-ee6e-4874-9708-20a42347ff18 +# ╟─5dc6be9c-e4d9-4fdb-90bf-f4c59bb66a70 +# ╠═64c2a6ce-5e3b-413b-bcb7-84936137439f +# ╟─64f6f1e3-5cbc-4e37-a806-d4fd45c20855 +# ╟─29b37a82-d022-453e-bf65-672aa94e4c87 +# ╠═822d9601-284d-4d30-9551-605684f83d90 +# ╟─351210d1-20b6-4695-b9fe-f1136d4447d5 +# ╠═dc882fd5-d0fd-4a7d-8d5b-40516f3a3bcb +# ╠═cbfaa1b6-f3c5-490a-9e54-006262d0c727 +# ╟─6e018fd3-7626-48b2-b56a-240ae62a1ac4 +# ╠═3fd0895e-7f1f-4ecd-855f-95c69a466dde +# ╟─b302e44c-29a9-4851-bb11-e95c0dfbacdb +# ╠═59444d63-8b2a-4b76-9af3-b92b4abd4a98 +# ╟─e389cf25-5f0b-4d94-bab0-7cf85ee0e6e0 +# ╟─8fa5cbbc-ad25-42ff-9ec8-284590fe1084 +# ╠═6a663bce-155b-4c0d-94ec-7dc5fbba348a +# ╟─d9272c48-a7da-4ca0-af15-98c0fe4a3f24 +# ╠═6a268dbb-e884-4b1f-b0c3-4ae1d36064a3 +# ╟─61b27735-25ba-4995-b506-7982db8c50b5 +# ╠═3692d164-4deb-4da2-834c-fc2eb8ac3fa0 +# ╠═0d5aaa64-ae46-4b88-ac05-df8910da1648 +# ╟─c9a57153-96f8-4bcb-905b-dc46bc1f7765 +# ╠═6e0bed41-6f06-47e2-a659-ec61fa9c0d40 +# ╟─f9e4ec58-ded3-4bf8-9207-a05596d15586 +# ╠═d9543762-e438-492c-a0b1-632c4c25c58b +# ╟─44cc6617-9262-44a5-8cec-90713819d03a +# ╠═0b595e4a-c6a3-4986-b311-f09bb53ee189 +# ╟─e40403aa-cb27-4bfb-b581-1795fc1cce41 +# ╠═8847f8b8-bb52-4a95-86f1-6483e5e0ab85 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From a067c521cad150fbf673282f1900bda5a173c054 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Tue, 27 Aug 2024 17:00:47 +0200 Subject: [PATCH 66/75] Replace broken constraint. Replace cfgrammar with csgrammar. --- docs/src/tutorials/defining_grammars.ipynb | 69 +++++++++++----------- 1 file changed, 33 insertions(+), 36 deletions(-) diff --git a/docs/src/tutorials/defining_grammars.ipynb b/docs/src/tutorials/defining_grammars.ipynb index 8bbfb79..dec24cb 100644 --- a/docs/src/tutorials/defining_grammars.ipynb +++ b/docs/src/tutorials/defining_grammars.ipynb @@ -36,10 +36,10 @@ "### Creating a simple grammar\n", "\n", "This cell contains a very simple arithmetic grammar. \n", - "The grammar is defined using the `@cfgrammar` macro. \n", + "The grammar is defined using the `@csgrammar` macro. \n", "This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. \n", "Macro's are executed during compilation.\n", - "If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2cfgrammar` function." + "If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2csgrammar` function." ] }, { @@ -48,7 +48,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₁ = HerbGrammar.@cfgrammar begin\n", + "g₁ = HerbGrammar.@csgrammar begin\n", " Int = 1\n", " Int = 2\n", " Int = 3\n", @@ -71,7 +71,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₂ = HerbGrammar.@cfgrammar begin\n", + "g₂ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -91,7 +91,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₃ = HerbGrammar.@cfgrammar begin\n", + "g₃ = HerbGrammar.@csgrammar begin\n", " Int = |([0, 2, 4, 6, 8])\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -111,7 +111,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₄ = HerbGrammar.@cfgrammar begin\n", + "g₄ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -136,7 +136,7 @@ "source": [ "f(a) = a + 1\n", "\n", - "g₅ = HerbGrammar.@cfgrammar begin\n", + "g₅ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = Int * Int\n", " Int = Int + Int\n", @@ -161,7 +161,7 @@ "source": [ "×(a, b) = a * b\n", "\n", - "g₆ = HerbGrammar.@cfgrammar begin\n", + "g₆ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = a\n", " Int = Int + Int\n", @@ -177,9 +177,9 @@ "### Working with grammars\n", "\n", "If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. \n", - "This section is not necessarily complete, but it aims to give an overview of the most important functions. \n", + "This section is not complete, but it aims to give an overview of the most important functions. \n", "\n", - "It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with that.\n", + "It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with the concept.\n", "\n", "One of the most important things about grammars is that each rule has an index associated with it:" ] @@ -190,7 +190,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₇ = HerbGrammar.@cfgrammar begin\n", + "g₇ = HerbGrammar.@csgrammar begin\n", " Int = |(0:9)\n", " Int = Int + Int\n", " Int = Int * Int\n", @@ -304,8 +304,7 @@ "source": [ "### Adding rules\n", "\n", - "It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function.\n", - "As with most functions in Julia that end with an exclamation mark, this function modifies its argument (the grammar).\n", + "It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function. The exclamatin mark is a Julia convention and is appended to name if a function modifies its arguments (in our example the grammar).\n", "\n", "A rule can be provided in the same syntax as is used in the grammar definition.\n", "The rule should be of the `Expr` type, which is a built-in type for representing expressions. \n", @@ -363,8 +362,8 @@ "source": [ "## Context-sensitive grammars\n", "\n", - "Context-sensitive grammars allow additional constraints to be added with respect to context-free grammars.\n", - "The syntax for defining a context-sensitive grammar is identical to defining a context-sensitive grammar:" + "Context-sensitive grammars introduce additional constraints compared to context-free grammars (like the simple grammar examples above).\n", + "As before, we use the `@csgrammar` macro:" ] }, { @@ -386,15 +385,8 @@ "metadata": {}, "source": [ "Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar.\n", - "Currently, Herb.jl only has propagators constraints. \n", - "These constraints each have a corresponding `propagate` function that removes all options that violate that constraint from the domain. \n", - "At the moment, there are three propagator constraints:\n", "\n", - "- `ComesAfter(rule, predecessors)`: It is only possible to use rule `rule` when `predecessors` are in its path to the root.\n", - "- `Forbidden(sequence)`: Forbids the derivation specified as a path in an expression tree.\n", - "- `Ordered(order)`: Rules have to be used in the specified order. That is, rule at index K can only be used if rules at indices `[1...K-1]` are used in the left subtree of the current expression.\n", - "\n", - "Below, an example is given of a context-sensitive grammar with a `ComesAfter` constraint:" + "For example, we can add a `` constraint to enforce that the input symbol `x` (rule 13) appears at least once in the program, to avoid programs that are just a constant. " ] }, { @@ -403,7 +395,14 @@ "metadata": {}, "outputs": [], "source": [ - "HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9]))" + "HerbGrammar.addconstraint!(g₈, Contains(13))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is a dedicated tutorial for constraints in Herb.jl and how to work with them." ] }, { @@ -423,7 +422,7 @@ "metadata": {}, "outputs": [], "source": [ - "g₉ = HerbGrammar.@pcfgrammar begin\n", + "g₉ = HerbGrammar.@pcsgrammar begin\n", " 0.4 : Int = |(0:9)\n", " 0.2 : Int = Int + Int\n", " 0.1 : Int = Int * Int\n", @@ -458,8 +457,8 @@ "\n", "### Saving & loading context-free grammars\n", "\n", - "If you want to store a grammar on the disk, you can use the `store_cfg`, `read_cfg` and functions to store and read grammars respectively. \n", - "The `store_cfg` grammar can also be used to store probabilistic grammars. Reading probabilistic grammars can be done using `read_pcfg`.\n", + "If you want to store a grammar on the disk, you can use the `store_csg`, `read_csg` and functions to store and read grammars respectively. \n", + "The `store_csg` grammar can also be used to store probabilistic grammars. To read probabilistic grammars, use `read_pcsg`.\n", "The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars." ] }, @@ -469,18 +468,16 @@ "metadata": {}, "outputs": [], "source": [ - "HerbGrammar.store_cfg(\"demo.txt\", g₇)" + "HerbGrammar.store_csg(g₇, \"demo.txt\")" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": false - }, + "metadata": {}, "outputs": [], "source": [ - "HerbGrammar.read_cfg(\"demo.txt\")" + "HerbGrammar.read_csg(\"demo.txt\")" ] }, { @@ -501,7 +498,7 @@ "metadata": {}, "outputs": [], "source": [ - "HerbGrammar.store_csg(\"demo.grammar\", \"demo.constraints\", g₈)\n", + "HerbGrammar.store_csg( g₈, \"demo.grammar\", \"demo.constraints\")\n", "g₈, g₈.constraints" ] }, @@ -518,15 +515,15 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.9.4", + "display_name": "Julia 1.10.3", "language": "julia", - "name": "julia-1.9" + "name": "julia-1.10" }, "language_info": { "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.9.4" + "version": "1.10.3" } }, "nbformat": 4, From 9c054afd3b92de50f6d1cec313531ff71d5fa6f1 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Wed, 28 Aug 2024 07:20:40 +0200 Subject: [PATCH 67/75] Pluto notebook for tutorial Defining Grammars. --- docs/src/tutorials/defining_grammars.jl | 981 ++++++++++++++++++++++++ 1 file changed, 981 insertions(+) create mode 100644 docs/src/tutorials/defining_grammars.jl diff --git a/docs/src/tutorials/defining_grammars.jl b/docs/src/tutorials/defining_grammars.jl new file mode 100644 index 0000000..08bb920 --- /dev/null +++ b/docs/src/tutorials/defining_grammars.jl @@ -0,0 +1,981 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ 84549502-876a-4ffc-8069-1ffc17622f9a +using HerbGrammar, HerbConstraints + +# ╔═╡ 93b96839-9676-477b-aebb-e50d733a6719 +md""" +# Defining Grammars in Herb.jl using HerbGrammar + +The program space in Herb.jl is defined using a grammar. +This notebook demonstrates how such a grammar can be created. +There are multiple kinds of grammars, but they can all be defined in a very similar way. +""" + +# ╔═╡ d863795b-5fea-4b69-8f63-e04352bb970a +md""" +### Setup +First, we import the necessary Herb packages. +""" + +# ╔═╡ 751e1119-fe59-41dc-8785-a3ed0a9cacb9 +md""" +### Creating a simple grammar + +This cell contains a very simple arithmetic grammar. +The grammar is defined using the `@csgrammar` macro. +This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. +Macro's are executed during compilation. +If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2csgrammar` function. +""" + +# ╔═╡ 1030a96d-b739-4232-820b-f21826512251 +g₁ = HerbGrammar.@csgrammar begin + Int = 1 + Int = 2 + Int = 3 + Int = Int * Int + Int = Int + Int +end + +# ╔═╡ 0224c914-3763-4c55-844c-e9aca430b377 +md""" +Defining every integer one-by-one can be quite tedious. Therefore, it is also possible to use the following syntax that makes use of a Julia iterator: +""" + +# ╔═╡ 4619b92e-2f9e-429f-9e9a-77b97be7edd1 +g₂ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int +end + +# ╔═╡ f8e82610-b0d0-4763-939d-aadc92336c52 +md""" +You can do the same with lists: +""" + +# ╔═╡ f4d34f0a-6c88-48db-84a0-ed3fb01fb7aa +g₃ = HerbGrammar.@csgrammar begin + Int = |([0, 2, 4, 6, 8]) + Int = Int * Int + Int = Int + Int +end + +# ╔═╡ 8732f232-3069-4fb1-9b6a-0e24b8724451 +md""" +Variables can also be added to the grammar by just using the variable name: +""" + +# ╔═╡ b68090b2-0eed-4e4a-acdc-6df71747a46d +g₄ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int + Int = x +end + +# ╔═╡ 89e8ab14-7097-4088-937f-6453722da9d0 +md""" +Grammars can also work with functions. +After all, `+` and `*` are just infix operators for Julia's identically-named functions. +You can use functions that are provided by Julia, or functions that you wrote yourself: +""" + +# ╔═╡ 26b9a050-c4b4-42a8-b1d7-e50fb98a7e8a +begin + f(a) = a + 1 + + g₅ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int + Int = f(Int) + Int = x + end +end + +# ╔═╡ 7ebb2202-8aa1-4404-b1dd-ac1669f9a120 +md""" +Similarly, we can also define the operator times (x) manually. +""" + +# ╔═╡ 9351c7ab-a39f-4f47-a3ee-e1684e2b3c07 +begin + ×(a, b) = a * b + + g₆ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = a + Int = Int + Int + Int = Int × Int + end +end + +# ╔═╡ 3f44d67b-bd18-481e-bca8-bb4904f6b418 +md""" +### Working with grammars + +If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. +This section is not complete, but it aims to give an overview of the most important functions. + +It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with the concept. + +One of the most important things about grammars is that each rule has an index associated with it: +""" + +# ╔═╡ 58af16a2-7f23-4d06-8836-fb737788ada0 +begin + g₇ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int + Int + Int = Int * Int + Int = x + end + + collect(enumerate(g₇.rules)) +end + +# ╔═╡ 303894cd-a0c2-461c-a671-7330f75b338e +md""" +We can use this index to extract information from the grammar. +""" + +# ╔═╡ 5c103ce3-0cb2-4004-ac44-50613a461153 +md""" +### isterminal + +`isterminal` returns `true` if a rule is terminal, i.e. it cannot be expanded. For example, rule 1 is terminal, but rule 11 is not, since it contains the non-terminal symbol `:Int`. +""" + +# ╔═╡ 38422e77-cfd8-4a2c-8cf2-f1b430f64ad1 +HerbGrammar.isterminal(g₇, 1) + +# ╔═╡ 112d4837-e646-4e24-84c8-771b9855aebf +HerbGrammar.isterminal(g₇, 11) + +# ╔═╡ e8d4890b-db1c-4b74-8a6d-4388597cead8 +md""" +### return_type + +This function is rather obvious; it returns the non-terminal symbol that corresponds to a certain rule. The return type for all rules in our grammar is `:Int`. +""" + +# ╔═╡ 842f6873-1f7f-4785-9be5-336dee828ad8 +HerbGrammar.return_type(g₇, 11) + +# ╔═╡ c894f9c1-d608-4a11-9146-ea4b83b40325 +md""" +### child_types + +`child_types` returns the types of the nonterminal children of a rule in a vector. +If you just want to know how many children a rule has, and not necessarily which types they have, you can use `nchildren` +""" + +# ╔═╡ c4c058fb-28e6-4aa4-8609-5d0e9d29f42e +HerbGrammar.child_types(g₇, 11) + +# ╔═╡ d17660c0-fe52-4b71-9f7e-c77533801e5b +HerbGrammar.nchildren(g₇, 11) + +# ╔═╡ 681ac0f0-b207-48d1-9524-e2bb93f79717 +md""" +### nonterminals + +The `nonterminals` function can be used to obtain a list of all nonterminals in the grammar. +""" + +# ╔═╡ df689c72-deeb-4d94-8c10-702c3621bcba +HerbGrammar.nonterminals(g₇) + +# ╔═╡ 5374925a-46ef-49bc-b3b1-14a5afc5f523 +md""" +### Adding rules + +It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function. The exclamatin mark is a Julia convention and is appended to name if a function modifies its arguments (in our example the grammar). + +A rule can be provided in the same syntax as is used in the grammar definition. +The rule should be of the `Expr` type, which is a built-in type for representing expressions. +An easy way of creating `Expr` values in Julia is to encapsulate it in brackets and use a colon as prefix: +""" + +# ╔═╡ d7f632bb-c3a5-4196-b4ce-9fc80ed24034 +HerbGrammar.add_rule!(g₇, :(Int = Int - Int)) + +# ╔═╡ 44aa6111-2374-4261-8616-73925c320b34 +md""" +### Removing rules + +It is also possible to remove rules in Herb.jl, however, this is a bit more involved. +As said before, rules have an index associated with them. +The internal representation of programs that are defined by the grammar makes use of those indices for efficiency. +Blindly removing a rule would shift the indices of other rules, and this could mean that existing programs get a different meaning or become invalid. + +Therefore, there are two functions for removing rules: + +- `remove_rule!` removes a rule from the grammar, but fills its place with a placeholder. Therefore, the indices stay the same, and only programs that use the removed rule become invalid. +- `cleanup_removed_rules!` removes all placeholders and shifts the indices of the other rules. + +""" + +# ╔═╡ f381e96a-a8c6-4ebd-adf2-e6da20ca63de +HerbGrammar.remove_rule!(g₇, 11) + +# ╔═╡ 5e0cb560-2a27-4372-945b-4be44fca1fbc +HerbGrammar.cleanup_removed_rules!(g₇) + +# ╔═╡ eb3bfde8-62c0-4497-b9cd-faba6ec9cf8d +md""" +## Context-sensitive grammars + +Context-sensitive grammars introduce additional constraints compared to context-free grammars (like the simple grammar examples above). +As before, we use the `@csgrammar` macro: +""" + +# ╔═╡ 112ce5fc-560d-438d-b58f-d52991a1651f +g₈ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int + Int + Int = Int * Int + Int = x +end + +# ╔═╡ 327fcddb-9bdc-4ae8-8908-86a0b0619639 +md""" +Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar. + +For example, we can add a `` constraint to enforce that the input symbol `x` (rule 13) appears at least once in the program, to avoid programs that are just a constant. +""" + +# ╔═╡ 2b23504e-8e2c-47c5-b9b8-07b7b0063d2e +HerbGrammar.addconstraint!(g₈, Contains(13)) + +# ╔═╡ 4fb800af-7ac7-45b0-a3a0-ce153c7eb464 +md""" +There is a dedicated tutorial for constraints in Herb.jl and how to work with them. +""" + +# ╔═╡ 85724067-ff56-40e2-99e5-e26698a4a444 +md""" +### Probabilistic grammars + +Herb.jl also supports probabilistic grammars. +These grammars allow the user to assign a probability to each rule in the grammar. +A probabilistic grammar can be defined in a very similar way to a standard grammar, but has some slightly different syntax: +""" + +# ╔═╡ 19ef2475-1ebd-4b73-af6b-d7079182f27a +begin + g₉ = HerbGrammar.@pcsgrammar begin + 0.4 : Int = |(0:9) + 0.2 : Int = Int + Int + 0.1 : Int = Int * Int + 0.3 : Int = x + end + + for r ∈ 1:length(g₃.rules) + p = HerbGrammar.probability(g₈, r) + + println("$p : $r") + end +end + +# ╔═╡ 7f78d745-8b16-4308-9191-4dbe8bd11da9 +md""" +The numbers before each rule represent the probability assigned to that rule. +The total probability for each return type should add up to 1.0. +If this isn't the case, Herb.jl will normalize the probabilities. + +If a single line in the grammar definition represents multiple rules, such as `0.4 : Int = |(0:9)`, the probability will be evenly divided over all these rules. +""" + +# ╔═╡ 1f0ef848-d9b0-414d-9e1a-82beb6633c02 +md""" +## File writing + +### Saving & loading context-free grammars + +If you want to store a grammar on the disk, you can use the `store_csg`, `read_csg` and functions to store and read grammars respectively. +The `store_csg` grammar can also be used to store probabilistic grammars. To read probabilistic grammars, use `read_pcsg`. +The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars. +""" + +# ╔═╡ 9426a96a-43e3-458d-933e-44b36966db57 +HerbGrammar.store_csg(g₇, "demo.txt") + +# ╔═╡ 5ac3dfc3-54c0-438a-a3b9-d2d24309f532 +HerbGrammar.read_csg("demo.txt") + +# ╔═╡ fc5bd55a-3936-4b93-8f62-b2f708b83a4e +md""" +### Saving & loading context-sensitive grammars + +Saving and loading context-sensitive grammars is very similar to how it is done with context-free grammars. +The only difference is that an additional file is created for the constraints. +The file that contains the grammars can be edited and can also be read using the reader for context-free grammars. +The file that contains the constraints cannot be edited. +""" + +# ╔═╡ 32f1afed-5ccf-4378-8f1d-14e293ff2c1a +HerbGrammar.store_csg( g₈, "demo.grammar", "demo.constraints") + +# ╔═╡ c21e2aa6-55ba-4068-b127-d7b53c17eb45 +g₈, g₈.constraints + +# ╔═╡ 407f8c03-a0ec-4b23-ba2c-22de526becc8 +g₁₀ = HerbGrammar.read_csg("demo.grammar", "demo.constraints") + +# ╔═╡ ad351cf6-8f2b-4cd0-b207-1ecb53eb2e38 +g₁₀, g₁₀.constraints + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" + +[compat] +HerbConstraints = "~0.2.2" +HerbGrammar = "~0.3.0" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "7e68dadf6c36ec756dee195b1ce6f2abc419c446" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "4f2b57488ac7ee16124396de4f2bbdd51b2602ad" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.0" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbConstraints]] +deps = ["DataStructures", "HerbCore", "HerbGrammar", "MLStyle"] +git-tree-sha1 = "2e54da1d19119847b242d1ceda212b180cca36a9" +uuid = "1fa96474-3206-4513-b4fa-23913f296dfc" +version = "0.2.2" + +[[deps.HerbCore]] +git-tree-sha1 = "923877c2715b8166d7ba9f9be2136d70eed87725" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.3.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "b4cbf9712dbb3ab281ff4ed517d3cd6bc12f3078" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.3.0" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.Inflate]] +git-tree-sha1 = "ea8031dea4aff6bd41f1df8f2fdfb25b33626381" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.4" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MLStyle]] +git-tree-sha1 = "bc38dff0548128765760c79eb7388a4b37fae2c8" +uuid = "d8e11817-5142-5d16-987a-aa16d5891078" +version = "0.4.17" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "9ae599cd7529cfce7fea36cf00a62cfc56f0f37c" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.4" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "36b3d696ce6366023a0ea192b4cd442268995a0d" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.2" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "52ff2af32e591541550bd753c0da8b9bc92bb9d9" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.12.7+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "Pkg", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "91844873c4085240b95e795f692c4cec4d805f8a" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.34+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "b4bfde5d5b652e22b9c790ad00af08b6d042b97d" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.15.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" + +# ╔═╡ Cell order: +# ╟─93b96839-9676-477b-aebb-e50d733a6719 +# ╟─d863795b-5fea-4b69-8f63-e04352bb970a +# ╠═84549502-876a-4ffc-8069-1ffc17622f9a +# ╟─751e1119-fe59-41dc-8785-a3ed0a9cacb9 +# ╠═1030a96d-b739-4232-820b-f21826512251 +# ╟─0224c914-3763-4c55-844c-e9aca430b377 +# ╠═4619b92e-2f9e-429f-9e9a-77b97be7edd1 +# ╟─f8e82610-b0d0-4763-939d-aadc92336c52 +# ╠═f4d34f0a-6c88-48db-84a0-ed3fb01fb7aa +# ╟─8732f232-3069-4fb1-9b6a-0e24b8724451 +# ╠═b68090b2-0eed-4e4a-acdc-6df71747a46d +# ╟─89e8ab14-7097-4088-937f-6453722da9d0 +# ╠═26b9a050-c4b4-42a8-b1d7-e50fb98a7e8a +# ╟─7ebb2202-8aa1-4404-b1dd-ac1669f9a120 +# ╠═9351c7ab-a39f-4f47-a3ee-e1684e2b3c07 +# ╟─3f44d67b-bd18-481e-bca8-bb4904f6b418 +# ╠═58af16a2-7f23-4d06-8836-fb737788ada0 +# ╟─303894cd-a0c2-461c-a671-7330f75b338e +# ╟─5c103ce3-0cb2-4004-ac44-50613a461153 +# ╠═38422e77-cfd8-4a2c-8cf2-f1b430f64ad1 +# ╠═112d4837-e646-4e24-84c8-771b9855aebf +# ╟─e8d4890b-db1c-4b74-8a6d-4388597cead8 +# ╠═842f6873-1f7f-4785-9be5-336dee828ad8 +# ╟─c894f9c1-d608-4a11-9146-ea4b83b40325 +# ╠═c4c058fb-28e6-4aa4-8609-5d0e9d29f42e +# ╠═d17660c0-fe52-4b71-9f7e-c77533801e5b +# ╟─681ac0f0-b207-48d1-9524-e2bb93f79717 +# ╠═df689c72-deeb-4d94-8c10-702c3621bcba +# ╟─5374925a-46ef-49bc-b3b1-14a5afc5f523 +# ╠═d7f632bb-c3a5-4196-b4ce-9fc80ed24034 +# ╟─44aa6111-2374-4261-8616-73925c320b34 +# ╠═f381e96a-a8c6-4ebd-adf2-e6da20ca63de +# ╠═5e0cb560-2a27-4372-945b-4be44fca1fbc +# ╟─eb3bfde8-62c0-4497-b9cd-faba6ec9cf8d +# ╠═112ce5fc-560d-438d-b58f-d52991a1651f +# ╟─327fcddb-9bdc-4ae8-8908-86a0b0619639 +# ╠═2b23504e-8e2c-47c5-b9b8-07b7b0063d2e +# ╟─4fb800af-7ac7-45b0-a3a0-ce153c7eb464 +# ╟─85724067-ff56-40e2-99e5-e26698a4a444 +# ╠═19ef2475-1ebd-4b73-af6b-d7079182f27a +# ╟─7f78d745-8b16-4308-9191-4dbe8bd11da9 +# ╟─1f0ef848-d9b0-414d-9e1a-82beb6633c02 +# ╠═9426a96a-43e3-458d-933e-44b36966db57 +# ╠═5ac3dfc3-54c0-438a-a3b9-d2d24309f532 +# ╟─fc5bd55a-3936-4b93-8f62-b2f708b83a4e +# ╠═32f1afed-5ccf-4378-8f1d-14e293ff2c1a +# ╠═c21e2aa6-55ba-4068-b127-d7b53c17eb45 +# ╠═407f8c03-a0ec-4b23-ba2c-22de526becc8 +# ╠═ad351cf6-8f2b-4cd0-b207-1ecb53eb2e38 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 From d463fc626ed2b85ef8c1334ea23666c79233e7aa Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Wed, 28 Aug 2024 07:31:45 +0200 Subject: [PATCH 68/75] Replace deprecated cfgrammar. --- docs/src/tutorials/getting_started_with_constraints.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/getting_started_with_constraints.jl b/docs/src/tutorials/getting_started_with_constraints.jl index b359985..5791d13 100644 --- a/docs/src/tutorials/getting_started_with_constraints.jl +++ b/docs/src/tutorials/getting_started_with_constraints.jl @@ -29,7 +29,7 @@ We will also redefine the simple arithmetic grammar from the previous tutorial. """ # ╔═╡ aacedef3-08d6-4bc0-a2c3-dd9a9aac340d -grammar = @cfgrammar begin +grammar = @csgrammar begin Int = 1 Int = x Int = - Int From 11faf5a086eecd7da03d7e369ba9f702f1004791 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 19 Sep 2024 09:12:00 +0200 Subject: [PATCH 69/75] Fix broken code for parameters section of tutorial. --- docs/src/get_started.md | 6 +- docs/src/tutorials/advanced_search.jl | 1307 +++++++++++++++++ .../tutorials/getting_started_with_herb.jl | 6 +- .../tutorials/working_with_interpreters.jl | 64 +- 4 files changed, 1348 insertions(+), 35 deletions(-) create mode 100644 docs/src/tutorials/advanced_search.jl diff --git a/docs/src/get_started.md b/docs/src/get_started.md index cdbbaa8..3dabafd 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -10,13 +10,13 @@ using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret To define a program synthesis problem, we need a grammar and specification. -First, a grammar can be constructed using the `@cfgrammar` macro included in `HerbGrammar`. Alternatively, we can use the `@csgrammar` macro, which will be shown later on in the tutorial when we use constraints. +First, a grammar can be constructed using the `@csgrammar` macro included in `HerbGrammar`. Here, we describe a simple integer arithmetic example, that can add and multiply an input variable `x` or the integers `1,2`, using ```julia -g = @cfgrammar begin +g = @csgrammar begin Number = |(1:2) Number = x Number = Number + Number @@ -62,7 +62,7 @@ using HerbSearch, HerbSpecification, HerbInterpret, HerbGrammar # define our very simple context-free grammar # Can add and multiply an input variable x or the integers 1,2. -g = @cfgrammar begin +g = @csgrammar begin Number = |(1:2) Number = x Number = Number + Number diff --git a/docs/src/tutorials/advanced_search.jl b/docs/src/tutorials/advanced_search.jl new file mode 100644 index 0000000..ae9ad66 --- /dev/null +++ b/docs/src/tutorials/advanced_search.jl @@ -0,0 +1,1307 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ a93d954d-0f09-4b6d-a3a5-62bfe39681e2 +using PlutoUI + +# ╔═╡ c4441fa4-09ec-4b81-9681-b13b93a9c9c0 +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints + + +# ╔═╡ dddca175-3d88-45ce-90da-575c0ba38175 +md""" +# Advanced Search Procedures in Herb.jl + +[A more verbose getting started with Herb.jl]() described the concept of a program space and showed how to search it with Herb.jl, using a simple search with a BFS iterator. +This tutorial takes a closer look at advanced search procedures. + +More specifically, you will learn about + +- **Parameters** that can be specified and their effect on the search procedure. +- **Search methods** that can be employed to find a solution program to a program synthesis problem, including basic search (BFS and DFS), stochastic search and genetic search methods. +- **Other functionality** of the module `HerbSearch.jl` + (TODO: why are they in this tutorial?) +""" + +# ╔═╡ 6ab37bbc-73e2-4d9a-a8b2-e715a0b61c8f +TableOfContents() + +# ╔═╡ 67931820-0f43-41e1-898e-5b5bd55e30d1 +md""" +We start with a simple grammar:. +""" + +# ╔═╡ e41c61a4-0b2c-46da-8f7b-fe6dc529c544 +g_1 = @csgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number +end + +# ╔═╡ 9ad0a92a-10d5-458a-8f05-9011c8553609 +md""" +Let's use the simple program `2x+1` as our problem and generate some input-output examples for the problem specification. +""" + +# ╔═╡ 65317911-bc92-4b84-9744-ed784adcab4a +problem_1 = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) + +# ╔═╡ e9c6bc00-21f5-4a99-8bec-63cf2156c233 +md""" +## Parameters + +Search procedures typically have some hyperparameters that can be cofigured by the user. + +### `max_depth` + +`max_depth` controls the maximum depth of the program trees we want to explore. + +In the following example, we can see the effect of `max_depth` on the number of allocations considered. +""" + +# ╔═╡ 338f19f1-3a62-4462-b8dc-24424d7644f2 +iterator_1 = BFSIterator(g_1, :Number, max_depth=3) + +# ╔═╡ 542cd47e-74cd-4b6f-acc9-bf524222e583 +iterator_2 = BFSIterator(g_1, :Number, max_depth=6) + +# ╔═╡ a6fb2e91-b73a-4032-930f-d884abd539e2 +begin + println("Solution for max_depth = 3:") + solution_1 = @time synth(problem_1, iterator_1) + println(solution_1) + println("----------------------") + println("Solution for max_depth = 6:") + solution_2 = @time synth(problem_1, iterator_2) + println(solution_2) +end + +# ╔═╡ 4b49f206-970e-47d2-af65-336ba65b1019 +md""" +TODO: Explain @time +""" + +# ╔═╡ 35405357-179b-4e77-9bdc-edf5a550b36d +md""" +### `max_enumerations` +Another parameter to use is `max_enumerations`, which describes the maximum number of programs that can be tested at evaluation. +Let's see how many enumerations are necessary to solve our simple problem. +""" + +# ╔═╡ 3954dd49-07a2-4ec2-91b4-9c9596d5c264 +begin + for i in range(1, 50) + println(i, " enumerations") + iterator = BFSIterator(g_1, :Number, max_depth=i) + solution = @time synth(problem_1, iterator) + println(solution) + end +end + +# ╔═╡ 9892e91b-9115-4520-9637-f8d7c8905825 +md""" +TODO: numbers seem to have changed. Not 24 anymore. How reproducible is this anyway? +What does allocations mean? + +We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. + +A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method. +""" + +# ╔═╡ a4e58bbc-7c14-4fce-b35d-688b56e0eb61 +md""" +### `allow_evaluation_errors` + +TODO: What do we mean with 'program will still run'? + +A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When set to `true`, the program will still run even when an exception is thrown during evaluation. + +We will use a new example to see the effect of `allow_evaluation_errors`. After defining a new grammar and problem, ... + + +We can also retrieve the error together with the solution from the search method. +""" + +# ╔═╡ 9fb40ceb-8d41-491b-8941-20a8b240eb82 +begin + g_2 = @cfgrammar begin + Number = 1 + List = [] + Index = List[Number] + end +end + +# ╔═╡ 94e0d676-a9c7-4291-8696-15301e541c30 +problem_2 = Problem([IOExample(Dict(), x) for x ∈ 1:5]) + +# ╔═╡ a4a7daed-f89b-44ad-8787-9199c05bf046 +iterator_3 = BFSIterator(g_2, :Number, max_depth=2) + +# ╔═╡ 4821fd3a-ff2d-4991-99ad-76608d11b1da +solution_3 = @time synth(problem_2, iterator_3, allow_evaluation_errors=true) + +# ╔═╡ 8d91b2e3-30b5-4ea2-bd3f-3055bb6d1d5a +# solution = search(g_2, problem_2, :Index, max_depth=2, allow_evaluation_errors=true) + +# ╔═╡ 52332fa2-7ea7-4226-9460-e0bbc905c619 +println("solution: ", solution_3) + +# ╔═╡ c26fea48-f812-4f24-bb8c-680e14a55df7 +md""" +There is another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`): +""" + +# ╔═╡ 57f5face-d9d5-441a-8e0e-6ef6319fc178 +solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) +println("solution: ", solution) +println("error: ", error) + +# ╔═╡ 9b4b21e0-dc6a-43ae-a511-79988ee99001 +md""" +## Search methods + +We now show examples of using different search procedures, which are initialized by passing different enumerators to the search function. + +### Breadth-First Search + +The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. +""" + +# ╔═╡ 3af650d9-19c6-4351-920d-d2361091f628 +g1 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real +end +programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real)) + +# ╔═╡ d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 +md""" +We can test that this function returns all and only the correct functions. +""" + +# ╔═╡ da7f326c-f0d5-4837-ac9a-5bcad604566e +answer_programs = [ + RuleNode(1), + RuleNode(2), + RuleNode(3, [RuleNode(1), RuleNode(1)]), + RuleNode(3, [RuleNode(1), RuleNode(2)]), + RuleNode(3, [RuleNode(2), RuleNode(1)]), + RuleNode(3, [RuleNode(2), RuleNode(2)]) +] + +println(all(p ∈ programs for p ∈ answer_programs)) + +# ╔═╡ 0020b79a-6352-4e2d-93f6-2a1d7b03ae2c +md""" +### Depth-First Search + +In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. +""" + +# ╔═╡ 789150a8-862c-48c3-88b8-710b81ab34cf +g1 = @cfgrammar begin +Real = 1 | 2 +Real = Real * Real +end +programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real)) +println(programs) + +# ╔═╡ 243165be-a9d2-484d-8046-811a2b0ba139 +md""" +`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. +""" + +# ╔═╡ 3d01c6f1-80a6-4904-97e2-775170e97bbf +g1 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real +end +programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost)) +println(programs) + +# ╔═╡ 168c71bf-ce5b-4ab3-b29a-5996981c42a5 +md""" +## Stochastic search +We now introduce a few stochastic search algorithms, for which we first create a simple grammar and a helper function for problems. +""" + +# ╔═╡ a4b522cf-78f0-4d44-88c8-82dd0cdbf952 +grammar = @csgrammar begin + X = |(1:5) + X = X * X + X = X + X + X = X - X + X = x +end + +# ╔═╡ f313edb9-8fd9-4d78-88cd-89226f5c769d +function create_problem(f, range=20) + examples = [IOExample(Dict(:x => x), f(x)) for x ∈ 1:range] + return Problem(examples), examples +end + +# ╔═╡ 0da9053a-959b-471e-8918-662ec63da71c +md""" +### Metropolis-Hastings + +One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). + +The example below uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. +""" + +# ╔═╡ 0a30fb40-cd45-4661-a501-ae8e45f1e07e +e = x -> x * x + 4 +problem, examples = create_problem(e) +enumerator = get_mh_enumerator(examples, mean_squared_error) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) + +# ╔═╡ 700270ea-90bd-474b-91d9-0e5ed329776a +md""" +### Very Large Scale Neighbourhood Search + +The second implemented stochastic search method is VLSN, which searches for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). + +Given the same grammar as before, we can try it with some simple examples. +""" + +# ╔═╡ 8731f312-bfcf-4f6c-86fa-60014dc146d6 +e = x -> 10 +max_depth = 2 +problem, examples = create_problem(e) +enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) + + +# ╔═╡ 46ca65d1-d876-4abc-a562-8d266bad195f +e = x -> x +max_depth = 1 +problem, examples = create_problem(e) +enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) + +# ╔═╡ 599194a8-3f47-4917-9143-a5fe0d43029f +md""" +### Simulated Annealing + +The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). + +We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Change the value below to see the effect. +""" + +# ╔═╡ cb5935ed-d89b-4e25-9243-d201daf18e78 +e = x -> x * x + 4 +initial_temperature = 1 +problem, examples = create_problem(e) +enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) + +# ╔═╡ d0c3742e-23e5-4ca1-9e83-b6d1e8a7cded +e = x -> x * x + 4 +initial_temperature = 2 +problem, examples = create_problem(e) +enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) +program, cost = @time search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) + +# ╔═╡ 5df0ba53-b528-4baf-9980-cafe5d73f9dd +md""" +### Genetic Search + +Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). + +We show the example of finding a lambda function. Try varying the parameters of the genetic search to see what happens. +""" + +# ╔═╡ a434645b-d592-4162-a8b4-b4b04cea30a9 +e = x -> 3 * x * x + (x + 2) +problem, examples = create_problem(e) +enumerator = get_genetic_enumerator(examples, + initial_population_size = 10, + mutation_probability = 0.8, + maximum_initial_population_depth = 3, +) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) + +# ╔═╡ 38cd9032-27c0-4179-a536-ce59a42ff16a +md""" +## Other functionality + +Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics. + +### Sampling +Sampling is implemented for the different stochastic search methods. + +We consider here a simple grammar, which gives different programs for different search depths. +""" + +# ╔═╡ f8415d48-a51d-4845-8425-fd61ed79c06e +grammar = @cfgrammar begin + A = B | C | F + F = G + C = D + D = E +end + +# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3) + +# For depth ≤ 1 the only option is A->B +expression = rand(RuleNode, grammar, :A, 1) +@assert rulenode2expr(expression, grammar) in [:B,:C,:F] + +# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2) +expression = rand(RuleNode, grammar, :A, 2) +@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G] + +# ╔═╡ 7f88bf4f-d82c-4e5a-a9eb-93870954c79e +md""" +### Heuristics +""" + +# ╔═╡ 53598f8f-9973-4cad-af0c-280f5531bb21 +md""" +# More interesting domains & Use of constraints +In the following examples, we introduce some larger grammars and show that Herb can still efficiently find the correct program. +""" + +# ╔═╡ bc971069-08c4-493c-a917-8092493d3233 +#Expects to return a program equivalent to 1 + (1 - x) = 2 - x + +g₁ = @csgrammar begin + Element = |(1 : 3) # 1 - 3 + Element = Element + Element # 4 + Element = 1 - Element # 5 + Element = x # 6 +end + +addconstraint!(g₁, ComesAfter(6, [5])) + +examples = [ + IOExample(Dict(:x => 0), 2), + IOExample(Dict(:x => 1), 1), + IOExample(Dict(:x => 2), 0) +] +problem = Problem(examples) +solution = search(g₁, problem, :Element, max_depth=3) + +@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4 + +# ╔═╡ 61a60c9c-36cf-4e86-b697-748b3524d3b4 +# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4 + +g₂ = @csgrammar begin + Element = Element + Element + Element # 1 + Element = Element + Element * Element # 2 + Element = x # 3 + Element = |(3 : 5) # 4 +end + +# Restrict .. + x * x +addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)]))) +# Restrict 4 and 5 in lower level +addconstraint!(g₂, ForbiddenPath([2, 1, 5])) +addconstraint!(g₂, ForbiddenPath([2, 1, 6])) + +examples = [ + IOExample(Dict(:x => 1), 11) + IOExample(Dict(:x => 2), 20) + IOExample(Dict(:x => -1), -1) +] +problem = Problem(examples) +solution = search(g₂, problem, :Element) + +@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4 + +# ╔═╡ a5b17c81-b667-4b1c-ab15-ddf1a162683b +# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1 + +g₃ = @csgrammar begin + Element = |(1 : 20) # 1 - 20 + Element = Element - 1 # 21 + Element = 1 - Element # 22 + Element = x # 23 +end + +addconstraint!(g₃, ComesAfter(23, [22, 21])) +addconstraint!(g₃, ComesAfter(22, [21])) + +examples = [ + IOExample(Dict(:x => 1), 2) + IOExample(Dict(:x => 10), 11) +] +problem = Problem(examples) +solution = search(g₃, problem, :Element) + +@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1 +@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101 + +# ╔═╡ e41feac7-6de6-4223-8a21-341b85da52c0 +# Expects to return a program equivalent to 18 + 4x + +g₄ = @csgrammar begin + Element = |(0 : 20) # 1 - 20 + Element = Element + Element + Element # 21 + Element = Element + Element * Element # 22 + Element = x # 23 +end + +# Enforce ordering on + + +addconstraint!(g₄, Ordered( + MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]), + [:x, :y, :z] +)) + +examples = [ + IOExample(Dict(:x => 1), 22), + IOExample(Dict(:x => 0), 18), + IOExample(Dict(:x => -1), 14) +] +problem = Problem(examples) +solution = search(g₄, problem, :Element) + +@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418 + +# ╔═╡ 1c4db74a-4caa-4ce5-815f-b631365c5129 +# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2) + +g₅ = @csgrammar begin + Element = Number # 1 + Element = Bool # 2 + + Number = |(1 : 3) # 3-5 + + Number = Number + Number # 6 + Bool = Number ≡ Number # 7 + Number = x # 8 + + Number = Bool ? Number : Number # 9 + Bool = Bool ? Bool : Bool # 10 +end + +# Forbid ? = ? +addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)]))) +# Order = +addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) +# Order + +addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) + +examples = [ + IOExample(Dict(:x => 0), 2) + IOExample(Dict(:x => 1), 3) + IOExample(Dict(:x => 2), 1) +] +problem = Problem(examples) +solution = search(g₅, problem, :Element) + +@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5 + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" +HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" + +[compat] +HerbConstraints = "~0.2.2" +HerbGrammar = "~0.3.0" +HerbInterpret = "~0.1.3" +HerbSearch = "~0.3.0" +HerbSpecification = "~0.1.0" +PlutoUI = "~0.7.59" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.10.4" +manifest_format = "2.0" +project_hash = "5ee7afaa57cf163e03a42d572d1cb2cb022598e5" + +[[deps.AbstractPlutoDingetjes]] +deps = ["Pkg"] +git-tree-sha1 = "6e1d2a35f2f90a4bc7c2ed98079b2ba09c35b83a" +uuid = "6e696c72-6542-2067-7265-42206c756150" +version = "1.3.2" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.1" + +[[deps.ArnoldiMethod]] +deps = ["LinearAlgebra", "Random", "StaticArrays"] +git-tree-sha1 = "d57bd3762d308bded22c3b82d033bff85f6195c6" +uuid = "ec485272-7323-5ecc-a04f-4719b315124d" +version = "0.4.0" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "9e2a6b69137e6969bab0152632dcb3bc108c8bdd" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.8+1" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "a2f1c8c668c8e3cb4cca4e57a8efdb09067bb3fd" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.0+2" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "b10d0b65641d57b8b4d5e234446582de5047050d" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.5" + +[[deps.CommonSubexpressions]] +deps = ["MacroTools", "Test"] +git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7" +uuid = "bbf7d656-a473-5ed7-a52c-81e309532950" +version = "0.3.0" + +[[deps.Compat]] +deps = ["TOML", "UUIDs"] +git-tree-sha1 = "b1c55339b7c6c350ee89f2c1604299660525b248" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "4.15.0" +weakdeps = ["Dates", "LinearAlgebra"] + + [deps.Compat.extensions] + CompatLinearAlgebraExt = "LinearAlgebra" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.1.1+0" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "1d0a14036acb104d9e89698bd408f63ab58cdc82" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.20" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[deps.Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[deps.DocStringExtensions]] +deps = ["LibGit2"] +git-tree-sha1 = "2fb1e02f2b635d0845df5d7c167fec4dd739b00d" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.3" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c6317308b9dc757616f0b5cb379db10494443a7" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.2+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.5" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "db16beca600632c95fc8aca29890d83788dd8b23" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.13.96+0" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "5c1d8ae0efc6c2e7b1fc502cbe25def8f661b7bc" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.2+0" + +[[deps.Gettext_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll", "Pkg", "XML2_jll"] +git-tree-sha1 = "9b02998aba7bf074d14de89f9d37ca24a1a0b046" +uuid = "78b55507-aeef-58d4-861c-77aaff3498b1" +version = "0.21.0+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "Gettext_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "7c82e6a6cd34e9d935e9aa4051b66c6ff3af59ba" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.80.2+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "344bf40dcab1073aca04aa0df4fb092f920e4011" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.14+0" + +[[deps.Graphs]] +deps = ["ArnoldiMethod", "Compat", "DataStructures", "Distributed", "Inflate", "LinearAlgebra", "Random", "SharedArrays", "SimpleTraits", "SparseArrays", "Statistics"] +git-tree-sha1 = "ebd18c326fa6cee1efb7da9a3b45cf69da2ed4d9" +uuid = "86223c79-3864-5bf0-83f7-82e725a168b6" +version = "1.11.2" + +[[deps.HarfBuzz_ICU_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "6ccbc4fdf65c8197738c2d68cc55b74b19c97ac2" +uuid = "655565e8-fb53-5cb3-b0cd-aec1ca0647ea" +version = "2.8.1+0" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Pkg"] +git-tree-sha1 = "129acf094d168394e80ee1dc4bc06ec835e510a3" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "2.8.1+1" + +[[deps.HerbConstraints]] +deps = ["DataStructures", "HerbCore", "HerbGrammar", "MLStyle"] +git-tree-sha1 = "2e54da1d19119847b242d1ceda212b180cca36a9" +uuid = "1fa96474-3206-4513-b4fa-23913f296dfc" +version = "0.2.2" + +[[deps.HerbCore]] +git-tree-sha1 = "923877c2715b8166d7ba9f9be2136d70eed87725" +uuid = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +version = "0.3.0" + +[[deps.HerbGrammar]] +deps = ["AbstractTrees", "DataStructures", "HerbCore", "Serialization", "TreeView"] +git-tree-sha1 = "b4cbf9712dbb3ab281ff4ed517d3cd6bc12f3078" +uuid = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" +version = "0.3.0" + +[[deps.HerbInterpret]] +deps = ["HerbCore", "HerbGrammar", "HerbSpecification"] +git-tree-sha1 = "9e19b4ee5f29eb8bb9b1049524728b38e878eca2" +uuid = "5bbddadd-02c5-4713-84b8-97364418cca7" +version = "0.1.3" + +[[deps.HerbSearch]] +deps = ["DataStructures", "HerbConstraints", "HerbCore", "HerbGrammar", "HerbInterpret", "HerbSpecification", "Logging", "MLStyle", "Random", "StatsBase"] +git-tree-sha1 = "472e3f427c148f334dde3837b0bb1549897ed00a" +uuid = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +version = "0.3.0" + +[[deps.HerbSpecification]] +git-tree-sha1 = "5385b81e40c3cd62aeea591319896148036863c9" +uuid = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +version = "0.1.0" + +[[deps.Hyperscript]] +deps = ["Test"] +git-tree-sha1 = "179267cfa5e712760cd43dcae385d7ea90cc25a4" +uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91" +version = "0.0.5" + +[[deps.HypertextLiteral]] +deps = ["Tricks"] +git-tree-sha1 = "7134810b1afce04bbc1045ca1985fbe81ce17653" +uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +version = "0.9.5" + +[[deps.ICU_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "20b6765a3016e1fca0c9c93c80d50061b94218b7" +uuid = "a51ab1cf-af8e-5615-a023-bc2c838bba6b" +version = "69.1.0+0" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "8b72179abc660bfab5e28472e019392b97d0985c" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.4" + +[[deps.Inflate]] +git-tree-sha1 = "d1b1b796e47d94588b3757fe84fbf65a5ec4a80d" +uuid = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" +version = "0.1.5" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "630b497eafcc20001bba38a4651b327dcfc491d2" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.2" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "7e5d6779a1e09a36db2a7b6cff50942a0a7d0fca" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.5.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c84a835e1a09b289ffcd2271bf2a337bbdda6637" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.0.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "bf36f528eec6634efc60d7ec062008f171071434" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "3.0.0+1" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d986ce2d884d49126836ea94ed5bfb0f12679713" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "15.0.7+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "70c5da094887fd2cae843b8db33920bac4b6f07d" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.2+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "50901ebc375ed41dbf8058da26f9de442febbbec" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.3.1" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.4.0+0" + +[[deps.LibGit2]] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.0+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "0b4a5d71f3e5200a7dff793393e09dfc2d874290" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.2.2+1" + +[[deps.Libgcrypt_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgpg_error_jll"] +git-tree-sha1 = "9fd170c4bbfd8b935fdc5f8b7aa33532c991a673" +uuid = "d4300ac3-e22c-5743-9152-c294e39db1e4" +version = "1.8.11+0" + +[[deps.Libgpg_error_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fbb1f2bef882392312feb1ede3615ddc1e9b99ed" +uuid = "7add5ba3-2f88-524e-9cd5-f83b8a55f7b8" +version = "1.49.0+0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "f9557a255370125b405568f9767d6d195822a175" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.17.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "0c4f9c4f1a50d8f35048fa0532dabbadf702f81e" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.40.1+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "Pkg", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "3eb79b0ca5764d4799c06699573fd8f533259713" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.4.0+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "5ee6203157c120d79034c748a2acba45b82b8807" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.40.1+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[deps.LittleCMS_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pkg"] +git-tree-sha1 = "110897e7db2d6836be22c18bffd9422218ee6284" +uuid = "d3a379c0-f9a3-5b72-a4c0-6bf4d2e8af0f" +version = "2.12.0+0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "18144f3e9cbe9b15b070288eef858f71b291ce37" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.27" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[deps.MIMEs]] +git-tree-sha1 = "65f28ad4b594aebe22157d6fac869786a255b7eb" +uuid = "6c6e2e6c-3030-632d-7369-2d6c69616d65" +version = "0.1.4" + +[[deps.MLStyle]] +git-tree-sha1 = "bc38dff0548128765760c79eb7388a4b37fae2c8" +uuid = "d8e11817-5142-5d16-987a-aa16d5891078" +version = "0.4.17" + +[[deps.MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "2fa9ee3e63fd3a4f7a9a4f4744a52f4856de82df" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.13" + +[[deps.Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.2+1" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2023.1.10" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.2.0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.23+4" + +[[deps.OpenJpeg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "76374b6e7f632c130e78100b166e5a48464256f8" +uuid = "643b3616-a352-519d-856d-80112ee9badc" +version = "2.4.0+0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a12e56c72edee3ce6b96667745e6cbbe5498f200" +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "1.1.23+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "dfdf5519f235516220579f949664f1bf44e741c5" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.6.3" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.42.0+1" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "8489905bcdbcfac64d1daa51ca07c0d8f0283821" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.1" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "35621f10a7531bc8fa58f74610b1bfb70a3cfc6b" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.43.4+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.10.0" + +[[deps.PlutoUI]] +deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "FixedPointNumbers", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "MIMEs", "Markdown", "Random", "Reexport", "URIs", "UUIDs"] +git-tree-sha1 = "ab55ee1510ad2af0ff674dbcced5e94921f867a9" +uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +version = "0.7.59" + +[[deps.Poppler_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] +git-tree-sha1 = "02148a0cb2532f22c0589ceb75c110e168fb3d1f" +uuid = "9c32591e-4766-534b-9725-b71a8799265b" +version = "21.9.0+0" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "5aa36f7049a63a1528fe8f7c3f2113413ffd4e1f" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.2.1" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[deps.REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[deps.SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[deps.SimpleTraits]] +deps = ["InteractiveUtils", "MacroTools"] +git-tree-sha1 = "5d7e3f4e11935503d3ecaf7186eac40602e7d231" +uuid = "699a6c99-e7fa-54fc-8d76-47d257e15c1d" +version = "0.9.4" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "66e0a8e672a0bdfca2c3f5937efb8538b9ddc085" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.1" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" + +[[deps.StaticArrays]] +deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] +git-tree-sha1 = "eeafab08ae20c62c44c8399ccb9354a04b80db50" +uuid = "90137ffa-7385-5640-81b9-e52037218182" +version = "1.9.7" + + [deps.StaticArrays.extensions] + StaticArraysChainRulesCoreExt = "ChainRulesCore" + StaticArraysStatisticsExt = "Statistics" + + [deps.StaticArrays.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[deps.StaticArraysCore]] +git-tree-sha1 = "192954ef1208c7019899fbf8049e717f92959682" +uuid = "1e83bf80-4336-4d27-bf5d-d5a4f845583c" +version = "1.4.3" + +[[deps.Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.10.0" + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1ff449ad350c9c4cbc756624d6f8a8c3ef56d3ed" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.0" + +[[deps.StatsBase]] +deps = ["DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "5cf7606d6cef84b543b483848d4ae08ad9832b21" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.3" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.2.1+1" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[deps.TikzGraphs]] +deps = ["Graphs", "LaTeXStrings", "TikzPictures"] +git-tree-sha1 = "e8f41ed9a2cabf6699d9906c195bab1f773d4ca7" +uuid = "b4f28e30-c73f-5eaf-a395-8a9db949a742" +version = "1.4.0" + +[[deps.TikzPictures]] +deps = ["LaTeXStrings", "Poppler_jll", "Requires", "tectonic_jll"] +git-tree-sha1 = "79e2d29b216ef24a0f4f905532b900dcf529aa06" +uuid = "37f6aa50-8035-52d0-81c2-5a1d08754b2d" +version = "3.5.0" + +[[deps.TreeView]] +deps = ["CommonSubexpressions", "Graphs", "MacroTools", "TikzGraphs"] +git-tree-sha1 = "41ddcefb625f2ab0f4d9f2081c2da1af2ccbbf8b" +uuid = "39424ebd-4cf3-5550-a685-96706a953f40" +version = "0.5.1" + +[[deps.Tricks]] +git-tree-sha1 = "eae1bb484cd63b36999ee58be2de6c178105112f" +uuid = "410a4b4d-49e4-4fbc-ab6d-cb71b17b3775" +version = "0.1.8" + +[[deps.URIs]] +git-tree-sha1 = "67db6cc7b3821e19ebe75791a9dd19c9b1188f2b" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.5.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[deps.XML2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libiconv_jll", "Zlib_jll"] +git-tree-sha1 = "d9717ce3518dc68a99e6b96300813760d887a01d" +uuid = "02c8fc9c-b97f-50b9-bbe4-9be30ff0a78a" +version = "2.13.1+0" + +[[deps.XSLT_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libgcrypt_jll", "Libgpg_error_jll", "Libiconv_jll", "XML2_jll", "Zlib_jll"] +git-tree-sha1 = "a54ee957f4c86b526460a720dbc882fa5edcbefc" +uuid = "aed1982a-8fda-507f-9586-7b0439959a61" +version = "1.1.41+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "afead5aba5aa507ad5a3bf01f58f82c8d1403495" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.6+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "6035850dcc70518ca32f012e46015b9beeda49d8" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.11+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "34d526d318358a859d7de23da945578e8e8727b7" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.4+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "d2d1a5c49fae4ba39983f63de6afcbea47194e85" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.6+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "47e45cd78224c53109495b3e324df0c37bb61fbe" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.11+0" + +[[deps.Xorg_libpthread_stubs_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8fdda4c692503d44d04a0603d9ac0982054635f9" +uuid = "14d82f49-176c-5ed1-bb49-ad3f5cbd8c74" +version = "0.1.1+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "XSLT_jll", "Xorg_libXau_jll", "Xorg_libXdmcp_jll", "Xorg_libpthread_stubs_jll"] +git-tree-sha1 = "bcd466676fef0878338c61e655629fa7bbc69d8e" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.17.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e92a1a012a10506618f10b7047e478403a046c77" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.5.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.2.13+1" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e678132f07ddb5bfa46857f0d7620fb9be675d3b" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.6+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.8.0+1" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "d7015d2e18a5fd9a4f47de711837e980519781a4" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.43+1" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.52.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.4.0+2" + +[[deps.tectonic_jll]] +deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "54867b00af20c70b52a1f9c00043864d8b926a21" +uuid = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" +version = "0.13.1+0" +""" + +# ╔═╡ Cell order: +# ╟─dddca175-3d88-45ce-90da-575c0ba38175 +# ╠═a93d954d-0f09-4b6d-a3a5-62bfe39681e2 +# ╠═6ab37bbc-73e2-4d9a-a8b2-e715a0b61c8f +# ╠═c4441fa4-09ec-4b81-9681-b13b93a9c9c0 +# ╟─67931820-0f43-41e1-898e-5b5bd55e30d1 +# ╠═e41c61a4-0b2c-46da-8f7b-fe6dc529c544 +# ╟─9ad0a92a-10d5-458a-8f05-9011c8553609 +# ╠═65317911-bc92-4b84-9744-ed784adcab4a +# ╟─e9c6bc00-21f5-4a99-8bec-63cf2156c233 +# ╠═338f19f1-3a62-4462-b8dc-24424d7644f2 +# ╠═542cd47e-74cd-4b6f-acc9-bf524222e583 +# ╠═a6fb2e91-b73a-4032-930f-d884abd539e2 +# ╠═4b49f206-970e-47d2-af65-336ba65b1019 +# ╟─35405357-179b-4e77-9bdc-edf5a550b36d +# ╠═3954dd49-07a2-4ec2-91b4-9c9596d5c264 +# ╠═9892e91b-9115-4520-9637-f8d7c8905825 +# ╠═a4e58bbc-7c14-4fce-b35d-688b56e0eb61 +# ╠═9fb40ceb-8d41-491b-8941-20a8b240eb82 +# ╠═94e0d676-a9c7-4291-8696-15301e541c30 +# ╠═a4a7daed-f89b-44ad-8787-9199c05bf046 +# ╠═4821fd3a-ff2d-4991-99ad-76608d11b1da +# ╠═8d91b2e3-30b5-4ea2-bd3f-3055bb6d1d5a +# ╠═52332fa2-7ea7-4226-9460-e0bbc905c619 +# ╟─c26fea48-f812-4f24-bb8c-680e14a55df7 +# ╠═57f5face-d9d5-441a-8e0e-6ef6319fc178 +# ╟─9b4b21e0-dc6a-43ae-a511-79988ee99001 +# ╠═3af650d9-19c6-4351-920d-d2361091f628 +# ╟─d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 +# ╠═da7f326c-f0d5-4837-ac9a-5bcad604566e +# ╟─0020b79a-6352-4e2d-93f6-2a1d7b03ae2c +# ╠═789150a8-862c-48c3-88b8-710b81ab34cf +# ╟─243165be-a9d2-484d-8046-811a2b0ba139 +# ╠═3d01c6f1-80a6-4904-97e2-775170e97bbf +# ╟─168c71bf-ce5b-4ab3-b29a-5996981c42a5 +# ╠═a4b522cf-78f0-4d44-88c8-82dd0cdbf952 +# ╠═f313edb9-8fd9-4d78-88cd-89226f5c769d +# ╟─0da9053a-959b-471e-8918-662ec63da71c +# ╠═0a30fb40-cd45-4661-a501-ae8e45f1e07e +# ╟─700270ea-90bd-474b-91d9-0e5ed329776a +# ╠═8731f312-bfcf-4f6c-86fa-60014dc146d6 +# ╠═46ca65d1-d876-4abc-a562-8d266bad195f +# ╟─599194a8-3f47-4917-9143-a5fe0d43029f +# ╠═cb5935ed-d89b-4e25-9243-d201daf18e78 +# ╠═d0c3742e-23e5-4ca1-9e83-b6d1e8a7cded +# ╟─5df0ba53-b528-4baf-9980-cafe5d73f9dd +# ╠═a434645b-d592-4162-a8b4-b4b04cea30a9 +# ╟─38cd9032-27c0-4179-a536-ce59a42ff16a +# ╠═f8415d48-a51d-4845-8425-fd61ed79c06e +# ╟─7f88bf4f-d82c-4e5a-a9eb-93870954c79e +# ╟─53598f8f-9973-4cad-af0c-280f5531bb21 +# ╠═bc971069-08c4-493c-a917-8092493d3233 +# ╠═61a60c9c-36cf-4e86-b697-748b3524d3b4 +# ╠═a5b17c81-b667-4b1c-ab15-ddf1a162683b +# ╠═e41feac7-6de6-4223-8a21-341b85da52c0 +# ╠═1c4db74a-4caa-4ce5-815f-b631365c5129 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 diff --git a/docs/src/tutorials/getting_started_with_herb.jl b/docs/src/tutorials/getting_started_with_herb.jl index 83adbd8..854c27f 100644 --- a/docs/src/tutorials/getting_started_with_herb.jl +++ b/docs/src/tutorials/getting_started_with_herb.jl @@ -25,11 +25,11 @@ First, we start with the setup. We need to access to all the function in the Her md""" ### Defining the program space -Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). +Next, we start by creating a grammar. We define a context-free grammar as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A context-free grammar is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md). -For now, we specify a simple grammar for dealing with integers and explain all the rules individually: +For now, we specify a simple grammar (using the `@csgrammar` macro) for dealing with integers and explain all the rules individually: 1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer. 2. Then, we can also use the variable `x` to hold an integer. @@ -41,7 +41,7 @@ If you run this cell, you can see all the rules rolled out. """ # ╔═╡ 763b378b-66f9-481e-a3da-ca37825eb255 -g = HerbGrammar.@cfgrammar begin +g = HerbGrammar.@csgrammar begin Real = |(0:9) Real = x Real = Real + Real diff --git a/docs/src/tutorials/working_with_interpreters.jl b/docs/src/tutorials/working_with_interpreters.jl index 66767a4..88f46ee 100644 --- a/docs/src/tutorials/working_with_interpreters.jl +++ b/docs/src/tutorials/working_with_interpreters.jl @@ -6,8 +6,8 @@ using InteractiveUtils # ╔═╡ e0a7076c-9345-40ef-a26e-99e8bad31463 begin - using HerbGrammar - using HerbInterpret + using HerbGrammar + using HerbInterpret end # ╔═╡ 55719688-3940-11ef-1f29-f51dea064ff3 @@ -19,12 +19,12 @@ For example, assume the following grammar. " # ╔═╡ 39eaa982-ba88-49b9-ad52-076a169d0439 -g = @cfgrammar begin - Number = |(1:2) - Number = x - Number = Number + Number - Number = Number * Number - end +g = @csgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number +end # ╔═╡ 2478d5a4-a11e-42aa-87dd-d97a3fa5d378 md" @@ -32,7 +32,7 @@ Let's construct a program `x+3`, which would correspond to the following `RuleNo " # ╔═╡ 1e15898e-568c-4211-ba00-27de61806aeb -myprog = RuleNode(4,[RuleNode(3),RuleNode(1)]) +myprog = RuleNode(4, [RuleNode(3), RuleNode(1)]) # ╔═╡ d43a2094-b215-4d6c-b6d8-8d32fe8898d6 md" @@ -87,7 +87,7 @@ grammar_robots = @csgrammar begin end # ╔═╡ aff77be9-365f-4672-bbd4-07f23528e32e - md" +md" This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs: - `moveRight(); moveLeft(); drop()` - WHILE(notAtTop(), moveUp()) @@ -161,7 +161,7 @@ HerbInterpret = "~0.1.3" PLUTO_MANIFEST_TOML_CONTENTS = """ # This file is machine-generated - editing it directly is not advised -julia_version = "1.9.1" +julia_version = "1.10.4" manifest_format = "2.0" project_hash = "3d463631c2622b04eb5c06a35c60329ce68a7044" @@ -217,7 +217,7 @@ weakdeps = ["Dates", "LinearAlgebra"] [[deps.CompilerSupportLibraries_jll]] deps = ["Artifacts", "Libdl"] uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" -version = "1.0.2+0" +version = "1.1.1+0" [[deps.DataStructures]] deps = ["Compat", "InteractiveUtils", "OrderedCollections"] @@ -370,21 +370,26 @@ version = "1.3.1" [[deps.LibCURL]] deps = ["LibCURL_jll", "MozillaCACerts_jll"] uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" -version = "0.6.3" +version = "0.6.4" [[deps.LibCURL_jll]] deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" -version = "7.84.0+0" +version = "8.4.0+0" [[deps.LibGit2]] -deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +deps = ["Base64", "LibGit2_jll", "NetworkOptions", "Printf", "SHA"] uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.6.4+0" + [[deps.LibSSH2_jll]] deps = ["Artifacts", "Libdl", "MbedTLS_jll"] uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" -version = "1.10.2+0" +version = "1.11.0+1" [[deps.Libdl]] uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" @@ -457,14 +462,14 @@ uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" [[deps.MbedTLS_jll]] deps = ["Artifacts", "Libdl"] uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" -version = "2.28.2+0" +version = "2.28.2+1" [[deps.Mmap]] uuid = "a63ad114-7e13-5084-954f-fe012c677804" [[deps.MozillaCACerts_jll]] uuid = "14a3606d-f60d-562e-9121-12d972cd8159" -version = "2022.10.11" +version = "2023.1.10" [[deps.NetworkOptions]] uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" @@ -473,7 +478,7 @@ version = "1.2.0" [[deps.OpenBLAS_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" -version = "0.3.21+4" +version = "0.3.23+4" [[deps.OpenJpeg_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Libtiff_jll", "LittleCMS_jll", "Pkg", "libpng_jll"] @@ -495,7 +500,7 @@ version = "1.6.3" [[deps.PCRE2_jll]] deps = ["Artifacts", "Libdl"] uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" -version = "10.42.0+0" +version = "10.42.0+1" [[deps.Pixman_jll]] deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] @@ -506,7 +511,7 @@ version = "0.43.4+0" [[deps.Pkg]] deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -version = "1.9.0" +version = "1.10.0" [[deps.Poppler_jll]] deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "OpenJpeg_jll", "Pkg", "libpng_jll"] @@ -535,7 +540,7 @@ deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" [[deps.Random]] -deps = ["SHA", "Serialization"] +deps = ["SHA"] uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" [[deps.Requires]] @@ -567,6 +572,7 @@ uuid = "6462fe0b-24de-5631-8697-dd941f90decc" [[deps.SparseArrays]] deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.10.0" [[deps.StaticArrays]] deps = ["LinearAlgebra", "PrecompileTools", "Random", "StaticArraysCore"] @@ -590,12 +596,12 @@ version = "1.4.3" [[deps.Statistics]] deps = ["LinearAlgebra", "SparseArrays"] uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" -version = "1.9.0" +version = "1.10.0" [[deps.SuiteSparse_jll]] -deps = ["Artifacts", "Libdl", "Pkg", "libblastrampoline_jll"] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" -version = "5.10.1+6" +version = "7.2.1+1" [[deps.TOML]] deps = ["Dates"] @@ -699,7 +705,7 @@ version = "1.5.0+0" [[deps.Zlib_jll]] deps = ["Libdl"] uuid = "83775a58-1f1d-513f-b197-d71354ab007a" -version = "1.2.13+0" +version = "1.2.13+1" [[deps.Zstd_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl"] @@ -710,7 +716,7 @@ version = "1.5.6+0" [[deps.libblastrampoline_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" -version = "5.8.0+0" +version = "5.8.0+1" [[deps.libpng_jll]] deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] @@ -721,12 +727,12 @@ version = "1.6.43+1" [[deps.nghttp2_jll]] deps = ["Artifacts", "Libdl"] uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" -version = "1.48.0+0" +version = "1.52.0+1" [[deps.p7zip_jll]] deps = ["Artifacts", "Libdl"] uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" -version = "17.4.0+0" +version = "17.4.0+2" [[deps.tectonic_jll]] deps = ["Artifacts", "Fontconfig_jll", "FreeType2_jll", "Graphite2_jll", "HarfBuzz_ICU_jll", "HarfBuzz_jll", "ICU_jll", "JLLWrappers", "Libdl", "OpenSSL_jll", "Zlib_jll", "libpng_jll"] From 6b3f8e02aded6e6944af451b90805bed5823e0e2 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Fri, 20 Sep 2024 14:57:11 +0200 Subject: [PATCH 70/75] Improve tutorials. --- docs/Project.toml | 5 ++ .../src/tutorials/abstract_syntax_trees.ipynb | 63 +++++++++++-------- .../tutorials/getting_started_with_herb.jl | 16 ++--- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 06eab32..f91b5e6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,10 +4,15 @@ DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" Herb = "c09c6b7f-4f63-49de-90d9-97a3563c0f4a" HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" +Pluto = "c3e4b0f8-55cb-11ea-2926-15256bba5781" +PlutoSliderServer = "2fc8631c-6f24-4c5b-bca7-cbb509c42db4" +PlutoStaticHTML = "359b1769-a58e-495b-9770-312e911026ad" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0" [compat] diff --git a/docs/src/tutorials/abstract_syntax_trees.ipynb b/docs/src/tutorials/abstract_syntax_trees.ipynb index 7220eaf..53be308 100644 --- a/docs/src/tutorials/abstract_syntax_trees.ipynb +++ b/docs/src/tutorials/abstract_syntax_trees.ipynb @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 1, "id": "69d543a7-3067-4376-b1bb-1a4f9217b786", "metadata": {}, "outputs": [ @@ -76,8 +76,9 @@ "13: Number = Number * Number\n" ] }, + "execution_count": 1, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -134,7 +135,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 2, "id": "9c61d359-1f38-410e-b654-5d36bc9a1d4e", "metadata": {}, "outputs": [ @@ -144,8 +145,9 @@ "13{6,12{11,4}}" ] }, + "execution_count": 2, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -162,7 +164,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 3, "id": "7c933e57-03f8-41ac-a464-70f95b9505c0", "metadata": {}, "outputs": [ @@ -181,7 +183,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 4, "id": "32fbb714-25aa-40d5-9c0b-c20ba8999d0c", "metadata": {}, "outputs": [ @@ -191,8 +193,9 @@ "65" ] }, + "execution_count": 4, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -213,7 +216,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 5, "id": "1f41bec4-d081-48bd-9fee-c30021d4a882", "metadata": {}, "outputs": [ @@ -223,8 +226,9 @@ "fizzbuzz (generic function with 1 method)" ] }, + "execution_count": 5, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -257,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 6, "id": "7b0ea30c-e415-4d7e-ba2f-a1590cd4c8fe", "metadata": {}, "outputs": [ @@ -283,8 +287,9 @@ "13: Bool = Bool && Bool\n" ] }, + "execution_count": 6, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -384,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 7, "id": "9c897ebc-4a79-45be-b349-c376bffa03df", "metadata": {}, "outputs": [ @@ -394,8 +399,9 @@ "12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}}" ] }, + "execution_count": 7, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -462,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 8, "id": "5a4ba50a-ccda-4e20-9301-051ee839fdf5", "metadata": {}, "outputs": [ @@ -493,7 +499,7 @@ }, { "cell_type": "code", - "execution_count": 24, + "execution_count": 9, "id": "9e0b4ec1-eab1-49b9-be2d-c923098d1850", "metadata": {}, "outputs": [ @@ -507,8 +513,9 @@ " \"22\"" ] }, + "execution_count": 9, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -537,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 25, + "execution_count": 10, "id": "65bfb9cc-bbdc-46e5-8ebf-9c15c6424339", "metadata": {}, "outputs": [ @@ -547,8 +554,9 @@ "6," ] }, + "execution_count": 10, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -568,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 11, "id": "742f7ee8-c088-4331-b17c-dc5a2078ca6b", "metadata": {}, "outputs": [ @@ -599,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 12, "id": "59d13ab2-7415-4463-89bf-e09ff55a74d8", "metadata": {}, "outputs": [ @@ -613,8 +621,9 @@ " \"22\"" ] }, + "execution_count": 12, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -633,7 +642,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 13, "id": "74d04a45-1697-4bcd-a5fd-adaa4ef455ff", "metadata": {}, "outputs": [ @@ -643,8 +652,9 @@ "6," ] }, + "execution_count": 13, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ @@ -666,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 14, "id": "6f965fe6-f5b1-4059-911c-362f4c1731d6", "metadata": {}, "outputs": [ @@ -697,7 +707,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 15, "id": "41395fec-9053-423b-b0fd-7089bb89c2d0", "metadata": {}, "outputs": [ @@ -711,8 +721,9 @@ " \"22\"" ] }, + "execution_count": 15, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ diff --git a/docs/src/tutorials/getting_started_with_herb.jl b/docs/src/tutorials/getting_started_with_herb.jl index 854c27f..8e841ae 100644 --- a/docs/src/tutorials/getting_started_with_herb.jl +++ b/docs/src/tutorials/getting_started_with_herb.jl @@ -69,7 +69,7 @@ In the cell below we automatically generate some examples for `x` assigning valu # ╔═╡ 8bf48b7a-0ff5-4015-81d3-ed2eeeceff1c # Create input-output examples -examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] +examples = [HerbSpecification.IOExample(Dict(:x => x), 3x + 5) for x ∈ 1:5] # ╔═╡ 2baa7f33-c86d-40e2-9253-720ec19e4c43 md""" @@ -126,10 +126,10 @@ Search is done by passing the grammar, the problem and the starting point like b # ╔═╡ cdab3f55-37e4-4aee-bae1-14d3475cbdcd begin - problem_2 = HerbSpecification.Problem("example2", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5]) - iterator_2 = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30) - expr_2 = HerbSearch.synth(problem_2, iterator_2) - print(expr_2) + problem_2 = HerbSpecification.Problem("example2", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5]) + iterator_2 = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30) + expr_2 = HerbSearch.synth(problem_2, iterator_2) + print(expr_2) end # ╔═╡ 5ad86beb-eb25-4bae-b0c2-a33d1a38581a @@ -141,9 +141,9 @@ In any case, this concludes our first introduction to the `Herb.jl` program synt # ╔═╡ c06d09a5-138a-4821-8a60-074fa7ec026d begin - problem_3 = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5]) - expr_3 = HerbSearch.synth(problem_3, iterator_2) - print(expr_3) + problem_3 = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5]) + expr_3 = HerbSearch.synth(problem_3, iterator_2) + print(expr_3) end # ╔═╡ 00000000-0000-0000-0000-000000000001 From f8b331dc3883a5ab77311aa6017fda9f6ff06c76 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 26 Sep 2024 18:50:58 +0200 Subject: [PATCH 71/75] Fix rightmost-first heuristic for BFSIterator. Add more explanations. Remove sampling, etc. part at the end. --- docs/src/tutorials/advanced_search.jl | 587 +++++++++++++------------- 1 file changed, 304 insertions(+), 283 deletions(-) diff --git a/docs/src/tutorials/advanced_search.jl b/docs/src/tutorials/advanced_search.jl index ae9ad66..e9fe327 100644 --- a/docs/src/tutorials/advanced_search.jl +++ b/docs/src/tutorials/advanced_search.jl @@ -11,24 +11,31 @@ using PlutoUI using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints +# ╔═╡ aa45e602-6219-44ea-82a0-b97639f6a450 +using HerbCore + # ╔═╡ dddca175-3d88-45ce-90da-575c0ba38175 md""" # Advanced Search Procedures in Herb.jl -[A more verbose getting started with Herb.jl]() described the concept of a program space and showed how to search it with Herb.jl, using a simple search with a BFS iterator. -This tutorial takes a closer look at advanced search procedures. +[A more verbose getting started with Herb.jl]() described the concept of a program space and showed how to search it with Herb.jl, using a simple breadth-first-search (BFS) iterator for the search. +This tutorial takes a closer look at advanced search procedures hat can be employed to find a solution program to a program synthesis problem. More specifically, you will learn about - **Parameters** that can be specified and their effect on the search procedure. -- **Search methods** that can be employed to find a solution program to a program synthesis problem, including basic search (BFS and DFS), stochastic search and genetic search methods. -- **Other functionality** of the module `HerbSearch.jl` - (TODO: why are they in this tutorial?) +- **Deterministic search methods** BFS and DFS. +- **Stochastic search methods**, which introduce randomness to search the program space. We will look at Metropolis-Hastings, Very Large Scale Neighbourhood Search, Simulated Annealing and Genetic Search. """ # ╔═╡ 6ab37bbc-73e2-4d9a-a8b2-e715a0b61c8f TableOfContents() +# ╔═╡ 61cee94c-2481-4268-823b-ca596592b63c +md""" +Let's import all the Herb modules that we will use throughout the tutorial. +""" + # ╔═╡ 67931820-0f43-41e1-898e-5b5bd55e30d1 md""" We start with a simple grammar:. @@ -54,13 +61,13 @@ problem_1 = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) md""" ## Parameters -Search procedures typically have some hyperparameters that can be cofigured by the user. +Search procedures typically have some hyperparameters that you can configure. ### `max_depth` -`max_depth` controls the maximum depth of the program trees we want to explore. +`max_depth` controls the maximum depth of the program trees that are explored during the search, effectively limiting the size and complexity of the synthesized program. The parameter is configured as part of the iterator. -In the following example, we can see the effect of `max_depth` on the number of allocations considered. +In the following example, we consider two different values for `max_depth`. """ # ╔═╡ 338f19f1-3a62-4462-b8dc-24424d7644f2 @@ -69,6 +76,11 @@ iterator_1 = BFSIterator(g_1, :Number, max_depth=3) # ╔═╡ 542cd47e-74cd-4b6f-acc9-bf524222e583 iterator_2 = BFSIterator(g_1, :Number, max_depth=6) +# ╔═╡ 63e97576-1c34-464d-a106-d59d5fb1ee38 +md""" +To see the effect `max_depth` has on the number of memory allocations made during the program synthesis process, we use the `@time` macro. +""" + # ╔═╡ a6fb2e91-b73a-4032-930f-d884abd539e2 begin println("Solution for max_depth = 3:") @@ -80,16 +92,17 @@ begin println(solution_2) end -# ╔═╡ 4b49f206-970e-47d2-af65-336ba65b1019 +# ╔═╡ 58c1a904-4d87-43f7-bcc3-884a8663c1da md""" -TODO: Explain @time +While increasing `max_depth` allows us to explore more complex and deeper program trees, which may lead to a better solution, it also requires more memory allocation and can increase the execution time. """ # ╔═╡ 35405357-179b-4e77-9bdc-edf5a550b36d md""" ### `max_enumerations` -Another parameter to use is `max_enumerations`, which describes the maximum number of programs that can be tested at evaluation. -Let's see how many enumerations are necessary to solve our simple problem. +`max_enumerations` defines the maximum number of candidate programs that can be evaluated before the search is terminated. + +Let's explore how many enumerations are necessary to solve our simple problem. """ # ╔═╡ 3954dd49-07a2-4ec2-91b4-9c9596d5c264 @@ -104,26 +117,17 @@ end # ╔═╡ 9892e91b-9115-4520-9637-f8d7c8905825 md""" -TODO: numbers seem to have changed. Not 24 anymore. How reproducible is this anyway? -What does allocations mean? - -We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. - -A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method. +At `i = 3`, we observe that an optimal program is found. Increasing the number of enumerations beyond that does not affect the solution or the number of memory allocations. """ # ╔═╡ a4e58bbc-7c14-4fce-b35d-688b56e0eb61 md""" ### `allow_evaluation_errors` -TODO: What do we mean with 'program will still run'? - -A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When set to `true`, the program will still run even when an exception is thrown during evaluation. +A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When `true`, the search continues even if an exception occurs during the evaluation of a candidate program. This allows the search process to handle faulty candidate programs and explore other ones, instead of throwing an error and terminating prematurely. -We will use a new example to see the effect of `allow_evaluation_errors`. After defining a new grammar and problem, ... - -We can also retrieve the error together with the solution from the search method. +We will use a new example to see the effect of `allow_evaluation_errors`. We begin defining a new simple grammar. We then create some input-output examples to specify the problem we want to solve. This time, we choose a problem that we cannot solve with the provided grammar. """ # ╔═╡ 9fb40ceb-8d41-491b-8941-20a8b240eb82 @@ -139,51 +143,77 @@ end problem_2 = Problem([IOExample(Dict(), x) for x ∈ 1:5]) # ╔═╡ a4a7daed-f89b-44ad-8787-9199c05bf046 -iterator_3 = BFSIterator(g_2, :Number, max_depth=2) +iterator_3 = BFSIterator(g_2, :Index, max_depth=2) # ╔═╡ 4821fd3a-ff2d-4991-99ad-76608d11b1da -solution_3 = @time synth(problem_2, iterator_3, allow_evaluation_errors=true) - -# ╔═╡ 8d91b2e3-30b5-4ea2-bd3f-3055bb6d1d5a -# solution = search(g_2, problem_2, :Index, max_depth=2, allow_evaluation_errors=true) +solution_3 = synth(problem_2, iterator_3) -# ╔═╡ 52332fa2-7ea7-4226-9460-e0bbc905c619 -println("solution: ", solution_3) - -# ╔═╡ c26fea48-f812-4f24-bb8c-680e14a55df7 +# ╔═╡ b2eb08d7-3e53-46c5-84b1-e1fa0e07e291 md""" -There is another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`): +As expected, an exception occurs during the synthesis process. Now we try the same again, with `allow_evaluation_errors=true`. """ -# ╔═╡ 57f5face-d9d5-441a-8e0e-6ef6319fc178 -solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) -println("solution: ", solution) -println("error: ", error) +# ╔═╡ 606070e1-83a7-4cca-a716-4fa459f78772 +solution_4 = synth(problem_2, iterator_3, allow_evaluation_errors=true) + +# ╔═╡ 52332fa2-7ea7-4226-9460-e0bbc905c619 +println("solution: ", solution_4) + +# ╔═╡ c262116e-138e-4133-a032-d2f50bfbf5bd +md""""This time we find a solution, although a suboptimal one.""" # ╔═╡ 9b4b21e0-dc6a-43ae-a511-79988ee99001 md""" ## Search methods -We now show examples of using different search procedures, which are initialized by passing different enumerators to the search function. +Herb.jl provides already implemented, ready-to-use search methods. The core building block of the search is the program iterator, which represents a walk through the program space. All program iterators share the top-level abstract type `ProgramIterator`. For more information on iterators and how to customize them, see [this tutorial](https://herb-ai.github.io/Herb.jl/dev/tutorials/TopDown/). +""" + +# ╔═╡ 115c02c9-ae0c-4623-a61d-831fc6ad55a2 +md""" +### Deterministic search: BFS and DFS -### Breadth-First Search +First, we explore two fundamental deterministic top-down search algorithms: **breadth-first search (BFS)** and **depth-first search (DFS)**. Both algorithms are implemented using the abstract type `TopDownIterator`, which can be customized through the functions priority_function, derivation_heuristic, and hole_heuristic. -The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. +#### Breadth-First Search + +The `BFSIterator` enumerates all possible programs at a given depth before progressing to the next level, ensuring that trees are explored in increasing order of size. This guarantees that smaller programs are evaluated first, and larger, more complex ones are considered only after all smaller ones have been processed. + +To explore `BFSIterator`, we define another very simple grammar. """ # ╔═╡ 3af650d9-19c6-4351-920d-d2361091f628 -g1 = @cfgrammar begin - Real = 1 | 2 - Real = Real * Real +begin g3 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real + end end -programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real)) + +# ╔═╡ 4cb08dba-aea5-4c31-998c-844d1fce8c81 +md""" +Next, we define a `BFSIterator` with a `max_depth` of 2 and a `max_size` of infinite (which we approximate with the maximum value of `Int`), and a starting symbol of type `Real`. By default, `BFSIterator` uses the heuristic 'left-most first', i.e., the left-most child in the tree is always explored first. +""" + +# ╔═╡ f2521a57-267e-4b49-9179-4e9c2e6bdec7 +iteratorbfs = BFSIterator(g3, :Real, max_depth=2, max_size=typemax(Int)) + +# ╔═╡ bf038215-1ecf-4e1c-a9be-e133e4497293 +md""" +To see all possible solution programs the iterator explores, we use `collect`. It returs a list of the programs, ordered by increasing size and depth. +""" + +# ╔═╡ 6aec7358-225a-4764-9a36-da86234b6cf8 +programsbfs = collect(iteratorbfs) + +# ╔═╡ 54ecf6b9-3341-49e0-92e9-71190e06d61b +println(programsbfs) # ╔═╡ d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 md""" -We can test that this function returns all and only the correct functions. +Let's verify that the iterator returns the programs we expect (keep in mind we use a leftmost-first heuristic). """ -# ╔═╡ da7f326c-f0d5-4837-ac9a-5bcad604566e +# ╔═╡ 07b54acf-0c0d-40ac-ae18-fb26094b4aca answer_programs = [ RuleNode(1), RuleNode(2), @@ -193,40 +223,101 @@ answer_programs = [ RuleNode(3, [RuleNode(2), RuleNode(2)]) ] -println(all(p ∈ programs for p ∈ answer_programs)) +# ╔═╡ 9efb01cf-b190-4e3e-aa19-11499ba46489 +println(all(p ∈ programsbfs for p ∈ answer_programs)) # ╔═╡ 0020b79a-6352-4e2d-93f6-2a1d7b03ae2c md""" -### Depth-First Search +#### Depth-First Search + +The `DFSIterator` looks at one branch of the search tree at a time, exploring it unitl a correct program is found or the specified `max_depth` is reached. Only then, the next branch is considered. -In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. +Let's `collect` the the trees/programs (TODO), using the same grammar but this time `DFSIterator`. """ # ╔═╡ 789150a8-862c-48c3-88b8-710b81ab34cf -g1 = @cfgrammar begin -Real = 1 | 2 -Real = Real * Real +begin + g1 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real + end + end -programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real)) -println(programs) + +# ╔═╡ db5be2c3-0b36-40b4-bf14-20e2c7063ad7 +iteratordfs = DFSIterator(g3, :Real, max_depth=2, max_size=typemax(Int)) + +# ╔═╡ 4048ff37-e7d1-44ee-bfa3-aa058b6f53b6 +programsdfs = collect(iteratordfs) + +# ╔═╡ 658c55ac-88c8-4657-a8eb-c9c9b91d0ded +println(programsdfs) # ╔═╡ 243165be-a9d2-484d-8046-811a2b0ba139 md""" -`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. +`DFSIterator` also uses by default a **leftmost-first** heuristic. If we want to use a **rightmost-first** heuristic instead, we can create our own iterator `DFSIteratorRightmost`. Also see the tutorial [Top Down Iterator](https://herb-ai.github.io/Herb.jl/dev/tutorials/TopDown/) for how to build iterators is Herb.jl. + + + + +TODO: How to use rightmost heuristic? +""" + +# ╔═╡ 3f18989a-ae25-4a66-9930-4459407af695 +# to modify the heuristics, we create a new type +# abstract type DFSIteratorRightmost <: TopDownIterator end + +# ╔═╡ 4b97602a-5226-429f-86ea-8ecac3c807fa +@programiterator DFSIteratorRightmost() <: TopDownIterator + +# ╔═╡ ed198b79-1b95-4531-b148-c1037cfdacf4 +md""" +By default, `priority_function` for a `TopDownIterator` is that of a BFS iterator. Hence, we need to overwrite it: +""" + +# ╔═╡ 3c7f6e75-ea70-4ccd-8ad6-5876d50a587d +function priority_function( + ::DFSIteratorRightmost, + ::AbstractGrammar, + ::AbstractRuleNode, + parent_value::Union{Real, Tuple{Vararg{Real}}}, + isrequeued::Bool +) + if isrequeued + return parent_value; + end + return parent_value - 1; +end + +# ╔═╡ d62918e4-4113-45bd-81ea-d17d007b83f5 +# Alternative +priority_function(::DFSIteratorRightmost) = priority_function(DFSIterator()) + +# ╔═╡ 7480d1e4-e417-4d87-80b7-513a098da70e +md""" +Next, we need to overwrite the `hole_heuristic` to rightmost-first. """ -# ╔═╡ 3d01c6f1-80a6-4904-97e2-775170e97bbf -g1 = @cfgrammar begin - Real = 1 | 2 - Real = Real * Real +# ╔═╡ 7e2af72d-b71c-4234-9bca-cb9a90732a91 +function hole_heuristic(::DFSIteratorRightmost, node::AbstractRuleNode, max_depth::Int)::Union{ExpandFailureReason, HoleReference} + return heuristic_rightmost(node, max_depth); end -programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost)) -println(programs) + +# ╔═╡ 00d05a7e-ca79-4d6b-828d-b24ef1cb80a2 +iteratordfs_rightmost = DFSIteratorRightmost(g3, :Real, max_depth=2, max_size=typemax(Int)) + +# ╔═╡ c70eb1a1-7d6b-413f-bd85-77e1b8c30b94 +println(collect(iteratordfs_rightmost)) # ╔═╡ 168c71bf-ce5b-4ab3-b29a-5996981c42a5 md""" ## Stochastic search -We now introduce a few stochastic search algorithms, for which we first create a simple grammar and a helper function for problems. + +Whereas deterministic search methods explore the search space in a predictable way, stochastic ones use randomness. + +We will look at several stochastic search algorithms, usinga a new example. + +We start with a simple grammar and a helper function to create the input-output examples for the problem we want to solve. """ # ╔═╡ a4b522cf-78f0-4d44-88c8-82dd0cdbf952 @@ -248,259 +339,154 @@ end md""" ### Metropolis-Hastings -One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). +Metropolis-Hastings (MH) is a method to produce samples from a distribution that may otherwise be difficult to sample. In the context of program synthesis, we want samples from a distribution of programs, based on the grammar. +For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). -The example below uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. +The example below uses a simple arithmetic example. Try run the code block (TODO) multiple times. You will get different programs every time, as the search is stochastic. """ +# ╔═╡ 4df84c71-99b5-487f-9db5-c048c0c74151 +e_mh = x -> x * x + 4 + +# ╔═╡ afe1872c-6641-4fa0-a53e-50c6b4a712ee +problem_mh, examples_mh = create_problem(e_mh) + +# ╔═╡ 969db94c-d583-40d1-a058-097f8117c2e9 +cost_function = mean_squared_error + + # ╔═╡ 0a30fb40-cd45-4661-a501-ae8e45f1e07e -e = x -> x * x + 4 -problem, examples = create_problem(e) -enumerator = get_mh_enumerator(examples, mean_squared_error) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) +begin + iteratormh = MHSearchIterator(grammar, :X, examples_mh, cost_function, max_depth=3) #TODO: What should max_depth be? + programmh = synth(problem_mh, iteratormh) +end + +# ╔═╡ 82f7ffa5-2bdc-4153-85fa-d7aca063da12 +programmh # ╔═╡ 700270ea-90bd-474b-91d9-0e5ed329776a md""" ### Very Large Scale Neighbourhood Search -The second implemented stochastic search method is VLSN, which searches for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). +The second stochastic search method we consider is Very Large Scale Neighbourhood Search (VLSN). In each iteration, the neighbourhood of the current candidate program is searched for the local optimum to find a better candidate. -Given the same grammar as before, we can try it with some simple examples. -""" +TODO: more? -# ╔═╡ 8731f312-bfcf-4f6c-86fa-60014dc146d6 -e = x -> 10 -max_depth = 2 -problem, examples = create_problem(e) -enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) +For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). -# ╔═╡ 46ca65d1-d876-4abc-a562-8d266bad195f -e = x -> x -max_depth = 1 -problem, examples = create_problem(e) -enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) +Given the same grammar as before, we can try it with some simple examples. -# ╔═╡ 599194a8-3f47-4917-9143-a5fe0d43029f -md""" -### Simulated Annealing -The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). -We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Change the value below to see the effect. -""" +Using +large neighborhoods makes it possible to find better candidate solutions in each it- +eration and hence traverse a more promising search path -# ╔═╡ cb5935ed-d89b-4e25-9243-d201daf18e78 -e = x -> x * x + 4 -initial_temperature = 1 -problem, examples = create_problem(e) -enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) +#### Example I +""" -# ╔═╡ d0c3742e-23e5-4ca1-9e83-b6d1e8a7cded -e = x -> x * x + 4 -initial_temperature = 2 -problem, examples = create_problem(e) -enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) -program, cost = @time search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) +# ╔═╡ e6e5c63b-34e8-40d6-bc12-bd31f40b4b16 +e_vlsn = x -> 10 -# ╔═╡ 5df0ba53-b528-4baf-9980-cafe5d73f9dd -md""" -### Genetic Search +# ╔═╡ 2397f65f-e6b4-4f11-bf66-83440c58b688 +problem_vlsn, examples_vlsn = create_problem(e_vlsn) -Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). +# ╔═╡ 7c738d7b-bf05-40c7-b3b7-1512fbae7299 +iteratorvlsn = VLSNSearchIterator(grammar, :X, examples_vlsn, cost_function, max_depth=2) -We show the example of finding a lambda function. Try varying the parameters of the genetic search to see what happens. -""" +# ╔═╡ 33af905e-e8ca-425d-9805-eb02bec7c26b +programvlsn = synth(problem_vlsn, iteratorvlsn) +# program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) -# ╔═╡ a434645b-d592-4162-a8b4-b4b04cea30a9 -e = x -> 3 * x * x + (x + 2) -problem, examples = create_problem(e) -enumerator = get_genetic_enumerator(examples, - initial_population_size = 10, - mutation_probability = 0.8, - maximum_initial_population_depth = 3, -) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) - -# ╔═╡ 38cd9032-27c0-4179-a536-ce59a42ff16a +# ╔═╡ 4208cd17-6c2a-4480-a535-5a7358e0ab25 md""" -## Other functionality - -Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics. - -### Sampling -Sampling is implemented for the different stochastic search methods. +#### Example 2 -We consider here a simple grammar, which gives different programs for different search depths. +TODO: What do we want to show with this example? """ -# ╔═╡ f8415d48-a51d-4845-8425-fd61ed79c06e -grammar = @cfgrammar begin - A = B | C | F - F = G - C = D - D = E -end +# ╔═╡ bea28b36-6613-4895-98f9-27dfd9e57b09 +e_vlsn2 = x -> x -# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3) +# ╔═╡ aa95cb5e-926d-4119-8d08-353f37a59039 +problem_vlsn2, examples_vlsn2 = create_problem(e_vlsn2) -# For depth ≤ 1 the only option is A->B -expression = rand(RuleNode, grammar, :A, 1) -@assert rulenode2expr(expression, grammar) in [:B,:C,:F] +# ╔═╡ 285043ef-c295-400f-91c5-f3c6c69ac2bf +iterator_vlsn2 = VLSNSearchIterator(grammar, :X, examples_vlsn2, cost_function, max_depth=1) -# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2) -expression = rand(RuleNode, grammar, :A, 2) -@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G] +# ╔═╡ 36f0e0cf-c871-42c9-956e-054767cbf693 +program_vlsn2 = synth(problem_vlsn2, iterator_vlsn2) -# ╔═╡ 7f88bf4f-d82c-4e5a-a9eb-93870954c79e +# ╔═╡ 599194a8-3f47-4917-9143-a5fe0d43029f md""" -### Heuristics +### Simulated Annealing + +Simualted Annealing (SA) explores smaller, incremental changes in the candidate program per iteration and refines the solution over time. It is a modified hill-climbing algorithm that, instead of picking the best move (program modification?), picks a random move. If the move improves the solution (candidate program), it is always accepted. Ocassionally, a move towards a worse solution is accepted to escape loca optima. However, this strategy follows a cooling (annealing) schedule, i.e., it starts exploring widely (at high temperature) and gradually becomes more selective (at low temperture), accepting worse solutions less often. + +For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). """ -# ╔═╡ 53598f8f-9973-4cad-af0c-280f5531bb21 +# ╔═╡ dd6aee87-cd96-4be1-b8fb-03fffee5ea43 md""" -# More interesting domains & Use of constraints -In the following examples, we introduce some larger grammars and show that Herb can still efficiently find the correct program. +We use the same example as for MH. SA additionally has the option to specify the `initial_temperature` for the annealing (default `initial_temperature=1`). Let's see what effect changing the temperature from 1 to 2 has on the solution program. """ -# ╔═╡ bc971069-08c4-493c-a917-8092493d3233 -#Expects to return a program equivalent to 1 + (1 - x) = 2 - x - -g₁ = @csgrammar begin - Element = |(1 : 3) # 1 - 3 - Element = Element + Element # 4 - Element = 1 - Element # 5 - Element = x # 6 -end - -addconstraint!(g₁, ComesAfter(6, [5])) +# ╔═╡ f6a3ed3f-bcf9-4f22-a86e-33255d88e0b2 +e_sa = x -> x * x + 4 -examples = [ - IOExample(Dict(:x => 0), 2), - IOExample(Dict(:x => 1), 1), - IOExample(Dict(:x => 2), 0) -] -problem = Problem(examples) -solution = search(g₁, problem, :Element, max_depth=3) +# ╔═╡ e25d115f-7549-4606-b96c-9ef700810f7b +problem_sa, examples_sa = create_problem(e_sa) -@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4 +# ╔═╡ 94f2bd5e-e11e-42e7-9a3e-3c9d5ae43cd4 +initial_temperature1 = 1 -# ╔═╡ 61a60c9c-36cf-4e86-b697-748b3524d3b4 -# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4 +# ╔═╡ eb851d7b-803e-45f6-ad10-fa0bde78826a +iterator_sa1 = SASearchIterator(grammar, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature1) -g₂ = @csgrammar begin - Element = Element + Element + Element # 1 - Element = Element + Element * Element # 2 - Element = x # 3 - Element = |(3 : 5) # 4 -end +# ╔═╡ 73304e3f-05bf-4f0c-9acd-fc8afa87b460 +program_sa1 = synth(problem_sa, iterator_sa1) -# Restrict .. + x * x -addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)]))) -# Restrict 4 and 5 in lower level -addconstraint!(g₂, ForbiddenPath([2, 1, 5])) -addconstraint!(g₂, ForbiddenPath([2, 1, 6])) +# ╔═╡ 07f11eb1-6b45-441a-a481-57628bad23ae +initial_temperature2 = 2 -examples = [ - IOExample(Dict(:x => 1), 11) - IOExample(Dict(:x => 2), 20) - IOExample(Dict(:x => -1), -1) -] -problem = Problem(examples) -solution = search(g₂, problem, :Element) +# ╔═╡ 4ff69f0a-6626-4593-b361-a2387eecc731 +iterator_sa2 = SASearchIterator(grammar, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature2) -@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4 +# ╔═╡ 0f7f228c-4f95-4f1a-9ca2-2d03384a00c0 -# ╔═╡ a5b17c81-b667-4b1c-ab15-ddf1a162683b -# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1 -g₃ = @csgrammar begin - Element = |(1 : 20) # 1 - 20 - Element = Element - 1 # 21 - Element = 1 - Element # 22 - Element = x # 23 -end +# ╔═╡ 5df0ba53-b528-4baf-9980-cafe5d73f9dd +md""" +### Genetic Search -addconstraint!(g₃, ComesAfter(23, [22, 21])) -addconstraint!(g₃, ComesAfter(22, [21])) +Genetic search is a type of evolutionary algorithm, which simulates the process of natural selection. It evolves a population of candidate programs through operations like mutation, crossover (recombination), and selection. Then, the fitness of each program is assessed (i.e., how well it satisfies the given specifications). Only the 'fittest' programs are selected for the next generation, thus gradually refining the population of candidate programs. -examples = [ - IOExample(Dict(:x => 1), 2) - IOExample(Dict(:x => 10), 11) -] -problem = Problem(examples) -solution = search(g₃, problem, :Element) +For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). -@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1 -@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101 +We show the example of finding a lambda function. Try varying the parameters of the genetic search to see what happens. +""" -# ╔═╡ e41feac7-6de6-4223-8a21-341b85da52c0 -# Expects to return a program equivalent to 18 + 4x +# ╔═╡ 99ea1c20-ca2c-4d77-bc3b-06814db1d666 +e_gs = x -> 3 * x * x + (x + 2) -g₄ = @csgrammar begin - Element = |(0 : 20) # 1 - 20 - Element = Element + Element + Element # 21 - Element = Element + Element * Element # 22 - Element = x # 23 -end +# ╔═╡ d991edb9-2291-42a7-97ff-58c456515505 +problem_gs, examples_gs = create_problem(e_gs) -# Enforce ordering on + + -addconstraint!(g₄, Ordered( - MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]), - [:x, :y, :z] -)) +# ╔═╡ d17c2a79-3c14-4c50-a4aa-465f3da011a2 -examples = [ - IOExample(Dict(:x => 1), 22), - IOExample(Dict(:x => 0), 18), - IOExample(Dict(:x => -1), 14) -] -problem = Problem(examples) -solution = search(g₄, problem, :Element) - -@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418 - -# ╔═╡ 1c4db74a-4caa-4ce5-815f-b631365c5129 -# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2) - -g₅ = @csgrammar begin - Element = Number # 1 - Element = Bool # 2 - - Number = |(1 : 3) # 3-5 - - Number = Number + Number # 6 - Bool = Number ≡ Number # 7 - Number = x # 8 - - Number = Bool ? Number : Number # 9 - Bool = Bool ? Bool : Bool # 10 -end -# Forbid ? = ? -addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)]))) -# Order = -addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) -# Order + -addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) - -examples = [ - IOExample(Dict(:x => 0), 2) - IOExample(Dict(:x => 1), 3) - IOExample(Dict(:x => 2), 1) -] -problem = Problem(examples) -solution = search(g₅, problem, :Element) +# ╔═╡ 069591a3-b89b-4fc6-afba-2145e32852b7 +iterator_gs = GeneticSearchIterator(grammar, :X, examples_gs, population_size = 10, mutation_probability = 0.8, maximum_initial_population_depth = 3) -@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5 +# ╔═╡ 5bef5754-d81b-4160-8ed6-396d02853d9a +program_gs, error_gs = synth(problem_gs, iterator_gs) # ╔═╡ 00000000-0000-0000-0000-000000000001 PLUTO_PROJECT_TOML_CONTENTS = """ [deps] HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" +HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" @@ -509,6 +495,7 @@ PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" [compat] HerbConstraints = "~0.2.2" +HerbCore = "~0.3.0" HerbGrammar = "~0.3.0" HerbInterpret = "~0.1.3" HerbSearch = "~0.3.0" @@ -522,7 +509,7 @@ PLUTO_MANIFEST_TOML_CONTENTS = """ julia_version = "1.10.4" manifest_format = "2.0" -project_hash = "5ee7afaa57cf163e03a42d572d1cb2cb022598e5" +project_hash = "e8df13c29bfd5a0060b6c72e678efa3df418b0d7" [[deps.AbstractPlutoDingetjes]] deps = ["Pkg"] @@ -1251,6 +1238,7 @@ version = "0.13.1+0" # ╟─dddca175-3d88-45ce-90da-575c0ba38175 # ╠═a93d954d-0f09-4b6d-a3a5-62bfe39681e2 # ╠═6ab37bbc-73e2-4d9a-a8b2-e715a0b61c8f +# ╟─61cee94c-2481-4268-823b-ca596592b63c # ╠═c4441fa4-09ec-4b81-9681-b13b93a9c9c0 # ╟─67931820-0f43-41e1-898e-5b5bd55e30d1 # ╠═e41c61a4-0b2c-46da-8f7b-fe6dc529c544 @@ -1259,49 +1247,82 @@ version = "0.13.1+0" # ╟─e9c6bc00-21f5-4a99-8bec-63cf2156c233 # ╠═338f19f1-3a62-4462-b8dc-24424d7644f2 # ╠═542cd47e-74cd-4b6f-acc9-bf524222e583 +# ╟─63e97576-1c34-464d-a106-d59d5fb1ee38 # ╠═a6fb2e91-b73a-4032-930f-d884abd539e2 -# ╠═4b49f206-970e-47d2-af65-336ba65b1019 +# ╟─58c1a904-4d87-43f7-bcc3-884a8663c1da # ╟─35405357-179b-4e77-9bdc-edf5a550b36d # ╠═3954dd49-07a2-4ec2-91b4-9c9596d5c264 -# ╠═9892e91b-9115-4520-9637-f8d7c8905825 -# ╠═a4e58bbc-7c14-4fce-b35d-688b56e0eb61 +# ╟─9892e91b-9115-4520-9637-f8d7c8905825 +# ╟─a4e58bbc-7c14-4fce-b35d-688b56e0eb61 # ╠═9fb40ceb-8d41-491b-8941-20a8b240eb82 # ╠═94e0d676-a9c7-4291-8696-15301e541c30 # ╠═a4a7daed-f89b-44ad-8787-9199c05bf046 # ╠═4821fd3a-ff2d-4991-99ad-76608d11b1da -# ╠═8d91b2e3-30b5-4ea2-bd3f-3055bb6d1d5a +# ╟─b2eb08d7-3e53-46c5-84b1-e1fa0e07e291 +# ╠═606070e1-83a7-4cca-a716-4fa459f78772 # ╠═52332fa2-7ea7-4226-9460-e0bbc905c619 -# ╟─c26fea48-f812-4f24-bb8c-680e14a55df7 -# ╠═57f5face-d9d5-441a-8e0e-6ef6319fc178 +# ╟─c262116e-138e-4133-a032-d2f50bfbf5bd # ╟─9b4b21e0-dc6a-43ae-a511-79988ee99001 +# ╟─115c02c9-ae0c-4623-a61d-831fc6ad55a2 # ╠═3af650d9-19c6-4351-920d-d2361091f628 +# ╟─4cb08dba-aea5-4c31-998c-844d1fce8c81 +# ╠═f2521a57-267e-4b49-9179-4e9c2e6bdec7 +# ╟─bf038215-1ecf-4e1c-a9be-e133e4497293 +# ╠═6aec7358-225a-4764-9a36-da86234b6cf8 +# ╠═54ecf6b9-3341-49e0-92e9-71190e06d61b # ╟─d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 -# ╠═da7f326c-f0d5-4837-ac9a-5bcad604566e -# ╟─0020b79a-6352-4e2d-93f6-2a1d7b03ae2c +# ╠═07b54acf-0c0d-40ac-ae18-fb26094b4aca +# ╠═9efb01cf-b190-4e3e-aa19-11499ba46489 +# ╠═0020b79a-6352-4e2d-93f6-2a1d7b03ae2c # ╠═789150a8-862c-48c3-88b8-710b81ab34cf -# ╟─243165be-a9d2-484d-8046-811a2b0ba139 -# ╠═3d01c6f1-80a6-4904-97e2-775170e97bbf -# ╟─168c71bf-ce5b-4ab3-b29a-5996981c42a5 +# ╠═db5be2c3-0b36-40b4-bf14-20e2c7063ad7 +# ╠═4048ff37-e7d1-44ee-bfa3-aa058b6f53b6 +# ╠═658c55ac-88c8-4657-a8eb-c9c9b91d0ded +# ╠═243165be-a9d2-484d-8046-811a2b0ba139 +# ╠═aa45e602-6219-44ea-82a0-b97639f6a450 +# ╠═3f18989a-ae25-4a66-9930-4459407af695 +# ╠═4b97602a-5226-429f-86ea-8ecac3c807fa +# ╟─ed198b79-1b95-4531-b148-c1037cfdacf4 +# ╠═3c7f6e75-ea70-4ccd-8ad6-5876d50a587d +# ╠═d62918e4-4113-45bd-81ea-d17d007b83f5 +# ╟─7480d1e4-e417-4d87-80b7-513a098da70e +# ╠═7e2af72d-b71c-4234-9bca-cb9a90732a91 +# ╠═00d05a7e-ca79-4d6b-828d-b24ef1cb80a2 +# ╠═c70eb1a1-7d6b-413f-bd85-77e1b8c30b94 +# ╠═168c71bf-ce5b-4ab3-b29a-5996981c42a5 # ╠═a4b522cf-78f0-4d44-88c8-82dd0cdbf952 # ╠═f313edb9-8fd9-4d78-88cd-89226f5c769d # ╟─0da9053a-959b-471e-8918-662ec63da71c +# ╠═4df84c71-99b5-487f-9db5-c048c0c74151 +# ╠═afe1872c-6641-4fa0-a53e-50c6b4a712ee +# ╠═969db94c-d583-40d1-a058-097f8117c2e9 # ╠═0a30fb40-cd45-4661-a501-ae8e45f1e07e +# ╠═82f7ffa5-2bdc-4153-85fa-d7aca063da12 # ╟─700270ea-90bd-474b-91d9-0e5ed329776a -# ╠═8731f312-bfcf-4f6c-86fa-60014dc146d6 -# ╠═46ca65d1-d876-4abc-a562-8d266bad195f +# ╠═e6e5c63b-34e8-40d6-bc12-bd31f40b4b16 +# ╠═2397f65f-e6b4-4f11-bf66-83440c58b688 +# ╠═7c738d7b-bf05-40c7-b3b7-1512fbae7299 +# ╠═33af905e-e8ca-425d-9805-eb02bec7c26b +# ╟─4208cd17-6c2a-4480-a535-5a7358e0ab25 +# ╠═bea28b36-6613-4895-98f9-27dfd9e57b09 +# ╠═aa95cb5e-926d-4119-8d08-353f37a59039 +# ╠═285043ef-c295-400f-91c5-f3c6c69ac2bf +# ╠═36f0e0cf-c871-42c9-956e-054767cbf693 # ╟─599194a8-3f47-4917-9143-a5fe0d43029f -# ╠═cb5935ed-d89b-4e25-9243-d201daf18e78 -# ╠═d0c3742e-23e5-4ca1-9e83-b6d1e8a7cded +# ╠═dd6aee87-cd96-4be1-b8fb-03fffee5ea43 +# ╠═f6a3ed3f-bcf9-4f22-a86e-33255d88e0b2 +# ╠═e25d115f-7549-4606-b96c-9ef700810f7b +# ╠═94f2bd5e-e11e-42e7-9a3e-3c9d5ae43cd4 +# ╠═eb851d7b-803e-45f6-ad10-fa0bde78826a +# ╠═73304e3f-05bf-4f0c-9acd-fc8afa87b460 +# ╠═07f11eb1-6b45-441a-a481-57628bad23ae +# ╠═4ff69f0a-6626-4593-b361-a2387eecc731 +# ╠═0f7f228c-4f95-4f1a-9ca2-2d03384a00c0 # ╟─5df0ba53-b528-4baf-9980-cafe5d73f9dd -# ╠═a434645b-d592-4162-a8b4-b4b04cea30a9 -# ╟─38cd9032-27c0-4179-a536-ce59a42ff16a -# ╠═f8415d48-a51d-4845-8425-fd61ed79c06e -# ╟─7f88bf4f-d82c-4e5a-a9eb-93870954c79e -# ╟─53598f8f-9973-4cad-af0c-280f5531bb21 -# ╠═bc971069-08c4-493c-a917-8092493d3233 -# ╠═61a60c9c-36cf-4e86-b697-748b3524d3b4 -# ╠═a5b17c81-b667-4b1c-ab15-ddf1a162683b -# ╠═e41feac7-6de6-4223-8a21-341b85da52c0 -# ╠═1c4db74a-4caa-4ce5-815f-b631365c5129 +# ╠═99ea1c20-ca2c-4d77-bc3b-06814db1d666 +# ╠═d991edb9-2291-42a7-97ff-58c456515505 +# ╠═d17c2a79-3c14-4c50-a4aa-465f3da011a2 +# ╠═069591a3-b89b-4fc6-afba-2145e32852b7 +# ╠═5bef5754-d81b-4160-8ed6-396d02853d9a # ╟─00000000-0000-0000-0000-000000000001 # ╟─00000000-0000-0000-0000-000000000002 From 661270e523b8e572311b995e593205272cf99215 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Mon, 30 Sep 2024 11:59:44 +0200 Subject: [PATCH 72/75] Better way to use rightmost-heuristic. Improvements on tutorial text. --- docs/src/tutorials/advanced_search.jl | 193 ++++++++++++-------------- 1 file changed, 86 insertions(+), 107 deletions(-) diff --git a/docs/src/tutorials/advanced_search.jl b/docs/src/tutorials/advanced_search.jl index e9fe327..3c42a59 100644 --- a/docs/src/tutorials/advanced_search.jl +++ b/docs/src/tutorials/advanced_search.jl @@ -8,12 +8,9 @@ using InteractiveUtils using PlutoUI # ╔═╡ c4441fa4-09ec-4b81-9681-b13b93a9c9c0 -using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints, HerbCore -# ╔═╡ aa45e602-6219-44ea-82a0-b97639f6a450 -using HerbCore - # ╔═╡ dddca175-3d88-45ce-90da-575c0ba38175 md""" # Advanced Search Procedures in Herb.jl @@ -183,7 +180,7 @@ To explore `BFSIterator`, we define another very simple grammar. """ # ╔═╡ 3af650d9-19c6-4351-920d-d2361091f628 -begin g3 = @cfgrammar begin +begin g_3 = @cfgrammar begin Real = 1 | 2 Real = Real * Real end @@ -195,7 +192,7 @@ Next, we define a `BFSIterator` with a `max_depth` of 2 and a `max_size` of infi """ # ╔═╡ f2521a57-267e-4b49-9179-4e9c2e6bdec7 -iteratorbfs = BFSIterator(g3, :Real, max_depth=2, max_size=typemax(Int)) +iterator_bfs = BFSIterator(g_3, :Real, max_depth=2, max_size=typemax(Int)) # ╔═╡ bf038215-1ecf-4e1c-a9be-e133e4497293 md""" @@ -203,10 +200,10 @@ To see all possible solution programs the iterator explores, we use `collect`. I """ # ╔═╡ 6aec7358-225a-4764-9a36-da86234b6cf8 -programsbfs = collect(iteratorbfs) +programs_bfs = collect(iterator_bfs) # ╔═╡ 54ecf6b9-3341-49e0-92e9-71190e06d61b -println(programsbfs) +println(programs_bfs) # ╔═╡ d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 md""" @@ -224,78 +221,45 @@ answer_programs = [ ] # ╔═╡ 9efb01cf-b190-4e3e-aa19-11499ba46489 -println(all(p ∈ programsbfs for p ∈ answer_programs)) +println(all(p ∈ programs_bfs for p ∈ answer_programs)) # ╔═╡ 0020b79a-6352-4e2d-93f6-2a1d7b03ae2c md""" #### Depth-First Search -The `DFSIterator` looks at one branch of the search tree at a time, exploring it unitl a correct program is found or the specified `max_depth` is reached. Only then, the next branch is considered. +The `DFSIterator` explores one branch of the search tree at a time, fully traversing it unitl a correct program is found or the specified `max_depth` is reached. Only after completing the current branch, it proceeds to the next branch. -Let's `collect` the the trees/programs (TODO), using the same grammar but this time `DFSIterator`. +As before, we `collect` the candidate programs using the same grammar, but a `DFSIterator`. """ -# ╔═╡ 789150a8-862c-48c3-88b8-710b81ab34cf -begin - g1 = @cfgrammar begin - Real = 1 | 2 - Real = Real * Real - end - -end - # ╔═╡ db5be2c3-0b36-40b4-bf14-20e2c7063ad7 -iteratordfs = DFSIterator(g3, :Real, max_depth=2, max_size=typemax(Int)) +iterator_dfs = DFSIterator(g_3, :Real, max_depth=2, max_size=typemax(Int)) # ╔═╡ 4048ff37-e7d1-44ee-bfa3-aa058b6f53b6 -programsdfs = collect(iteratordfs) +programs_dfs = collect(iterator_dfs) # ╔═╡ 658c55ac-88c8-4657-a8eb-c9c9b91d0ded -println(programsdfs) +println(programs_dfs) # ╔═╡ 243165be-a9d2-484d-8046-811a2b0ba139 md""" -`DFSIterator` also uses by default a **leftmost-first** heuristic. If we want to use a **rightmost-first** heuristic instead, we can create our own iterator `DFSIteratorRightmost`. Also see the tutorial [Top Down Iterator](https://herb-ai.github.io/Herb.jl/dev/tutorials/TopDown/) for how to build iterators is Herb.jl. - - - - -TODO: How to use rightmost heuristic? +`DFSIterator` also uses by default a **leftmost-first** heuristic. If we want to use a **rightmost-first** heuristic instead, we can create our own iterator `DFSIteratorRightmost` as a sub-type of `TopDownIterator`, using the `@programiterator` macro. Then we implement the functions `priority_function` and `hole_heuristic`. Also see the tutorial [Top Down Iterator](https://herb-ai.github.io/Herb.jl/dev/tutorials/TopDown/) for how to build iterators is Herb.jl. """ -# ╔═╡ 3f18989a-ae25-4a66-9930-4459407af695 -# to modify the heuristics, we create a new type -# abstract type DFSIteratorRightmost <: TopDownIterator end - # ╔═╡ 4b97602a-5226-429f-86ea-8ecac3c807fa @programiterator DFSIteratorRightmost() <: TopDownIterator # ╔═╡ ed198b79-1b95-4531-b148-c1037cfdacf4 md""" -By default, `priority_function` for a `TopDownIterator` is that of a BFS iterator. Hence, we need to overwrite it: +By default, `priority_function` for a `TopDownIterator` is that of a BFS iterator. Hence, we need to provide a new implementation. For this, we can make use of the existing `priority_function` of `DFSIterator`. """ -# ╔═╡ 3c7f6e75-ea70-4ccd-8ad6-5876d50a587d -function priority_function( - ::DFSIteratorRightmost, - ::AbstractGrammar, - ::AbstractRuleNode, - parent_value::Union{Real, Tuple{Vararg{Real}}}, - isrequeued::Bool -) - if isrequeued - return parent_value; - end - return parent_value - 1; -end - # ╔═╡ d62918e4-4113-45bd-81ea-d17d007b83f5 -# Alternative priority_function(::DFSIteratorRightmost) = priority_function(DFSIterator()) # ╔═╡ 7480d1e4-e417-4d87-80b7-513a098da70e md""" -Next, we need to overwrite the `hole_heuristic` to rightmost-first. +Next, we need to implement the `hole_heuristic` to be rightmost-first. """ # ╔═╡ 7e2af72d-b71c-4234-9bca-cb9a90732a91 @@ -304,24 +268,41 @@ function hole_heuristic(::DFSIteratorRightmost, node::AbstractRuleNode, max_dept end # ╔═╡ 00d05a7e-ca79-4d6b-828d-b24ef1cb80a2 -iteratordfs_rightmost = DFSIteratorRightmost(g3, :Real, max_depth=2, max_size=typemax(Int)) +iteratordfs_rightmost = DFSIteratorRightmost(g_3, :Real, max_depth=2, max_size=typemax(Int)) + +# ╔═╡ e0e8042d-ae41-4046-ab4f-5954a0d1cfb7 +programs_dfs_rightmost = collect(iteratordfs_rightmost) # ╔═╡ c70eb1a1-7d6b-413f-bd85-77e1b8c30b94 -println(collect(iteratordfs_rightmost)) +println(programs_dfs_rightmost) + +# ╔═╡ 02010940-df9f-4847-b0be-0bc9c6bb2ad4 +md""" +We observe that the order of programs has changed. We can also test if both DFS iterators return the same programs: +""" + +# ╔═╡ f5edcb4d-da72-4eeb-b55e-0ace1697133a +Set(programs_dfs)==Set(programs_dfs_rightmost) # ╔═╡ 168c71bf-ce5b-4ab3-b29a-5996981c42a5 md""" ## Stochastic search -Whereas deterministic search methods explore the search space in a predictable way, stochastic ones use randomness. +While deterministic search methods explore the search space in a predictable way, stochastic ones introduce randomness to allow for more flexibility. + +In this section, we will look at the stochastic search algorithms: Metropolis-Hastings (MH), Very Large Scale Neighbourhood Search (VLSNS), and Simulated Annealing (SA). In Herb.jl, all of these search methodsthe share a common supertype `StochasticSearchIterator`, which defines the following fields + - `examples` + - `cost_function` + - `initial_temperature` + - `evaluation_function`. -We will look at several stochastic search algorithms, usinga a new example. +They are customized by overriding the functions `neighbourhood`, `propose`, `accept` and `temperature` as required. We start with a simple grammar and a helper function to create the input-output examples for the problem we want to solve. """ # ╔═╡ a4b522cf-78f0-4d44-88c8-82dd0cdbf952 -grammar = @csgrammar begin +g_4 = @csgrammar begin X = |(1:5) X = X * X X = X + X @@ -335,14 +316,23 @@ function create_problem(f, range=20) return Problem(examples), examples end +# ╔═╡ 8e75ec35-94dc-4292-ab36-83731b3b9318 +md""" +Throughout the stochastic search examples, we will use mean-squared-error as cost function. The cost function helps to guide the search by evaluating how well a candidate program solves the given task. This is used to decide whether a proposed program should be accepted or rejected. +""" + +# ╔═╡ eebb9554-b657-4be3-aecf-c0869a2b38fa +cost_function = mean_squared_error + # ╔═╡ 0da9053a-959b-471e-8918-662ec63da71c md""" ### Metropolis-Hastings -Metropolis-Hastings (MH) is a method to produce samples from a distribution that may otherwise be difficult to sample. In the context of program synthesis, we want samples from a distribution of programs, based on the grammar. +Metropolis-Hastings (MH) is a method to produce samples from a distribution that may otherwise be difficult to sample. In the context of program synthesis, we sample from a distribution of programs defined by the grammar. + For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). -The example below uses a simple arithmetic example. Try run the code block (TODO) multiple times. You will get different programs every time, as the search is stochastic. +To illustrate MH, we use a simple arithmetic example. """ # ╔═╡ 4df84c71-99b5-487f-9db5-c048c0c74151 @@ -351,14 +341,16 @@ e_mh = x -> x * x + 4 # ╔═╡ afe1872c-6641-4fa0-a53e-50c6b4a712ee problem_mh, examples_mh = create_problem(e_mh) -# ╔═╡ 969db94c-d583-40d1-a058-097f8117c2e9 -cost_function = mean_squared_error - +# ╔═╡ 69e91ae9-8475-47dd-826e-8c229faa11e8 +md""" +Run the following code block to define the iterator and perform the program synthesis multiple times. Since the search process is stochastic, you will likely see different solution programs with each run. +""" # ╔═╡ 0a30fb40-cd45-4661-a501-ae8e45f1e07e begin - iteratormh = MHSearchIterator(grammar, :X, examples_mh, cost_function, max_depth=3) #TODO: What should max_depth be? + iteratormh = MHSearchIterator(g_4, :X, examples_mh, cost_function, max_depth=3) programmh = synth(problem_mh, iteratormh) + println("Sollution using MH: ", programmh) end # ╔═╡ 82f7ffa5-2bdc-4153-85fa-d7aca063da12 @@ -368,43 +360,29 @@ programmh md""" ### Very Large Scale Neighbourhood Search -The second stochastic search method we consider is Very Large Scale Neighbourhood Search (VLSN). In each iteration, the neighbourhood of the current candidate program is searched for the local optimum to find a better candidate. - -TODO: more? +TODO: what do we want to show with the examples? +The second stochastic search method we consider is Very Large Scale Neighbourhood Search (VLSN). In each iteration, the algorithm searches the neighbourhood of the current candidate program for a local optimum, aiming to find a better candidate solution. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). Given the same grammar as before, we can try it with some simple examples. - - - -Using -large neighborhoods makes it possible to find better candidate solutions in each it- -eration and hence traverse a more promising search path - -#### Example I """ # ╔═╡ e6e5c63b-34e8-40d6-bc12-bd31f40b4b16 e_vlsn = x -> 10 # ╔═╡ 2397f65f-e6b4-4f11-bf66-83440c58b688 -problem_vlsn, examples_vlsn = create_problem(e_vlsn) +problem_vlsn1, examples_vlsn1 = create_problem(e_vlsn) # ╔═╡ 7c738d7b-bf05-40c7-b3b7-1512fbae7299 -iteratorvlsn = VLSNSearchIterator(grammar, :X, examples_vlsn, cost_function, max_depth=2) +iterator_vlsn1 = VLSNSearchIterator(g_4, :X, examples_vlsn1, cost_function, max_depth=2) # ╔═╡ 33af905e-e8ca-425d-9805-eb02bec7c26b -programvlsn = synth(problem_vlsn, iteratorvlsn) -# program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) +program_vlsn1 = synth(problem_vlsn1, iterator_vlsn1) -# ╔═╡ 4208cd17-6c2a-4480-a535-5a7358e0ab25 -md""" -#### Example 2 - -TODO: What do we want to show with this example? -""" +# ╔═╡ 7cbaa721-2bab-4a27-8c18-83f3f81bb336 +println("Solution: ", program_vlsn1) # ╔═╡ bea28b36-6613-4895-98f9-27dfd9e57b09 e_vlsn2 = x -> x @@ -413,16 +391,21 @@ e_vlsn2 = x -> x problem_vlsn2, examples_vlsn2 = create_problem(e_vlsn2) # ╔═╡ 285043ef-c295-400f-91c5-f3c6c69ac2bf -iterator_vlsn2 = VLSNSearchIterator(grammar, :X, examples_vlsn2, cost_function, max_depth=1) +iterator_vlsn2 = VLSNSearchIterator(g_4, :X, examples_vlsn2, cost_function, max_depth=1) # ╔═╡ 36f0e0cf-c871-42c9-956e-054767cbf693 program_vlsn2 = synth(problem_vlsn2, iterator_vlsn2) +# ╔═╡ bff8026e-49ee-4200-ae86-7687767389a5 +println("Solution: ", program_vlsn2) + # ╔═╡ 599194a8-3f47-4917-9143-a5fe0d43029f md""" ### Simulated Annealing -Simualted Annealing (SA) explores smaller, incremental changes in the candidate program per iteration and refines the solution over time. It is a modified hill-climbing algorithm that, instead of picking the best move (program modification?), picks a random move. If the move improves the solution (candidate program), it is always accepted. Ocassionally, a move towards a worse solution is accepted to escape loca optima. However, this strategy follows a cooling (annealing) schedule, i.e., it starts exploring widely (at high temperature) and gradually becomes more selective (at low temperture), accepting worse solutions less often. +Simulated Annealing (SA) explores smaller, incremental changes to the candidate program in each iteration, gradually refining the solution. It is a variation of the hill-climbing algorithm: Instead of always selecting the best move, SA picks a random move. If the move improves the solution (i.e., the candidate program), it is accepted. + +Occasionally, SA will accept a move that worsens the solution. This allows the algorithm to escape local optima and explore more of the solution space. However, this strategy follows a cooling (annealing) schedule: at the beginning (high temperature), the algorithm explores more broadly and is more likely to accept worse solutions. As the temperature decreases, it becomes more selective, accepting worse solutions less often. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). """ @@ -432,17 +415,14 @@ md""" We use the same example as for MH. SA additionally has the option to specify the `initial_temperature` for the annealing (default `initial_temperature=1`). Let's see what effect changing the temperature from 1 to 2 has on the solution program. """ -# ╔═╡ f6a3ed3f-bcf9-4f22-a86e-33255d88e0b2 -e_sa = x -> x * x + 4 - # ╔═╡ e25d115f-7549-4606-b96c-9ef700810f7b -problem_sa, examples_sa = create_problem(e_sa) +problem_sa, examples_sa = create_problem(e_mh) # ╔═╡ 94f2bd5e-e11e-42e7-9a3e-3c9d5ae43cd4 initial_temperature1 = 1 # ╔═╡ eb851d7b-803e-45f6-ad10-fa0bde78826a -iterator_sa1 = SASearchIterator(grammar, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature1) +iterator_sa1 = SASearchIterator(g_4, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature1) # ╔═╡ 73304e3f-05bf-4f0c-9acd-fc8afa87b460 program_sa1 = synth(problem_sa, iterator_sa1) @@ -451,14 +431,16 @@ program_sa1 = synth(problem_sa, iterator_sa1) initial_temperature2 = 2 # ╔═╡ 4ff69f0a-6626-4593-b361-a2387eecc731 -iterator_sa2 = SASearchIterator(grammar, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature2) +iterator_sa2 = SASearchIterator(g_4, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature2) # ╔═╡ 0f7f228c-4f95-4f1a-9ca2-2d03384a00c0 # ╔═╡ 5df0ba53-b528-4baf-9980-cafe5d73f9dd md""" -### Genetic Search +## Genetic Search + +TODO: talk a bit about the iterator. Genetic search is a type of evolutionary algorithm, which simulates the process of natural selection. It evolves a population of candidate programs through operations like mutation, crossover (recombination), and selection. Then, the fitness of each program is assessed (i.e., how well it satisfies the given specifications). Only the 'fittest' programs are selected for the next generation, thus gradually refining the population of candidate programs. @@ -473,11 +455,8 @@ e_gs = x -> 3 * x * x + (x + 2) # ╔═╡ d991edb9-2291-42a7-97ff-58c456515505 problem_gs, examples_gs = create_problem(e_gs) -# ╔═╡ d17c2a79-3c14-4c50-a4aa-465f3da011a2 - - # ╔═╡ 069591a3-b89b-4fc6-afba-2145e32852b7 -iterator_gs = GeneticSearchIterator(grammar, :X, examples_gs, population_size = 10, mutation_probability = 0.8, maximum_initial_population_depth = 3) +iterator_gs = GeneticSearchIterator(g_4, :X, examples_gs, population_size = 10, mutation_probability = 0.8, maximum_initial_population_depth = 3) # ╔═╡ 5bef5754-d81b-4160-8ed6-396d02853d9a program_gs, error_gs = synth(problem_gs, iterator_gs) @@ -1273,29 +1252,30 @@ version = "0.13.1+0" # ╟─d3ff497e-d2c2-4df6-8e4c-cdca70fd0677 # ╠═07b54acf-0c0d-40ac-ae18-fb26094b4aca # ╠═9efb01cf-b190-4e3e-aa19-11499ba46489 -# ╠═0020b79a-6352-4e2d-93f6-2a1d7b03ae2c -# ╠═789150a8-862c-48c3-88b8-710b81ab34cf +# ╟─0020b79a-6352-4e2d-93f6-2a1d7b03ae2c # ╠═db5be2c3-0b36-40b4-bf14-20e2c7063ad7 # ╠═4048ff37-e7d1-44ee-bfa3-aa058b6f53b6 # ╠═658c55ac-88c8-4657-a8eb-c9c9b91d0ded -# ╠═243165be-a9d2-484d-8046-811a2b0ba139 -# ╠═aa45e602-6219-44ea-82a0-b97639f6a450 -# ╠═3f18989a-ae25-4a66-9930-4459407af695 +# ╟─243165be-a9d2-484d-8046-811a2b0ba139 # ╠═4b97602a-5226-429f-86ea-8ecac3c807fa # ╟─ed198b79-1b95-4531-b148-c1037cfdacf4 -# ╠═3c7f6e75-ea70-4ccd-8ad6-5876d50a587d # ╠═d62918e4-4113-45bd-81ea-d17d007b83f5 # ╟─7480d1e4-e417-4d87-80b7-513a098da70e # ╠═7e2af72d-b71c-4234-9bca-cb9a90732a91 # ╠═00d05a7e-ca79-4d6b-828d-b24ef1cb80a2 +# ╠═e0e8042d-ae41-4046-ab4f-5954a0d1cfb7 # ╠═c70eb1a1-7d6b-413f-bd85-77e1b8c30b94 -# ╠═168c71bf-ce5b-4ab3-b29a-5996981c42a5 +# ╟─02010940-df9f-4847-b0be-0bc9c6bb2ad4 +# ╠═f5edcb4d-da72-4eeb-b55e-0ace1697133a +# ╟─168c71bf-ce5b-4ab3-b29a-5996981c42a5 # ╠═a4b522cf-78f0-4d44-88c8-82dd0cdbf952 # ╠═f313edb9-8fd9-4d78-88cd-89226f5c769d +# ╟─8e75ec35-94dc-4292-ab36-83731b3b9318 +# ╠═eebb9554-b657-4be3-aecf-c0869a2b38fa # ╟─0da9053a-959b-471e-8918-662ec63da71c # ╠═4df84c71-99b5-487f-9db5-c048c0c74151 # ╠═afe1872c-6641-4fa0-a53e-50c6b4a712ee -# ╠═969db94c-d583-40d1-a058-097f8117c2e9 +# ╟─69e91ae9-8475-47dd-826e-8c229faa11e8 # ╠═0a30fb40-cd45-4661-a501-ae8e45f1e07e # ╠═82f7ffa5-2bdc-4153-85fa-d7aca063da12 # ╟─700270ea-90bd-474b-91d9-0e5ed329776a @@ -1303,14 +1283,14 @@ version = "0.13.1+0" # ╠═2397f65f-e6b4-4f11-bf66-83440c58b688 # ╠═7c738d7b-bf05-40c7-b3b7-1512fbae7299 # ╠═33af905e-e8ca-425d-9805-eb02bec7c26b -# ╟─4208cd17-6c2a-4480-a535-5a7358e0ab25 +# ╠═7cbaa721-2bab-4a27-8c18-83f3f81bb336 # ╠═bea28b36-6613-4895-98f9-27dfd9e57b09 # ╠═aa95cb5e-926d-4119-8d08-353f37a59039 # ╠═285043ef-c295-400f-91c5-f3c6c69ac2bf # ╠═36f0e0cf-c871-42c9-956e-054767cbf693 +# ╠═bff8026e-49ee-4200-ae86-7687767389a5 # ╟─599194a8-3f47-4917-9143-a5fe0d43029f -# ╠═dd6aee87-cd96-4be1-b8fb-03fffee5ea43 -# ╠═f6a3ed3f-bcf9-4f22-a86e-33255d88e0b2 +# ╟─dd6aee87-cd96-4be1-b8fb-03fffee5ea43 # ╠═e25d115f-7549-4606-b96c-9ef700810f7b # ╠═94f2bd5e-e11e-42e7-9a3e-3c9d5ae43cd4 # ╠═eb851d7b-803e-45f6-ad10-fa0bde78826a @@ -1321,7 +1301,6 @@ version = "0.13.1+0" # ╟─5df0ba53-b528-4baf-9980-cafe5d73f9dd # ╠═99ea1c20-ca2c-4d77-bc3b-06814db1d666 # ╠═d991edb9-2291-42a7-97ff-58c456515505 -# ╠═d17c2a79-3c14-4c50-a4aa-465f3da011a2 # ╠═069591a3-b89b-4fc6-afba-2145e32852b7 # ╠═5bef5754-d81b-4160-8ed6-396d02853d9a # ╟─00000000-0000-0000-0000-000000000001 From 4e9ebd3ab955df97fe160c33c43c20fc64a094fc Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Mon, 30 Sep 2024 12:32:24 +0200 Subject: [PATCH 73/75] Deletes md/notbook files of tutorials that are replaced by Pluto notebooks. Update htmls for docs. --- docs/src/tutorials/abstract_syntax_trees.html | 18 + .../src/tutorials/abstract_syntax_trees.ipynb | 750 ------------------ docs/src/tutorials/abstract_syntax_trees.md | 435 ---------- docs/src/tutorials/advanced_search.ipynb | 616 -------------- docs/src/tutorials/advanced_search.md | 403 ---------- docs/src/tutorials/defining_grammars.html | 17 + docs/src/tutorials/defining_grammars.ipynb | 531 ------------- docs/src/tutorials/defining_grammars.md | 590 -------------- .../getting_started_with_constraints.html | 4 +- .../getting_started_with_constraints.ipynb | 481 ----------- .../getting_started_with_constraints.md | 738 ----------------- .../tutorials/getting_started_with_herb.html | 4 +- .../tutorials/getting_started_with_herb.ipynb | 346 -------- .../tutorials/getting_started_with_herb.md | 180 ----- .../tutorials/working_with_interpreters.html | 4 +- .../tutorials/working_with_interpreters.ipynb | 271 ------- .../tutorials/working_with_interpreters.md | 141 ---- 17 files changed, 41 insertions(+), 5488 deletions(-) create mode 100644 docs/src/tutorials/abstract_syntax_trees.html delete mode 100644 docs/src/tutorials/abstract_syntax_trees.ipynb delete mode 100644 docs/src/tutorials/abstract_syntax_trees.md delete mode 100644 docs/src/tutorials/advanced_search.ipynb delete mode 100644 docs/src/tutorials/advanced_search.md create mode 100644 docs/src/tutorials/defining_grammars.html delete mode 100644 docs/src/tutorials/defining_grammars.ipynb delete mode 100644 docs/src/tutorials/defining_grammars.md delete mode 100644 docs/src/tutorials/getting_started_with_constraints.ipynb delete mode 100644 docs/src/tutorials/getting_started_with_constraints.md delete mode 100644 docs/src/tutorials/getting_started_with_herb.ipynb delete mode 100644 docs/src/tutorials/getting_started_with_herb.md delete mode 100644 docs/src/tutorials/working_with_interpreters.ipynb delete mode 100644 docs/src/tutorials/working_with_interpreters.md diff --git a/docs/src/tutorials/abstract_syntax_trees.html b/docs/src/tutorials/abstract_syntax_trees.html new file mode 100644 index 0000000..f002fb3 --- /dev/null +++ b/docs/src/tutorials/abstract_syntax_trees.html @@ -0,0 +1,18 @@ + + + + + + + + +
\ No newline at end of file diff --git a/docs/src/tutorials/abstract_syntax_trees.ipynb b/docs/src/tutorials/abstract_syntax_trees.ipynb deleted file mode 100644 index 53be308..0000000 --- a/docs/src/tutorials/abstract_syntax_trees.ipynb +++ /dev/null @@ -1,750 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "adb6401d-97cf-4926-86d1-d93cd888cced", - "metadata": {}, - "source": [ - "# Herb tutorial: Abstract syntax trees" - ] - }, - { - "cell_type": "markdown", - "id": "6aab945c-b81f-43f3-bcb3-36521ed3a50e", - "metadata": {}, - "source": [ - "In this tutorial, you will learn\n", - " \n", - "- How to represent a computer program as an abstract syntax tree in Herb.\n", - "- How to replace parts of the tree to modify the program." - ] - }, - { - "cell_type": "markdown", - "id": "56460a31-bfdd-4c33-8a2b-a0db1430477e", - "metadata": {}, - "source": [ - "## Abstract syntax trees\n", - "\n", - "The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammar, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. \n", - "\n", - "In the context of program synthesis, ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found.\n", - "\n", - "Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. \n", - "An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. " - ] - }, - { - "cell_type": "markdown", - "id": "3f28908e-adcf-47ff-b369-556bc6e2dae4", - "metadata": {}, - "source": [ - "## A simple example program\n", - "\n", - "We first consider the simple program 5*(x+3). We will define a grammar that is sufficient to represent this program and use it to construct a AST for our program." - ] - }, - { - "cell_type": "markdown", - "id": "4d6961c8-2fd5-4cd9-b4a1-166d46500aaa", - "metadata": {}, - "source": [ - "### Define the grammar" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "69d543a7-3067-4376-b1bb-1a4f9217b786", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Number = 0\n", - "2: Number = 1\n", - "3: Number = 2\n", - "4: Number = 3\n", - "5: Number = 4\n", - "6: Number = 5\n", - "7: Number = 6\n", - "8: Number = 7\n", - "9: Number = 8\n", - "10: Number = 9\n", - "11: Number = x\n", - "12: Number = Number + Number\n", - "13: Number = Number * Number\n" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "using HerbCore, HerbGrammar, HerbInterpret\n", - "\n", - "grammar = @csgrammar begin\n", - " Number = |(0:9)\n", - " Number = x\n", - " Number = Number + Number\n", - " Number = Number * Number\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "id": "8de36f71-5051-4d1b-b725-c2a94fbe9119", - "metadata": {}, - "source": [ - "### Construct the syntax tree" - ] - }, - { - "cell_type": "markdown", - "id": "33b9e13e-f444-496f-8931-56a87887ae9c", - "metadata": {}, - "source": [ - "The AST of this program is shown in the diagram below. The number in each node refers to the index of the corresponding rule in our grammar. " - ] - }, - { - "cell_type": "markdown", - "id": "5578f49b-741b-4f4a-a660-ce5bb4673ea1", - "metadata": {}, - "source": [ - "```mermaid\n", - " flowchart \n", - " id1((13)) ---\n", - " id2((6))\n", - " id1 --- id3((12))\n", - " id4((11))\n", - " id5((4))\n", - " id3 --- id4\n", - " id3 --- id5\n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "a41f36e1-ce66-4303-8ad1-d74aeba72a70", - "metadata": {}, - "source": [ - "In `Herb.jl`, the `HerbCore.RuleNode` is used to represent both an individual node, but also entire ASTs or sub-trees. This is achieved by nesting instances of `RuleNode`. A `RuleNode` can be instantiated by providing the index of the grammar rule that the node represents and a vector of child nodes. " - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "9c61d359-1f38-410e-b654-5d36bc9a1d4e", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "13{6,12{11,4}}" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "syntaxtree = RuleNode(13, [RuleNode(6), RuleNode(12, [RuleNode(11), RuleNode(4)])])" - ] - }, - { - "cell_type": "markdown", - "id": "da78a5d9-038e-4d97-ab1a-32efc830c1e8", - "metadata": {}, - "source": [ - "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.execute_on_input`." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "7c933e57-03f8-41ac-a464-70f95b9505c0", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "5 * (x + 3)\n" - ] - } - ], - "source": [ - "program = rulenode2expr(syntaxtree, grammar)\n", - "println(program)" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "32fbb714-25aa-40d5-9c0b-c20ba8999d0c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "65" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# test solution on inputs\n", - "output = execute_on_input(grammar, syntaxtree, Dict(:x => 10))" - ] - }, - { - "cell_type": "markdown", - "id": "b724f97d-ae36-455c-94b8-10632beffe6e", - "metadata": {}, - "source": [ - "## Another example: FizzBuzz\n", - "\n", - "Let's look at a more interesting example. \n", - "The program `fizbuzz()` is based on the popular _FizzBuzz_ problem. Given an integer number, the program simply returns a `String` of that number, but replace numbers divisible by 3 with `\"Fizz\"`, numbers divisible by 5 with `\"Buzz\"`, and number divisible by both 3 and 5 with `\"FizzBuzz\"`." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "1f41bec4-d081-48bd-9fee-c30021d4a882", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "fizzbuzz (generic function with 1 method)" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "function fizzbuzz(x)\n", - " if x % 5 == 0 && x % 3 == 0\n", - " return \"FizzBuzz\"\n", - " else\n", - " if x % 3 == 0\n", - " return \"Fizz\"\n", - " else\n", - " if x % 5 == 0\n", - " return \"Buzz\"\n", - " else\n", - " return string(x)\n", - " end\n", - " end\n", - " end\n", - "end" - ] - }, - { - "cell_type": "markdown", - "id": "bc5a248d-87e7-4eda-9a3a-80620da45f9c", - "metadata": {}, - "source": [ - "### Define the grammar\n", - "\n", - "Let's define a grammar with all the rules that we need." - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "7b0ea30c-e415-4d7e-ba2f-a1590cd4c8fe", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Int = input1\n", - "2: Int = 0\n", - "3: Int = 3\n", - "4: Int = 5\n", - "5: String = Fizz\n", - "6: String = Buzz\n", - "7: String = FizzBuzz\n", - "8: String = string(Int)\n", - "9: Return = String\n", - "10: Int = Int % Int\n", - "11: Bool = Int == Int\n", - "12: Int = if Bool\n", - " Int\n", - "else\n", - " Int\n", - "end\n", - "13: Bool = Bool && Bool\n" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "grammar_fizzbuzz = @csgrammar begin\n", - " Int = input1\n", - " Int = 0 | 3 | 5\n", - " String = \"Fizz\" | \"Buzz\" | \"FizzBuzz\"\n", - " String = string(Int)\n", - " Return = String\n", - " Int = Int % Int\n", - " Bool = Int == Int\n", - " Int = Bool ? Int : Int\n", - " Bool = Bool && Bool\n", - "end" - ] - }, - { - "cell_type": "markdown", - "id": "f0c06383-ce23-446e-8b9f-b323abacfbcb", - "metadata": {}, - "source": [ - "### Construct the syntax tree" - ] - }, - { - "cell_type": "markdown", - "id": "57fc9e37-d243-49c4-9fc9-7669a3cc7e47", - "metadata": {}, - "source": [ - "Given the grammar, the AST of `fizzbuzz()` looks like this:" - ] - }, - { - "cell_type": "markdown", - "id": "219585f5-a4b2-473a-ad98-b3ceb45e37c0", - "metadata": {}, - "source": [ - "```mermaid\n", - "flowchart \n", - " id1((12)) --- id21((13))\n", - " id1--- id22((9))\n", - " id1--- id23((12))\n", - "\n", - " id21 --- id31((11))\n", - " id21 --- id32((11))\n", - "\n", - " id31 --- id41((10))\n", - " id31 --- id42((2))\n", - "\n", - " id41 --- id51((1))\n", - " id41 --- id52((4))\n", - "\n", - " id32 --- id43((10)) \n", - " id32 --- id44((2))\n", - "\n", - " id43 --- id53((1))\n", - " id43 --- id54((3))\n", - "\n", - " id22 --- id33((7))\n", - " id23 --- id34((11))\n", - "\n", - " id34 --- id45((10))\n", - " id34 --- id46((2))\n", - "\n", - " id45 --- id55((1))\n", - " id45 --- id56((3))\n", - "\n", - " id23 --- id35((9))\n", - " id35 --- id47((5))\n", - "\n", - " id23 --- id36((12))\n", - " id36 --- id48((11))\n", - " id48 --- id57((10))\n", - " id57 --- id61((1))\n", - " id57 --- id62((4))\n", - " id48 --- id58((2))\n", - "\n", - " id36 --- id49((9))\n", - " id49 --- id59((6))\n", - "\n", - " id36 --- id410((9))\n", - " id410 --- id510((8))\n", - " id510 --- id63((1))\n", - " \n", - " \n", - " \n", - "```" - ] - }, - { - "cell_type": "markdown", - "id": "ca814e00-95e9-4b18-971d-ca82335bcb98", - "metadata": {}, - "source": [ - "As before, we use nest instanced of `RuleNode` to implement the AST." - ] - }, - { - "cell_type": "code", - "execution_count": 7, - "id": "9c897ebc-4a79-45be-b349-c376bffa03df", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}}" - ] - }, - "execution_count": 7, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "fizzbuzz_syntaxtree = RuleNode(12, [\n", - " RuleNode(13, [\n", - " RuleNode(11, [\n", - " RuleNode(10, [\n", - " RuleNode(1),\n", - " RuleNode(4)\n", - " ]),\n", - " RuleNode(2)\n", - " ]),\n", - " RuleNode(11, [\n", - " RuleNode(10, [\n", - " RuleNode(1),\n", - " RuleNode(3)\n", - " ]),\n", - " RuleNode(2)\n", - " ])\n", - " ]),\n", - " RuleNode(9, [\n", - " RuleNode(7)\n", - " \n", - " ]),\n", - " RuleNode(12, [\n", - " RuleNode(11, [\n", - " RuleNode(10, [\n", - " RuleNode(1),\n", - " RuleNode(3),\n", - " ]),\n", - " RuleNode(2)\n", - " ]),\n", - " RuleNode(9, [\n", - " RuleNode(5)\n", - " ]),\n", - " RuleNode(12, [\n", - " RuleNode(11, [\n", - " RuleNode(10, [\n", - " RuleNode(1),\n", - " RuleNode(4)\n", - " ]),\n", - " RuleNode(2)\n", - " ]),\n", - " RuleNode(9, [\n", - " RuleNode(6)\n", - " ]),\n", - " RuleNode(9, [\n", - " RuleNode(8, [\n", - " RuleNode(1)\n", - " ])\n", - " ])\n", - " ])\n", - " ]) \n", - " ])" - ] - }, - { - "cell_type": "markdown", - "id": "ac0f888f-0376-48b2-bc68-cbbde67f650f", - "metadata": {}, - "source": [ - "And we check our syntax tree is correct:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "5a4ba50a-ccda-4e20-9301-051ee839fdf5", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "if input1 % 5 == 0 && input1 % 3 == 0\n", - " \"FizzBuzz\"\n", - "else\n", - " if input1 % 3 == 0\n", - " \"Fizz\"\n", - " else\n", - " if input1 % 5 == 0\n", - " \"Buzz\"\n", - " else\n", - " string(input1)\n", - " end\n", - " end\n", - "end\n" - ] - } - ], - "source": [ - "program = rulenode2expr(fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", - "println(program)" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "9e0b4ec1-eab1-49b9-be2d-c923098d1850", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4-element Vector{Any}:\n", - " \"Fizz\"\n", - " \"Buzz\"\n", - " \"FizzBuzz\"\n", - " \"22\"" - ] - }, - "execution_count": 9, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# test solution on inputs\n", - "input = [Dict(:input1 => 3), Dict(:input1 => 5), Dict(:input1 =>15), Dict(:input1 => 22)]\n", - "output = execute_on_input(grammar_fizzbuzz, fizzbuzz_syntaxtree, input)" - ] - }, - { - "cell_type": "markdown", - "id": "2cd4b5a5-f715-49e7-a3e2-dbc638af8330", - "metadata": {}, - "source": [ - "### Modify the AST/program\n", - "\n", - "There are several ways to modify an AST and hence, a program. You can\n", - "\n", - "- directly replace a node with `HerbCore.swap_node()`\n", - "- insert a rule node with `insert!`\n", - "\n", - "Let's modify our example such that if the input number is divisible by 3, the program returns \"Buzz\" instead of \"Fizz\". \n", - "We use `swap_node()` to replace the node of the AST that corresponds to rule 5 in the grammar (`String = Fizz`) with rule 6 (`String = Buzz`). To do so, `swap_node()` needs the tree that contains the node we want to modify, the new node we want to replace the node with, and the path to that node.\n", - "\n", - "Note that `swap_node()` modifies the tree, hence we make a deep copy of it first." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "65bfb9cc-bbdc-46e5-8ebf-9c15c6424339", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6," - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree)\n", - "newnode = RuleNode(6)\n", - "path = [3, 2, 1]\n", - "swap_node(modified_fizzbuzz_syntaxtree, newnode, path)" - ] - }, - { - "cell_type": "markdown", - "id": "9bc85789-00de-4f54-aa80-1e2ab789750e", - "metadata": {}, - "source": [ - "Let's confirm that we modified the AST, and hence the program, correctly:" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "742f7ee8-c088-4331-b17c-dc5a2078ca6b", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "if input1 % 5 == 0 && input1 % 3 == 0\n", - " \"FizzBuzz\"\n", - "else\n", - " if input1 % 3 == 0\n", - " \"Buzz\"\n", - " else\n", - " if input1 % 5 == 0\n", - " \"Buzz\"\n", - " else\n", - " string(input1)\n", - " end\n", - " end\n", - "end\n" - ] - } - ], - "source": [ - "program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", - "println(program)" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "59d13ab2-7415-4463-89bf-e09ff55a74d8", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4-element Vector{Any}:\n", - " \"Buzz\"\n", - " \"Buzz\"\n", - " \"FizzBuzz\"\n", - " \"22\"" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# test solution on same inputs as before\n", - "output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input)" - ] - }, - { - "cell_type": "markdown", - "id": "550839d5-3219-4097-b015-538d84a4416f", - "metadata": {}, - "source": [ - "An alternative way to modify the AST is by using `insert!()`. This requires to provide the location of the node that we want to as `NodeLoc`. `NodeLoc` points to a node in the tree and consists of the parent and the child index of the node.\n", - "Again, we make a deep copy of the original AST first." - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "74d04a45-1697-4bcd-a5fd-adaa4ef455ff", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "6," - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree)\n", - "# get the node we want to modify and instantiate a NodeLoc from it.\n", - "node = get_node_at_location(modified_fizzbuzz_syntaxtree, [3, 2, 1])\n", - "nodeloc = NodeLoc(node, 0)\n", - "# replace the node\n", - "insert!(node, nodeloc, newnode)" - ] - }, - { - "cell_type": "markdown", - "id": "9f0bf320-92bd-4e64-b8ed-6ea841a91cc0", - "metadata": {}, - "source": [ - "Again, we check that we modified the program as intended:" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "6f965fe6-f5b1-4059-911c-362f4c1731d6", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "if input1 % 5 == 0 && input1 % 3 == 0\n", - " \"FizzBuzz\"\n", - "else\n", - " if input1 % 3 == 0\n", - " \"Buzz\"\n", - " else\n", - " if input1 % 5 == 0\n", - " \"Buzz\"\n", - " else\n", - " string(input1)\n", - " end\n", - " end\n", - "end\n" - ] - } - ], - "source": [ - "program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", - "println(program)" - ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "41395fec-9053-423b-b0fd-7089bb89c2d0", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4-element Vector{Any}:\n", - " \"Buzz\"\n", - " \"Buzz\"\n", - " \"FizzBuzz\"\n", - " \"22\"" - ] - }, - "execution_count": 15, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# test on same inputs as before\n", - "output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10.3", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.10.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/docs/src/tutorials/abstract_syntax_trees.md b/docs/src/tutorials/abstract_syntax_trees.md deleted file mode 100644 index 85b1e31..0000000 --- a/docs/src/tutorials/abstract_syntax_trees.md +++ /dev/null @@ -1,435 +0,0 @@ -# Herb tutorial: Abstract syntax trees - -In this tutorial, you will learn - -- How to represent a computer program as an abstract syntax tree in Herb. -- How to replace parts of the tree to modify the program. - -## Abstract syntax trees - -The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammar, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. - -In the context of program synthesis, ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found. - -Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. -An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. - -## A simple example program - -We first consider the simple program 5*(x+3). We will define a grammar that is sufficient to represent this program and use it to construct a AST for our program. - -### Define the grammar - - -```julia -using HerbCore, HerbGrammar, HerbInterpret - -grammar = @csgrammar begin - Number = |(0:9) - Number = x - Number = Number + Number - Number = Number * Number -end - -``` - - - 1: Number = 0 - 2: Number = 1 - 3: Number = 2 - 4: Number = 3 - 5: Number = 4 - 6: Number = 5 - 7: Number = 6 - 8: Number = 7 - 9: Number = 8 - 10: Number = 9 - 11: Number = x - 12: Number = Number + Number - 13: Number = Number * Number - - - -### Construct the syntax tree - -The AST of this program is shown in the diagram below. The number in each node refers to the index of the corresponding rule in our grammar. - -```mermaid - flowchart - id1((13)) --- - id2((6)) - id1 --- id3((12)) - id4((11)) - id5((4)) - id3 --- id4 - id3 --- id5 -``` - -In `Herb.jl`, the `HerbCore.RuleNode` is used to represent both an individual node, but also entire ASTs or sub-trees. This is achieved by nesting instances of `RuleNode`. A `RuleNode` can be instantiated by providing the index of the grammar rule that the node represents and a vector of child nodes. - - -```julia -syntaxtree = RuleNode(13, [RuleNode(6), RuleNode(12, [RuleNode(11), RuleNode(4)])]) -``` - - - 13{6,12{11,4}} - - -We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.execute_on_input`. - - -```julia -program = rulenode2expr(syntaxtree, grammar) -println(program) -``` - - 5 * (x + 3) - - - -```julia -# test solution on inputs -output = execute_on_input(grammar, syntaxtree, Dict(:x => 10)) -``` - - - 65 - - -## Another example: FizzBuzz - -Let's look at a more interesting example. -The program `fizbuzz()` is based on the popular _FizzBuzz_ problem. Given an integer number, the program simply returns a `String` of that number, but replace numbers divisible by 3 with `"Fizz"`, numbers divisible by 5 with `"Buzz"`, and number divisible by both 3 and 5 with `"FizzBuzz"`. - - -```julia -function fizzbuzz(x) - if x % 5 == 0 && x % 3 == 0 - return "FizzBuzz" - else - if x % 3 == 0 - return "Fizz" - else - if x % 5 == 0 - return "Buzz" - else - return string(x) - end - end - end -end -``` - - - fizzbuzz (generic function with 1 method) - - -### Define the grammar - -Let's define a grammar with all the rules that we need. - - -```julia -grammar_fizzbuzz = @csgrammar begin - Int = input1 - Int = 0 | 3 | 5 - String = "Fizz" | "Buzz" | "FizzBuzz" - String = string(Int) - Return = String - Int = Int % Int - Bool = Int == Int - Int = Bool ? Int : Int - Bool = Bool && Bool -end -``` - - - 1: Int = input1 - 2: Int = 0 - 3: Int = 3 - 4: Int = 5 - 5: String = Fizz - 6: String = Buzz - 7: String = FizzBuzz - 8: String = string(Int) - 9: Return = String - 10: Int = Int % Int - 11: Bool = Int == Int - 12: Int = if Bool - Int - else - Int - end - 13: Bool = Bool && Bool - - - -### Construct the syntax tree - -Given the grammar, the AST of `fizzbuzz()` looks like this: - -```mermaid -flowchart - id1((12)) --- id21((13)) - id1--- id22((9)) - id1--- id23((12)) - - id21 --- id31((11)) - id21 --- id32((11)) - - id31 --- id41((10)) - id31 --- id42((2)) - - id41 --- id51((1)) - id41 --- id52((4)) - - id32 --- id43((10)) - id32 --- id44((2)) - - id43 --- id53((1)) - id43 --- id54((3)) - - id22 --- id33((7)) - id23 --- id34((11)) - - id34 --- id45((10)) - id34 --- id46((2)) - - id45 --- id55((1)) - id45 --- id56((3)) - - id23 --- id35((9)) - id35 --- id47((5)) - - id23 --- id36((12)) - id36 --- id48((11)) - id48 --- id57((10)) - id57 --- id61((1)) - id57 --- id62((4)) - id48 --- id58((2)) - - id36 --- id49((9)) - id49 --- id59((6)) - - id36 --- id410((9)) - id410 --- id510((8)) - id510 --- id63((1)) - - - -``` - -As before, we use nest instanced of `RuleNode` to implement the AST. - - -```julia -fizzbuzz_syntaxtree = RuleNode(12, [ - RuleNode(13, [ - RuleNode(11, [ - RuleNode(10, [ - RuleNode(1), - RuleNode(4) - ]), - RuleNode(2) - ]), - RuleNode(11, [ - RuleNode(10, [ - RuleNode(1), - RuleNode(3) - ]), - RuleNode(2) - ]) - ]), - RuleNode(9, [ - RuleNode(7) - - ]), - RuleNode(12, [ - RuleNode(11, [ - RuleNode(10, [ - RuleNode(1), - RuleNode(3), - ]), - RuleNode(2) - ]), - RuleNode(9, [ - RuleNode(5) - ]), - RuleNode(12, [ - RuleNode(11, [ - RuleNode(10, [ - RuleNode(1), - RuleNode(4) - ]), - RuleNode(2) - ]), - RuleNode(9, [ - RuleNode(6) - ]), - RuleNode(9, [ - RuleNode(8, [ - RuleNode(1) - ]) - ]) - ]) - ]) - ]) -``` - - - 12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}} - - -And we check our syntax tree is correct: - - -```julia -program = rulenode2expr(fizzbuzz_syntaxtree, grammar_fizzbuzz) -println(program) -``` - - if input1 % 5 == 0 && input1 % 3 == 0 - "FizzBuzz" - else - if input1 % 3 == 0 - "Fizz" - else - if input1 % 5 == 0 - "Buzz" - else - string(input1) - end - end - end - - - -```julia -# test solution on inputs -input = [Dict(:input1 => 3), Dict(:input1 => 5), Dict(:input1 =>15), Dict(:input1 => 22)] -output = execute_on_input(grammar_fizzbuzz, fizzbuzz_syntaxtree, input) -``` - - - 4-element Vector{Any}: - "Fizz" - "Buzz" - "FizzBuzz" - "22" - - -### Modify the AST/program - -There are several ways to modify an AST and hence, a program. You can - -- directly replace a node with `HerbCore.swap_node()` -- insert a rule node with `insert!` - -Let's modify our example such that if the input number is divisible by 3, the program returns "Buzz" instead of "Fizz". -We use `swap_node()` to replace the node of the AST that corresponds to rule 5 in the grammar (`String = Fizz`) with rule 6 (`String = Buzz`). To do so, `swap_node()` needs the tree that contains the node we want to modify, the new node we want to replace the node with, and the path to that node. - -Note that `swap_node()` modifies the tree, hence we make a deep copy of it first. - - -```julia -modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) -newnode = RuleNode(6) -path = [3, 2, 1] -swap_node(modified_fizzbuzz_syntaxtree, newnode, path) -``` - - - 6, - - -Let's confirm that we modified the AST, and hence the program, correctly: - - -```julia -program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz) -println(program) -``` - - if input1 % 5 == 0 && input1 % 3 == 0 - "FizzBuzz" - else - if input1 % 3 == 0 - "Buzz" - else - if input1 % 5 == 0 - "Buzz" - else - string(input1) - end - end - end - - - -```julia -# test solution on same inputs as before -output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input) -``` - - - 4-element Vector{Any}: - "Buzz" - "Buzz" - "FizzBuzz" - "22" - - -An alternative way to modify the AST is by using `insert!()`. This requires to provide the location of the node that we want to as `NodeLoc`. `NodeLoc` points to a node in the tree and consists of the parent and the child index of the node. -Again, we make a deep copy of the original AST first. - - -```julia -modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree) -# get the node we want to modify and instantiate a NodeLoc from it. -node = get_node_at_location(modified_fizzbuzz_syntaxtree, [3, 2, 1]) -nodeloc = NodeLoc(node, 0) -# replace the node -insert!(node, nodeloc, newnode) -``` - - - 6, - - -Again, we check that we modified the program as intended: - - -```julia -program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz) -println(program) -``` - - if input1 % 5 == 0 && input1 % 3 == 0 - "FizzBuzz" - else - if input1 % 3 == 0 - "Buzz" - else - if input1 % 5 == 0 - "Buzz" - else - string(input1) - end - end - end - - - -```julia -# test on same inputs as before -output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input) -``` - - - 4-element Vector{Any}: - "Buzz" - "Buzz" - "FizzBuzz" - "22" - diff --git a/docs/src/tutorials/advanced_search.ipynb b/docs/src/tutorials/advanced_search.ipynb deleted file mode 100644 index dc4c248..0000000 --- a/docs/src/tutorials/advanced_search.ipynb +++ /dev/null @@ -1,616 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Advanced Search Procedures in Herb.jl\n", - "\n", - "In this tutorial, we show how to use the search procedure using more advanced methods." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "using Pkg\n", - "Pkg.add([\"HerbGrammar\", \"HerbSpecification\", \"HerbSearch\", \"HerbInterpret\", \"HerbConstraints\"])\n", - "using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We start with a simple grammar and a simple problem." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g = @cfgrammar begin\n", - " Number = |(1:2)\n", - " Number = x\n", - " Number = Number + Number\n", - " Number = Number * Number\n", - "end\n", - "\n", - "problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parameters\n", - "\n", - "We can use a search strategy, where we can specify different parameters. For example, by setting the `max_depth`, we limit the depth of the search. In the next example, we can see the effect of the depth on the number of allocations considered. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "solution = @time search(g, problem, :Number, max_depth=3)\n", - "println(solution)\n", - "\n", - "solution = @time search(g, problem, :Number, max_depth=6)\n", - "println(solution)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Another parameter to use is `max_enumerations`, which limits the number of programs that can be tested at evaluation. We can see the number of enumerations necessary to solve a simple problem in the next example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "for i in range(1, 50)\n", - " println(i, \" enumerations\")\n", - " solution = @time search(g, problem, :Number, max_enumerations=i)\n", - " println(solution)\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. \n", - "\n", - "A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g = @cfgrammar begin\n", - " Number = 1\n", - " List = []\n", - " Index = List[Number]\n", - "end\n", - "\n", - "problem = Problem([IOExample(Dict(), x) for x ∈ 1:5])\n", - "solution = search(g, problem, :Index, max_depth=2, allow_evaluation_errors=true)\n", - "println(\"solution: \", solution)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`):" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true)\n", - "println(\"solution: \", solution)\n", - "println(\"error: \", error)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Search methods\n", - "\n", - "We now show examples of using different search procedures, which are initialized by passing different enumerators to the search function.\n", - "\n", - "### Breadth-First Search\n", - "\n", - "The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g1 = @cfgrammar begin\n", - " Real = 1 | 2\n", - " Real = Real * Real\n", - "end\n", - "programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can test that this function returns all and only the correct functions. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "answer_programs = [\n", - " RuleNode(1),\n", - " RuleNode(2),\n", - " RuleNode(3, [RuleNode(1), RuleNode(1)]),\n", - " RuleNode(3, [RuleNode(1), RuleNode(2)]),\n", - " RuleNode(3, [RuleNode(2), RuleNode(1)]),\n", - " RuleNode(3, [RuleNode(2), RuleNode(2)])\n", - "]\n", - "\n", - "println(all(p ∈ programs for p ∈ answer_programs))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Depth-First Search\n", - "\n", - "In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g1 = @cfgrammar begin\n", - "Real = 1 | 2\n", - "Real = Real * Real\n", - "end\n", - "programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real))\n", - "println(programs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g1 = @cfgrammar begin\n", - " Real = 1 | 2\n", - " Real = Real * Real\n", - "end\n", - "programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost))\n", - "println(programs)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Stochastic search\n", - "We now introduce a few stochastic search algorithms, for which we first create a simple grammar and a helper function for problems." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grammar = @csgrammar begin\n", - " X = |(1:5)\n", - " X = X * X\n", - " X = X + X\n", - " X = X - X\n", - " X = x\n", - "end" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "function create_problem(f, range=20)\n", - " examples = [IOExample(Dict(:x => x), f(x)) for x ∈ 1:range]\n", - " return Problem(examples), examples\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Metropolis-Hastings\n", - "\n", - "One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html).\n", - "\n", - "The example below uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> x * x + 4\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_mh_enumerator(examples, mean_squared_error)\n", - "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Very Large Scale Neighbourhood Search \n", - "\n", - "The second implemented stochastic search method is VLSN, which searches for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf).\n", - "\n", - "Given the same grammar as before, we can try it with some simple examples." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> 10\n", - "max_depth = 2\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", - "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> x\n", - "max_depth = 1\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", - "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Simulated Annealing\n", - "\n", - "The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html).\n", - "\n", - "We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Change the value below to see the effect." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> x * x + 4\n", - "initial_temperature = 1\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", - "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> x * x + 4\n", - "initial_temperature = 2\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", - "program, cost = @time search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Genetic Search\n", - "\n", - "Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/).\n", - "\n", - "We show the example of finding a lambda function. Try varying the parameters of the genetic search to see what happens." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "e = x -> 3 * x * x + (x + 2)\n", - "problem, examples = create_problem(e)\n", - "enumerator = get_genetic_enumerator(examples, \n", - " initial_population_size = 10,\n", - " mutation_probability = 0.8,\n", - " maximum_initial_population_depth = 3,\n", - ")\n", - "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) " - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Other functionality\n", - "\n", - "Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics.\n", - "\n", - "### Sampling\n", - "Sampling is implemented for the different stochastic search methods.\n", - "\n", - "We consider here a simple grammar, which gives different programs for different search depths." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grammar = @cfgrammar begin \n", - " A = B | C | F\n", - " F = G\n", - " C = D\n", - " D = E\n", - "end\n", - "\n", - "# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3)\n", - "\n", - "# For depth ≤ 1 the only option is A->B\n", - "expression = rand(RuleNode, grammar, :A, 1)\n", - "@assert rulenode2expr(expression, grammar) in [:B,:C,:F]\n", - "\n", - "# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2)\n", - "expression = rand(RuleNode, grammar, :A, 2)\n", - "@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Heuristics" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# More interesting domains & Use of constraints\n", - "In the following examples, we introduce some larger grammars and show that Herb can still efficiently find the correct program." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#Expects to return a program equivalent to 1 + (1 - x) = 2 - x\n", - "\n", - "g₁ = @csgrammar begin\n", - " Element = |(1 : 3) # 1 - 3\n", - " Element = Element + Element # 4\n", - " Element = 1 - Element # 5\n", - " Element = x # 6\n", - "end\n", - "\n", - "addconstraint!(g₁, ComesAfter(6, [5]))\n", - "\n", - "examples = [\n", - " IOExample(Dict(:x => 0), 2),\n", - " IOExample(Dict(:x => 1), 1),\n", - " IOExample(Dict(:x => 2), 0)\n", - "]\n", - "problem = Problem(examples)\n", - "solution = search(g₁, problem, :Element, max_depth=3)\n", - "\n", - "@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4\n", - "\n", - "g₂ = @csgrammar begin\n", - " Element = Element + Element + Element # 1\n", - " Element = Element + Element * Element # 2\n", - " Element = x # 3\n", - " Element = |(3 : 5) # 4\n", - "end\n", - "\n", - "# Restrict .. + x * x\n", - "addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)])))\n", - "# Restrict 4 and 5 in lower level\n", - "addconstraint!(g₂, ForbiddenPath([2, 1, 5]))\n", - "addconstraint!(g₂, ForbiddenPath([2, 1, 6]))\n", - "\n", - "examples = [\n", - " IOExample(Dict(:x => 1), 11)\n", - " IOExample(Dict(:x => 2), 20)\n", - " IOExample(Dict(:x => -1), -1)\n", - "]\n", - "problem = Problem(examples)\n", - "solution = search(g₂, problem, :Element)\n", - "\n", - "@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1\n", - "\n", - "g₃ = @csgrammar begin\n", - " Element = |(1 : 20) # 1 - 20\n", - " Element = Element - 1 # 21\n", - " Element = 1 - Element # 22\n", - " Element = x # 23\n", - "end\n", - "\n", - "addconstraint!(g₃, ComesAfter(23, [22, 21]))\n", - "addconstraint!(g₃, ComesAfter(22, [21]))\n", - "\n", - "examples = [\n", - " IOExample(Dict(:x => 1), 2)\n", - " IOExample(Dict(:x => 10), 11)\n", - "]\n", - "problem = Problem(examples)\n", - "solution = search(g₃, problem, :Element)\n", - "\n", - "@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1\n", - "@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Expects to return a program equivalent to 18 + 4x\n", - "\n", - "g₄ = @csgrammar begin\n", - " Element = |(0 : 20) # 1 - 20\n", - " Element = Element + Element + Element # 21\n", - " Element = Element + Element * Element # 22\n", - " Element = x # 23\n", - "end\n", - "\n", - "# Enforce ordering on + +\n", - "addconstraint!(g₄, Ordered(\n", - " MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]),\n", - " [:x, :y, :z]\n", - "))\n", - "\n", - "examples = [\n", - " IOExample(Dict(:x => 1), 22),\n", - " IOExample(Dict(:x => 0), 18),\n", - " IOExample(Dict(:x => -1), 14)\n", - "]\n", - "problem = Problem(examples)\n", - "solution = search(g₄, problem, :Element)\n", - "\n", - "@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "metadata": {}, - "outputs": [], - "source": [ - "# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2)\n", - "\n", - "g₅ = @csgrammar begin\n", - " Element = Number # 1\n", - " Element = Bool # 2\n", - "\n", - " Number = |(1 : 3) # 3-5\n", - " \n", - " Number = Number + Number # 6\n", - " Bool = Number ≡ Number # 7\n", - " Number = x # 8\n", - " \n", - " Number = Bool ? Number : Number # 9\n", - " Bool = Bool ? Bool : Bool # 10\n", - "end\n", - "\n", - "# Forbid ? = ?\n", - "addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)])))\n", - "# Order =\n", - "addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y]))\n", - "# Order +\n", - "addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y]))\n", - "\n", - "examples = [\n", - " IOExample(Dict(:x => 0), 2)\n", - " IOExample(Dict(:x => 1), 3)\n", - " IOExample(Dict(:x => 2), 1)\n", - "]\n", - "problem = Problem(examples)\n", - "solution = search(g₅, problem, :Element)\n", - "\n", - "@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.9.0", - "language": "julia", - "name": "julia-1.9" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.9.0" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/src/tutorials/advanced_search.md b/docs/src/tutorials/advanced_search.md deleted file mode 100644 index bb10fa1..0000000 --- a/docs/src/tutorials/advanced_search.md +++ /dev/null @@ -1,403 +0,0 @@ -# Getting started - -You can either paste this code into the Julia REPL or run it using the `advanced_search.ipynb` notebook. Alternatively you can copy the code into a file like `get_started.jl`, followed by running `julia get_started.jl`. - -To start, we import the necessary packages. - -```julia -using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret -``` - -We start with the same simple grammar from the main file [get_started.md](../get_started.md). - -```julia -g = @cfgrammar begin - Number = |(1:2) - Number = x - Number = Number + Number - Number = Number * Number -end -``` - -We use a simple problem. - -```julia - problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) -``` - -## Parameters - -We can use a search strategy, where we can specify different parameters. For example, by setting the `max_depth`, we limit the depth of the search. In the next example, we can see the effect of the depth on the number of allocations considered. - -```julia -solution = @time search(g, problem, :Number, max_depth=3) ->>> 0.003284 seconds (50.08 k allocations: 2.504 MiB) -println(solution) ->>> (x + 1) + x - -solution = @time search(g, problem, :Number, max_depth=6) ->>> 0.005696 seconds (115.28 k allocations: 5.910 MiB) -println(solution) ->>> (1 + x) + x -``` - -Another parameter to use is `max_enumerations`, which limits the number of programs that can be tested at evaluation. We can see the number of enumerations necessary to solve a simple problem in the next example. - -```julia -for i in range(1, 50) - println(i, " enumerations") - solution = @time search(g, problem, :Number, max_enumerations=i) - println(solution) -end - ->>> .... ->>> 23 enums: nothing ->>> 0.010223 seconds (117.01 k allocations: 5.935 MiB, 44.23% gc time) ->>> 24 enums: (1 + x) + x ->>> 0.005305 seconds (117.01 k allocations: 5.935 MiB) ->>> 25 enums: (1 + x) + x ->>> 0.005381 seconds (117.01 k allocations: 5.935 MiB) ->>> ... -``` - -We see that only when `i >= 24`, there is a result. After that, increasing `i` does not have any effect on the number of allocations. - -A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method. - -```julia -g = @cfgrammar begin - Number = 1 - List = [] - Index = List[Number] -end - -problem = Problem([IOExample(Dict(), x) for x ∈ 1:5]) -solution = search(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) -println(solution) ->>> nothing -``` - -There is also another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error: - -```julia -solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) -println(solution) ->>> nothing -println(error) ->>> 9223372036854775807 # or: typemax(Int) -``` - -## Search methods - -We now show examples of using different search procedures. - -### Breadth-First Search - -The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. - -```julia -g1 = @cfgrammar begin - Real = 1 | 2 - Real = Real * Real -end -programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real)) -``` - -We can test that this function returns all and only the correct functions. - -```julia -answer_programs = [ - RuleNode(1), - RuleNode(2), - RuleNode(3, [RuleNode(1), RuleNode(1)]), - RuleNode(3, [RuleNode(1), RuleNode(2)]), - RuleNode(3, [RuleNode(2), RuleNode(1)]), - RuleNode(3, [RuleNode(2), RuleNode(2)]) -] - -println(all(p ∈ programs for p ∈ answer_programs)) ->>> true -``` -### Depth-First Search - -In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. - -```julia -g1 = @cfgrammar begin -Real = 1 | 2 -Real = Real * Real -end -programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real)) -println(programs) ->>> RuleNode[1,, 3{1,1}, 3{1,2}, 3{2,1}, 3{2,2}, 2,] -``` - -`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. - - -```julia -g1 = @cfgrammar begin - Real = 1 | 2 - Real = Real * Real -end -programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost)) -println(programs) ->>> RuleNode[1,, 3{1,1}, 3{2,1}, 3{1,2}, 3{2,2}, 2,] -``` - -## Stochastic search - -For the examples below, we use this grammar and helper function. -```julia -grammar = @csgrammar begin - X = |(1:5) - X = X * X - X = X + X - X = X - X - X = x -end -function create_problem(f, range=20) - examples = [IOExample(Dict(:x => x), f(x)) for x ∈ 1:range] - return Problem(examples), examples -end -``` - -### Metropolis-Hastings - -One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). - - - -The below example uses a simple arithmetic example. As the search method is stochastic, different programs may be returned, as shown below. - -```julia -e = Meta.parse("x -> x * x + 4") -problem, examples = create_problem(eval(e)) -enumerator = get_mh_enumerator(examples, mean_squared_error) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) - ->>> (:(x * x - (1 - 5)), 0) ->>> (:(4 + x * x), 0) ->>> (:(x * x + 4), 0) -``` - -### Very Large Scale Neighbourhood Search - -The second implemented stochastic search method is VLSN, which search for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). - -Given the same grammar as before, we can try with some simple examples. - -```julia -e = Meta.parse("x -> 10") -max_depth = 2 -problem, examples = create_problem(eval(e)) -enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) ->>> (:(5 + 5), 0) -``` - -```julia -e = Meta.parse("x -> x") -max_depth = 1 -problem, examples = create_problem(eval(e)) -enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) ->>> (:x, 0) -``` - -### Simulated Annealing - -The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). - -We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Two possible answers to the program are given as well. - -```julia -e = Meta.parse("x -> x * x + 4") -initial_temperature = 2 -problem, examples = create_problem(eval(e)) -enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) ->>> (:(4 + x * x), 0) ->>> (:(x * x + (5 - 1)), 0) -``` - -### Genetic Search - -Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). - -We show the example of finding a lambda function. - -```julia -e = x -> 3 * x * x + (x + 2) -problem, examples = create_problem(e) -enumerator = get_genetic_enumerator(examples, - initial_population_size = 10, - mutation_probability = 0.8, - maximum_initial_population_depth = 3, -) -program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) ->>> (:(((((x - 5) + x) + x * x) + 1) + (((((2 + x * x) + 3) + x * x) + 1) - ((x - x) + x))), 0) ->>> (:(x * 1 + (x * ((x + x) + x * 1) + (1 + 1) * 1)), 0) ->>> (:((((x + x) + x) + 2) * x + ((x - x) + (2 - x))), 0) -``` - -## Other functionality - -Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics. - -### Sampling -Sampling is implemented for the different stochastic search methods. - -We consider here a simple grammar, which gives different programs for different search depths. - -```julia -grammar = @cfgrammar begin - A = B | C | F - F = G - C = D - D = E -end - -# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3) - -# For depth ≤ 1 the only option is A->B -expression = rand(RuleNode, grammar, :A, 1) -@assert rulenode2expr(expression, grammar) in [:B,:C,:F] - -# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2) -expression = rand(RuleNode, grammar, :A, 2) -@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G] -``` - -### Heuristics - - -# Examples with larger domains and constraints -Here, we showcase a few examples using a more complicated grammar and a few types of constraints -```julia -# Expects to return a program equivalent to 1 + (1 - x) = 2 - x - -g₁ = @csgrammar begin - Element = |(1 : 3) # 1 - 3 - Element = Element + Element # 4 - Element = 1 - Element # 5 - Element = x # 6 -end - -addconstraint!(g₁, ComesAfter(6, [5])) - -examples = [ - IOExample(Dict(:x => 0), 2), - IOExample(Dict(:x => 1), 1), - IOExample(Dict(:x => 2), 0) -] -problem = Problem(examples) -solution = search(g₁, problem, :Element, max_depth=3) - -@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4 - -# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4 - -g₂ = @csgrammar begin - Element = Element + Element + Element # 1 - Element = Element + Element * Element # 2 - Element = x # 3 - Element = |(3 : 5) # 4 -end - -# Restrict .. + x * x -addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)]))) -# Restrict 4 and 5 in lower level -addconstraint!(g₂, ForbiddenPath([2, 1, 5])) -addconstraint!(g₂, ForbiddenPath([2, 1, 6])) - -examples = [ - IOExample(Dict(:x => 1), 11) - IOExample(Dict(:x => 2), 20) - IOExample(Dict(:x => -1), -1) -] -problem = Problem(examples) -solution = search(g₂, problem, :Element) - -@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4 - -# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1 - -g₃ = @csgrammar begin - Element = |(1 : 20) # 1 - 20 - Element = Element - 1 # 21 - Element = 1 - Element # 22 - Element = x # 23 -end - -addconstraint!(g₃, ComesAfter(23, [22, 21])) -addconstraint!(g₃, ComesAfter(22, [21])) - -examples = [ - IOExample(Dict(:x => 1), 2) - IOExample(Dict(:x => 10), 11) -] -problem = Problem(examples) -solution = search(g₃, problem, :Element) - -@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1 -@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101 - -# Expects to return a program equivalent to 18 + 4x - -g₄ = @csgrammar begin - Element = |(0 : 20) # 1 - 20 - Element = Element + Element + Element # 21 - Element = Element + Element * Element # 22 - Element = x # 23 -end - -# Enforce ordering on + + -addconstraint!(g₄, Ordered( - MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]), - [:x, :y, :z] -)) - -examples = [ - IOExample(Dict(:x => 1), 22), - IOExample(Dict(:x => 0), 18), - IOExample(Dict(:x => -1), 14) -] -problem = Problem(examples) -solution = search(g₄, problem, :Element) - -@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418 - -# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2) - -g₅ = @csgrammar begin - Element = Number # 1 - Element = Bool # 2 - - Number = |(1 : 3) # 3-5 - - Number = Number + Number # 6 - Bool = Number ≡ Number # 7 - Number = x # 8 - - Number = Bool ? Number : Number # 9 - Bool = Bool ? Bool : Bool # 10 -end - -# Forbid ? = ? -addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)]))) -# Order = -addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) -# Order + -addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) - -examples = [ - IOExample(Dict(:x => 0), 2) - IOExample(Dict(:x => 1), 3) - IOExample(Dict(:x => 2), 1) -] -problem = Problem(examples) -solution = search(g₅, problem, :Element) - -@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5 -``` \ No newline at end of file diff --git a/docs/src/tutorials/defining_grammars.html b/docs/src/tutorials/defining_grammars.html new file mode 100644 index 0000000..63bbd5c --- /dev/null +++ b/docs/src/tutorials/defining_grammars.html @@ -0,0 +1,17 @@ + + + + + + + +
\ No newline at end of file diff --git a/docs/src/tutorials/defining_grammars.ipynb b/docs/src/tutorials/defining_grammars.ipynb deleted file mode 100644 index dec24cb..0000000 --- a/docs/src/tutorials/defining_grammars.ipynb +++ /dev/null @@ -1,531 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Defining Grammars in Herb.jl using HerbGrammar\n", - "\n", - "The program space in Herb.jl is defined using a grammar. \n", - "This notebook demonstrates how such a grammar can be created. \n", - "There are multiple kinds of grammars, but they can all be defined in a very similar way." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup\n", - "First, we import the necessary Herb packages." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "using HerbGrammar, HerbConstraints" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Creating a simple grammar\n", - "\n", - "This cell contains a very simple arithmetic grammar. \n", - "The grammar is defined using the `@csgrammar` macro. \n", - "This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. \n", - "Macro's are executed during compilation.\n", - "If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2csgrammar` function." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₁ = HerbGrammar.@csgrammar begin\n", - " Int = 1\n", - " Int = 2\n", - " Int = 3\n", - " Int = Int * Int\n", - " Int = Int + Int\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Defining every integer one-by-one can be quite tedious. Therefore, it is also possible to use the following syntax that makes use of a Julia iterator:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₂ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = Int * Int\n", - " Int = Int + Int\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can do the same with lists:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₃ = HerbGrammar.@csgrammar begin\n", - " Int = |([0, 2, 4, 6, 8])\n", - " Int = Int * Int\n", - " Int = Int + Int\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Variables can also be added to the grammar by just using the variable name:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₄ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = Int * Int\n", - " Int = Int + Int\n", - " Int = x\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Grammars can also work with functions. \n", - "After all, `+` and `*` are just infix operators for Julia's identically-named functions.\n", - "You can use functions that are provided by Julia, or functions that you wrote yourself:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "f(a) = a + 1\n", - "\n", - "g₅ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = Int * Int\n", - " Int = Int + Int\n", - " Int = f(Int)\n", - " Int = x\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Similarly, we can also define the operator times (x) manually." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "×(a, b) = a * b\n", - "\n", - "g₆ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = a\n", - " Int = Int + Int\n", - " Int = Int × Int\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Working with grammars\n", - "\n", - "If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. \n", - "This section is not complete, but it aims to give an overview of the most important functions. \n", - "\n", - "It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with the concept.\n", - "\n", - "One of the most important things about grammars is that each rule has an index associated with it:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₇ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = Int + Int\n", - " Int = Int * Int\n", - " Int = x\n", - "end\n", - "\n", - "collect(enumerate(g₇.rules))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We can use this index to extract information from the grammar." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### isterminal\n", - "\n", - "`isterminal` returns `true` if a rule is terminal, i.e. it cannot be expanded. For example, rule 1 is terminal, but rule 11 is not, since it contains the non-terminal symbol `:Int`. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.isterminal(g₇, 1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.isterminal(g₇, 11)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### return_type\n", - "\n", - "This function is rather obvious; it returns the non-terminal symbol that corresponds to a certain rule. The return type for all rules in our grammar is `:Int`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.return_type(g₇, 11)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### child_types\n", - "\n", - "`child_types` returns the types of the nonterminal children of a rule in a vector.\n", - "If you just want to know how many children a rule has, and not necessarily which types they have, you can use `nchildren`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.child_types(g₇, 11)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.nchildren(g₇, 11)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### nonterminals\n", - "\n", - "The `nonterminals` function can be used to obtain a list of all nonterminals in the grammar." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.nonterminals(g₇)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Adding rules\n", - "\n", - "It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function. The exclamatin mark is a Julia convention and is appended to name if a function modifies its arguments (in our example the grammar).\n", - "\n", - "A rule can be provided in the same syntax as is used in the grammar definition.\n", - "The rule should be of the `Expr` type, which is a built-in type for representing expressions. \n", - "An easy way of creating `Expr` values in Julia is to encapsulate it in brackets and use a colon as prefix:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.add_rule!(g₇, :(Int = Int - Int))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Removing rules\n", - "\n", - "It is also possible to remove rules in Herb.jl, however, this is a bit more involved. \n", - "As said before, rules have an index associated with them. \n", - "The internal representation of programs that are defined by the grammar makes use of those indices for efficiency.\n", - "Blindly removing a rule would shift the indices of other rules, and this could mean that existing programs get a different meaning or become invalid. \n", - "\n", - "Therefore, there are two functions for removing rules:\n", - "\n", - "- `remove_rule!` removes a rule from the grammar, but fills its place with a placeholder. Therefore, the indices stay the same, and only programs that use the removed rule become invalid.\n", - "- `cleanup_removed_rules!` removes all placeholders and shifts the indices of the other rules.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.remove_rule!(g₇, 11)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.cleanup_removed_rules!(g₇)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Context-sensitive grammars\n", - "\n", - "Context-sensitive grammars introduce additional constraints compared to context-free grammars (like the simple grammar examples above).\n", - "As before, we use the `@csgrammar` macro:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₈ = HerbGrammar.@csgrammar begin\n", - " Int = |(0:9)\n", - " Int = Int + Int\n", - " Int = Int * Int\n", - " Int = x\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar.\n", - "\n", - "For example, we can add a `` constraint to enforce that the input symbol `x` (rule 13) appears at least once in the program, to avoid programs that are just a constant. " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.addconstraint!(g₈, Contains(13))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "There is a dedicated tutorial for constraints in Herb.jl and how to work with them." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Probabilistic grammars\n", - "\n", - "Herb.jl also supports probabilistic grammars. \n", - "These grammars allow the user to assign a probability to each rule in the grammar.\n", - "A probabilistic grammar can be defined in a very similar way to a standard grammar, but has some slightly different syntax:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₉ = HerbGrammar.@pcsgrammar begin\n", - " 0.4 : Int = |(0:9)\n", - " 0.2 : Int = Int + Int\n", - " 0.1 : Int = Int * Int\n", - " 0.3 : Int = x\n", - "end\n", - "\n", - "for r ∈ 1:length(g₃.rules)\n", - " p = HerbGrammar.probability(g₈, r)\n", - "\n", - " println(\"$p : $r\")\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The numbers before each rule represent the probability assigned to that rule.\n", - "The total probability for each return type should add up to 1.0.\n", - "If this isn't the case, Herb.jl will normalize the probabilities.\n", - "\n", - "If a single line in the grammar definition represents multiple rules, such as `0.4 : Int = |(0:9)`, the probability will be evenly divided over all these rules." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## File writing\n", - "\n", - "### Saving & loading context-free grammars\n", - "\n", - "If you want to store a grammar on the disk, you can use the `store_csg`, `read_csg` and functions to store and read grammars respectively. \n", - "The `store_csg` grammar can also be used to store probabilistic grammars. To read probabilistic grammars, use `read_pcsg`.\n", - "The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.store_csg(g₇, \"demo.txt\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.read_csg(\"demo.txt\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Saving & loading context-sensitive grammars\n", - "\n", - "Saving and loading context-sensitive grammars is very similar to how it is done with context-free grammars.\n", - "The only difference is that an additional file is created for the constraints. \n", - "The file that contains the grammars can be edited and can also be read using the reader for context-free grammars.\n", - "The file that contains the constraints cannot be edited." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "HerbGrammar.store_csg( g₈, \"demo.grammar\", \"demo.constraints\")\n", - "g₈, g₈.constraints" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "g₉ = HerbGrammar.read_csg(\"demo.grammar\", \"demo.constraints\")\n", - "g₉, g₉.constraints" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10.3", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.10.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/src/tutorials/defining_grammars.md b/docs/src/tutorials/defining_grammars.md deleted file mode 100644 index 6223b12..0000000 --- a/docs/src/tutorials/defining_grammars.md +++ /dev/null @@ -1,590 +0,0 @@ -# Defining Grammars in Herb.jl using HerbGrammar - -The program space in Herb.jl is defined using a grammar. -This notebook demonstrates how such a grammar can be created. -There are multiple kinds of grammars, but they can all be defined in a very similar way. - -### Setup -First, we import the necessary Herb packages. - - -```julia -using HerbGrammar, HerbConstraints -``` - -### Creating a simple grammar - -This cell contains a very simple arithmetic grammar. -The grammar is defined using the `@cfgrammar` macro. -This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. -Macro's are executed during compilation. -If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2cfgrammar` function. - - -```julia -g₁ = HerbGrammar.@cfgrammar begin - Int = 1 - Int = 2 - Int = 3 - Int = Int * Int - Int = Int + Int -end -``` - - - 1: Int = 1 - 2: Int = 2 - 3: Int = 3 - 4: Int = Int * Int - 5: Int = Int + Int - - - -Defining every integer one-by-one can be quite tedious. Therefore, it is also possible to use the following syntax that makes use of a Julia iterator: - - -```julia -g₂ = HerbGrammar.@cfgrammar begin - Int = |(0:9) - Int = Int * Int - Int = Int + Int -end -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int * Int - 12: Int = Int + Int - - - -You can do the same with lists: - - -```julia -g₃ = HerbGrammar.@cfgrammar begin - Int = |([0, 2, 4, 6, 8]) - Int = Int * Int - Int = Int + Int -end -``` - - - 1: Int = 0 - 2: Int = 2 - 3: Int = 4 - 4: Int = 6 - 5: Int = 8 - 6: Int = Int * Int - 7: Int = Int + Int - - - -Variables can also be added to the grammar by just using the variable name: - - -```julia -g₄ = HerbGrammar.@cfgrammar begin - Int = |(0:9) - Int = Int * Int - Int = Int + Int - Int = x -end -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int * Int - 12: Int = Int + Int - 13: Int = x - - - -Grammars can also work with functions. -After all, `+` and `*` are just infix operators for Julia's identically-named functions. -You can use functions that are provided by Julia, or functions that you wrote yourself: - - -```julia -f(a) = a + 1 - -g₅ = HerbGrammar.@cfgrammar begin - Int = |(0:9) - Int = Int * Int - Int = Int + Int - Int = f(Int) - Int = x -end -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int * Int - 12: Int = Int + Int - 13: Int = f(Int) - 14: Int = x - - - -Similarly, we can also define the operator times (x) manually. - - -```julia -×(a, b) = a * b - -g₆ = HerbGrammar.@cfgrammar begin - Int = |(0:9) - Int = a - Int = Int + Int - Int = Int × Int -end -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = a - 12: Int = Int + Int - 13: Int = Int × Int - - - -### Working with grammars - -If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. -This section is not necessarily complete, but it aims to give an overview of the most important functions. - -It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with that. - -One of the most important things about grammars is that each rule has an index associated with it: - - -```julia -g₇ = HerbGrammar.@cfgrammar begin - Int = |(0:9) - Int = Int + Int - Int = Int * Int - Int = x -end - -collect(enumerate(g₇.rules)) -``` - - - 13-element Vector{Tuple{Int64, Any}}: - (1, 0) - (2, 1) - (3, 2) - (4, 3) - (5, 4) - (6, 5) - (7, 6) - (8, 7) - (9, 8) - (10, 9) - (11, :(Int + Int)) - (12, :(Int * Int)) - (13, :x) - - -We can use this index to extract information from the grammar. - -### isterminal - -`isterminal` returns `true` if a rule is terminal, i.e. it cannot be expanded. For example, rule 1 is terminal, but rule 11 is not, since it contains the non-terminal symbol `:Int`. - - -```julia -HerbGrammar.isterminal(g₇, 1) -``` - - - true - - - -```julia -HerbGrammar.isterminal(g₇, 11) -``` - - - false - - -### return_type - -This function is rather obvious; it returns the non-terminal symbol that corresponds to a certain rule. The return type for all rules in our grammar is `:Int`. - - -```julia -HerbGrammar.return_type(g₇, 11) -``` - - - :Int - - -### child_types - -`child_types` returns the types of the nonterminal children of a rule in a vector. -If you just want to know how many children a rule has, and not necessarily which types they have, you can use `nchildren` - - -```julia -HerbGrammar.child_types(g₇, 11) -``` - - - 2-element Vector{Symbol}: - :Int - :Int - - - -```julia -HerbGrammar.nchildren(g₇, 11) -``` - - - 2 - - -### nonterminals - -The `nonterminals` function can be used to obtain a list of all nonterminals in the grammar. - - -```julia -HerbGrammar.nonterminals(g₇) -``` - - - 1-element Vector{Symbol}: - :Int - - -### Adding rules - -It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function. -As with most functions in Julia that end with an exclamation mark, this function modifies its argument (the grammar). - -A rule can be provided in the same syntax as is used in the grammar definition. -The rule should be of the `Expr` type, which is a built-in type for representing expressions. -An easy way of creating `Expr` values in Julia is to encapsulate it in brackets and use a colon as prefix: - - -```julia -HerbGrammar.add_rule!(g₇, :(Int = Int - Int)) -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int + Int - 12: Int = Int * Int - 13: Int = x - 14: Int = Int - Int - - - -### Removing rules - -It is also possible to remove rules in Herb.jl, however, this is a bit more involved. -As said before, rules have an index associated with them. -The internal representation of programs that are defined by the grammar makes use of those indices for efficiency. -Blindly removing a rule would shift the indices of other rules, and this could mean that existing programs get a different meaning or become invalid. - -Therefore, there are two functions for removing rules: - -- `remove_rule!` removes a rule from the grammar, but fills its place with a placeholder. Therefore, the indices stay the same, and only programs that use the removed rule become invalid. -- `cleanup_removed_rules!` removes all placeholders and shifts the indices of the other rules. - - - -```julia -HerbGrammar.remove_rule!(g₇, 11) -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: nothing = nothing - 12: Int = Int * Int - 13: Int = x - 14: Int = Int - Int - - - - -```julia -HerbGrammar.cleanup_removed_rules!(g₇) -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int * Int - 12: Int = x - 13: Int = Int - Int - - - -## Context-sensitive grammars - -Context-sensitive grammars allow additional constraints to be added with respect to context-free grammars. -The syntax for defining a context-sensitive grammar is identical to defining a context-sensitive grammar: - - -```julia -g₈ = HerbGrammar.@csgrammar begin - Int = |(0:9) - Int = Int + Int - Int = Int * Int - Int = x -end -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int + Int - 12: Int = Int * Int - 13: Int = x - - - -Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar. -Currently, Herb.jl only has propagators constraints. -These constraints each have a corresponding `propagate` function that removes all options that violate that constraint from the domain. -At the moment, there are three propagator constraints: - -- `ComesAfter(rule, predecessors)`: It is only possible to use rule `rule` when `predecessors` are in its path to the root. -- `Forbidden(sequence)`: Forbids the derivation specified as a path in an expression tree. -- `Ordered(order)`: Rules have to be used in the specified order. That is, rule at index K can only be used if rules at indices `[1...K-1]` are used in the left subtree of the current expression. - -Below, an example is given of a context-sensitive grammar with a `ComesAfter` constraint: - - -```julia -HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9])) -``` - - - 1-element Vector{HerbCore.Constraint}: - ComesAfter(1, [9]) - - -### Probabilistic grammars - -Herb.jl also supports probabilistic grammars. -These grammars allow the user to assign a probability to each rule in the grammar. -A probabilistic grammar can be defined in a very similar way to a standard grammar, but has some slightly different syntax: - - -```julia -g₉ = HerbGrammar.@pcfgrammar begin - 0.4 : Int = |(0:9) - 0.2 : Int = Int + Int - 0.1 : Int = Int * Int - 0.3 : Int = x -end - -for r ∈ 1:length(g₃.rules) - p = HerbGrammar.probability(g₈, r) - - println("$p : $r") -end -``` - - 0.07692307692307693 : 1 - 0.07692307692307693 : 2 - 0.07692307692307693 : 3 - 0.07692307692307693 : 4 - 0.07692307692307693 : 5 - 0.07692307692307693 : 6 - 0.07692307692307693 : 7 - - - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - ┌ Warning: Requesting probability in a non-probabilistic grammar. - │ Uniform distribution is assumed. - └ @ HerbGrammar /Users/issahanou/.julia/packages/HerbGrammar/x0E9w/src/grammar_base.jl:155 - - -The numbers before each rule represent the probability assigned to that rule. -The total probability for each return type should add up to 1.0. -If this isn't the case, Herb.jl will normalize the probabilities. - -If a single line in the grammar definition represents multiple rules, such as `0.4 : Int = |(0:9)`, the probability will be evenly divided over all these rules. - -## File writing - -### Saving & loading context-free grammars - -If you want to store a grammar on the disk, you can use the `store_cfg`, `read_cfg` and functions to store and read grammars respectively. -The `store_cfg` grammar can also be used to store probabilistic grammars. Reading probabilistic grammars can be done using `read_pcfg`. -The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars. - - -```julia -HerbGrammar.store_cfg("demo.txt", g₇) -``` - - -```julia -HerbGrammar.read_cfg("demo.txt") -``` - - - 1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int * Int - 12: Int = x - 13: Int = Int - Int - - - -### Saving & loading context-sensitive grammars - -Saving and loading context-sensitive grammars is very similar to how it is done with context-free grammars. -The only difference is that an additional file is created for the constraints. -The file that contains the grammars can be edited and can also be read using the reader for context-free grammars. -The file that contains the constraints cannot be edited. - - -```julia -HerbGrammar.store_csg("demo.grammar", "demo.constraints", g₈) -g₈, g₈.constraints -``` - - - (1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int + Int - 12: Int = Int * Int - 13: Int = x - , HerbCore.Constraint[ComesAfter(1, [9])]) - - - -```julia -g₉ = HerbGrammar.read_csg("demo.grammar", "demo.constraints") -g₉, g₉.constraints -``` - - - (1: Int = 0 - 2: Int = 1 - 3: Int = 2 - 4: Int = 3 - 5: Int = 4 - 6: Int = 5 - 7: Int = 6 - 8: Int = 7 - 9: Int = 8 - 10: Int = 9 - 11: Int = Int + Int - 12: Int = Int * Int - 13: Int = x - , HerbCore.Constraint[ComesAfter(1, [9])]) - diff --git a/docs/src/tutorials/getting_started_with_constraints.html b/docs/src/tutorials/getting_started_with_constraints.html index a16e59f..67e1c71 100644 --- a/docs/src/tutorials/getting_started_with_constraints.html +++ b/docs/src/tutorials/getting_started_with_constraints.html @@ -3,11 +3,11 @@ diff --git a/docs/src/tutorials/getting_started_with_constraints.ipynb b/docs/src/tutorials/getting_started_with_constraints.ipynb deleted file mode 100644 index 2bb63b3..0000000 --- a/docs/src/tutorials/getting_started_with_constraints.ipynb +++ /dev/null @@ -1,481 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Getting started with HerbConstraints\n", - "\n", - "When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup\n", - "\n", - "For this tutorial, we need to import the following modules of the Herb.jl framework:\n", - "\n", - "* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s\n", - "* `HerbGrammar` to define the grammar\n", - "* `HerbConstraints` to define the constraints\n", - "* `HerbSearch` to execute a constrained enumeration\n", - "\n", - "We will also redefine the simple arithmetic grammar from the previous tutorial." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "using HerbCore, HerbGrammar, HerbConstraints, HerbSearch\n", - "\n", - "grammar = @cfgrammar begin\n", - " Int = 1\n", - " Int = x\n", - " Int = - Int\n", - " Int = Int + Int\n", - " Int = Int * Int\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Working with constraints\n", - "\n", - "To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes).\n", - "\n", - "(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clearconstraints!(grammar)\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints.\n", - "\n", - "To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids:\n", - "\n", - "* `-(-1)`\n", - "* `-(-X)`\n", - "* `-(-(1 + 1))`\n", - "* `1 + -(-(1 + 1))`\n", - "* etc" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "one = 1\n", - "x = 2\n", - "minus = 3\n", - "plus = 4\n", - "times = 5\n", - "\n", - "addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A\n", - "addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A)\n", - "\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Forbidden Constraint\n", - "\n", - "The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types:\n", - "* `RuleNode(1)`. Matches exactly the given rule.\n", - "* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5.\n", - "* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "#this constraint forbids A+A and A*A\n", - "constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)]))\n", - "\n", - "# Without this constraint, we encounter 154 programs\n", - "clearconstraints!(grammar)\n", - "iter = BFSIterator(grammar, :Int, max_size=5)\n", - "println(length(iter))\n", - "\n", - "# With this constraint, we encounter 106 programs\n", - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, constraint)\n", - "iter = BFSIterator(grammar, :Int, max_size=5)\n", - "println(length(iter))\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Contains Constraint\n", - "\n", - "The `Contains` constraint enforces that a given rule appears in the program tree at least once. \n", - "\n", - "In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Contains Subtree Constraint\n", - "\n", - "Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clearconstraints!(grammar)\n", - "addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree\n", - "iter = BFSIterator(grammar, :Int, max_size=4)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Ordered Constraint\n", - "\n", - "The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants.\n", - "\n", - "To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree.\n", - "\n", - "In the upcoming example we will set up a template tree representing `a+b` and `a*b`.\n", - "Then, we will impose an ordering `a<=b` on all the subtrees that match the template.\n", - "\n", - "The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clearconstraints!(grammar)\n", - "\n", - "template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)])\n", - "order = [:a, :b]\n", - "\n", - "addconstraint!(grammar, Ordered(template_tree, order))\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Forbidden Sequence Constraint\n", - "\n", - "The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. \n", - "\n", - "An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. \n", - "\n", - "Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence.\n", - "\n", - "This constraint will **forbid** the following programs:\n", - "\n", - "* x + 1\n", - "* x + -1\n", - "* x + -(-1)\n", - "* x + (x + 1)\n", - "* x * (x + 1)\n", - "\n", - "But it will **allow** the following program (as * disrupts the sequence):\n", - "\n", - "* x + (x * 1)\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "constraint = ForbiddenSequence([plus, one], ignore_if=[times])\n", - "addconstraint!(grammar, constraint)\n", - "iter = BFSIterator(grammar, :Int, max_size=3)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Custom Constraint\n", - "\n", - "To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`.\n", - "\n", - "A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location.\n", - "\n", - "A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. \n", - "\n", - "Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function.\n", - "\n", - "(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"\n", - "Forbids the consecutive application of the specified rule.\n", - "For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row.\n", - "\"\"\"\n", - "struct ForbidConsecutive <: AbstractGrammarConstraint\n", - " rule::Int\n", - "end\n", - "\n", - "\"\"\"\n", - "Post a local constraint on each new node that appears in the tree\n", - "\"\"\"\n", - "function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int})\n", - " HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule))\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path:\n", - "\n", - "* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})`\n", - "* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)`\n", - "* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", - "* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree)\n", - "\n", - "In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver:\n", - "\n", - "* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators.\n", - "* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation.\n", - "* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint.\n", - "\n", - "The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions:\n", - "\n", - "* `get_tree(solver)` returns the root node of the current (partial) program tree\n", - "* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants.\n", - "* `get_path(solver, node)` returns the path at which the node is located.\n", - "* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations).\n", - "* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2).\n", - "\n", - "To get information about a node, we can use the following getter functions:\n", - "\n", - "* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1.\n", - "* `get_rule(node)`. Get the rule of a filled node.\n", - "* `get_children(node)`. Get the children of a node.\n", - "* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain.\n", - "\n", - "Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\"\"\"\n", - "Forbids the consecutive application of the specified rule at path `path`.\n", - "\"\"\"\n", - "struct LocalForbidConsecutive <: AbstractLocalConstraint\n", - " path::Vector{Int}\n", - " rule::Int\n", - "end\n", - "\n", - "\"\"\"\n", - "Propagates the constraints by preventing a consecutive application of the specified rule.\n", - "\"\"\"\n", - "function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive)\n", - " node = get_node_at_location(solver, constraint.path)\n", - " if isfilled(node)\n", - " if get_rule(node) == constraint.rule\n", - " #the specified rule is used, make sure the rule will not be used by any of the children\n", - " for (i, child) ∈ enumerate(get_children(node))\n", - " if isfilled(child)\n", - " if get_rule(child) == constraint.rule\n", - " #the specified rule was used twice in a row, which is violating the constraint\n", - " set_infeasible!(solver)\n", - " return\n", - " end\n", - " elseif child.domain[constraint.rule]\n", - " child_path = push!(copy(constraint.path), i)\n", - " remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child\n", - " end\n", - " end\n", - " end\n", - " elseif node.domain[constraint.rule]\n", - " #our node is a hole with the specified rule in its domain\n", - " #we will now check if any of the children already uses the specified rule\n", - " softfail = false\n", - " for (i, child) ∈ enumerate(get_children(node))\n", - " if isfilled(child)\n", - " if get_rule(child) == constraint.rule\n", - " #the child holds the specified rule, so the parent cannot have this rule\n", - " remove!(solver, constraint.path, constraint.rule)\n", - " end\n", - " elseif child.domain[constraint.rule]\n", - " #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail.\n", - " softfail = true\n", - " end\n", - " end\n", - " if softfail\n", - " #we cannot deactivate the constraint, because it needs to be repropagated\n", - " return\n", - " end\n", - " end\n", - "\n", - " #the constraint is satisfied and can be deactivated\n", - " HerbConstraints.deactivate!(solver, constraint)\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation.\n", - "\n", - "Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation.\n", - "\n", - "In our case, we want to repropagate if either:\n", - "* a tree manipulation occured at the `constraint.path`\n", - "* a tree manipulation occured at the child of the `constraint.path`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "\"\"\"\n", - "Gets called whenever an tree manipulation occurs at the given `path`.\n", - "Returns true iff the `constraint` should be rescheduled for propagation.\n", - "\"\"\"\n", - "function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool\n", - " return (path == constraint.path) || (path == constraint.path[1:end-1])\n", - "end\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "clearconstraints!(grammar)\n", - "\n", - "addconstraint!(grammar, ForbidConsecutive(minus))\n", - "addconstraint!(grammar, ForbidConsecutive(plus))\n", - "addconstraint!(grammar, ForbidConsecutive(times))\n", - "\n", - "iter = BFSIterator(grammar, :Int, max_size=6)\n", - "\n", - "for program ∈ iter\n", - " println(rulenode2expr(program, grammar))\n", - "end" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.9.4", - "language": "julia", - "name": "julia-1.9" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.9.4" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/src/tutorials/getting_started_with_constraints.md b/docs/src/tutorials/getting_started_with_constraints.md deleted file mode 100644 index 7c95e90..0000000 --- a/docs/src/tutorials/getting_started_with_constraints.md +++ /dev/null @@ -1,738 +0,0 @@ -# Getting started with HerbConstraints - -When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space. - -### Setup - -For this tutorial, we need to import the following modules of the Herb.jl framework: - -* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s -* `HerbGrammar` to define the grammar -* `HerbConstraints` to define the constraints -* `HerbSearch` to execute a constrained enumeration - -We will also redefine the simple arithmetic grammar from the previous tutorial. - - -```julia -using HerbCore, HerbGrammar, HerbConstraints, HerbSearch - -grammar = @cfgrammar begin - Int = 1 - Int = x - Int = - Int - Int = Int + Int - Int = Int * Int -end -``` - - - 1: Int = 1 - 2: Int = x - 3: Int = -Int - 4: Int = Int + Int - 5: Int = Int * Int - - - -### Working with constraints - -To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes). - -(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar) - - -```julia -clearconstraints!(grammar) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x * 1 - x + 1 - x + x - 1 + x - 1 + 1 - - -Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. - -To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids: - -* `-(-1)` -* `-(-X)` -* `-(-(1 + 1))` -* `1 + -(-(1 + 1))` -* etc - - -```julia -one = 1 -x = 2 -minus = 3 -plus = 4 -times = 5 - -addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A -addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A) - -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - 1 - x - -1 - -x - x * 1 - x * x - x + x - x + 1 - 1 + 1 - 1 + x - - -### Forbidden Constraint - -The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types: -* `RuleNode(1)`. Matches exactly the given rule. -* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5. -* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same. - - -```julia -#this constraint forbids A+A and A*A -constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)])) - -# Without this constraint, we encounter 154 programs -clearconstraints!(grammar) -iter = BFSIterator(grammar, :Int, max_size=5) -println(length(iter)) - -# With this constraint, we encounter 106 programs -clearconstraints!(grammar) -addconstraint!(grammar, constraint) -iter = BFSIterator(grammar, :Int, max_size=5) -println(length(iter)) - -``` - - 154 - 106 - - -### Contains Constraint - -The `Contains` constraint enforces that a given rule appears in the program tree at least once. - -In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant. - - -```julia -clearconstraints!(grammar) -addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - x - -x - -(-x) - 1x - x * x - x * 1 - x + 1 - x + x - 1 + x - - -### Contains Subtree Constraint - -Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once. - - -```julia -clearconstraints!(grammar) -addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree -iter = BFSIterator(grammar, :Int, max_size=4) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - x * x - -(x * x) - - -### Ordered Constraint - -The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants. - -To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree. - -In the upcoming example we will set up a template tree representing `a+b` and `a*b`. -Then, we will impose an ordering `a<=b` on all the subtrees that match the template. - -The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`. - - - -```julia -clearconstraints!(grammar) - -template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)]) -order = [:a, :b] - -addconstraint!(grammar, Ordered(template_tree, order)) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x + x - 1 + x - 1 + 1 - - -### Forbidden Sequence Constraint - -The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. - -An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. - -Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence. - -This constraint will **forbid** the following programs: - -* x + 1 -* x + -1 -* x + -(-1) -* x + (x + 1) -* x * (x + 1) - -But it will **allow** the following program (as * disrupts the sequence): - -* x + (x * 1) - - - -```julia -constraint = ForbiddenSequence([plus, one], ignore_if=[times]) -addconstraint!(grammar, constraint) -iter = BFSIterator(grammar, :Int, max_size=3) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end - -``` - - 1 - x - -1 - -x - 1 * 1 - -(-1) - 1x - -(-x) - x * x - x + x - - -### Custom Constraint - -To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`. - -A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location. - -A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies. - -Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. - -Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function. - -(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace) - - -```julia -""" -Forbids the consecutive application of the specified rule. -For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row. -""" -struct ForbidConsecutive <: AbstractGrammarConstraint - rule::Int -end - -""" -Post a local constraint on each new node that appears in the tree -""" -function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int}) - HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule)) -end -``` - - - HerbConstraints.on_new_node - - -Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path: - -* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})` -* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)` -* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)` -* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree) - -In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver: - -* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators. -* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation. -* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint. - -The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions: - -* `get_tree(solver)` returns the root node of the current (partial) program tree -* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants. -* `get_path(solver, node)` returns the path at which the node is located. -* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations). -* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2). - -To get information about a node, we can use the following getter functions: - -* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1. -* `get_rule(node)`. Get the rule of a filled node. -* `get_children(node)`. Get the children of a node. -* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain. - -Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match. - - - -```julia -""" -Forbids the consecutive application of the specified rule at path `path`. -""" -struct LocalForbidConsecutive <: AbstractLocalConstraint - path::Vector{Int} - rule::Int -end - -""" -Propagates the constraints by preventing a consecutive application of the specified rule. -""" -function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive) - node = get_node_at_location(solver, constraint.path) - if isfilled(node) - if get_rule(node) == constraint.rule - #the specified rule is used, make sure the rule will not be used by any of the children - for (i, child) ∈ enumerate(get_children(node)) - if isfilled(child) - if get_rule(child) == constraint.rule - #the specified rule was used twice in a row, which is violating the constraint - set_infeasible!(solver) - return - end - elseif child.domain[constraint.rule] - child_path = push!(copy(constraint.path), i) - remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child - end - end - end - elseif node.domain[constraint.rule] - #our node is a hole with the specified rule in its domain - #we will now check if any of the children already uses the specified rule - softfail = false - for (i, child) ∈ enumerate(get_children(node)) - if isfilled(child) - if get_rule(child) == constraint.rule - #the child holds the specified rule, so the parent cannot have this rule - remove!(solver, constraint.path, constraint.rule) - end - elseif child.domain[constraint.rule] - #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail. - softfail = true - end - end - if softfail - #we cannot deactivate the constraint, because it needs to be repropagated - return - end - end - - #the constraint is satisfied and can be deactivated - HerbConstraints.deactivate!(solver, constraint) -end -``` - - - HerbConstraints.propagate! - - -Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation. - -Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation. - -In our case, we want to repropagate if either: -* a tree manipulation occured at the `constraint.path` -* a tree manipulation occured at the child of the `constraint.path` - - -```julia - -""" -Gets called whenever an tree manipulation occurs at the given `path`. -Returns true iff the `constraint` should be rescheduled for propagation. -""" -function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool - return (path == constraint.path) || (path == constraint.path[1:end-1]) -end - -``` - - - HerbConstraints.shouldschedule - - -With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint. - - -```julia -clearconstraints!(grammar) - -addconstraint!(grammar, ForbidConsecutive(minus)) -addconstraint!(grammar, ForbidConsecutive(plus)) -addconstraint!(grammar, ForbidConsecutive(times)) - -iter = BFSIterator(grammar, :Int, max_size=6) - -for program ∈ iter - println(rulenode2expr(program, grammar)) -end -``` - - 1 - x - -1 - -x - 1 * 1 - 1x - x * x - x * 1 - x + 1 - x + x - 1 + x - 1 + 1 - 1 * -1 - 1 * -x - x * -x - x * -1 - x + -1 - x + -x - 1 + -x - -(1 * 1) - 1 + -1 - -(1x) - -(x * x) - -(x * 1) - -((x + 1)) - -((x + x)) - -1 * 1 - -((1 + x)) - -1 * x - -((1 + 1)) - -x * x - -x * 1 - -x + 1 - -x + x - -1 + x - -1 + 1 - (1 + 1) * 1 - -(1 * -1) - (1 + 1) * x - -(1 * -x) - (1 + x) * x - -(x * -x) - (1 + x) * 1 - -(x * -1) - (x + x) * 1 - -((x + -1)) - (x + x) * x - -((x + -x)) - (x + 1) * x - -((1 + -x)) - (x + 1) * 1 - -((1 + -1)) - x * 1 + 1 - x * 1 + x - x * x + x - x * x + 1 - 1x + 1 - 1x + x - -(-1 * 1) - 1 * 1 + x - -(-1 * x) - 1 * 1 + 1 - -(-x * x) - -(-x * 1) - -((-x + 1)) - 1 * (1 + 1) - -((-x + x)) - 1 * (1 + x) - -((-1 + x)) - 1 * (x + x) - -((-1 + 1)) - 1 * (x + 1) - x * (x + 1) - x * (x + x) - x * (1 + x) - x * (1 + 1) - -1 * -1 - x + 1 * 1 - -1 * -x - x + 1x - -x * -x - x + x * x - -x * -1 - x + x * 1 - -x + -1 - 1 + x * 1 - -x + -x - 1 + x * x - -1 + -x - 1 + 1x - -1 + -1 - 1 + 1 * 1 - -1 * (1 + 1) - 1 * (1 + -1) - -1 * (1 + x) - 1 * (1 + -x) - -1 * (x + x) - 1 * (x + -x) - -1 * (x + 1) - 1 * (x + -1) - -x * (x + 1) - x * (x + -1) - -x * (x + x) - x * (x + -x) - -x * (1 + x) - x * (1 + -x) - -x * (1 + 1) - x * (1 + -1) - -x + 1 * 1 - x + 1 * -1 - -x + 1x - x + 1 * -x - -x + x * x - x + x * -x - -x + x * 1 - x + x * -1 - -1 + x * 1 - 1 + x * -1 - -1 + x * x - 1 + x * -x - -1 + 1x - 1 + 1 * -x - -1 + 1 * 1 - 1 + 1 * -1 - 1 * -(1 * 1) - (1 + -1) * 1 - 1 * -(1x) - (1 + -1) * x - 1 * -(x * x) - (1 + -x) * x - 1 * -(x * 1) - (1 + -x) * 1 - 1 * -((x + 1)) - (x + -x) * 1 - 1 * -((x + x)) - (x + -x) * x - 1 * -((1 + x)) - (x + -1) * x - 1 * -((1 + 1)) - (x + -1) * 1 - x * -((1 + 1)) - x * -1 + 1 - x * -((1 + x)) - x * -1 + x - x * -((x + x)) - x * -x + x - x * -((x + 1)) - x * -x + 1 - x * -(x * 1) - 1 * -x + 1 - x * -(x * x) - 1 * -x + x - x * -(1x) - 1 * -1 + x - x * -(1 * 1) - 1 * -1 + 1 - x + -(1 * 1) - x + -(1x) - 1 * (-1 + 1) - x + -(x * x) - 1 * (-1 + x) - x + -(x * 1) - 1 * (-x + x) - x + -((x + 1)) - 1 * (-x + 1) - x + -((x + x)) - x * (-x + 1) - x + -((1 + x)) - x * (-x + x) - x + -((1 + 1)) - x * (-1 + x) - 1 + -((1 + 1)) - x * (-1 + 1) - 1 + -((1 + x)) - x + -1 * 1 - 1 + -((x + x)) - x + -1 * x - 1 + -((x + 1)) - x + -x * x - 1 + -(x * 1) - x + -x * 1 - 1 + -(x * x) - 1 + -x * 1 - 1 + -(1x) - 1 + -x * x - 1 + -(1 * 1) - 1 + -1 * x - 1 + -1 * 1 - -(1 * 1) * 1 - -(1 * 1) * x - -((1 + 1) * 1) - -(1x) * x - -((1 + 1) * x) - -(1x) * 1 - -((1 + x) * x) - -(x * x) * 1 - -((1 + x) * 1) - -(x * x) * x - -((x + x) * 1) - -(x * 1) * x - -((x + x) * x) - -(x * 1) * 1 - -((x + 1) * x) - -((x + 1)) * 1 - -((x + 1) * 1) - -((x + 1)) * x - -((x * 1 + 1)) - -((x + x)) * x - -((x * 1 + x)) - -((x + x)) * 1 - -((x * x + x)) - -((1 + x)) * 1 - -((x * x + 1)) - -((1 + x)) * x - -((1x + 1)) - -((1 + 1)) * x - -((1x + x)) - -((1 + 1)) * 1 - -((1 * 1 + x)) - -((1 + 1)) + 1 - -((1 * 1 + 1)) - -((1 + 1)) + x - -((1 + x)) + x - -(-1 * -1) - -((1 + x)) + 1 - -(-1 * -x) - -((x + x)) + 1 - -(-x * -x) - -((x + x)) + x - -(-x * -1) - -((x + 1)) + x - -((-x + -1)) - -((x + 1)) + 1 - -((-x + -x)) - -(x * 1) + 1 - -((-1 + -x)) - -(x * 1) + x - -((-1 + -1)) - -(x * x) + x - -(x * x) + 1 - (-1 + 1) * 1 - -(1x) + 1 - (-1 + 1) * x - -(1x) + x - (-1 + x) * x - -(1 * 1) + x - (-1 + x) * 1 - -(1 * 1) + 1 - (-x + x) * 1 - (-x + x) * x - (1 + 1) * -1 - (-x + 1) * x - (1 + 1) * -x - (-x + 1) * 1 - (1 + x) * -x - -x * 1 + 1 - (1 + x) * -1 - -x * 1 + x - (x + x) * -1 - -x * x + x - (x + x) * -x - -x * x + 1 - (x + 1) * -x - -1 * x + 1 - (x + 1) * -1 - -1 * x + x - x * 1 + -1 - -1 * 1 + x - x * 1 + -x - -1 * 1 + 1 - x * x + -x - x * x + -1 - -(1 * (1 + 1)) - 1x + -1 - -(1 * (1 + x)) - 1x + -x - -(1 * (x + x)) - 1 * 1 + -x - -(1 * (x + 1)) - 1 * 1 + -1 - -(x * (x + 1)) - -(x * (x + x)) - -(x * (1 + x)) - -(x * (1 + 1)) - -((x + 1 * 1)) - -((x + 1x)) - -((x + x * x)) - -((x + x * 1)) - -((1 + x * 1)) - -((1 + x * x)) - -((1 + 1x)) - -((1 + 1 * 1)) - diff --git a/docs/src/tutorials/getting_started_with_herb.html b/docs/src/tutorials/getting_started_with_herb.html index 990d2a6..c21184d 100644 --- a/docs/src/tutorials/getting_started_with_herb.html +++ b/docs/src/tutorials/getting_started_with_herb.html @@ -3,11 +3,11 @@ diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb deleted file mode 100644 index f9690e0..0000000 --- a/docs/src/tutorials/getting_started_with_herb.ipynb +++ /dev/null @@ -1,346 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Search\n", - "\n", - "This notebook describes how you can search a program space as defined by a grammar.\n", - "Specifically, we will look at example-based search, where the goal is to find a program that is able to transform the inputs of every example to the corresponding output." - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Setup\n", - "First, we start with the setup. We need to access to all the function in the Herb.jl framework." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Defining the program space\n", - "\n", - "Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). \n", - "\n", - "Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md).\n", - "\n", - "For now, we specify a simple grammar for dealing with integers and explain all the rules individually:\n", - "\n", - "1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer.\n", - "2. Then, we can also use the variable `x` to hold an integer.\n", - "3. The third rule determines we can add two integers.\n", - "4. The fourth rule determines we can subtract an integer from another.\n", - "5. Finally, we also allow the multiplication of two integers.\n", - "\n", - "If you run this cell, you can see all the rules rolled out." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Real = 0\n", - "2: Real = 1\n", - "3: Real = 2\n", - "4: Real = 3\n", - "5: Real = 4\n", - "6: Real = 5\n", - "7: Real = 6\n", - "8: Real = 7\n", - "9: Real = 8\n", - "10: Real = 9\n", - "11: Real = x\n", - "12: Real = Real + Real\n", - "13: Real = Real - Real\n", - "14: Real = Real * Real\n" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "g = HerbGrammar.@cfgrammar begin\n", - " Real = |(0:9)\n", - " Real = x\n", - " Real = Real + Real\n", - " Real = Real - Real\n", - " Real = Real * Real\n", - "end" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Defining the problem" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As mentioned before, we are looking at example-based search. \n", - "This means that the problem is defined by a set of input-output examples. \n", - "A single example hence consists of an input and an output.\n", - "The input is defined as a dictionary, with a value assigned to each variable in the grammar.\n", - "It is important to write the variable name as a `Symbol` instead of a string.\n", - "A `Symbol` in Julia is written with a colon prefix, i.e. `:x`. \n", - "The output of the input-output example is just a single value for this specific grammar, but could possibly relate to e.g. arrays of values, too.\n", - "\n", - "In the cell below we automatically generate some examples for `x` assigning values `1-5`." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "5-element Vector{IOExample}:\n", - " IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", - " IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", - " IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", - " IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", - " IOExample(Dict{Symbol, Any}(:x => 5), 20)" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# Create input-output examples\n", - "examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now that we have some input-output examples, we can define the problem. \n", - "Next to the examples, a problem also contains a name meant to link to the file path, which can be used to keep track of current examples. \n", - "For now, this is irrelevant, and you can give the program any name you like." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Problem{Vector{IOExample}}(\"example\", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)])" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "problem = HerbSpecification.Problem(\"example\", examples)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Searching\n", - "\n", - "Now that we have defined the search space and the goal of the search, we can start the search. \n", - "\n", - "Of course, our problem is underdefined as there might be multiple programs that satisfy our examples. \n", - "Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. \n", - "This program satisfies all our examples, but we don't expect it to generalize very well.\n", - "\n", - "To search through a program space, we first need to define a [`HerbSearch.ProgramIterator`](@ref), which can be instantiated with different iterators, for now we use a simple [`HerbSearch.BFSIterator`](@ref). For more advanced search methods check out our tutorial on [advanced search](.advanced_search.md). For more information about iterators, check out our tutorial on [working with interpreters](.working_with_interpreters.md). \n", - "\n", - "In general, we assume that a smaller program is more general than a larger program. \n", - "Therefore, we search for the smallest program in our grammar that satisfies our examples. \n", - "This can be done using a breadth-first search over the program/search space.\n", - "\n", - "This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. \n", - "\n", - "So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. " - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "BFSIterator(GenericSolver(1: Real = 0\n", - "2: Real = 1\n", - "3: Real = 2\n", - "4: Real = 3\n", - "5: Real = 4\n", - "6: Real = 5\n", - "7: Real = 6\n", - "8: Real = 7\n", - "9: Real = 8\n", - "10: Real = 9\n", - "11: Real = x\n", - "12: Real = Real + Real\n", - "13: Real = Real - Real\n", - "14: Real = Real * Real\n", - ", SolverState(hole[Bool[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]],, Set{AbstractLocalConstraint}(), true), DataStructures.PriorityQueue{AbstractLocalConstraint, Int64, Base.Order.ForwardOrdering}(), nothing, true, false, 9223372036854775807, 9223372036854775807))" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "iterator = BFSIterator(g, :Real)" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(12{14{4,11}6}, optimal_program)" - ] - }, - "execution_count": 6, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "synth(problem, iterator)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "As you can see, the search procedure found the correct program!" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Defining the search procedure\n", - "\n", - "In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values.\n", - "\n", - "We first define a new problem to test with, we are looking for the programs that can compute the value `167`. We immediately pass the examples to the problem and then set up the new search.\n", - "\n", - "Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(14{14{4,9}8}, optimal_program)" - ] - } - ], - "source": [ - "problem = HerbSpecification.Problem(\"example2\", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5])\n", - "iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30)\n", - "expr = HerbSearch.synth(problem, iterator)\n", - "print(expr)" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? You can try below, using the same iterator.\n", - "\n", - "In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy!" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[33m\u001b[1m┌ \u001b[22m\u001b[39m\u001b[33m\u001b[1mWarning: \u001b[22m\u001b[39mUniformSolver is iterating over more than 1000000 solutions...\n", - "\u001b[33m\u001b[1m└ \u001b[22m\u001b[39m\u001b[90m@ HerbSearch ~/.julia/packages/HerbSearch/PIeQW/src/uniform_iterator.jl:96\u001b[39m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "(12{13{6,1}14{14{3,10}10}}, optimal_program)" - ] - } - ], - "source": [ - "problem = HerbSpecification.Problem(\"example3\", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5])\n", - "expr = HerbSearch.synth(problem, iterator)\n", - "print(expr)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.10.3", - "language": "julia", - "name": "julia-1.10" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.10.3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} diff --git a/docs/src/tutorials/getting_started_with_herb.md b/docs/src/tutorials/getting_started_with_herb.md deleted file mode 100644 index 3467ee9..0000000 --- a/docs/src/tutorials/getting_started_with_herb.md +++ /dev/null @@ -1,180 +0,0 @@ -# Search - -This notebook describes how you can search a program space as defined by a grammar. -Specifically, we will look at example-based search, where the goal is to find a program that is able to transform the inputs of every example to the corresponding output. - -### Setup -First, we start with the setup. We need to access to all the function in the Herb.jl framework. - - -```julia -using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints -``` - -### Defining the program space - -Next, we start by creating a grammar. We define a context-free grammar (cfg) as a [`HerbGrammar.ContextSpecificGrammar`](@ref) without any constraints. A cfg is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). - -Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see our tutorial on [defining grammars](defining_grammars.md). - -For now, we specify a simple grammar for dealing with integers and explain all the rules individually: - -1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer. -2. Then, we can also use the variable `x` to hold an integer. -3. The third rule determines we can add two integers. -4. The fourth rule determines we can subtract an integer from another. -5. Finally, we also allow the multiplication of two integers. - -If you run this cell, you can see all the rules rolled out. - - -```julia -g = HerbGrammar.@cfgrammar begin - Real = |(0:9) - Real = x - Real = Real + Real - Real = Real - Real - Real = Real * Real -end -``` - - - 1: Real = 0 - 2: Real = 1 - 3: Real = 2 - 4: Real = 3 - 5: Real = 4 - 6: Real = 5 - 7: Real = 6 - 8: Real = 7 - 9: Real = 8 - 10: Real = 9 - 11: Real = x - 12: Real = Real + Real - 13: Real = Real - Real - 14: Real = Real * Real - - - -### Defining the problem - -As mentioned before, we are looking at example-based search. -This means that the problem is defined by a set of input-output examples. -A single example hence consists of an input and an output. -The input is defined as a dictionary, with a value assigned to each variable in the grammar. -It is important to write the variable name as a `Symbol` instead of a string. -A `Symbol` in Julia is written with a colon prefix, i.e. `:x`. -The output of the input-output example is just a single value for this specific grammar, but could possibly relate to e.g. arrays of values, too. - -In the cell below we automatically generate some examples for `x` assigning values `1-5`. - - -```julia -# Create input-output examples -examples = [HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] -``` - - - 5-element Vector{IOExample}: - IOExample(Dict{Symbol, Any}(:x => 1), 8) - IOExample(Dict{Symbol, Any}(:x => 2), 11) - IOExample(Dict{Symbol, Any}(:x => 3), 14) - IOExample(Dict{Symbol, Any}(:x => 4), 17) - IOExample(Dict{Symbol, Any}(:x => 5), 20) - - -Now that we have some input-output examples, we can define the problem. -Next to the examples, a problem also contains a name meant to link to the file path, which can be used to keep track of current examples. -For now, this is irrelevant, and you can give the program any name you like. - - -```julia -problem = HerbSpecification.Problem("example", examples) -``` - - - Problem{Vector{IOExample}}("example", IOExample[IOExample(Dict{Symbol, Any}(:x => 1), 8), IOExample(Dict{Symbol, Any}(:x => 2), 11), IOExample(Dict{Symbol, Any}(:x => 3), 14), IOExample(Dict{Symbol, Any}(:x => 4), 17), IOExample(Dict{Symbol, Any}(:x => 5), 20)]) - - -### Searching - -Now that we have defined the search space and the goal of the search, we can start the search. - -Of course, our problem is underdefined as there might be multiple programs that satisfy our examples. -Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. -This program satisfies all our examples, but we don't expect it to generalize very well. - -To search through a program space, we first need to define a [`HerbSearch.ProgramIterator`](@ref), which can be instantiated with different iterators, for now we use a simple [`HerbSearch.BFSIterator`](@ref). For more advanced search methods check out our tutorial on [advanced search](.advanced_search.md). For more information about iterators, check out our tutorial on [working with interpreters](.working_with_interpreters.md). - -In general, we assume that a smaller program is more general than a larger program. -Therefore, we search for the smallest program in our grammar that satisfies our examples. -This can be done using a breadth-first search over the program/search space. - -This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. - -So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. - - -```julia -iterator = BFSIterator(g, :Real) -``` - - - BFSIterator(1: Real = 0 - 2: Real = 1 - 3: Real = 2 - 4: Real = 3 - 5: Real = 4 - 6: Real = 5 - 7: Real = 6 - 8: Real = 7 - 9: Real = 8 - 10: Real = 9 - 11: Real = x - 12: Real = Real + Real - 13: Real = Real - Real - 14: Real = Real * Real - , :Real, 9223372036854775807, 9223372036854775807, 9223372036854775807, 9223372036854775807) - - - -```julia -synth(problem, iterator) -``` - - - (12{14{11,4}6}, optimal_program) - - -As you can see, the search procedure found the correct program! - -### Defining the search procedure - -In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values. - -We first define a new problem to test with, we are looking for the programs that can compute the value `167`. We immediately pass the examples to the problem and then set up the new search. - -Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search. - - -```julia -problem = HerbSpecification.Problem("example2", [HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5]) -iterator = HerbSearch.BFSIterator(g, :Real, max_depth=4, max_size=30, max_time=180) -expr = HerbSearch.synth(problem, iterator) -print(expr) -``` - - (14{7,14{5,8}}, optimal_program) - -We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? You can try below, using the same iterator. - -In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy! - - -```julia -problem = HerbSpecification.Problem("example3", [HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5]) -expr = HerbSearch.synth(problem, iterator) -print(expr) -``` - - (12{14{7,14{10,4}}6}, optimal_program) diff --git a/docs/src/tutorials/working_with_interpreters.html b/docs/src/tutorials/working_with_interpreters.html index e4a9971..f9ae880 100644 --- a/docs/src/tutorials/working_with_interpreters.html +++ b/docs/src/tutorials/working_with_interpreters.html @@ -3,11 +3,11 @@ diff --git a/docs/src/tutorials/working_with_interpreters.ipynb b/docs/src/tutorials/working_with_interpreters.ipynb deleted file mode 100644 index ce05015..0000000 --- a/docs/src/tutorials/working_with_interpreters.ipynb +++ /dev/null @@ -1,271 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Using the Julia interpreter\n", - "\n", - "To know how good a candidate program is, program synthesisers execute them. The easiest way to execute a program is to rely on Julia itself. To leverage the Julia interpreter, you only have to ensure that your programs are valid Julia expressions. \n", - "\n", - "First, let's import the necessary packages" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "using HerbGrammar, HerbInterpret" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\n", - "Now, assume the following grammar." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "1: Number = 1\n", - "2: Number = 2\n", - "3: Number = x\n", - "4: Number = Number + Number\n", - "5: Number = Number * Number\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "g = @cfgrammar begin\n", - " Number = |(1:2)\n", - " Number = x\n", - " Number = Number + Number\n", - " Number = Number * Number\n", - " end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's construct a program `x+3`, which would correspond to the following `RuleNode` representation" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4{3,1}" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "myprog = RuleNode(4,[RuleNode(3),RuleNode(1)])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "To run this program, we have to convert it into a Julia expression, which we can do in the following way:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - ":(x + 1)" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "myprog_julia = rulenode2expr(myprog, g)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we have a valid Julia expression, but we are still missing one key ingredient: we have to inform the interpreter about the special symbols. In our case, these are `:x` and `:+`. To do so, we need to create a symbol table, which is nothing more than a dictionary mapping symbols to their values:" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Dict{Symbol, Any} with 2 entries:\n", - " :+ => +\n", - " :x => 2" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "symboltable = Dict{Symbol,Any}(:x => 2, :+ => +)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now we can execute our program through the defaul interpreter available in `HerbInterpret`:\n" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "3" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "interpret(symboltable, myprog_julia)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "And that's it!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Defining a custom interpreter\n", - "\n", - "A disadvantage of the default Julia interpreter is that it needs to traverse abstract syntax tree twice -- once to convert it into a Julia expression, and the second time to execute that expression. Program execution is regularly the most consuming part of the entire pipeline and, by eliminating one of these steps, we can cut the runtime in half.\n", - "\n", - "We can define an interpreter that works directly over `RuleNode`s. \n", - "Consider the scenario in which we want to write programs for robot navigation: imagine a 2D world in which the robot can move around and pick up a ball. The programs we could write direct the robot to go up, down, left, and right. For convenience, the programming language also offers conditionals and loops:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "grammar_robots = @csgrammar begin\n", - " Start = Sequence #1\n", - "\n", - " Sequence = Operation #2\n", - " Sequence = (Operation; Sequence) #3\n", - " Operation = Transformation #4\n", - " Operation = ControlStatement #5\n", - "\n", - " Transformation = moveRight() | moveDown() | moveLeft() | moveUp() | drop() | grab() #6\n", - " ControlStatement = IF(Condition, Sequence, Sequence) #12\n", - " ControlStatement = WHILE(Condition, Sequence) #13\n", - "\n", - " Condition = atTop() | atBottom() | atLeft() | atRight() | notAtTop() | notAtBottom() | notAtLeft() | notAtRight() #14\n", - "end" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs:\n", - " - `moveRight(); moveLeft(); drop()`\n", - " - WHILE(notAtTop(), moveUp())\n", - "\n", - "The idea behind this programming language is that the program specifies a set of transformations over a state of the robot world. Thus, a program can only be executed over a particular state. In this case, the state represents the size of the 2D world, the current position of a robot, the current position of a ball, and whether the robot is currently holding a ball. The execution of a particular instruction acts as a state transformation: each instruction takes a state as an input, transforms it, and passes it to the subsequent instruction. For example, execution of the program `moveRight(); moveLeft(); drop()` would proceed as:\n", - " 1. take an input state, \n", - " 2. pass it to the `moveRight()` instruction,\n", - " 3. pass the output of `moveRight()` to `moveLeft()` instructions,\n", - " 4. pass the output of `moveLeft()` to `drop()`,\n", - " 5. return the output of `drop()`.\n", - "\n", - "\n", - " The following is only one possible way to implement a custom interpreter, but it demonstrates a general template that can always be followed.\n", - "\n", - " We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program:\n", - "\n", - " interpret(prog::AbstractRuleNode, grammar::ContextSensitiveGrammar, state::RobotState)::RobotState\n", - "\n", - " As `RuleNode`s only store indices of derivation rules from the grammar, not the functions themselves, we will first pull the function call associated with every derivation rule. In Julia, this is indicated by the top-level symbol of the rules. For example, the top-level symbol for the derivation rule 6 is `:moveRight`; for rule 12, that is `:IF`." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "The remaining functions follow a similar idea. (You can see the full implementation of this interpreter [here](https://github.com/Herb-AI/HerbBenchmarks.jl/blob/new-robots/src/data/Robots_2020/robots_primitives.jl))." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "PLUTO_PROJECT_TOML_CONTENTS = \"\"\"\n", - "[deps]\n", - "HerbGrammar = \"4ef9e186-2fe5-4b24-8de7-9f7291f24af7\"\n", - "HerbInterpret = \"5bbddadd-02c5-4713-84b8-97364418cca7\"\n", - "\n", - "[compat]\n", - "HerbGrammar = \"~0.3.0\"\n", - "HerbInterpret = \"~0.1.3\"\n", - "\"\"\"" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Julia 1.9.4", - "language": "julia", - "name": "julia-1.9" - }, - "language_info": { - "file_extension": ".jl", - "mimetype": "application/julia", - "name": "julia", - "version": "1.9.4" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/docs/src/tutorials/working_with_interpreters.md b/docs/src/tutorials/working_with_interpreters.md deleted file mode 100644 index d1cc62e..0000000 --- a/docs/src/tutorials/working_with_interpreters.md +++ /dev/null @@ -1,141 +0,0 @@ -# Using the Julia interpreter - -To know how good a candidate program is, program synthesisers execute them. The easiest way to execute a program is to rely on Julia itself. To leverage the Julia interpreter, you only have to ensure that your programs are valid Julia expressions. - -First, let's import the necessary packages - - -```julia -using HerbGrammar, HerbInterpret -``` - - -Now, assume the following grammar. - - -```julia -g = @cfgrammar begin - Number = |(1:2) - Number = x - Number = Number + Number - Number = Number * Number - end -``` - - - 1: Number = 1 - 2: Number = 2 - 3: Number = x - 4: Number = Number + Number - 5: Number = Number * Number - - - -Let's construct a program `x+3`, which would correspond to the following `RuleNode` representation - - -```julia -myprog = RuleNode(4,[RuleNode(3),RuleNode(1)]) -``` - - - 4{3,1} - - -To run this program, we have to convert it into a Julia expression, which we can do in the following way: - - -```julia -myprog_julia = rulenode2expr(myprog, g) -``` - - - :(x + 1) - - -Now we have a valid Julia expression, but we are still missing one key ingredient: we have to inform the interpreter about the special symbols. In our case, these are `:x` and `:+`. To do so, we need to create a symbol table, which is nothing more than a dictionary mapping symbols to their values: - - -```julia -symboltable = Dict{Symbol,Any}(:x => 2, :+ => +) -``` - - - Dict{Symbol, Any} with 2 entries: - :+ => + - :x => 2 - - -Now we can execute our program through the defaul interpreter available in `HerbInterpret`: - - - -```julia -interpret(symboltable, myprog_julia) -``` - - - 3 - - -And that's it! - -# Defining a custom interpreter - -A disadvantage of the default Julia interpreter is that it needs to traverse abstract syntax tree twice -- once to convert it into a Julia expression, and the second time to execute that expression. Program execution is regularly the most consuming part of the entire pipeline and, by eliminating one of these steps, we can cut the runtime in half. - -We can define an interpreter that works directly over `RuleNode`s. -Consider the scenario in which we want to write programs for robot navigation: imagine a 2D world in which the robot can move around and pick up a ball. The programs we could write direct the robot to go up, down, left, and right. For convenience, the programming language also offers conditionals and loops: - - -```julia -grammar_robots = @csgrammar begin - Start = Sequence #1 - - Sequence = Operation #2 - Sequence = (Operation; Sequence) #3 - Operation = Transformation #4 - Operation = ControlStatement #5 - - Transformation = moveRight() | moveDown() | moveLeft() | moveUp() | drop() | grab() #6 - ControlStatement = IF(Condition, Sequence, Sequence) #12 - ControlStatement = WHILE(Condition, Sequence) #13 - - Condition = atTop() | atBottom() | atLeft() | atRight() | notAtTop() | notAtBottom() | notAtLeft() | notAtRight() #14 -end -``` - -This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs: - - `moveRight(); moveLeft(); drop()` - - WHILE(notAtTop(), moveUp()) - -The idea behind this programming language is that the program specifies a set of transformations over a state of the robot world. Thus, a program can only be executed over a particular state. In this case, the state represents the size of the 2D world, the current position of a robot, the current position of a ball, and whether the robot is currently holding a ball. The execution of a particular instruction acts as a state transformation: each instruction takes a state as an input, transforms it, and passes it to the subsequent instruction. For example, execution of the program `moveRight(); moveLeft(); drop()` would proceed as: - 1. take an input state, - 2. pass it to the `moveRight()` instruction, - 3. pass the output of `moveRight()` to `moveLeft()` instructions, - 4. pass the output of `moveLeft()` to `drop()`, - 5. return the output of `drop()`. - - - The following is only one possible way to implement a custom interpreter, but it demonstrates a general template that can always be followed. - - We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program: - - interpret(prog::AbstractRuleNode, grammar::ContextSensitiveGrammar, state::RobotState)::RobotState - - As `RuleNode`s only store indices of derivation rules from the grammar, not the functions themselves, we will first pull the function call associated with every derivation rule. In Julia, this is indicated by the top-level symbol of the rules. For example, the top-level symbol for the derivation rule 6 is `:moveRight`; for rule 12, that is `:IF`. - -The remaining functions follow a similar idea. (You can see the full implementation of this interpreter [here](https://github.com/Herb-AI/HerbBenchmarks.jl/blob/new-robots/src/data/Robots_2020/robots_primitives.jl)). - - -```julia -PLUTO_PROJECT_TOML_CONTENTS = """ -[deps] -HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" -HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" - -[compat] -HerbGrammar = "~0.3.0" -HerbInterpret = "~0.1.3" -""" -``` From 8609bb194db4c0a85381176811a56c87ae104850 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Mon, 30 Sep 2024 12:47:35 +0200 Subject: [PATCH 74/75] Change section headings for BFS/DFS. --- docs/src/tutorials/advanced_search.jl | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/src/tutorials/advanced_search.jl b/docs/src/tutorials/advanced_search.jl index 3c42a59..8bb6ee3 100644 --- a/docs/src/tutorials/advanced_search.jl +++ b/docs/src/tutorials/advanced_search.jl @@ -161,18 +161,21 @@ md""""This time we find a solution, although a suboptimal one.""" # ╔═╡ 9b4b21e0-dc6a-43ae-a511-79988ee99001 md""" -## Search methods +## Top-down search Herb.jl provides already implemented, ready-to-use search methods. The core building block of the search is the program iterator, which represents a walk through the program space. All program iterators share the top-level abstract type `ProgramIterator`. For more information on iterators and how to customize them, see [this tutorial](https://herb-ai.github.io/Herb.jl/dev/tutorials/TopDown/). + +First, we explore two fundamental deterministic top-down search algorithms: **breadth-first search (BFS)** and **depth-first search (DFS)**. Both algorithms are implemented using the abstract type `TopDownIterator`, which can be customized through the functions +- `priority_function` +- `derivation_heuristic` +- `hole_heuristic` """ # ╔═╡ 115c02c9-ae0c-4623-a61d-831fc6ad55a2 md""" -### Deterministic search: BFS and DFS - First, we explore two fundamental deterministic top-down search algorithms: **breadth-first search (BFS)** and **depth-first search (DFS)**. Both algorithms are implemented using the abstract type `TopDownIterator`, which can be customized through the functions priority_function, derivation_heuristic, and hole_heuristic. -#### Breadth-First Search +### Breadth-First Search The `BFSIterator` enumerates all possible programs at a given depth before progressing to the next level, ensuring that trees are explored in increasing order of size. This guarantees that smaller programs are evaluated first, and larger, more complex ones are considered only after all smaller ones have been processed. @@ -225,7 +228,7 @@ println(all(p ∈ programs_bfs for p ∈ answer_programs)) # ╔═╡ 0020b79a-6352-4e2d-93f6-2a1d7b03ae2c md""" -#### Depth-First Search +### Depth-First Search The `DFSIterator` explores one branch of the search tree at a time, fully traversing it unitl a correct program is found or the specified `max_depth` is reached. Only after completing the current branch, it proceeds to the next branch. From 27701f8609d273815a815fc41f16c0010f4132e8 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 3 Oct 2024 09:43:45 +0200 Subject: [PATCH 75/75] Defines priority function for DFS rightmost properly. Removes unnecessary begin...end, and TODO notes. --- docs/src/tutorials/advanced_search.html | 17 ++++++++ docs/src/tutorials/advanced_search.jl | 54 +++++++++++-------------- 2 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 docs/src/tutorials/advanced_search.html diff --git a/docs/src/tutorials/advanced_search.html b/docs/src/tutorials/advanced_search.html new file mode 100644 index 0000000..c43adcc --- /dev/null +++ b/docs/src/tutorials/advanced_search.html @@ -0,0 +1,17 @@ + + + + + + + +
\ No newline at end of file diff --git a/docs/src/tutorials/advanced_search.jl b/docs/src/tutorials/advanced_search.jl index 8bb6ee3..ba70384 100644 --- a/docs/src/tutorials/advanced_search.jl +++ b/docs/src/tutorials/advanced_search.jl @@ -103,13 +103,11 @@ Let's explore how many enumerations are necessary to solve our simple problem. """ # ╔═╡ 3954dd49-07a2-4ec2-91b4-9c9596d5c264 -begin - for i in range(1, 50) +for i in range(1, 50) println(i, " enumerations") iterator = BFSIterator(g_1, :Number, max_depth=i) solution = @time synth(problem_1, iterator) println(solution) - end end # ╔═╡ 9892e91b-9115-4520-9637-f8d7c8905825 @@ -128,12 +126,10 @@ We will use a new example to see the effect of `allow_evaluation_errors`. We beg """ # ╔═╡ 9fb40ceb-8d41-491b-8941-20a8b240eb82 -begin - g_2 = @cfgrammar begin - Number = 1 - List = [] - Index = List[Number] - end +g_2 = @csgrammar begin + Number = 1 + List = [] + Index = List[Number] end # ╔═╡ 94e0d676-a9c7-4291-8696-15301e541c30 @@ -183,10 +179,9 @@ To explore `BFSIterator`, we define another very simple grammar. """ # ╔═╡ 3af650d9-19c6-4351-920d-d2361091f628 -begin g_3 = @cfgrammar begin +g_3 = @csgrammar begin Real = 1 | 2 Real = Real * Real - end end # ╔═╡ 4cb08dba-aea5-4c31-998c-844d1fce8c81 @@ -254,11 +249,22 @@ md""" # ╔═╡ ed198b79-1b95-4531-b148-c1037cfdacf4 md""" -By default, `priority_function` for a `TopDownIterator` is that of a BFS iterator. Hence, we need to provide a new implementation. For this, we can make use of the existing `priority_function` of `DFSIterator`. +By default, `priority_function` for a `TopDownIterator` is that of a BFS iterator. Hence, we need to provide a new implementation. """ -# ╔═╡ d62918e4-4113-45bd-81ea-d17d007b83f5 -priority_function(::DFSIteratorRightmost) = priority_function(DFSIterator()) +# ╔═╡ 75b1abfd-19ed-43f5-ac65-f8ffde76c581 +function priority_function( + ::DFSIteratorRightmost, + ::AbstractGrammar, + ::AbstractRuleNode, + parent_value::Union{Real, Tuple{Vararg{Real}}}, + isrequeued::Bool +) + if isrequeued + return parent_value; + end + return parent_value - 1; +end # ╔═╡ 7480d1e4-e417-4d87-80b7-513a098da70e md""" @@ -351,20 +357,15 @@ Run the following code block to define the iterator and perform the program synt # ╔═╡ 0a30fb40-cd45-4661-a501-ae8e45f1e07e begin - iteratormh = MHSearchIterator(g_4, :X, examples_mh, cost_function, max_depth=3) - programmh = synth(problem_mh, iteratormh) - println("Sollution using MH: ", programmh) + iterator_mh = MHSearchIterator(g_4, :X, examples_mh, cost_function, max_depth=3) + program_mh = synth(problem_mh, iterator_mh) + println("Sollution using MH: ", program_mh) end -# ╔═╡ 82f7ffa5-2bdc-4153-85fa-d7aca063da12 -programmh - # ╔═╡ 700270ea-90bd-474b-91d9-0e5ed329776a md""" ### Very Large Scale Neighbourhood Search -TODO: what do we want to show with the examples? - The second stochastic search method we consider is Very Large Scale Neighbourhood Search (VLSN). In each iteration, the algorithm searches the neighbourhood of the current candidate program for a local optimum, aiming to find a better candidate solution. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). @@ -436,15 +437,10 @@ initial_temperature2 = 2 # ╔═╡ 4ff69f0a-6626-4593-b361-a2387eecc731 iterator_sa2 = SASearchIterator(g_4, :X, examples_sa, cost_function, max_depth=3, initial_temperature = initial_temperature2) -# ╔═╡ 0f7f228c-4f95-4f1a-9ca2-2d03384a00c0 - - # ╔═╡ 5df0ba53-b528-4baf-9980-cafe5d73f9dd md""" ## Genetic Search -TODO: talk a bit about the iterator. - Genetic search is a type of evolutionary algorithm, which simulates the process of natural selection. It evolves a population of candidate programs through operations like mutation, crossover (recombination), and selection. Then, the fitness of each program is assessed (i.e., how well it satisfies the given specifications). Only the 'fittest' programs are selected for the next generation, thus gradually refining the population of candidate programs. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). @@ -1262,7 +1258,7 @@ version = "0.13.1+0" # ╟─243165be-a9d2-484d-8046-811a2b0ba139 # ╠═4b97602a-5226-429f-86ea-8ecac3c807fa # ╟─ed198b79-1b95-4531-b148-c1037cfdacf4 -# ╠═d62918e4-4113-45bd-81ea-d17d007b83f5 +# ╠═75b1abfd-19ed-43f5-ac65-f8ffde76c581 # ╟─7480d1e4-e417-4d87-80b7-513a098da70e # ╠═7e2af72d-b71c-4234-9bca-cb9a90732a91 # ╠═00d05a7e-ca79-4d6b-828d-b24ef1cb80a2 @@ -1280,7 +1276,6 @@ version = "0.13.1+0" # ╠═afe1872c-6641-4fa0-a53e-50c6b4a712ee # ╟─69e91ae9-8475-47dd-826e-8c229faa11e8 # ╠═0a30fb40-cd45-4661-a501-ae8e45f1e07e -# ╠═82f7ffa5-2bdc-4153-85fa-d7aca063da12 # ╟─700270ea-90bd-474b-91d9-0e5ed329776a # ╠═e6e5c63b-34e8-40d6-bc12-bd31f40b4b16 # ╠═2397f65f-e6b4-4f11-bf66-83440c58b688 @@ -1300,7 +1295,6 @@ version = "0.13.1+0" # ╠═73304e3f-05bf-4f0c-9acd-fc8afa87b460 # ╠═07f11eb1-6b45-441a-a481-57628bad23ae # ╠═4ff69f0a-6626-4593-b361-a2387eecc731 -# ╠═0f7f228c-4f95-4f1a-9ca2-2d03384a00c0 # ╟─5df0ba53-b528-4baf-9980-cafe5d73f9dd # ╠═99ea1c20-ca2c-4d77-bc3b-06814db1d666 # ╠═d991edb9-2291-42a7-97ff-58c456515505

xaB`>R~{9E=N{p7VyUAVA< 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 \ No newline at end of file + + + diff --git a/docs/src/assets/logo_old.svg b/docs/src/assets/logo_old.svg new file mode 100644 index 0000000..4f084f8 --- /dev/null +++ b/docs/src/assets/logo_old.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8d30c3a32782136761ef0940dea6eb3591d51a35 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Wed, 1 Nov 2023 17:51:57 +0100 Subject: [PATCH 15/75] Add tutorials from HerbExamples --- docs/make.jl | 4 + docs/src/tutorials/defining_grammars.ipynb | 948 ++++++++++++++++++ docs/src/tutorials/defining_grammars.md | 588 +++++++++++ .../tutorials/getting_started_with_herb.ipynb | 295 ++++++ .../tutorials/getting_started_with_herb.md | 154 +++ 5 files changed, 1989 insertions(+) create mode 100644 docs/src/tutorials/defining_grammars.ipynb create mode 100644 docs/src/tutorials/defining_grammars.md create mode 100644 docs/src/tutorials/getting_started_with_herb.ipynb create mode 100644 docs/src/tutorials/getting_started_with_herb.md diff --git a/docs/make.jl b/docs/make.jl index 3d03691..47d124e 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,6 +21,10 @@ makedocs(; "get_started.md", "concepts.md" ], + "Tutorials" => [ + "An more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", + "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md" + ] "Sub-Modules" => [ "HerbGrammar.jl" => "HerbGrammar/index.md", "HerbSearch.jl" => "HerbSearch/index.md", diff --git a/docs/src/tutorials/defining_grammars.ipynb b/docs/src/tutorials/defining_grammars.ipynb new file mode 100644 index 0000000..c0828fe --- /dev/null +++ b/docs/src/tutorials/defining_grammars.ipynb @@ -0,0 +1,948 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Defining Grammars in Herb.jl\n", + "\n", + "The program space in Herb.jl is defined using a grammar. \n", + "This notebook demonstrates how such a grammar can be created. \n", + "There are multiple kinds of grammars, but they can all be defined in a very similar way." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "include(\"../../src/Herb.jl\") # this can be deleted when we have modules\n", + "using ..Herb" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Creating a simple grammar\n", + "\n", + "This cell contains a very simple arithmetic grammar. \n", + "The grammar is defined using the `@cfgrammar` macro. \n", + "This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. \n", + "Macro's are executed during compilation.\n", + "If you want to load a grammar during execution, have a look at the `Herb.HerbGrammar.expr2cfgrammar` function." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 1\n", + "2: Int = 2\n", + "3: Int = 3\n", + "4: Int = Int * Int\n", + "5: Int = Int + Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₁ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = 1\n", + " Int = 2\n", + " Int = 3\n", + " Int = Int * Int\n", + " Int = Int + Int\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Defining every integer one-by-one can be quite tedious. Therefore, it is also possible to use the following syntax that makes use of a Julia iterator:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int * Int\n", + "12: Int = Int + Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₂ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |(0:9)\n", + " Int = Int * Int\n", + " Int = Int + Int\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can do the same with lists:" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 2\n", + "3: Int = 4\n", + "4: Int = 6\n", + "5: Int = 8\n", + "6: Int = Int * Int\n", + "7: Int = Int + Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₃ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |([0, 2, 4, 6, 8])\n", + " Int = Int * Int\n", + " Int = Int + Int\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Variables can also be added to the grammar by just using the variable name:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int * Int\n", + "12: Int = Int + Int\n", + "13: Int = x\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₄ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |(0:9)\n", + " Int = Int * Int\n", + " Int = Int + Int\n", + " Int = x\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Grammars can also work with functions. \n", + "After all, `+` and `*` are just infix operators for Julia's identically-named functions.\n", + "You can use functions that are provided by Julia, or functions that you wrote yourself:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int * Int\n", + "12: Int = Int + Int\n", + "13: Int = f(Int)\n", + "14: Int = x\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "f(a) = a + 1\n", + "\n", + "g₅ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |(0:9)\n", + " Int = Int * Int\n", + " Int = Int + Int\n", + " Int = f(Int)\n", + " Int = x\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Similarly, we can also define the operator times (x) manually." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = a\n", + "12: Int = Int + Int\n", + "13: Int = Int × Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "×(a, b) = a * b\n", + "\n", + "g₆ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |(0:9)\n", + " Int = a\n", + " Int = Int + Int\n", + " Int = Int × Int\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working with grammars\n", + "\n", + "If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. \n", + "This section is not necessarily complete, but it aims to give an overview of the most important functions. \n", + "\n", + "It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with that.\n", + "\n", + "One of the most important things about grammars is that each rule has an index associated with it:" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13-element Vector{Tuple{Int64, Any}}:\n", + " (1, 0)\n", + " (2, 1)\n", + " (3, 2)\n", + " (4, 3)\n", + " (5, 4)\n", + " (6, 5)\n", + " (7, 6)\n", + " (8, 7)\n", + " (9, 8)\n", + " (10, 9)\n", + " (11, :(Int + Int))\n", + " (12, :(Int * Int))\n", + " (13, :x)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₇ = Herb.HerbGrammar.@cfgrammar begin\n", + " Int = |(0:9)\n", + " Int = Int + Int\n", + " Int = Int * Int\n", + " Int = x\n", + "end\n", + "\n", + "collect(enumerate(g₇.rules))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use this index to extract information from the grammar." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### isterminal\n", + "\n", + "`isterminal` returns `true` if a rule is terminal, i.e. it cannot be expanded. For example, rule 1 is terminal, but rule 11 is not, since it contains the non-terminal symbol `:Int`. " + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "true" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.isterminal(g₇, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "false" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.isterminal(g₇, 11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### return_type\n", + "\n", + "This function is rather obvious; it returns the non-terminal symbol that corresponds to a certain rule. The return type for all rules in our grammar is `:Int`." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ":Int" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.return_type(g₇, 11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### child_types\n", + "\n", + "`child_types` returns the types of the nonterminal children of a rule in a vector.\n", + "If you just want to know how many children a rule has, and not necessarily which types they have, you can use `nchildren`" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2-element Vector{Symbol}:\n", + " :Int\n", + " :Int" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.child_types(g₇, 11)" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.nchildren(g₇, 11)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### nonterminals\n", + "\n", + "The `nonterminals` function can be used to obtain a list of all nonterminals in the grammar." + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1-element Vector{Symbol}:\n", + " :Int" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.nonterminals(g₇)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Adding rules\n", + "\n", + "It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function.\n", + "As with most functions in Julia that end with an exclamation mark, this function modifies its argument (the grammar).\n", + "\n", + "A rule can be provided in the same syntax as is used in the grammar definition.\n", + "The rule should be of the `Expr` type, which is a built-in type for representing expressions. \n", + "An easy way of creating `Expr` values in Julia is to encapsulate it in brackets and use a colon as prefix:" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int + Int\n", + "12: Int = Int * Int\n", + "13: Int = x\n", + "14: Int = Int - Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.add_rule!(g₇, :(Int = Int - Int))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Removing rules\n", + "\n", + "It is also possible to remove rules in Herb.jl, however, this is a bit more involved. \n", + "As said before, rules have an index associated with them. \n", + "The internal representation of programs that are defined by the grammar makes use of those indices for efficiency.\n", + "Blindly removing a rule would shift the indices of other rules, and this could mean that existing programs get a different meaning or become invalid. \n", + "\n", + "Therefore, there are two functions for removing rules:\n", + "\n", + "- `remove_rule!` removes a rule from the grammar, but fills its place with a placeholder. Therefore, the indices stay the same, and only programs that use the removed rule become invalid.\n", + "- `cleanup_removed_rules!` removes all placeholders and shifts the indices of the other rules.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: nothing = nothing\n", + "12: Int = Int * Int\n", + "13: Int = x\n", + "14: Int = Int - Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.remove_rule!(g₇, 11)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int * Int\n", + "12: Int = x\n", + "13: Int = Int - Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.cleanup_removed_rules!(g₇)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Context-sensitive grammars\n", + "\n", + "Context-sensitive grammars allow additional constraints to be added with respect to context-free grammars.\n", + "The syntax for defining a context-sensitive grammar is identical to defining a context-sensitive grammar:" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int + Int\n", + "12: Int = Int * Int\n", + "13: Int = x\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₈ = Herb.HerbGrammar.@csgrammar begin\n", + " Int = |(0:9)\n", + " Int = Int + Int\n", + " Int = Int * Int\n", + " Int = x\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar.\n", + "Currently, Herb.jl only has propagators constraints. \n", + "These constraints each have a corresponding `propagate` function that removes all options that violate that constraint from the domain. \n", + "At the moment, there are three propagator constraints:\n", + "\n", + "- `ComesAfter(rule, predecessors)`: It is only possible to use rule `rule` when `predecessors` are in its path to the root.\n", + "- `Forbidden(sequence)`: Forbids the derivation specified as a path in an expression tree.\n", + "- `Ordered(order)`: Rules have to be used in the specified order. That is, rule at index K can only be used if rules at indices `[1...K-1]` are used in the left subtree of the current expression.\n", + "\n", + "Below, an example is given of a context-sensitive grammar with a `ComesAfter` constraint:" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1-element Vector{Main.Herb.HerbCore.Constraint}:\n", + " Main.Herb.HerbConstraints.ComesAfter(1, [9])" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.addconstraint!(g₈, Herb.HerbConstraints.ComesAfter(1, [9]))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Probabilistic grammars\n", + "\n", + "Herb.jl also supports probabilistic grammars. \n", + "These grammars allow the user to assign a probability to each rule in the grammar.\n", + "A probabilistic grammar can be defined in a very similar way to a standard grammar, but has some slightly different syntax:" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "0.07692307692307693 : 1\n", + "0.07692307692307693 : 2\n", + "0.07692307692307693 : 3\n", + "0.07692307692307693 : 4\n", + "0.07692307692307693 : 5\n", + "0.07692307692307693 : 6\n", + "0.07692307692307693 : 7\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n", + "┌ Warning: Requesting probability in a non-probabilistic grammar.\n", + "│ Uniform distribution is assumed.\n", + "└ @ Main.Herb.HerbGrammar d:\\GitHub\\Herb\\HerbGrammar.jl\\src\\grammar_base.jl:155\n" + ] + } + ], + "source": [ + "g₉ = Herb.HerbGrammar.@pcfgrammar begin\n", + " 0.4 : Int = |(0:9)\n", + " 0.2 : Int = Int + Int\n", + " 0.1 : Int = Int * Int\n", + " 0.3 : Int = x\n", + "end\n", + "\n", + "for r ∈ 1:length(g₃.rules)\n", + " p = Herb.HerbGrammar.probability(g₈, r)\n", + "\n", + " println(\"$p : $r\")\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The numbers before each rule represent the probability assigned to that rule.\n", + "The total probability for each return type should add up to 1.0.\n", + "If this isn't the case, Herb.jl will normalize the probabilities.\n", + "\n", + "If a single line in the grammar definition represents multiple rules, such as `0.4 : Int = |(0:9)`, the probability will be evenly divided over all these rules." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## File writing\n", + "\n", + "### Saving & loading context-free grammars\n", + "\n", + "If you want to store a grammar on the disk, you can use the `store_cfg`, `read_cfg` and functions to store and read grammars respectively. \n", + "The `store_cfg` grammar can also be used to store probabilistic grammars. Reading probabilistic grammars can be done using `read_pcfg`.\n", + "The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [], + "source": [ + "Herb.HerbGrammar.store_cfg(\"demo.txt\", g₇)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int * Int\n", + "12: Int = x\n", + "13: Int = Int - Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.read_cfg(\"demo.txt\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Saving & loading context-sensitive grammars\n", + "\n", + "Saving and loading context-sensitive grammars is very similar to how it is done with context-free grammars.\n", + "The only difference is that an additional file is created for the constraints. \n", + "The file that contains the grammars can be edited and can also be read using the reader for context-free grammars.\n", + "The file that contains the constraints cannot be edited." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int + Int\n", + "12: Int = Int * Int\n", + "13: Int = x\n", + ", Main.Herb.HerbCore.Constraint[Main.Herb.HerbConstraints.ComesAfter(1, [9])])" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbGrammar.store_csg(\"demo.grammar\", \"demo.constraints\", g₈)\n", + "g₈, g₈.constraints" + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(1: Int = 0\n", + "2: Int = 1\n", + "3: Int = 2\n", + "4: Int = 3\n", + "5: Int = 4\n", + "6: Int = 5\n", + "7: Int = 6\n", + "8: Int = 7\n", + "9: Int = 8\n", + "10: Int = 9\n", + "11: Int = Int + Int\n", + "12: Int = Int * Int\n", + "13: Int = x\n", + ", Main.Herb.HerbCore.Constraint[Main.Herb.HerbConstraints.ComesAfter(1, [9])])" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g₉ = Herb.HerbGrammar.read_csg(\"demo.grammar\", \"demo.constraints\")\n", + "g₉, g₉.constraints" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.8.5", + "language": "julia", + "name": "julia-1.8" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/src/tutorials/defining_grammars.md b/docs/src/tutorials/defining_grammars.md new file mode 100644 index 0000000..c8e2548 --- /dev/null +++ b/docs/src/tutorials/defining_grammars.md @@ -0,0 +1,588 @@ +# Defining Grammars in Herb.jl using `HerbGrammar` + +The program space in Herb.jl is defined using a grammar. +This notebook demonstrates how such a grammar can be created. +There are multiple kinds of grammars, but they can all be defined in a very similar way. + +### Setup + +```julia +using HerbGrammar, HerbConstraints +``` + +### Creating a simple grammar + +This cell contains a very simple arithmetic grammar. +The grammar is defined using the `@cfgrammar` macro. +This macro converts the grammar definition in the form of a Julia expression into Herb's internal grammar representation. +Macro's are executed during compilation. +If you want to load a grammar during execution, have a look at the `HerbGrammar.expr2cfgrammar` function. + + +```julia +g₁ = HerbGrammar.@cfgrammar begin + Int = 1 + Int = 2 + Int = 3 + Int = Int * Int + Int = Int + Int +end +``` + + + 1: Int = 1 + 2: Int = 2 + 3: Int = 3 + 4: Int = Int * Int + 5: Int = Int + Int + + + +Defining every integer one-by-one can be quite tedious. Therefore, it is also possible to use the following syntax that makes use of a Julia iterator: + + +```julia +g₂ = HerbGrammar.@cfgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int +end +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int * Int + 12: Int = Int + Int + + + +You can do the same with lists: + + +```julia +g₃ = HerbGrammar.@cfgrammar begin + Int = |([0, 2, 4, 6, 8]) + Int = Int * Int + Int = Int + Int +end +``` + + + 1: Int = 0 + 2: Int = 2 + 3: Int = 4 + 4: Int = 6 + 5: Int = 8 + 6: Int = Int * Int + 7: Int = Int + Int + + + +Variables can also be added to the grammar by just using the variable name: + + +```julia +g₄ = HerbGrammar.@cfgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int + Int = x +end +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int * Int + 12: Int = Int + Int + 13: Int = x + + + +Grammars can also work with functions. +After all, `+` and `*` are just infix operators for Julia's identically-named functions. +You can use functions that are provided by Julia, or functions that you wrote yourself: + + +```julia +f(a) = a + 1 + +g₅ = HerbGrammar.@cfgrammar begin + Int = |(0:9) + Int = Int * Int + Int = Int + Int + Int = f(Int) + Int = x +end +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int * Int + 12: Int = Int + Int + 13: Int = f(Int) + 14: Int = x + + + +Similarly, we can also define the operator times (x) manually. + + +```julia +×(a, b) = a * b + +g₆ = HerbGrammar.@cfgrammar begin + Int = |(0:9) + Int = a + Int = Int + Int + Int = Int × Int +end +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = a + 12: Int = Int + Int + 13: Int = Int × Int + + + +### Working with grammars + +If you want to implement something using these grammars, it is useful to know about the functions that you can use to manipulate grammars and extract information. +This section is not necessarily complete, but it aims to give an overview of the most important functions. + +It is recommended to also read up on [Julia metaprogramming](https://docs.julialang.org/en/v1/manual/metaprogramming/) if you are not already familiar with that. + +One of the most important things about grammars is that each rule has an index associated with it: + + +```julia +g₇ = HerbGrammar.@cfgrammar begin + Int = |(0:9) + Int = Int + Int + Int = Int * Int + Int = x +end + +collect(enumerate(g₇.rules)) +``` + + + 13-element Vector{Tuple{Int64, Any}}: + (1, 0) + (2, 1) + (3, 2) + (4, 3) + (5, 4) + (6, 5) + (7, 6) + (8, 7) + (9, 8) + (10, 9) + (11, :(Int + Int)) + (12, :(Int * Int)) + (13, :x) + + +We can use this index to extract information from the grammar. + +### isterminal + +`isterminal` returns `true` if a rule is terminal, i.e. it cannot be expanded. For example, rule 1 is terminal, but rule 11 is not, since it contains the non-terminal symbol `:Int`. + + +```julia +HerbGrammar.isterminal(g₇, 1) +``` + + + true + + + +```julia +HerbGrammar.isterminal(g₇, 11) +``` + + + false + + +### return_type + +This function is rather obvious; it returns the non-terminal symbol that corresponds to a certain rule. The return type for all rules in our grammar is `:Int`. + + +```julia +HerbGrammar.return_type(g₇, 11) +``` + + + :Int + + +### child_types + +`child_types` returns the types of the nonterminal children of a rule in a vector. +If you just want to know how many children a rule has, and not necessarily which types they have, you can use `nchildren` + + +```julia +HerbGrammar.child_types(g₇, 11) +``` + + + 2-element Vector{Symbol}: + :Int + :Int + + + +```julia +HerbGrammar.nchildren(g₇, 11) +``` + + + 2 + + +### nonterminals + +The `nonterminals` function can be used to obtain a list of all nonterminals in the grammar. + + +```julia +HerbGrammar.nonterminals(g₇) +``` + + + 1-element Vector{Symbol}: + :Int + + +### Adding rules + +It is also possible to add rules to a grammar during execution. This can be done using the `add_rule!` function. +As with most functions in Julia that end with an exclamation mark, this function modifies its argument (the grammar). + +A rule can be provided in the same syntax as is used in the grammar definition. +The rule should be of the `Expr` type, which is a built-in type for representing expressions. +An easy way of creating `Expr` values in Julia is to encapsulate it in brackets and use a colon as prefix: + + +```julia +HerbGrammar.add_rule!(g₇, :(Int = Int - Int)) +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int + Int + 12: Int = Int * Int + 13: Int = x + 14: Int = Int - Int + + + +### Removing rules + +It is also possible to remove rules in Herb.jl, however, this is a bit more involved. +As said before, rules have an index associated with them. +The internal representation of programs that are defined by the grammar makes use of those indices for efficiency. +Blindly removing a rule would shift the indices of other rules, and this could mean that existing programs get a different meaning or become invalid. + +Therefore, there are two functions for removing rules: + +- `remove_rule!` removes a rule from the grammar, but fills its place with a placeholder. Therefore, the indices stay the same, and only programs that use the removed rule become invalid. +- `cleanup_removed_rules!` removes all placeholders and shifts the indices of the other rules. + + + +```julia +HerbGrammar.remove_rule!(g₇, 11) +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: nothing = nothing + 12: Int = Int * Int + 13: Int = x + 14: Int = Int - Int + + + + +```julia +HerbGrammar.cleanup_removed_rules!(g₇) +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int * Int + 12: Int = x + 13: Int = Int - Int + + + +## Context-sensitive grammars + +Context-sensitive grammars allow additional constraints to be added with respect to context-free grammars. +The syntax for defining a context-sensitive grammar is identical to defining a context-sensitive grammar: + + +```julia +g₈ = HerbGrammar.@csgrammar begin + Int = |(0:9) + Int = Int + Int + Int = Int * Int + Int = x +end +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int + Int + 12: Int = Int * Int + 13: Int = x + + + +Constraints can be added using the `addconstraint!` function, which takes a context-sensitive grammar and a constraint and adds the constraint to the grammar. +Currently, Herb.jl only has propagators constraints. +These constraints each have a corresponding `propagate` function that removes all options that violate that constraint from the domain. +At the moment, there are three propagator constraints: + +- `ComesAfter(rule, predecessors)`: It is only possible to use rule `rule` when `predecessors` are in its path to the root. +- `Forbidden(sequence)`: Forbids the derivation specified as a path in an expression tree. +- `Ordered(order)`: Rules have to be used in the specified order. That is, rule at index K can only be used if rules at indices `[1...K-1]` are used in the left subtree of the current expression. + +Below, an example is given of a context-sensitive grammar with a `ComesAfter` constraint: + + +```julia +HerbGrammar.addconstraint!(g₈, HerbConstraints.ComesAfter(1, [9])) +``` + + + 1-element Vector{Main.HerbCore.Constraint}: + Main.HerbConstraints.ComesAfter(1, [9]) + + +### Probabilistic grammars + +Herb.jl also supports probabilistic grammars. +These grammars allow the user to assign a probability to each rule in the grammar. +A probabilistic grammar can be defined in a very similar way to a standard grammar, but has some slightly different syntax: + + +```julia +g₉ = HerbGrammar.@pcfgrammar begin + 0.4 : Int = |(0:9) + 0.2 : Int = Int + Int + 0.1 : Int = Int * Int + 0.3 : Int = x +end + +for r ∈ 1:length(g₃.rules) + p = HerbGrammar.probability(g₈, r) + + println("$p : $r") +end +``` + + 0.07692307692307693 : 1 + 0.07692307692307693 : 2 + 0.07692307692307693 : 3 + 0.07692307692307693 : 4 + 0.07692307692307693 : 5 + 0.07692307692307693 : 6 + 0.07692307692307693 : 7 + + + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + ┌ Warning: Requesting probability in a non-probabilistic grammar. + │ Uniform distribution is assumed. + └ @ Main.HerbGrammar d:\GitHub\HerbGrammar.jl\src\grammar_base.jl:155 + + +The numbers before each rule represent the probability assigned to that rule. +The total probability for each return type should add up to 1.0. +If this isn't the case, Herb.jl will normalize the probabilities. + +If a single line in the grammar definition represents multiple rules, such as `0.4 : Int = |(0:9)`, the probability will be evenly divided over all these rules. + +## File writing + +### Saving & loading context-free grammars + +If you want to store a grammar on the disk, you can use the `store_cfg`, `read_cfg` and functions to store and read grammars respectively. +The `store_cfg` grammar can also be used to store probabilistic grammars. Reading probabilistic grammars can be done using `read_pcfg`. +The stored grammar files can also be opened using a text editor to be modified, as long as the contents of the file doesn't violate the syntax for defining grammars. + + +```julia +HerbGrammar.store_cfg("demo.txt", g₇) +``` + + +```julia +HerbGrammar.read_cfg("demo.txt") +``` + + + 1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int * Int + 12: Int = x + 13: Int = Int - Int + + + +### Saving & loading context-sensitive grammars + +Saving and loading context-sensitive grammars is very similar to how it is done with context-free grammars. +The only difference is that an additional file is created for the constraints. +The file that contains the grammars can be edited and can also be read using the reader for context-free grammars. +The file that contains the constraints cannot be edited. + + +```julia +HerbGrammar.store_csg("demo.grammar", "demo.constraints", g₈) +g₈, g₈.constraints +``` + + + (1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int + Int + 12: Int = Int * Int + 13: Int = x + , Main.HerbCore.Constraint[Main.HerbConstraints.ComesAfter(1, [9])]) + + + +```julia +g₉ = HerbGrammar.read_csg("demo.grammar", "demo.constraints") +g₉, g₉.constraints +``` + + + (1: Int = 0 + 2: Int = 1 + 3: Int = 2 + 4: Int = 3 + 5: Int = 4 + 6: Int = 5 + 7: Int = 6 + 8: Int = 7 + 9: Int = 8 + 10: Int = 9 + 11: Int = Int + Int + 12: Int = Int * Int + 13: Int = x + , Main.HerbCore.Constraint[Main.HerbConstraints.ComesAfter(1, [9])]) + diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb new file mode 100644 index 0000000..dcb9f1d --- /dev/null +++ b/docs/src/tutorials/getting_started_with_herb.ipynb @@ -0,0 +1,295 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Search\n", + "\n", + "This notebook describes how you can search a program space as defined by a grammar.\n", + "Specifically, we will look at example-based search, where the goal is to find a program that is able to transform the inputs of every example to the corresponding output." + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup\n", + "First, we start with the setup. We need to access to all the function in the Herb.jl framework." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "include(\"../../src/Herb.jl\") # these are only until we get modules going\n", + "using ..Herb" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining the program space\n", + "\n", + "Next, we start by creating a grammar. We define a context-free grammar which is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). \n", + "\n", + "Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see `example2_defining_grammars.ipynb`.\n", + "\n", + "For now, we specify a simple grammar for dealing with integers and explain all the rules individually:\n", + "\n", + "1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer.\n", + "2. Then, we can also use the variable `x` to hold an integer.\n", + "3. The third rule determines we can add two integers.\n", + "4. The fourth rule determines we can subtract an integer from another.\n", + "5. Finally, we also allow the multiplication of two integers.\n", + "\n", + "If you run this cell, you can see all the rules rolled out." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Real = 0\n", + "2: Real = 1\n", + "3: Real = 2\n", + "4: Real = 3\n", + "5: Real = 4\n", + "6: Real = 5\n", + "7: Real = 6\n", + "8: Real = 7\n", + "9: Real = 8\n", + "10: Real = 9\n", + "11: Real = x\n", + "12: Real = Real + Real\n", + "13: Real = Real - Real\n", + "14: Real = Real * Real\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "g = Herb.HerbGrammar.@cfgrammar begin\n", + " Real = |(0:9)\n", + " Real = x\n", + " Real = Real + Real\n", + " Real = Real - Real\n", + " Real = Real * Real\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining the problem" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As mentioned before, we are looking at example-based search. \n", + "This means that the problem is defined by a set of input-output examples. \n", + "A single example hence consists of an input and an output.\n", + "The input is defined as a dictionary, with a value assigned to each variable in the grammar.\n", + "It is important to write the variable name as a `Symbol` instead of a string.\n", + "A `Symbol` in Julia is written with a colon prefix, i.e. `:x`. \n", + "The output of the input-output example is just a single value for this specific grammar, but could possibly relate to e.g. arrays of values, too.\n", + "\n", + "In the cell below we automatically generate some examples for `x` assigning values `1-5`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "5-element Vector{Main.Herb.HerbData.IOExample}:\n", + " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", + " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", + " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", + " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", + " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Create input-output examples\n", + "examples = [Herb.HerbData.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that we have some input-output examples, we can define the problem. \n", + "Next to the examples, a problem also contains a name meant to link to the file path, which can be used to keep track of current examples. \n", + "For now, this is irrelevant, and you can give the program any name you like." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Main.Herb.HerbData.Problem(Main.Herb.HerbData.Example[Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)], \"example\")" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "problem = Herb.HerbData.Problem(examples, \"example\")" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Searching\n", + "\n", + "Now that we have defined the search space and the goal of the search, we can start the search. \n", + "\n", + "Of course, our problem is underdefined as there might be multiple programs that satisfy our examples. \n", + "Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. \n", + "This program satisfies all our examples, but we don't expect it to generalize very well.\n", + "\n", + "In general, we assume that a smaller program is more general than a larger program. \n", + "Therefore, we search for the smallest program in our grammar that satisfies our examples. \n", + "This can be done using a breadth-first search over the program/search space.\n", + "\n", + "This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. \n", + "\n", + "So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + ":(x * 3 + 5)" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "Herb.HerbSearch.search(g, problem, :Real)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As you can see, the search procedure found the correct program!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Defining the search procedure\n", + "\n", + "In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values.\n", + "\n", + "We first define a new problem to test with, we are looking for the programs that can compute the value`167`. We immediately pass the examples to the problem and then set up the new search.\n", + "\n", + "Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nothing" + ] + } + ], + "source": [ + "problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 168) for x ∈ 1:5], \"example2\")\n", + "expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180)\n", + "print(expr)" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? If you want you can try below.\n", + "\n", + "In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 167) for x ∈ 1:5], \"example2\")\n", + "expr = Herb.HerbSearch.search(g, problem, :Real)\n", + "print(expr)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.8.5", + "language": "julia", + "name": "julia-1.8" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/src/tutorials/getting_started_with_herb.md b/docs/src/tutorials/getting_started_with_herb.md new file mode 100644 index 0000000..b87ac0a --- /dev/null +++ b/docs/src/tutorials/getting_started_with_herb.md @@ -0,0 +1,154 @@ +# Search + +This notebook describes how you can search a program space as defined by a grammar. +Specifically, we will look at example-based search, where the goal is to find a program that is able to transform the inputs of every example to the corresponding output. + +### Setup +First, we start with the setup. We need to access to all the function in the Herb.jl framework. + + +```julia +using Herb +``` + +### Defining the program space + +Next, we start by creating a grammar. We define a context-free grammar which is just a simple set of production rules for defining combinations of terminal symbols (in our case real numbers). + +Contrary, we could define a context-sensitive grammar, when the production rules only hold in a certain context. However, for more information on this, please see `example2_defining_grammars.ipynb`. + +For now, we specify a simple grammar for dealing with integers and explain all the rules individually: + +1. First, we specify our interval `[0:9]` on real numbers and also constrain them to be integer. +2. Then, we can also use the variable `x` to hold an integer. +3. The third rule determines we can add two integers. +4. The fourth rule determines we can subtract an integer from another. +5. Finally, we also allow the multiplication of two integers. + +If you run this cell, you can see all the rules rolled out. + + +```julia +g = Herb.HerbGrammar.@cfgrammar begin + Real = |(0:9) + Real = x + Real = Real + Real + Real = Real - Real + Real = Real * Real +end +``` + + + 1: Real = 0 + 2: Real = 1 + 3: Real = 2 + 4: Real = 3 + 5: Real = 4 + 6: Real = 5 + 7: Real = 6 + 8: Real = 7 + 9: Real = 8 + 10: Real = 9 + 11: Real = x + 12: Real = Real + Real + 13: Real = Real - Real + 14: Real = Real * Real + + + +### Defining the problem + +As mentioned before, we are looking at example-based search. +This means that the problem is defined by a set of input-output examples. +A single example hence consists of an input and an output. +The input is defined as a dictionary, with a value assigned to each variable in the grammar. +It is important to write the variable name as a `Symbol` instead of a string. +A `Symbol` in Julia is written with a colon prefix, i.e. `:x`. +The output of the input-output example is just a single value for this specific grammar, but could possibly relate to e.g. arrays of values, too. + +In the cell below we automatically generate some examples for `x` assigning values `1-5`. + + +```julia +# Create input-output examples +examples = [Herb.HerbData.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] +``` + + + 5-element Vector{Main.Herb.HerbData.IOExample}: + Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8) + Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11) + Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14) + Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17) + Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20) + + +Now that we have some input-output examples, we can define the problem. +Next to the examples, a problem also contains a name meant to link to the file path, which can be used to keep track of current examples. +For now, this is irrelevant, and you can give the program any name you like. + + +```julia +problem = Herb.HerbData.Problem(examples, "example") +``` + + + Main.Herb.HerbData.Problem(Main.Herb.HerbData.Example[Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)], "example") + + +### Searching + +Now that we have defined the search space and the goal of the search, we can start the search. + +Of course, our problem is underdefined as there might be multiple programs that satisfy our examples. +Let us consider the case where we also have a ternary if-then-else operator and standard boolean operators in our grammar: we could synthesize the program `x ≤ 5 ? 3x+5 : 0`. +This program satisfies all our examples, but we don't expect it to generalize very well. + +In general, we assume that a smaller program is more general than a larger program. +Therefore, we search for the smallest program in our grammar that satisfies our examples. +This can be done using a breadth-first search over the program/search space. + +This search is very basic; it makes use of an enumeration technique, where we enumerate programs one-by-one until we find a program that matches our examples. The search procedure has a built-in default evaluator to verify the candidate programs with the given input. The search procedure also has a built-in search procedure using breadth-first search. + +So, we only need to give our grammar and the problem to our search procedure, along with a starting `Symbol`, in our case a `Real`. + + +```julia +Herb.HerbSearch.search(g, problem, :Real) +``` + + + :(x * 3 + 5) + + +As you can see, the search procedure found the correct program! + + + +### Defining the search procedure + +In the previous case, we used the built-ins of the search procedure. However, we can also give a custom enumerator to the search procedure and define a few more values. + +We first define a new problem to test with, we are looking for the programs that can compute the value`167`. We immediately pass the examples to the problem and then set up the new search. + +Search is done by passing the grammar, the problem and the starting point like before. We now also specify the enumeration function to be used, and now we use depth-first search. Then, we give the maximum depth of the programs we want to search for `(3)`, the maximum number of nodes in the Abstract Syntax Tree that exists during search `(10)`, and the maximum time in seconds allowed for the search. + + +```julia +problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 168) for x ∈ 1:5], "example2") +expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180) +print(expr) +``` + + nothing + +We see that our synthesizer can find a program to construct the value `168`, though a fun experiment would be trying to get the value `167`, what do you think would happen? If you want you can try below. + +In any case, this concludes our first introduction to the `Herb.jl` program synthesis framework. You can see more examples in this repository, or explore yourself. Enjoy! + + +```julia +problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 167) for x ∈ 1:5], "example2") +expr = Herb.HerbSearch.search(g, problem, :Real) +println(expr) +``` From ac8b6e18b6689a843bf5d43dec0480b618ccefe6 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Wed, 1 Nov 2023 17:54:54 +0100 Subject: [PATCH 16/75] Fix typo in make.jl --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 47d124e..c02d937 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -24,7 +24,7 @@ makedocs(; "Tutorials" => [ "An more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md" - ] + ], "Sub-Modules" => [ "HerbGrammar.jl" => "HerbGrammar/index.md", "HerbSearch.jl" => "HerbSearch/index.md", From cfa1addd74904b94946ada4eaf01bcf078b910ae Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 30 Nov 2023 14:26:02 +0100 Subject: [PATCH 17/75] Fixed get stated guide --- docs/src/get_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index 320b842..aa4147f 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -53,7 +53,7 @@ See our other tutorials! ## The full code example ```julia -using HerbSearch, HerbData, HerbInterpret +using HerbSearch, HerbData, HerbInterpret, HerbGrammar # define our very simple context-free grammar # Can add and multiply an input variable x or the integers 1,2. From 29fee8a4f78741b9dde2d2781f98e8f3000d4abd Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Thu, 18 Jan 2024 15:40:36 +0100 Subject: [PATCH 18/75] added tutorial for search --- docs/make.jl | 3 +- docs/src/HerbSearch/index.md | 12 + docs/src/tutorials/advanced_search.ipynb | 614 +++++++++++++++++++++++ docs/src/tutorials/advanced_search.md | 399 +++++++++++++++ 4 files changed, 1027 insertions(+), 1 deletion(-) create mode 100644 docs/src/tutorials/advanced_search.ipynb create mode 100644 docs/src/tutorials/advanced_search.md diff --git a/docs/make.jl b/docs/make.jl index c02d937..b89ade3 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -22,8 +22,9 @@ makedocs(; "concepts.md" ], "Tutorials" => [ - "An more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", + "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md" + "Advanced Search Procedures" => "tutorials/advanced_search.md" ], "Sub-Modules" => [ "HerbGrammar.jl" => "HerbGrammar/index.md", diff --git a/docs/src/HerbSearch/index.md b/docs/src/HerbSearch/index.md index 17f7b30..74c70ea 100644 --- a/docs/src/HerbSearch/index.md +++ b/docs/src/HerbSearch/index.md @@ -9,6 +9,18 @@ Modules = [HerbSearch] Order = [:type, :const, :macro, :function] ``` +The HerbSearch package takes care of all operations related to searching for the desired program. This includes +- the functionality to sample a certain program given a grammar, +- the implementation of several heuristic functions, +- searching for a program that satisfies the specification, and +- implementations of several search algorithms in terms of how they enumerate the search space + - Breadth-First Search + - Depth-First Search + - Metropolis Hastings + - Very Large Scale Neighbourhood Search + - Simulated Annealing + - Genetic Search + ## Index ```@index diff --git a/docs/src/tutorials/advanced_search.ipynb b/docs/src/tutorials/advanced_search.ipynb new file mode 100644 index 0000000..0b8df68 --- /dev/null +++ b/docs/src/tutorials/advanced_search.ipynb @@ -0,0 +1,614 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Advanced Search Procedures in Herb.jl\n", + "\n", + "In this tutorial, we show how to use the search procedure using more advanced methods." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using HerbGrammar, HerbData, HerbSearch, HerbInterpret, HerbConstraints" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We start with a simple grammar and a simple problem." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g = @cfgrammar begin\n", + " Number = |(1:2)\n", + " Number = x\n", + " Number = Number + Number\n", + " Number = Number * Number\n", + "end\n", + "\n", + "problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Parameters\n", + "\n", + "We can use a search strategy, where we can specify different parameters. For example, by setting the `max_depth`, we limit the depth of the search. In the next example, we can see the effect of the depth on the number of allocations considered. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "solution = @time search(g, problem, :Number, max_depth=3)\n", + "println(solution)\n", + "\n", + "solution = @time search(g, problem, :Number, max_depth=6)\n", + "println(solution)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Another parameter to use is `max_enumerations`, which limits the number of programs that can be tested at evaluation. We can see the number of enumerations necessary to solve a simple problem in the next example." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for i in range(1, 50)\n", + " println(i, \" enumerations\")\n", + " solution = @time search(g, problem, :Number, max_enumerations=i)\n", + " println(solution)\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. \n", + "\n", + "A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g = @cfgrammar begin\n", + " Number = 1\n", + " List = []\n", + " Index = List[Number]\n", + "end\n", + "\n", + "problem = Problem([IOExample(Dict(), x) for x ∈ 1:5])\n", + "solution = search(g, problem, :Index, max_depth=2, allow_evaluation_errors=true)\n", + "println(\"solution: \", solution)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There is also another search method called `search_best` which return both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`):" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true)\n", + "println(\"solution: \", solution)\n", + "println(\"error: \", error)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Search methods\n", + "\n", + "We now show examples of using different search procedures, which are initialized by using different enumerators that are passed to the search function.\n", + "\n", + "### Breadth-First Search\n", + "\n", + "The breadth-first search will first enumerate all possible programs at the same depth before considering a program with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g1 = @cfgrammar begin\n", + " Real = 1 | 2\n", + " Real = Real * Real\n", + "end\n", + "programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can test that this function returns the correct functions and all functions. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "answer_programs = [\n", + " RuleNode(1),\n", + " RuleNode(2),\n", + " RuleNode(3, [RuleNode(1), RuleNode(1)]),\n", + " RuleNode(3, [RuleNode(1), RuleNode(2)]),\n", + " RuleNode(3, [RuleNode(2), RuleNode(1)]),\n", + " RuleNode(3, [RuleNode(2), RuleNode(2)])\n", + "]\n", + "\n", + "println(all(p ∈ programs for p ∈ answer_programs))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Depth-First Search\n", + "\n", + "In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g1 = @cfgrammar begin\n", + "Real = 1 | 2\n", + "Real = Real * Real\n", + "end\n", + "programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real))\n", + "println(programs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "g1 = @cfgrammar begin\n", + " Real = 1 | 2\n", + " Real = Real * Real\n", + "end\n", + "programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost))\n", + "println(programs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stochastic search\n", + "We now introduce a few stochastic search algorithms, for which we first create a simple grammar and a helper function for problems." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grammar = @csgrammar begin\n", + " X = |(1:5)\n", + " X = X * X\n", + " X = X + X\n", + " X = X - X\n", + " X = x\n", + "end" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "function create_problem(f, range=20)\n", + " examples = [IOExample(Dict(:x => x), f(x)) for x ∈ 1:range]\n", + " return Problem(examples), examples\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Metropolis-Hastings\n", + "\n", + "One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html).\n", + "\n", + "The below example uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> x * x + 4\")\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_mh_enumerator(examples, mean_squared_error)\n", + "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Very Large Scale Neighbourhood Search \n", + "\n", + "The second implemented stochastic search method is VLSN, which search for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf).\n", + "\n", + "Given the same grammar as before, we can try with some simple examples." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> 10\")\n", + "max_depth = 2\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", + "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> x\")\n", + "max_depth = 1\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", + "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Simulated Annealing\n", + "\n", + "The third stochastic search method is called simulated annealing, is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html).\n", + "\n", + "We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Change the value below to see the effect." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> x * x + 4\")\n", + "initial_temperature = 1\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", + "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> x * x + 4\")\n", + "initial_temperature = 2\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", + "program, cost = @time search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Genetic Search\n", + "\n", + "Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/).\n", + "\n", + "We show the example of finding a lambda function. Try varying the parameters of the genetic search to see what happens." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "e = Meta.parse(\"x -> 3 * x * x + (x + 2)\")\n", + "problem, examples = create_problem(eval(e))\n", + "enumerator = get_genetic_enumerator(examples, \n", + " initial_population_size = 10,\n", + " mutation_probability = 0.8,\n", + " maximum_initial_population_depth = 3,\n", + ")\n", + "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Other functionality\n", + "\n", + "Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics.\n", + "\n", + "### Sampling\n", + "Sampling is implemented for the different stochastic search methods.\n", + "\n", + "We consider here a simple grammar, which gives different programs for different search depths." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "grammar = @cfgrammar begin \n", + " A = B | C | F\n", + " F = G\n", + " C = D\n", + " D = E\n", + "end\n", + "\n", + "# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3)\n", + "\n", + "# For depth ≤ 1 the only option is A->B\n", + "expression = rand(RuleNode, grammar, :A, 1)\n", + "@assert rulenode2expr(expression, grammar) in [:B,:C,:F]\n", + "\n", + "# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2)\n", + "expression = rand(RuleNode, grammar, :A, 2)\n", + "@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Heuristics" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# More interesting domains & Use of constraints\n", + "In the following examples, we introduce some larger grammars and show that Herb can still efficiently find the correct program." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Expects to return a program equivalent to 1 + (1 - x) = 2 - x\n", + "\n", + "g₁ = @csgrammar begin\n", + " Element = |(1 : 3) # 1 - 3\n", + " Element = Element + Element # 4\n", + " Element = 1 - Element # 5\n", + " Element = x # 6\n", + "end\n", + "\n", + "addconstraint!(g₁, ComesAfter(6, [5]))\n", + "\n", + "examples = [\n", + " IOExample(Dict(:x => 0), 2),\n", + " IOExample(Dict(:x => 1), 1),\n", + " IOExample(Dict(:x => 2), 0)\n", + "]\n", + "problem = Problem(examples)\n", + "solution = search(g₁, problem, :Element, max_depth=3)\n", + "\n", + "@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4\n", + "\n", + "g₂ = @csgrammar begin\n", + " Element = Element + Element + Element # 1\n", + " Element = Element + Element * Element # 2\n", + " Element = x # 3\n", + " Element = |(3 : 5) # 4\n", + "end\n", + "\n", + "# Restrict .. + x * x\n", + "addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)])))\n", + "# Restrict 4 and 5 in lower level\n", + "addconstraint!(g₂, ForbiddenPath([2, 1, 5]))\n", + "addconstraint!(g₂, ForbiddenPath([2, 1, 6]))\n", + "\n", + "examples = [\n", + " IOExample(Dict(:x => 1), 11)\n", + " IOExample(Dict(:x => 2), 20)\n", + " IOExample(Dict(:x => -1), -1)\n", + "]\n", + "problem = Problem(examples)\n", + "solution = search(g₂, problem, :Element)\n", + "\n", + "@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1\n", + "\n", + "g₃ = @csgrammar begin\n", + " Element = |(1 : 20) # 1 - 20\n", + " Element = Element - 1 # 21\n", + " Element = 1 - Element # 22\n", + " Element = x # 23\n", + "end\n", + "\n", + "addconstraint!(g₃, ComesAfter(23, [22, 21]))\n", + "addconstraint!(g₃, ComesAfter(22, [21]))\n", + "\n", + "examples = [\n", + " IOExample(Dict(:x => 1), 2)\n", + " IOExample(Dict(:x => 10), 11)\n", + "]\n", + "problem = Problem(examples)\n", + "solution = search(g₃, problem, :Element)\n", + "\n", + "@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1\n", + "@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Expects to return a program equivalent to 18 + 4x\n", + "\n", + "g₄ = @csgrammar begin\n", + " Element = |(0 : 20) # 1 - 20\n", + " Element = Element + Element + Element # 21\n", + " Element = Element + Element * Element # 22\n", + " Element = x # 23\n", + "end\n", + "\n", + "# Enforce ordering on + +\n", + "addconstraint!(g₄, Ordered(\n", + " MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]),\n", + " [:x, :y, :z]\n", + "))\n", + "\n", + "examples = [\n", + " IOExample(Dict(:x => 1), 22),\n", + " IOExample(Dict(:x => 0), 18),\n", + " IOExample(Dict(:x => -1), 14)\n", + "]\n", + "problem = Problem(examples)\n", + "solution = search(g₄, problem, :Element)\n", + "\n", + "@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2)\n", + "\n", + "g₅ = @csgrammar begin\n", + " Element = Number # 1\n", + " Element = Bool # 2\n", + "\n", + " Number = |(1 : 3) # 3-5\n", + " \n", + " Number = Number + Number # 6\n", + " Bool = Number ≡ Number # 7\n", + " Number = x # 8\n", + " \n", + " Number = Bool ? Number : Number # 9\n", + " Bool = Bool ? Bool : Bool # 10\n", + "end\n", + "\n", + "# Forbid ? = ?\n", + "addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)])))\n", + "# Order =\n", + "addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y]))\n", + "# Order +\n", + "addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y]))\n", + "\n", + "examples = [\n", + " IOExample(Dict(:x => 0), 2)\n", + " IOExample(Dict(:x => 1), 3)\n", + " IOExample(Dict(:x => 2), 1)\n", + "]\n", + "problem = Problem(examples)\n", + "solution = search(g₅, problem, :Element)\n", + "\n", + "@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.9.4", + "language": "julia", + "name": "julia-1.9" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.9.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/src/tutorials/advanced_search.md b/docs/src/tutorials/advanced_search.md new file mode 100644 index 0000000..f53eac0 --- /dev/null +++ b/docs/src/tutorials/advanced_search.md @@ -0,0 +1,399 @@ +# Getting started + +You can either paste this code into the Julia REPL or into a seperate file, e.g. `get_started.jl` followed by running `julia get_started.jl`. + +To start, we import the necessary packages. + +```julia +using HerbGrammar, HerbData, HerbSearch, HerbInterpret +``` + +We start with the same simple grammar from the main file [get_started.md](../get_started.md). + +```julia +g = @cfgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number +end +``` + +We use a simple problem. + +```julia + problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) +``` + +## Parameters + +We can use a search strategy, where we can specify different parameters. For example, by setting the `max_depth`, we limit the depth of the search. In the next example, we can see the effect of the depth on the number of allocations considered. + +```julia +solution = @time search(g, problem, :Number, max_depth=3) +>>> 0.003284 seconds (50.08 k allocations: 2.504 MiB) +println(solution) +>>> (x + 1) + x + +solution = @time search(g, problem, :Number, max_depth=6) +>>> 0.005696 seconds (115.28 k allocations: 5.910 MiB) +println(solution) +>>> (1 + x) + x +``` + +Another parameter to use is `max_enumerations`, which limits the number of programs that can be tested at evaluation. We can see the number of enumerations necessary to solve a simple problem in the next example. + +```julia +for i in range(1, 50) + println(i, " enumerations") + solution = @time search(g, problem, :Number, max_enumerations=i) + println(solution) +end + +>>> .... +>>> 23 enums: nothing +>>> 0.010223 seconds (117.01 k allocations: 5.935 MiB, 44.23% gc time) +>>> 24 enums: (1 + x) + x +>>> 0.005305 seconds (117.01 k allocations: 5.935 MiB) +>>> 25 enums: (1 + x) + x +>>> 0.005381 seconds (117.01 k allocations: 5.935 MiB) +>>> ... +``` + +We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. + +A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method. + +```julia +g = @cfgrammar begin + Number = 1 + List = [] + Index = List[Number] +end + +problem = Problem([IOExample(Dict(), x) for x ∈ 1:5]) +solution = search(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) +println(solution) +>>> nothing +``` + +There is also another search method called `search_best` which return both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error: + +```julia +solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) +println(solution) +>>> nothing +println(error) +>>> 9223372036854775807 # or: typemax(Int) +``` + +## Search methods + +We now show examples of using different search procedures. + +### Breadth-First Search + +The breadth-first search will first enumerate all possible programs at the same depth before considering a program with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. + +```julia +g1 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real +end +programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real)) +``` + +We can test that this function returns the correct functions and all functions. + +```julia +answer_programs = [ + RuleNode(1), + RuleNode(2), + RuleNode(3, [RuleNode(1), RuleNode(1)]), + RuleNode(3, [RuleNode(1), RuleNode(2)]), + RuleNode(3, [RuleNode(2), RuleNode(1)]), + RuleNode(3, [RuleNode(2), RuleNode(2)]) +] + +println(all(p ∈ programs for p ∈ answer_programs)) +>>> true +``` +### Depth-First Search + +In depth-first search, we first explore a certain branch of the search tree till the `max_depth` or a correct program is reached before we consider the next branch. + +```julia +g1 = @cfgrammar begin +Real = 1 | 2 +Real = Real * Real +end +programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real)) +println(programs) +>>> RuleNode[1,, 3{1,1}, 3{1,2}, 3{2,1}, 3{2,2}, 2,] +``` + +`get_dfs_enumerator` also has a default left-most heuristic and we consider what the difference is in output. + + +```julia +g1 = @cfgrammar begin + Real = 1 | 2 + Real = Real * Real +end +programs = collect(get_dfs_enumerator(g1, 2, typemax(Int), :Real, heuristic_rightmost)) +println(programs) +>>> RuleNode[1,, 3{1,1}, 3{2,1}, 3{1,2}, 3{2,2}, 2,] +``` + +### Metropolis-Hastings + +One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). + +For the example below, we use this grammar and helper function. +```julia +grammar = @csgrammar begin + X = |(1:5) + X = X * X + X = X + X + X = X - X + X = x +end +function create_problem(f, range=20) + examples = [IOExample(Dict(:x => x), f(x)) for x ∈ 1:range] + return Problem(examples), examples +end +``` + +The below example uses a simple arithmetic example. As the search method is stochastic, different programs may be returned, as shown below. + +```julia +e = Meta.parse("x -> x * x + 4") +problem, examples = create_problem(eval(e)) +enumerator = get_mh_enumerator(examples, mean_squared_error) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) + +>>> (:(x * x - (1 - 5)), 0) +>>> (:(4 + x * x), 0) +>>> (:(x * x + 4), 0) +``` + +### Very Large Scale Neighbourhood Search + +The second implemented stochastic search method is VLSN, which search for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf). + +Given the same grammar as before, we can try with some simple examples. + +```julia +e = Meta.parse("x -> 10") +max_depth = 2 +problem, examples = create_problem(eval(e)) +enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) +>>> (:(5 + 5), 0) +``` + +```julia +e = Meta.parse("x -> x") +max_depth = 1 +problem, examples = create_problem(eval(e)) +enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth) +>>> (:x, 0) +``` + +### Simulated Annealing + +The third stochastic search method is called simulated annealing, is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). + +We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Two possible answers to the program are given as well. + +```julia +e = Meta.parse("x -> x * x + 4") +initial_temperature = 2 +problem, examples = create_problem(eval(e)) +enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) +>>> (:(4 + x * x), 0) +>>> (:(x * x + (5 - 1)), 0) +``` + +### Genetic Search + +Genetic search is a type of evolutionary algorithm, which will simulate the process of natural selection and return the 'fittest' program of the population. For more information, see [here](https://www.geeksforgeeks.org/genetic-algorithms/). + +We show the example of finding a lambda function. + +```julia +e = Meta.parse("x -> 3 * x * x + (x + 2)") +problem, examples = create_problem(eval(e)) +enumerator = get_genetic_enumerator(examples, + initial_population_size = 10, + mutation_probability = 0.8, + maximum_initial_population_depth = 3, +) +program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=nothing, max_time=20) +>>> (:(((((x - 5) + x) + x * x) + 1) + (((((2 + x * x) + 3) + x * x) + 1) - ((x - x) + x))), 0) +>>> (:(x * 1 + (x * ((x + x) + x * 1) + (1 + 1) * 1)), 0) +>>> (:((((x + x) + x) + 2) * x + ((x - x) + (2 - x))), 0) +``` + +## Other functionality + +Finally, we showcase two other functionalities of HerbSearch, sampling and heuristics. + +### Sampling +Sampling is implemented for the different stochastic search methods. + +We consider here a simple grammar, which gives different programs for different search depths. + +```julia +grammar = @cfgrammar begin + A = B | C | F + F = G + C = D + D = E +end + +# A->B (depth 1) or A->F->G (depth 2) or A->C->D->E (depth 3) + +# For depth ≤ 1 the only option is A->B +expression = rand(RuleNode, grammar, :A, 1) +@assert rulenode2expr(expression, grammar) in [:B,:C,:F] + +# For depth ≤ 2 the two options are A->B (depth 1) and A->B->G| A->C->G | A->F->G (depth 2) +expression = rand(RuleNode, grammar, :A, 2) +@assert rulenode2expr(expression,grammar) in [:B,:C,:F,:G] +``` + +### Heuristics + + +# Examples with larger domains and constraints +Here, we showcase a few examples using a more complicated grammar and a few types of constraints +```julia +# Expects to return a program equivalent to 1 + (1 - x) = 2 - x + +g₁ = @csgrammar begin + Element = |(1 : 3) # 1 - 3 + Element = Element + Element # 4 + Element = 1 - Element # 5 + Element = x # 6 +end + +addconstraint!(g₁, ComesAfter(6, [5])) + +examples = [ + IOExample(Dict(:x => 0), 2), + IOExample(Dict(:x => 1), 1), + IOExample(Dict(:x => 2), 0) +] +problem = Problem(examples) +solution = search(g₁, problem, :Element, max_depth=3) + +@assert test_with_input(SymbolTable(g₁), solution, Dict(:x => -2)) == 4 + +# Expects to return a program equivalent to 4 + x * (x + 3 + 3) = x^2 + 6x + 4 + +g₂ = @csgrammar begin + Element = Element + Element + Element # 1 + Element = Element + Element * Element # 2 + Element = x # 3 + Element = |(3 : 5) # 4 +end + +# Restrict .. + x * x +addconstraint!(g₂, Forbidden(MatchNode(2, [MatchVar(:x), MatchNode(3), MatchNode(3)]))) +# Restrict 4 and 5 in lower level +addconstraint!(g₂, ForbiddenPath([2, 1, 5])) +addconstraint!(g₂, ForbiddenPath([2, 1, 6])) + +examples = [ + IOExample(Dict(:x => 1), 11) + IOExample(Dict(:x => 2), 20) + IOExample(Dict(:x => -1), -1) +] +problem = Problem(examples) +solution = search(g₂, problem, :Element) + +@assert test_with_input(SymbolTable(g₂), solution, Dict(:x => 0)) == 4 + +# Expects to return a program equivalent to (1 - (((1 - x) - 1) - 1)) - 1 = x + 1 + +g₃ = @csgrammar begin + Element = |(1 : 20) # 1 - 20 + Element = Element - 1 # 21 + Element = 1 - Element # 22 + Element = x # 23 +end + +addconstraint!(g₃, ComesAfter(23, [22, 21])) +addconstraint!(g₃, ComesAfter(22, [21])) + +examples = [ + IOExample(Dict(:x => 1), 2) + IOExample(Dict(:x => 10), 11) +] +problem = Problem(examples) +solution = search(g₃, problem, :Element) + +@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 0)) == 1 +@assert test_with_input(SymbolTable(g₃), solution, Dict(:x => 100)) == 101 + +# Expects to return a program equivalent to 18 + 4x + +g₄ = @csgrammar begin + Element = |(0 : 20) # 1 - 20 + Element = Element + Element + Element # 21 + Element = Element + Element * Element # 22 + Element = x # 23 +end + +# Enforce ordering on + + +addconstraint!(g₄, Ordered( + MatchNode(21, [MatchVar(:x), MatchVar(:y), MatchVar(:z)]), + [:x, :y, :z] +)) + +examples = [ + IOExample(Dict(:x => 1), 22), + IOExample(Dict(:x => 0), 18), + IOExample(Dict(:x => -1), 14) +] +problem = Problem(examples) +solution = search(g₄, problem, :Element) + +@assert test_with_input(SymbolTable(g₄), solution, Dict(:x => 100)) == 418 + +# Expects to return a program equivalent to (x == 2) ? 1 : (x + 2) + +g₅ = @csgrammar begin + Element = Number # 1 + Element = Bool # 2 + + Number = |(1 : 3) # 3-5 + + Number = Number + Number # 6 + Bool = Number ≡ Number # 7 + Number = x # 8 + + Number = Bool ? Number : Number # 9 + Bool = Bool ? Bool : Bool # 10 +end + +# Forbid ? = ? +addconstraint!(g₅, Forbidden(MatchNode(7, [MatchVar(:x), MatchVar(:x)]))) +# Order = +addconstraint!(g₅, Ordered(MatchNode(7, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) +# Order + +addconstraint!(g₅, Ordered(MatchNode(6, [MatchVar(:x), MatchVar(:y)]), [:x, :y])) + +examples = [ + IOExample(Dict(:x => 0), 2) + IOExample(Dict(:x => 1), 3) + IOExample(Dict(:x => 2), 1) +] +problem = Problem(examples) +solution = search(g₅, problem, :Element) + +@assert test_with_input(SymbolTable(g₅), solution, Dict(:x => 3)) == 5 +``` \ No newline at end of file From 36c58730cb5447f2a672f1e82a432954704a7556 Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Thu, 18 Jan 2024 16:19:09 +0100 Subject: [PATCH 19/75] forgot a comma in make --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index b89ade3..f527ff9 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,7 +23,7 @@ makedocs(; ], "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", - "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md" + "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md" ], "Sub-Modules" => [ From dab81f1678919b4669f57012f60a8e10612ff8bb Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Thu, 18 Jan 2024 16:44:39 +0100 Subject: [PATCH 20/75] included Pkg.add() to be able to run notebook from scratch --- docs/src/tutorials/advanced_search.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/src/tutorials/advanced_search.ipynb b/docs/src/tutorials/advanced_search.ipynb index 0b8df68..d0ad878 100644 --- a/docs/src/tutorials/advanced_search.ipynb +++ b/docs/src/tutorials/advanced_search.ipynb @@ -11,10 +11,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 169, "metadata": {}, "outputs": [], "source": [ + "using Pkg\n", + "Pkg.add([\"HerbGrammar\", \"HerbData\", \"HerbSearch\", \"HerbInterpret\", \"HerbConstraints\"])\n", "using HerbGrammar, HerbData, HerbSearch, HerbInterpret, HerbConstraints" ] }, From d64eb9dbdcc5e632cbeb70a21c3f654863f31219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Janji=C4=87?= Date: Fri, 19 Jan 2024 11:18:46 +0100 Subject: [PATCH 21/75] fix typos, wording and clarify code examples --- docs/src/tutorials/advanced_search.ipynb | 48 ++++++++++++------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/src/tutorials/advanced_search.ipynb b/docs/src/tutorials/advanced_search.ipynb index d0ad878..29dba13 100644 --- a/docs/src/tutorials/advanced_search.ipynb +++ b/docs/src/tutorials/advanced_search.ipynb @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 169, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -115,7 +115,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "There is also another search method called `search_best` which return both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`):" + "There is another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error (`typemax(Int)`):" ] }, { @@ -135,11 +135,11 @@ "source": [ "## Search methods\n", "\n", - "We now show examples of using different search procedures, which are initialized by using different enumerators that are passed to the search function.\n", + "We now show examples of using different search procedures, which are initialized by passing different enumerators to the search function.\n", "\n", "### Breadth-First Search\n", "\n", - "The breadth-first search will first enumerate all possible programs at the same depth before considering a program with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first." + "The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first." ] }, { @@ -159,7 +159,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can test that this function returns the correct functions and all functions. " + "We can test that this function returns all and only the correct functions. " ] }, { @@ -267,7 +267,7 @@ "\n", "One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html).\n", "\n", - "The below example uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. " + "The example below uses a simple arithmetic example. You can try running this code block multiple times, which will give different programs, as the search is stochastic. " ] }, { @@ -276,8 +276,8 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> x * x + 4\")\n", - "problem, examples = create_problem(eval(e))\n", + "e = x -> x * x + 4\n", + "problem, examples = create_problem(e)\n", "enumerator = get_mh_enumerator(examples, mean_squared_error)\n", "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" ] @@ -288,9 +288,9 @@ "source": [ "### Very Large Scale Neighbourhood Search \n", "\n", - "The second implemented stochastic search method is VLSN, which search for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf).\n", + "The second implemented stochastic search method is VLSN, which searches for a local optimum in the neighbourhood. For more information, see [this article](https://backend.orbit.dtu.dk/ws/portalfiles/portal/5293785/Pisinger.pdf).\n", "\n", - "Given the same grammar as before, we can try with some simple examples." + "Given the same grammar as before, we can try it with some simple examples." ] }, { @@ -299,9 +299,9 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> 10\")\n", + "e = x -> 10\n", "max_depth = 2\n", - "problem, examples = create_problem(eval(e))\n", + "problem, examples = create_problem(e)\n", "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)\n" ] @@ -312,9 +312,9 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> x\")\n", + "e = x -> x\n", "max_depth = 1\n", - "problem, examples = create_problem(eval(e))\n", + "problem, examples = create_problem(e)\n", "enumerator = get_vlsn_enumerator(examples, mean_squared_error, max_depth)\n", "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=max_depth)" ] @@ -325,7 +325,7 @@ "source": [ "### Simulated Annealing\n", "\n", - "The third stochastic search method is called simulated annealing, is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html).\n", + "The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html).\n", "\n", "We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Change the value below to see the effect." ] @@ -336,9 +336,9 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> x * x + 4\")\n", + "e = x -> x * x + 4\n", "initial_temperature = 1\n", - "problem, examples = create_problem(eval(e))\n", + "problem, examples = create_problem(e)\n", "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", "program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3) " ] @@ -349,9 +349,9 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> x * x + 4\")\n", + "e = x -> x * x + 4\n", "initial_temperature = 2\n", - "problem, examples = create_problem(eval(e))\n", + "problem, examples = create_problem(e)\n", "enumerator = get_sa_enumerator(examples, mean_squared_error, initial_temperature)\n", "program, cost = @time search_best(grammar, problem, :X, enumerator=enumerator, error_function=mse_error_function, max_depth=3)" ] @@ -373,8 +373,8 @@ "metadata": {}, "outputs": [], "source": [ - "e = Meta.parse(\"x -> 3 * x * x + (x + 2)\")\n", - "problem, examples = create_problem(eval(e))\n", + "e = x -> 3 * x * x + (x + 2)\n", + "problem, examples = create_problem(e)\n", "enumerator = get_genetic_enumerator(examples, \n", " initial_population_size = 10,\n", " mutation_probability = 0.8,\n", @@ -559,7 +559,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -600,7 +600,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Julia 1.9.4", + "display_name": "Julia 1.9.0", "language": "julia", "name": "julia-1.9" }, @@ -608,7 +608,7 @@ "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.9.4" + "version": "1.9.0" } }, "nbformat": 4, From 9ce318993361620f380a4e14711e772bad2c51d5 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 19 Jan 2024 14:21:19 +0100 Subject: [PATCH 22/75] Update docs fixing source links --- docs/make.jl | 7 ++++--- docs/src/index.md | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index f527ff9..be23bb7 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -9,11 +9,11 @@ using HerbData using HerbInterpret using HerbCore -makedocs(; +makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbInterpret, HerbCore], - authors="SEBs", + format + authors="PONYs", sitename="Herb.jl", - repo="https://github.com/Herb-AI/Herb.jl", pages = [ "Basics" => [ "index.md", @@ -35,6 +35,7 @@ makedocs(; "HerbData.jl" => "HerbData/index.md", ], ], + warnonly = [:missing_docs] ) deploydocs(; diff --git a/docs/src/index.md b/docs/src/index.md index 1343902..df738e3 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -26,6 +26,7 @@ Herb.jl's sub-packages provide fast and easily extendable implementations of ## Why Julia? +Julia is a perfect fit for program synthesis due to numerous reasons. Starting from scientific reasons like speed of execution and composability over to practical reasons like speed of writing Julia code. For a full ode on why to use Julia, please see [the WhyJulia manifesto](https://github.com/pitmonticone/whyjulia-manifesto/tree/main). ## Sub-Modules From cc821b327ddf21f8551c8f9f7d500bbfb6f58746 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 19 Jan 2024 14:24:08 +0100 Subject: [PATCH 23/75] Fix typo in make --- docs/make.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index be23bb7..396bd5a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -11,7 +11,6 @@ using HerbCore makedocs( modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbInterpret, HerbCore], - format authors="PONYs", sitename="Herb.jl", pages = [ From 3205f05925faea5a73159dfb1ca75be8e6af8cae Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 19 Jan 2024 14:37:43 +0100 Subject: [PATCH 24/75] Update dependencies for documentation compilation --- .github/workflows/documentation.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index c18c2f2..3d864ce 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -18,7 +18,16 @@ jobs: with: version: '1.8' - name: Install dependencies - run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' + run: julia --project=docs/ -e ' + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.develop("HerbCore") + Pkg.develop("HerbGrammar") + Pkg.develop("HerbData") + Pkg.develop("HerbInterpret") + Pkg.develop("HerbConstraints") + Pkg.develop("HerbSearch") + Pkg.instantiate()' - name: Build and deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # If authenticating with GitHub Actions token From cd0da6933c43620c27f4e926f0f5dd669ded5ac1 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 19 Jan 2024 14:39:02 +0100 Subject: [PATCH 25/75] Update dependencies for documentation compilation 2 --- .github/workflows/documentation.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3d864ce..e7e6847 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -19,14 +19,14 @@ jobs: version: '1.8' - name: Install dependencies run: julia --project=docs/ -e ' - using Pkg - Pkg.develop(PackageSpec(path=pwd())) - Pkg.develop("HerbCore") - Pkg.develop("HerbGrammar") - Pkg.develop("HerbData") - Pkg.develop("HerbInterpret") - Pkg.develop("HerbConstraints") - Pkg.develop("HerbSearch") + using Pkg; + Pkg.develop(PackageSpec(path=pwd())); + Pkg.develop("HerbCore"); + Pkg.develop("HerbGrammar"); + Pkg.develop("HerbData"); + Pkg.develop("HerbInterpret"); + Pkg.develop("HerbConstraints"); + Pkg.develop("HerbSearch"); Pkg.instantiate()' - name: Build and deploy env: From dd10769b5cb59b6475d78405dafc32dad2d1e563 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 19 Jan 2024 15:02:21 +0100 Subject: [PATCH 26/75] Update update tests --- .github/workflows/documentation.yml | 2 +- test/runtests.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index e7e6847..7c7eb67 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -22,8 +22,8 @@ jobs: using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.develop("HerbCore"); - Pkg.develop("HerbGrammar"); Pkg.develop("HerbData"); + Pkg.develop("HerbGrammar"); Pkg.develop("HerbInterpret"); Pkg.develop("HerbConstraints"); Pkg.develop("HerbSearch"); diff --git a/test/runtests.jl b/test/runtests.jl index 7878150..133fd25 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -8,7 +8,7 @@ using HerbSearch using HerbSpecification using Test -import Pkg +using Pkg @testset verbose=false "Herb" begin @test 1==1 # dummy test From 7eaf18041480a933597941b490e07ec3158fc07c Mon Sep 17 00:00:00 2001 From: Issa Hanou Date: Fri, 19 Jan 2024 15:09:45 +0100 Subject: [PATCH 27/75] updated tutorial md based on notebook --- docs/src/get_started.md | 4 +++- docs/src/tutorials/advanced_search.md | 28 +++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index aa4147f..1ecd253 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -10,7 +10,9 @@ using HerbGrammar, HerbData, HerbSearch, HerbInterpret To define a program synthesis problem, we need a grammar and specification. -First, the grammar can be constructed using the `@cfgrammar` macro included in `HerbGrammar`. Here we describe a simple integer arithmetic example, that can add and multiply an input variable `x` or the integers `1,2`, using +First, a grammar can be constructed using the `@cfgrammar` macro included in `HerbGrammar`. Alternatively, we can use the `@csgrammar` macro, which will be shown later on in the tutorial when we use constraints. + +Here, we describe a simple integer arithmetic example, that can add and multiply an input variable `x` or the integers `1,2`, using ```julia diff --git a/docs/src/tutorials/advanced_search.md b/docs/src/tutorials/advanced_search.md index f53eac0..113567e 100644 --- a/docs/src/tutorials/advanced_search.md +++ b/docs/src/tutorials/advanced_search.md @@ -1,6 +1,6 @@ # Getting started -You can either paste this code into the Julia REPL or into a seperate file, e.g. `get_started.jl` followed by running `julia get_started.jl`. +You can either paste this code into the Julia REPL or run it using the `advanced_search.ipynb` notebook. Alternatively you can copy the code into a file like `get_started.jl`, followed by running `julia get_started.jl`. To start, we import the necessary packages. @@ -60,7 +60,7 @@ end >>> ... ``` -We see that only when `i >= 24`, there is a result, after that, increasing `i` does not have any effect on the number of allocations. +We see that only when `i >= 24`, there is a result. After that, increasing `i` does not have any effect on the number of allocations. A final parameter we consider here is `allow_evaluation_errors`, which is `false` by default. When this is set to `true`, the program will still run even when an exception is thrown during evaluation. To see the effect of this, we create a new grammar. We can also retrieve the error together with the solution from the search method. @@ -77,7 +77,7 @@ println(solution) >>> nothing ``` -There is also another search method called `search_best` which return both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error: +There is also another search method called `search_best` which returns both the solution and the possible error. The method returns the best program found so far. In this case, we can also see the error: ```julia solution, error = search_best(g, problem, :Index, max_depth=2, allow_evaluation_errors=true) @@ -93,7 +93,7 @@ We now show examples of using different search procedures. ### Breadth-First Search -The breadth-first search will first enumerate all possible programs at the same depth before considering a program with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. +The breadth-first search will first enumerate all possible programs at the same depth before considering programs with a depth of one more. A tree of the grammar is returned with programs ordered in increasing sizes. We can first `collect` the programs that have a `max-depth` of 2 and a `max_size` of infinite (integer maximum value), where the starting symbol is of type `Real`. This function uses a default heuristic 'left-most first', such that the left-most child in the tree is always explored first. ```julia g1 = @cfgrammar begin @@ -103,7 +103,7 @@ end programs = collect(get_bfs_enumerator(g1, 2, typemax(Int), :Real)) ``` -We can test that this function returns the correct functions and all functions. +We can test that this function returns all and only the correct functions. ```julia answer_programs = [ @@ -145,11 +145,9 @@ println(programs) >>> RuleNode[1,, 3{1,1}, 3{2,1}, 3{1,2}, 3{2,2}, 2,] ``` -### Metropolis-Hastings - -One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). +## Stochastic search -For the example below, we use this grammar and helper function. +For the examples below, we use this grammar and helper function. ```julia grammar = @csgrammar begin X = |(1:5) @@ -164,6 +162,12 @@ function create_problem(f, range=20) end ``` +### Metropolis-Hastings + +One of the stochastic search methods that is implemented is Metropolis-Hastings (MH), which samples from a distribution of programs based on the grammar. For more information on MH, see for example [this webpage](https://stephens999.github.io/fiveMinuteStats/MH_intro.html). + + + The below example uses a simple arithmetic example. As the search method is stochastic, different programs may be returned, as shown below. ```julia @@ -203,7 +207,7 @@ program, cost = search_best(grammar, problem, :X, enumerator=enumerator, error_f ### Simulated Annealing -The third stochastic search method is called simulated annealing, is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). +The third stochastic search method is called simulated annealing. This is another hill-climbing method to find local optima. For more information, see [this page](https://www.cs.cmu.edu/afs/cs.cmu.edu/project/learn-43/lib/photoz/.g/web/glossary/anneal.html). We try the example from earlier, but now we can additionally define the `initial_temperature` of the algorithm, which is 1 by default. Two possible answers to the program are given as well. @@ -224,8 +228,8 @@ Genetic search is a type of evolutionary algorithm, which will simulate the proc We show the example of finding a lambda function. ```julia -e = Meta.parse("x -> 3 * x * x + (x + 2)") -problem, examples = create_problem(eval(e)) +e = x -> 3 * x * x + (x + 2) +problem, examples = create_problem(e) enumerator = get_genetic_enumerator(examples, initial_population_size = 10, mutation_probability = 0.8, From 82965f5a19315748c818bec906a36d73944f290c Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Mon, 26 Feb 2024 12:33:05 +0100 Subject: [PATCH 28/75] Add HerbSpecification to doc deps --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index ccfa3e6..abf970e 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -3,10 +3,10 @@ Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" -HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" +HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" [compat] Documenter = "0.27" From 6c66e2b468f00416cf7c679022d6d16e7f6382f6 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Thu, 14 Mar 2024 11:55:48 +0100 Subject: [PATCH 29/75] Update getting started guide --- docs/src/get_started.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index 1ecd253..2ce12ce 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -5,7 +5,7 @@ You can either paste this code into the Julia REPL or into a seperate file, e.g. To begin, we need to import all needed packages using ```julia -using HerbGrammar, HerbData, HerbSearch, HerbInterpret +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret ``` To define a program synthesis problem, we need a grammar and specification. @@ -33,7 +33,8 @@ problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) The problem is given now, let us search for a solution with `HerbSearch`. For now we will just use the default parameters searching for a satisfying program over the grammar, given the problem and a starting symbol using ```julia -solution = search(g, problem, :Number, max_depth=3) +iterator = BFSIterator(g₁, :Number, max_depth=5) +solution, flag = synth(problem, iterator) println(solution) ``` @@ -42,7 +43,9 @@ There are various ways to adapt the search technique to your needs. Please have Eventually, we want to test our solution on some other inputs using `HerbInterpret`. We transform our grammar `g` to a Julia expression with `Symboltable(g)`, add our solution and the input, assigning the value `6` to the variable `x`. ```julia -output = test_with_input(SymbolTable(g), solution, Dict(:x => 6)) +program = rulenode2expr(solution, g) # should yield 2*6+1 + +output = execute_on_input(SymbolTable(g), program, Dict(:x => 6)) println(output) ``` @@ -67,9 +70,14 @@ g = @cfgrammar begin end problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) -solution = search(g, problem, :Number, max_depth=3) +iterator = BFSIterator(g₁, :Number, max_depth=5) + +solution, flag = synth(problem, iterator) +program = rulenode2expr(solution, g) # should yield 2*6 +1 + +output = execute_on_input(SymbolTable(g), program, Dict(:x => 6)) +println(output) -test_with_input(SymbolTable(g), solution, Dict(:x => 6)) ``` From 4f3afc901a1b3b62ce2eb22f6dadd197be8de583 Mon Sep 17 00:00:00 2001 From: Reuben Gardos Reid <5456207+ReubenJ@users.noreply.github.com> Date: Fri, 15 Mar 2024 09:16:31 +0100 Subject: [PATCH 30/75] Update docs dependencies Additionally: bump GitHub checkout action to latest version to address warnings regarding Node 16 use in the v2 version of the action. --- .github/workflows/documentation.yml | 2 +- docs/Project.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 7c7eb67..a698e8a 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,7 +13,7 @@ jobs: contents: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: version: '1.8' diff --git a/docs/Project.toml b/docs/Project.toml index abf970e..4082b75 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,8 +1,10 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" +Herb = "c09c6b7f-4f63-49de-90d9-97a3563c0f4a" HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" +HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" From 86d0ac7ec0f7bbc982e14bfbf6b88df602be94f7 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 15 Mar 2024 10:10:06 +0100 Subject: [PATCH 31/75] Remove Herb.jl from sidebar --- docs/make.jl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/make.jl b/docs/make.jl index 396bd5a..1ab5480 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -34,6 +34,9 @@ makedocs( "HerbData.jl" => "HerbData/index.md", ], ], + format = Documenter.HTML( + sidebar_sitename = false + ), warnonly = [:missing_docs] ) From d1afe3cabb7e8a28ce8e27ad4fdb4f08dc13e872 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Apr 2024 09:47:27 +0200 Subject: [PATCH 32/75] HerbData -> HerbSpecification; add push_preview; try latest Documenter.jl version --- .github/workflows/documentation.yml | 2 +- docs/Project.toml | 3 +-- docs/make.jl | 15 +++++++------ docs/src/HerbData/index.md | 15 ------------- docs/src/get_started.md | 4 ++-- docs/src/index.md | 2 +- docs/src/install.md | 2 +- docs/src/tutorials/advanced_search.ipynb | 4 ++-- docs/src/tutorials/advanced_search.md | 2 +- .../tutorials/getting_started_with_herb.ipynb | 22 +++++++++---------- .../tutorials/getting_started_with_herb.md | 22 +++++++++---------- 11 files changed, 39 insertions(+), 54 deletions(-) delete mode 100644 docs/src/HerbData/index.md diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index a698e8a..268c42f 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v4 - uses: julia-actions/setup-julia@v1 with: - version: '1.8' + version: '1' - name: Install dependencies run: julia --project=docs/ -e ' using Pkg; diff --git a/docs/Project.toml b/docs/Project.toml index 4082b75..2fdf779 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -4,11 +4,10 @@ DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" Herb = "c09c6b7f-4f63-49de-90d9-97a3563c0f4a" HerbConstraints = "1fa96474-3206-4513-b4fa-23913f296dfc" HerbCore = "2b23ba43-8213-43cb-b5ea-38c12b45bd45" -HerbData = "495a3ad3-8034-41b3-a087-aacf2fd71098" HerbGrammar = "4ef9e186-2fe5-4b24-8de7-9f7291f24af7" HerbInterpret = "5bbddadd-02c5-4713-84b8-97364418cca7" HerbSearch = "3008d8e8-f9aa-438a-92ed-26e9c7b4829f" HerbSpecification = "6d54aada-062f-46d8-85cf-a1ceaf058a06" [compat] -Documenter = "0.27" +Documenter = "1" diff --git a/docs/make.jl b/docs/make.jl index 1ab5480..5cdb96b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -5,12 +5,12 @@ using Herb using HerbConstraints using HerbSearch using HerbGrammar -using HerbData using HerbInterpret using HerbCore +using HerbSpecification makedocs( - modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbData, HerbInterpret, HerbCore], + modules=[HerbConstraints, HerbSearch, HerbGrammar, HerbSpecification, HerbInterpret, HerbCore], authors="PONYs", sitename="Herb.jl", pages = [ @@ -26,12 +26,12 @@ makedocs( "Advanced Search Procedures" => "tutorials/advanced_search.md" ], "Sub-Modules" => [ - "HerbGrammar.jl" => "HerbGrammar/index.md", - "HerbSearch.jl" => "HerbSearch/index.md", - "HerbConstraints.jl" => "HerbConstraints/index.md", "HerbCore.jl" => "HerbData/index.md", + "HerbGrammar.jl" => "HerbGrammar/index.md", + "HerbSpecification.jl" => "HerbSpecification/index.md", "HerbInterpret.jl" => "HerbInterpret/index.md", - "HerbData.jl" => "HerbData/index.md", + "HerbConstraints.jl" => "HerbConstraints/index.md", + "HerbSearch.jl" => "HerbSearch/index.md", ], ], format = Documenter.HTML( @@ -43,6 +43,7 @@ makedocs( deploydocs(; repo="github.com/Herb-AI/Herb.jl.git", devbranch="documentation", - devurl="dev", + # devurl="dev", + push_preview=true ) diff --git a/docs/src/HerbData/index.md b/docs/src/HerbData/index.md deleted file mode 100644 index bbb3577..0000000 --- a/docs/src/HerbData/index.md +++ /dev/null @@ -1,15 +0,0 @@ -# [HerbData.jl Documentation](@id HerbData_docs) - -```@meta -CurrentModule=HerbData -``` - -```@autodocs -Modules = [HerbData] -Order = [:type, :const, :macro, :function] -``` - -## Index - -```@index -``` diff --git a/docs/src/get_started.md b/docs/src/get_started.md index 2ce12ce..09b486f 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -24,7 +24,7 @@ g = @cfgrammar begin end ``` -Second, the problem specification can be provided using e.g. input/output examples using `HerbData`. Inputs are provided as a `Dict` assigning values to variables, and outputs as arbitrary values. The problem itself is then a list of `IOExample`s using +Second, the problem specification can be provided using e.g. input/output examples using `HerbSpecification`. Inputs are provided as a `Dict` assigning values to variables, and outputs as arbitrary values. The problem itself is then a list of `IOExample`s using ```julia problem = Problem([IOExample(Dict(:x => x), 2x+1) for x ∈ 1:5]) @@ -58,7 +58,7 @@ See our other tutorials! ## The full code example ```julia -using HerbSearch, HerbData, HerbInterpret, HerbGrammar +using HerbSearch, HerbSpecification, HerbInterpret, HerbGrammar # define our very simple context-free grammar # Can add and multiply an input variable x or the integers 1,2. diff --git a/docs/src/index.md b/docs/src/index.md index df738e3..7a1c593 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -33,7 +33,7 @@ Julia is a perfect fit for program synthesis due to numerous reasons. Starting f Herb's functionality is distributed among several sub-packages: - [HerbCore.jl](@ref HerbCore_docs): The core of Herb.jl defining core concepts to avoid circular dependencies. - [HerbGrammar.jl](@ref HerbGrammar_docs): -- [HerbData.jl](@ref HerbData_docs): +- [HerbSpecification.jl](@ref HerbSpecification_docs): - [HerbInterpret.jl](@ref HerbInterpret_docs): - [HerbSearch.jl](@ref HerbSearch_docs): - [HerbConstraints.jl](@ref HerbConstraints_docs): diff --git a/docs/src/install.md b/docs/src/install.md index 20f723a..6953624 100644 --- a/docs/src/install.md +++ b/docs/src/install.md @@ -32,7 +32,7 @@ which will both install all dependencies automatically. For later convenience we can also add the respective dependencies to our project, so that we do not have to write `Herb.HerbGrammar` every time. ```julia -] add HerbConstraints HerbCore HerbData HerbInterpret HerbGrammar HerbSearch +] add HerbConstraints HerbCore HerbSpecification HerbInterpret HerbGrammar HerbSearch ``` And just like this you are done! Welcome to Herb.jl! diff --git a/docs/src/tutorials/advanced_search.ipynb b/docs/src/tutorials/advanced_search.ipynb index 29dba13..dc4c248 100644 --- a/docs/src/tutorials/advanced_search.ipynb +++ b/docs/src/tutorials/advanced_search.ipynb @@ -16,8 +16,8 @@ "outputs": [], "source": [ "using Pkg\n", - "Pkg.add([\"HerbGrammar\", \"HerbData\", \"HerbSearch\", \"HerbInterpret\", \"HerbConstraints\"])\n", - "using HerbGrammar, HerbData, HerbSearch, HerbInterpret, HerbConstraints" + "Pkg.add([\"HerbGrammar\", \"HerbSpecification\", \"HerbSearch\", \"HerbInterpret\", \"HerbConstraints\"])\n", + "using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret, HerbConstraints" ] }, { diff --git a/docs/src/tutorials/advanced_search.md b/docs/src/tutorials/advanced_search.md index 113567e..bb10fa1 100644 --- a/docs/src/tutorials/advanced_search.md +++ b/docs/src/tutorials/advanced_search.md @@ -5,7 +5,7 @@ You can either paste this code into the Julia REPL or run it using the `advanced To start, we import the necessary packages. ```julia -using HerbGrammar, HerbData, HerbSearch, HerbInterpret +using HerbGrammar, HerbSpecification, HerbSearch, HerbInterpret ``` We start with the same simple grammar from the main file [get_started.md](../get_started.md). diff --git a/docs/src/tutorials/getting_started_with_herb.ipynb b/docs/src/tutorials/getting_started_with_herb.ipynb index dcb9f1d..4d3f2fc 100644 --- a/docs/src/tutorials/getting_started_with_herb.ipynb +++ b/docs/src/tutorials/getting_started_with_herb.ipynb @@ -122,12 +122,12 @@ { "data": { "text/plain": [ - "5-element Vector{Main.Herb.HerbData.IOExample}:\n", - " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", - " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", - " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", - " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", - " Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)" + "5-element Vector{Main.Herb.HerbSpecification.IOExample}:\n", + " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8)\n", + " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11)\n", + " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14)\n", + " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17)\n", + " Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)" ] }, "metadata": {}, @@ -136,7 +136,7 @@ ], "source": [ "# Create input-output examples\n", - "examples = [Herb.HerbData.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" + "examples = [Herb.HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5]" ] }, { @@ -157,7 +157,7 @@ { "data": { "text/plain": [ - "Main.Herb.HerbData.Problem(Main.Herb.HerbData.Example[Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)], \"example\")" + "Main.Herb.HerbSpecification.Problem(Main.Herb.HerbSpecification.Example[Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)], \"example\")" ] }, "metadata": {}, @@ -165,7 +165,7 @@ } ], "source": [ - "problem = Herb.HerbData.Problem(examples, \"example\")" + "problem = Herb.HerbSpecification.Problem(examples, \"example\")" ] }, { @@ -250,7 +250,7 @@ } ], "source": [ - "problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 168) for x ∈ 1:5], \"example2\")\n", + "problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5], \"example2\")\n", "expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180)\n", "print(expr)" ] @@ -271,7 +271,7 @@ "metadata": {}, "outputs": [], "source": [ - "problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 167) for x ∈ 1:5], \"example2\")\n", + "problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5], \"example2\")\n", "expr = Herb.HerbSearch.search(g, problem, :Real)\n", "print(expr)" ] diff --git a/docs/src/tutorials/getting_started_with_herb.md b/docs/src/tutorials/getting_started_with_herb.md index b87ac0a..591549e 100644 --- a/docs/src/tutorials/getting_started_with_herb.md +++ b/docs/src/tutorials/getting_started_with_herb.md @@ -71,16 +71,16 @@ In the cell below we automatically generate some examples for `x` assigning valu ```julia # Create input-output examples -examples = [Herb.HerbData.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] +examples = [Herb.HerbSpecification.IOExample(Dict(:x => x), 3x+5) for x ∈ 1:5] ``` - 5-element Vector{Main.Herb.HerbData.IOExample}: - Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8) - Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11) - Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14) - Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17) - Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20) + 5-element Vector{Main.Herb.HerbSpecification.IOExample}: + Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8) + Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11) + Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14) + Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17) + Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20) Now that we have some input-output examples, we can define the problem. @@ -89,11 +89,11 @@ For now, this is irrelevant, and you can give the program any name you like. ```julia -problem = Herb.HerbData.Problem(examples, "example") +problem = Herb.HerbSpecification.Problem(examples, "example") ``` - Main.Herb.HerbData.Problem(Main.Herb.HerbData.Example[Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbData.IOExample(Dict{Symbol, Any}(:x => 5), 20)], "example") + Main.Herb.HerbSpecification.Problem(Main.Herb.HerbSpecification.Example[Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 1), 8), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 2), 11), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 3), 14), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 4), 17), Main.Herb.HerbSpecification.IOExample(Dict{Symbol, Any}(:x => 5), 20)], "example") ### Searching @@ -135,7 +135,7 @@ Search is done by passing the grammar, the problem and the starting point like b ```julia -problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 168) for x ∈ 1:5], "example2") +problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 168) for x ∈ 1:5], "example2") expr = Herb.HerbSearch.search(g, problem, :Real, enumerator=Herb.HerbSearch.get_dfs_enumerator, max_depth=4, max_size=30, max_time=180) print(expr) ``` @@ -148,7 +148,7 @@ In any case, this concludes our first introduction to the `Herb.jl` program synt ```julia -problem = Herb.HerbData.Problem([Herb.HerbData.IOExample(Dict(:x => x), 167) for x ∈ 1:5], "example2") +problem = Herb.HerbSpecification.Problem([Herb.HerbSpecification.IOExample(Dict(:x => x), 167) for x ∈ 1:5], "example2") expr = Herb.HerbSearch.search(g, problem, :Real) println(expr) ``` From 4746f6ee944c8c83f43323f102d232dbe6f69d33 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Apr 2024 09:47:51 +0200 Subject: [PATCH 33/75] HerbData -> HerbSpecification --- docs/src/HerbSpecification/index.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/src/HerbSpecification/index.md diff --git a/docs/src/HerbSpecification/index.md b/docs/src/HerbSpecification/index.md new file mode 100644 index 0000000..a5ae9ca --- /dev/null +++ b/docs/src/HerbSpecification/index.md @@ -0,0 +1,15 @@ +# [HerbSpecification.jl Documentation](@id HerbSpecification_docs) + +```@meta +CurrentModule=HerbSpecification +``` + +```@autodocs +Modules = [HerbSpecification] +Order = [:type, :const, :macro, :function] +``` + +## Index + +```@index +``` From 906d1393eea96a93491ec2cdae39bb2c7e9af539 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Apr 2024 09:50:28 +0200 Subject: [PATCH 34/75] Fix typo in make.jl --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 5cdb96b..dd5f889 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -26,7 +26,7 @@ makedocs( "Advanced Search Procedures" => "tutorials/advanced_search.md" ], "Sub-Modules" => [ - "HerbCore.jl" => "HerbData/index.md", + "HerbCore.jl" => "HerbCore/index.md", "HerbGrammar.jl" => "HerbGrammar/index.md", "HerbSpecification.jl" => "HerbSpecification/index.md", "HerbInterpret.jl" => "HerbInterpret/index.md", From dc9fe326fab670d02c5ffbd8c83209eeee9e205a Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Apr 2024 11:34:19 +0200 Subject: [PATCH 35/75] Ignore crossref issues --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index dd5f889..16c086c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -37,7 +37,7 @@ makedocs( format = Documenter.HTML( sidebar_sitename = false ), - warnonly = [:missing_docs] + warnonly = [:missing_docs, :cross_reference] ) deploydocs(; From 560a5503997175d0c611efc776bf0fdcbe1eb295 Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Fri, 5 Apr 2024 11:37:11 +0200 Subject: [PATCH 36/75] Add doctest ignore to doc deployment --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index 16c086c..46efd2a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -37,7 +37,7 @@ makedocs( format = Documenter.HTML( sidebar_sitename = false ), - warnonly = [:missing_docs, :cross_reference] + warnonly = [:missing_docs, :cross_references, :doctest] ) deploydocs(; From 0d9df3a16c6b0ede5498e3bf05b2115abc0ba214 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 11 Apr 2024 11:07:56 +0200 Subject: [PATCH 37/75] Fix broken reference. --- docs/src/get_started.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/get_started.md b/docs/src/get_started.md index 09b486f..cdbbaa8 100644 --- a/docs/src/get_started.md +++ b/docs/src/get_started.md @@ -38,7 +38,7 @@ solution, flag = synth(problem, iterator) println(solution) ``` -There are various ways to adapt the search technique to your needs. Please have a look at the [`search`](@ref) documentation. +There are various ways to adapt the search technique to your needs. Please have a look at the [`synth`](@ref) documentation. Eventually, we want to test our solution on some other inputs using `HerbInterpret`. We transform our grammar `g` to a Julia expression with `Symboltable(g)`, add our solution and the input, assigning the value `6` to the variable `x`. From b6df4c1e4af024633b09baa926c0d4c56a63a50e Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Wed, 1 May 2024 16:13:27 +0200 Subject: [PATCH 38/75] Adds new tutorial on ASTs --- docs/src/tutorials/syntax-trees.ipynb | 649 ++++++++++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 docs/src/tutorials/syntax-trees.ipynb diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/syntax-trees.ipynb new file mode 100644 index 0000000..96c3187 --- /dev/null +++ b/docs/src/tutorials/syntax-trees.ipynb @@ -0,0 +1,649 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "adb6401d-97cf-4926-86d1-d93cd888cced", + "metadata": {}, + "source": [ + "# Herb tutorial: Abstract syntax trees" + ] + }, + { + "cell_type": "markdown", + "id": "6aab945c-b81f-43f3-bcb3-36521ed3a50e", + "metadata": {}, + "source": [ + "In this tutorial, you will learn\n", + " \n", + "- How to represent a computer program as abstract syntax tree in Herb.\n", + "- How to replace parts of the tree to modify the program." + ] + }, + { + "cell_type": "markdown", + "id": "56460a31-bfdd-4c33-8a2b-a0db1430477e", + "metadata": {}, + "source": [ + "## Abstract syntax trees\n", + "\n", + "The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammer, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. \n", + "\n", + "In the context of program synthesis ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found.\n", + "\n", + "Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. \n", + "An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. " + ] + }, + { + "cell_type": "markdown", + "id": "3f28908e-adcf-47ff-b369-556bc6e2dae4", + "metadata": {}, + "source": [ + "## A simple example program\n", + "\n", + "We first consider the simple program 5*(x+3). We will define a grammar that is sufficient to represent this program and use it to construct a AST for our program." + ] + }, + { + "cell_type": "markdown", + "id": "4d6961c8-2fd5-4cd9-b4a1-166d46500aaa", + "metadata": {}, + "source": [ + "### Define the grammar" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "69d543a7-3067-4376-b1bb-1a4f9217b786", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Number = 0\n", + "2: Number = 1\n", + "3: Number = 2\n", + "4: Number = 3\n", + "5: Number = 4\n", + "6: Number = 5\n", + "7: Number = 6\n", + "8: Number = 7\n", + "9: Number = 8\n", + "10: Number = 9\n", + "11: Number = x\n", + "12: Number = Number + Number\n", + "13: Number = Number * Number\n" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "using HerbCore, HerbGrammar\n", + "\n", + "grammar = @csgrammar begin\n", + " Number = |(0:9)\n", + " Number = x\n", + " Number = Number + Number\n", + " Number = Number * Number\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "id": "8de36f71-5051-4d1b-b725-c2a94fbe9119", + "metadata": {}, + "source": [ + "### Construct the syntax tree" + ] + }, + { + "cell_type": "markdown", + "id": "33b9e13e-f444-496f-8931-56a87887ae9c", + "metadata": {}, + "source": [ + "The AST of this program is shown in the diagram below. The number in each node refers to the index of the corresponding rule in our grammar. " + ] + }, + { + "cell_type": "markdown", + "id": "5578f49b-741b-4f4a-a660-ce5bb4673ea1", + "metadata": {}, + "source": [ + "```mermaid\n", + " flowchart \n", + " id1((13)) ---\n", + " id2((6))\n", + " id1 --- id3((12))\n", + " id4((11))\n", + " id5((4))\n", + " id3 --- id4\n", + " id3 --- id5\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "a41f36e1-ce66-4303-8ad1-d74aeba72a70", + "metadata": {}, + "source": [ + "In `Herb.jl`, the `HerbCore.RuleNode` is used to represent both an individual node, but also entire ASTs or sub-trees. This is achieved by nesting instances of `RuleNode`. A `RuleNode` can be instantiated by providing the index of the grammar rule that the node represents and a vector of child nodes. " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9c61d359-1f38-410e-b654-5d36bc9a1d4e", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13{6,12{11,4}}" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "syntaxtree = RuleNode(13, [RuleNode(6), RuleNode(12, [RuleNode(11), RuleNode(4)])])" + ] + }, + { + "cell_type": "markdown", + "id": "da78a5d9-038e-4d97-ab1a-32efc830c1e8", + "metadata": {}, + "source": [ + "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "7c933e57-03f8-41ac-a464-70f95b9505c0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5 * (x + 3)\n" + ] + } + ], + "source": [ + "program = rulenode2expr(syntaxtree, grammar)\n", + "println(program)" + ] + }, + { + "cell_type": "markdown", + "id": "b724f97d-ae36-455c-94b8-10632beffe6e", + "metadata": {}, + "source": [ + "## Another example: FizzBuzz\n", + "\n", + "Let's look at a more interesting example. \n", + "The program `fizbuzz()` is based on the popular _FizzBuzz_ problem. Given an integer number, the program simply returns a `String` of that number, but replace numbers divisible by 3 with `\"Fizz\"`, numbers divisible by 5 with `\"Buzz\"`, and number divisible by both 3 and 5 with `\"FizzBuzz\"`." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "1f41bec4-d081-48bd-9fee-c30021d4a882", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "fizzbuzz (generic function with 1 method)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "function fizzbuzz(x)\n", + " if x % 5 == 0 && x % 3 == 0\n", + " return Dict(:output1 => \"FizzBuzz\")\n", + " else\n", + " if x % 3 == 0\n", + " return Dict(:output1 => \"Fizz\")\n", + " else\n", + " if x % 5 == 0\n", + " return Dict(:output1 => \"Buzz\")\n", + " else\n", + " return Dict(:output1 => string(x))\n", + " end\n", + " end\n", + " end\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "bc5a248d-87e7-4eda-9a3a-80620da45f9c", + "metadata": {}, + "source": [ + "### Define the grammar\n", + "\n", + "Let's define a grammar with all the rules that we need." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "7b0ea30c-e415-4d7e-ba2f-a1590cd4c8fe", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = input1\n", + "2: Int = 0\n", + "3: Int = 3\n", + "4: Int = 5\n", + "5: String = Fizz\n", + "6: String = Buzz\n", + "7: String = FizzBuzz\n", + "8: String = string(Int)\n", + "9: Return = Dict(:output1 => String)\n", + "10: Int = Int % Int\n", + "11: Bool = Int == Int\n", + "12: Int = if Bool\n", + " Int\n", + "else\n", + " Int\n", + "end\n", + "13: Bool = Bool && Bool\n" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "grammar_fizzbuzz = @csgrammar begin\n", + " Int = input1\n", + " Int = 0 | 3 | 5\n", + " String = \"Fizz\" | \"Buzz\" | \"FizzBuzz\"\n", + " String = string(Int)\n", + " Return = Dict(:output1 => String)\n", + " Int = Int % Int\n", + " Bool = Int == Int\n", + " Int = Bool ? Int : Int\n", + " Bool = Bool && Bool\n", + "end" + ] + }, + { + "cell_type": "markdown", + "id": "f0c06383-ce23-446e-8b9f-b323abacfbcb", + "metadata": {}, + "source": [ + "### Construct the syntax tree" + ] + }, + { + "cell_type": "markdown", + "id": "57fc9e37-d243-49c4-9fc9-7669a3cc7e47", + "metadata": {}, + "source": [ + "Given the grammar, the AST of `fizzbuzz()` looks like this:" + ] + }, + { + "cell_type": "markdown", + "id": "219585f5-a4b2-473a-ad98-b3ceb45e37c0", + "metadata": {}, + "source": [ + "```mermaid\n", + "flowchart \n", + " id1((12)) --- id21((13))\n", + " id1--- id22((9))\n", + " id1--- id23((12))\n", + "\n", + " id21 --- id31((11))\n", + " id21 --- id32((11))\n", + "\n", + " id31 --- id41((10))\n", + " id31 --- id42((2))\n", + "\n", + " id41 --- id51((1))\n", + " id41 --- id52((4))\n", + "\n", + " id32 --- id43((10)) \n", + " id32 --- id44((2))\n", + "\n", + " id43 --- id53((1))\n", + " id43 --- id54((3))\n", + "\n", + " id22 --- id33((7))\n", + " id23 --- id34((11))\n", + "\n", + " id34 --- id45((10))\n", + " id34 --- id46((2))\n", + "\n", + " id45 --- id55((1))\n", + " id45 --- id56((3))\n", + "\n", + " id23 --- id35((9))\n", + " id35 --- id47((5))\n", + "\n", + " id23 --- id36((12))\n", + " id36 --- id48((11))\n", + " id48 --- id57((10))\n", + " id57 --- id61((1))\n", + " id57 --- id62((4))\n", + " id48 --- id58((2))\n", + "\n", + " id36 --- id49((9))\n", + " id49 --- id59((6))\n", + "\n", + " id36 --- id410((9))\n", + " id410 --- id510((8))\n", + " id510 --- id63((1))\n", + " \n", + " \n", + " \n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "ca814e00-95e9-4b18-971d-ca82335bcb98", + "metadata": {}, + "source": [ + "As before, we use nest instanced of `RuleNode` to implement the AST." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "9c897ebc-4a79-45be-b349-c376bffa03df", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}}" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fizzbuzz_syntaxtree = RuleNode(12, [\n", + " RuleNode(13, [\n", + " RuleNode(11, [\n", + " RuleNode(10, [\n", + " RuleNode(1),\n", + " RuleNode(4)\n", + " ]),\n", + " RuleNode(2)\n", + " ]),\n", + " RuleNode(11, [\n", + " RuleNode(10, [\n", + " RuleNode(1),\n", + " RuleNode(3)\n", + " ]),\n", + " RuleNode(2)\n", + " ])\n", + " ]),\n", + " RuleNode(9, [\n", + " RuleNode(7)\n", + " \n", + " ]),\n", + " RuleNode(12, [\n", + " RuleNode(11, [\n", + " RuleNode(10, [\n", + " RuleNode(1),\n", + " RuleNode(3),\n", + " ]),\n", + " RuleNode(2)\n", + " ]),\n", + " RuleNode(9, [\n", + " RuleNode(5)\n", + " ]),\n", + " RuleNode(12, [\n", + " RuleNode(11, [\n", + " RuleNode(10, [\n", + " RuleNode(1),\n", + " RuleNode(4)\n", + " ]),\n", + " RuleNode(2)\n", + " ]),\n", + " RuleNode(9, [\n", + " RuleNode(6)\n", + " ]),\n", + " RuleNode(9, [\n", + " RuleNode(8, [\n", + " RuleNode(1)\n", + " ])\n", + " ])\n", + " ])\n", + " ]) \n", + " ])" + ] + }, + { + "cell_type": "markdown", + "id": "ac0f888f-0376-48b2-bc68-cbbde67f650f", + "metadata": {}, + "source": [ + "And we check our syntax tree is correct:" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "5a4ba50a-ccda-4e20-9301-051ee839fdf5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "if input1 % 5 == 0 && input1 % 3 == 0\n", + " Dict(:output1 => \"FizzBuzz\")\n", + "else\n", + " if input1 % 3 == 0\n", + " Dict(:output1 => \"Fizz\")\n", + " else\n", + " if input1 % 5 == 0\n", + " Dict(:output1 => \"Buzz\")\n", + " else\n", + " Dict(:output1 => string(input1))\n", + " end\n", + " end\n", + "end\n" + ] + } + ], + "source": [ + "program = rulenode2expr(fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", + "println(program)" + ] + }, + { + "cell_type": "markdown", + "id": "2cd4b5a5-f715-49e7-a3e2-dbc638af8330", + "metadata": {}, + "source": [ + "### Modify the AST/program\n", + "\n", + "There are several ways to modify an AST and hence, a program. You can\n", + "\n", + "- directly replace a node with `HerbCore.swap_node()`\n", + "- insert a rule node with `insert!`\n", + "\n", + "Let's modify our example such that if the input number is divisible by 3, the program returns \"Buzz\" instead of \"Fizz\". \n", + "We use `swap_node()` to replace the node of the AST that corresponds to rule 5 in the grammar (`String = Fizz`) with rule 6 (`String = Buzz`). To do so, `swap_node()` needs the tree that contains the node we want to modify, the new node we want to replace the node with, and the path to that node.\n", + "\n", + "Note that `swap_node()` modifies the tree, hence we make a deep copy of it first." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "65bfb9cc-bbdc-46e5-8ebf-9c15c6424339", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6," + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree)\n", + "newnode = RuleNode(6)\n", + "path = [3, 2, 1]\n", + "swap_node(modified_fizzbuzz_syntaxtree, newnode, path)" + ] + }, + { + "cell_type": "markdown", + "id": "9bc85789-00de-4f54-aa80-1e2ab789750e", + "metadata": {}, + "source": [ + "Let's confirm that we modified the AST, and hence the program, correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "742f7ee8-c088-4331-b17c-dc5a2078ca6b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "if input1 % 5 == 0 && input1 % 3 == 0\n", + " Dict(:output1 => \"FizzBuzz\")\n", + "else\n", + " if input1 % 3 == 0\n", + " Dict(:output1 => \"Buzz\")\n", + " else\n", + " if input1 % 5 == 0\n", + " Dict(:output1 => \"Buzz\")\n", + " else\n", + " Dict(:output1 => string(input1))\n", + " end\n", + " end\n", + "end\n" + ] + } + ], + "source": [ + "program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", + "println(program)" + ] + }, + { + "cell_type": "markdown", + "id": "550839d5-3219-4097-b015-538d84a4416f", + "metadata": {}, + "source": [ + "An alternative way to modify the AST is by using `insert!()`. This requires to provide the location of the node that we want to as `NodeLoc`. `NodeLoc` points to a node in the tree and consists of the parent and the child index of the node.\n", + "Again, we make a deep copy of the original AST first." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "74d04a45-1697-4bcd-a5fd-adaa4ef455ff", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "6," + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "modified_fizzbuzz_syntaxtree = deepcopy(fizzbuzz_syntaxtree)\n", + "# get the node we want to modify and instantiate a NodeLoc from it.\n", + "node = get_node_at_location(modified_fizzbuzz_syntaxtree, [3, 2, 1])\n", + "nodeloc = NodeLoc(node, 0)\n", + "# replace the node\n", + "insert!(node, nodeloc, newnode)" + ] + }, + { + "cell_type": "markdown", + "id": "9f0bf320-92bd-4e64-b8ed-6ea841a91cc0", + "metadata": {}, + "source": [ + "Again, we check that we modified the program as intended:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "6f965fe6-f5b1-4059-911c-362f4c1731d6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "if input1 % 5 == 0 && input1 % 3 == 0\n", + " Dict(:output1 => \"FizzBuzz\")\n", + "else\n", + " if input1 % 3 == 0\n", + " Dict(:output1 => \"Buzz\")\n", + " else\n", + " if input1 % 5 == 0\n", + " Dict(:output1 => \"Buzz\")\n", + " else\n", + " Dict(:output1 => string(input1))\n", + " end\n", + " end\n", + "end\n" + ] + } + ], + "source": [ + "program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", + "println(program)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.10.2", + "language": "julia", + "name": "julia-1.10" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.10.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 732a9ce06752a0d7d480c0aa8efe2eac1fe6cd61 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Wed, 8 May 2024 13:17:31 +0200 Subject: [PATCH 39/75] Adds evaluation of programs on provided inputs. Fixes typos --- docs/src/tutorials/syntax-trees.ipynb | 141 ++++++++++++++++++++++---- 1 file changed, 121 insertions(+), 20 deletions(-) diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/syntax-trees.ipynb index 96c3187..f9601d8 100644 --- a/docs/src/tutorials/syntax-trees.ipynb +++ b/docs/src/tutorials/syntax-trees.ipynb @@ -15,7 +15,7 @@ "source": [ "In this tutorial, you will learn\n", " \n", - "- How to represent a computer program as abstract syntax tree in Herb.\n", + "- How to represent a computer program as an abstract syntax tree in Herb.\n", "- How to replace parts of the tree to modify the program." ] }, @@ -26,9 +26,9 @@ "source": [ "## Abstract syntax trees\n", "\n", - "The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammer, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. \n", + "The syntactic structure of a computer program can be represented in a hierarchical tree structure, a so-called _Abstract Syntax Tree (AST)_. The syntax of a programming language is typically defined using a formal grammar, a set of rules on how valid programs can be constructed. ASTs are derived from the grammar, but are abstractions in the sense that they omit details such as parenthesis, semicolons, etc. and only retain what's necessary to capture the program structure. \n", "\n", - "In the context of program synthesis ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found.\n", + "In the context of program synthesis, ASTs are often used to define the space of all possible programs which is searched to find one that satisfies the given specifications. During the search process, different ASTs, each corresponding to a different program, are generated and evaluated until a suitable one is found.\n", "\n", "Each _node_ of the AST represents a construct in the program (e.g., a variable, an operator, a statement, or a function) and this construct corresponds to a rule in the formal grammar. \n", "An _edge_ describes the relationship between constructs, and the tree structure captures the nesting of constructs. " @@ -82,7 +82,7 @@ } ], "source": [ - "using HerbCore, HerbGrammar\n", + "using HerbCore, HerbGrammar, HerbInterpret\n", "\n", "grammar = @csgrammar begin\n", " Number = |(0:9)\n", @@ -159,7 +159,7 @@ "id": "da78a5d9-038e-4d97-ab1a-32efc830c1e8", "metadata": {}, "source": [ - "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr`." + "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using ???." ] }, { @@ -181,6 +181,28 @@ "println(program)" ] }, + { + "cell_type": "code", + "execution_count": 4, + "id": "32fbb714-25aa-40d5-9c0b-c20ba8999d0c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "65" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test solution on inputs\n", + "output = execute_on_input(grammar, syntaxtree, Dict(:x => 10))" + ] + }, { "cell_type": "markdown", "id": "b724f97d-ae36-455c-94b8-10632beffe6e", @@ -194,7 +216,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "1f41bec4-d081-48bd-9fee-c30021d4a882", "metadata": {}, "outputs": [ @@ -204,7 +226,7 @@ "fizzbuzz (generic function with 1 method)" ] }, - "execution_count": 4, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -239,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "7b0ea30c-e415-4d7e-ba2f-a1590cd4c8fe", "metadata": {}, "outputs": [ @@ -265,7 +287,7 @@ "13: Bool = Bool && Bool\n" ] }, - "execution_count": 5, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -367,7 +389,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "9c897ebc-4a79-45be-b349-c376bffa03df", "metadata": {}, "outputs": [ @@ -377,7 +399,7 @@ "12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}}" ] }, - "execution_count": 6, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } @@ -446,7 +468,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "5a4ba50a-ccda-4e20-9301-051ee839fdf5", "metadata": {}, "outputs": [ @@ -475,6 +497,33 @@ "println(program)" ] }, + { + "cell_type": "code", + "execution_count": 9, + "id": "9e0b4ec1-eab1-49b9-be2d-c923098d1850", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element Vector{Any}:\n", + " Dict{QuoteNode, String}(:(:output1) => \"Fizz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"22\")" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test solution on inputs\n", + "input = [Dict(:input1 => 3), Dict(:input1 => 5), Dict(:input1 =>15), Dict(:input1 => 22)]\n", + "output = execute_on_input(grammar_fizzbuzz, fizzbuzz_syntaxtree, input)" + ] + }, { "cell_type": "markdown", "id": "2cd4b5a5-f715-49e7-a3e2-dbc638af8330", @@ -495,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "65bfb9cc-bbdc-46e5-8ebf-9c15c6424339", "metadata": {}, "outputs": [ @@ -505,7 +554,7 @@ "6," ] }, - "execution_count": 8, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } @@ -527,7 +576,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "id": "742f7ee8-c088-4331-b17c-dc5a2078ca6b", "metadata": {}, "outputs": [ @@ -556,6 +605,32 @@ "println(program)" ] }, + { + "cell_type": "code", + "execution_count": 12, + "id": "59d13ab2-7415-4463-89bf-e09ff55a74d8", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element Vector{Any}:\n", + " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"22\")" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test solution on same inputs as before\n", + "output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input)" + ] + }, { "cell_type": "markdown", "id": "550839d5-3219-4097-b015-538d84a4416f", @@ -567,7 +642,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 13, "id": "74d04a45-1697-4bcd-a5fd-adaa4ef455ff", "metadata": {}, "outputs": [ @@ -577,7 +652,7 @@ "6," ] }, - "execution_count": 10, + "execution_count": 13, "metadata": {}, "output_type": "execute_result" } @@ -601,7 +676,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 14, "id": "6f965fe6-f5b1-4059-911c-362f4c1731d6", "metadata": {}, "outputs": [ @@ -629,11 +704,37 @@ "program = rulenode2expr(modified_fizzbuzz_syntaxtree, grammar_fizzbuzz)\n", "println(program)" ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "41395fec-9053-423b-b0fd-7089bb89c2d0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "4-element Vector{Any}:\n", + " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", + " Dict{QuoteNode, String}(:(:output1) => \"22\")" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# test on same inputs as before\n", + "output = execute_on_input(grammar_fizzbuzz, modified_fizzbuzz_syntaxtree, input)" + ] } ], "metadata": { "kernelspec": { - "display_name": "Julia 1.10.2", + "display_name": "Julia 1.10.3", "language": "julia", "name": "julia-1.10" }, @@ -641,7 +742,7 @@ "file_extension": ".jl", "mimetype": "application/julia", "name": "julia", - "version": "1.10.2" + "version": "1.10.3" } }, "nbformat": 4, From 33b94e4383fff24fc916493006fa747db8fa26e6 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Wed, 8 May 2024 13:19:44 +0200 Subject: [PATCH 40/75] Update tutorial text. --- docs/src/tutorials/syntax-trees.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/syntax-trees.ipynb index f9601d8..1329ace 100644 --- a/docs/src/tutorials/syntax-trees.ipynb +++ b/docs/src/tutorials/syntax-trees.ipynb @@ -159,7 +159,7 @@ "id": "da78a5d9-038e-4d97-ab1a-32efc830c1e8", "metadata": {}, "source": [ - "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using ???." + "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.e`." ] }, { From 3215c0e730127fb3f0c22e719a7f5809b5cee2bb Mon Sep 17 00:00:00 2001 From: Tilman Hinnerichs Date: Tue, 28 May 2024 11:37:58 +0200 Subject: [PATCH 41/75] Add package descriptions --- docs/src/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 7a1c593..abc456e 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -31,12 +31,12 @@ Julia is a perfect fit for program synthesis due to numerous reasons. Starting f ## Sub-Modules Herb's functionality is distributed among several sub-packages: -- [HerbCore.jl](@ref HerbCore_docs): The core of Herb.jl defining core concepts to avoid circular dependencies. -- [HerbGrammar.jl](@ref HerbGrammar_docs): -- [HerbSpecification.jl](@ref HerbSpecification_docs): -- [HerbInterpret.jl](@ref HerbInterpret_docs): -- [HerbSearch.jl](@ref HerbSearch_docs): -- [HerbConstraints.jl](@ref HerbConstraints_docs): +- [HerbCore.jl](@ref HerbCore_docs): The core of Herb.jl defining abstract concepts, +- [HerbGrammar.jl](@ref HerbGrammar_docs): Functionality for declaring grammars, +- [HerbSpecification.jl](@ref HerbSpecification_docs): For describing user intent as specifications, +- [HerbInterpret.jl](@ref HerbInterpret_docs): For running programs in different languages and environments, +- [HerbConstraints.jl](@ref HerbConstraints_docs): For defining and effectively propagating and managing constraints during search, and +- [HerbSearch.jl](@ref HerbSearch_docs): For actually searching for solutions. ## Basics From 922c8f70a974c56e6bb6ee6d5a05d0efcfb71577 Mon Sep 17 00:00:00 2001 From: Whebon Date: Wed, 3 Jul 2024 17:17:49 +0200 Subject: [PATCH 42/75] Add tutorial for constraints --- docs/src/tutorials/constraints.ipynb | 932 +++++++++++++++++++++++++++ docs/src/tutorials/constraints.md | 738 +++++++++++++++++++++ 2 files changed, 1670 insertions(+) create mode 100644 docs/src/tutorials/constraints.ipynb create mode 100644 docs/src/tutorials/constraints.md diff --git a/docs/src/tutorials/constraints.ipynb b/docs/src/tutorials/constraints.ipynb new file mode 100644 index 0000000..624f169 --- /dev/null +++ b/docs/src/tutorials/constraints.ipynb @@ -0,0 +1,932 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Getting started with HerbConstraints\n", + "\n", + "When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setup\n", + "\n", + "For this tutorial, we need to import the following modules of the Herb.jl framework:\n", + "\n", + "* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s\n", + "* `HerbGrammar` to define the grammar\n", + "* `HerbConstraints` to define the constraints\n", + "* `HerbSearch` to execute a constrained enumeration\n", + "\n", + "We will also redefine the simple arithmetic grammar from the previous tutorial." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "1: Int = 1\n", + "2: Int = x\n", + "3: Int = -Int\n", + "4: Int = Int + Int\n", + "5: Int = Int * Int\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "using HerbCore, HerbGrammar, HerbConstraints, HerbSearch\n", + "\n", + "grammar = @cfgrammar begin\n", + " Int = 1\n", + " Int = x\n", + " Int = - Int\n", + " Int = Int + Int\n", + " Int = Int * Int\n", + "end" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Working with constraints\n", + "\n", + "To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes).\n", + "\n", + "(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n", + "1 + 1\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints.\n", + "\n", + "To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids:\n", + "\n", + "* `-(-1)`\n", + "* `-(-X)`\n", + "* `-(-(1 + 1))`\n", + "* `1 + -(-(1 + 1))`\n", + "* etc" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "x * 1\n", + "x * x\n", + "x + x\n", + "x + 1\n", + "1 + 1\n", + "1 + x\n" + ] + } + ], + "source": [ + "one = 1\n", + "x = 2\n", + "minus = 3\n", + "plus = 4\n", + "times = 5\n", + "\n", + "addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A\n", + "addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A)\n", + "\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forbidden Constraint\n", + "\n", + "The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types:\n", + "* `RuleNode(1)`. Matches exactly the given rule.\n", + "* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5.\n", + "* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "154\n", + "106\n" + ] + } + ], + "source": [ + "#this constraint forbids A+A and A*A\n", + "constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)]))\n", + "\n", + "# Without this constraint, we encounter 154 programs\n", + "clearconstraints!(grammar)\n", + "iter = BFSIterator(grammar, :Int, max_size=5)\n", + "println(length(iter))\n", + "\n", + "# With this constraint, we encounter 106 programs\n", + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, constraint)\n", + "iter = BFSIterator(grammar, :Int, max_size=5)\n", + "println(length(iter))\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contains Constraint\n", + "\n", + "The `Contains` constraint enforces that a given rule appears in the program tree at least once. \n", + "\n", + "In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x\n", + "-x\n", + "-(-x)\n", + "1x\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Contains Subtree Constraint\n", + "\n", + "Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "x * x\n", + "-(x * x)\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree\n", + "iter = BFSIterator(grammar, :Int, max_size=4)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Ordered Constraint\n", + "\n", + "The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants.\n", + "\n", + "To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree.\n", + "\n", + "In the upcoming example we will set up a template tree representing `a+b` and `a*b`.\n", + "Then, we will impose an ordering `a<=b` on all the subtrees that match the template.\n", + "\n", + "The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x + x\n", + "1 + x\n", + "1 + 1\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "\n", + "template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)])\n", + "order = [:a, :b]\n", + "\n", + "addconstraint!(grammar, Ordered(template_tree, order))\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Forbidden Sequence Constraint\n", + "\n", + "The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. \n", + "\n", + "An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. \n", + "\n", + "Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence.\n", + "\n", + "This constraint will **forbid** the following programs:\n", + "\n", + "* x + 1\n", + "* x + -1\n", + "* x + -(-1)\n", + "* x + (x + 1)\n", + "* x * (x + 1)\n", + "\n", + "But it will **allow** the following program (as * disrupts the sequence):\n", + "\n", + "* x + (x * 1)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "-(-1)\n", + "1x\n", + "-(-x)\n", + "x * x\n", + "x + x\n" + ] + } + ], + "source": [ + "constraint = ForbiddenSequence([plus, one], ignore_if=[times])\n", + "addconstraint!(grammar, constraint)\n", + "iter = BFSIterator(grammar, :Int, max_size=3)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Custom Constraint\n", + "\n", + "To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`.\n", + "\n", + "A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location.\n", + "\n", + "A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. \n", + "\n", + "Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function.\n", + "\n", + "(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.on_new_node" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "Forbids the consecutive application of the specified rule.\n", + "For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row.\n", + "\"\"\"\n", + "struct ForbidConsecutive <: AbstractGrammarConstraint\n", + " rule::Int\n", + "end\n", + "\n", + "\"\"\"\n", + "Post a local constraint on each new node that appears in the tree\n", + "\"\"\"\n", + "function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int})\n", + " HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule))\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path:\n", + "\n", + "* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})`\n", + "* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)`\n", + "* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)`\n", + "* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree)\n", + "\n", + "In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver:\n", + "\n", + "* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators.\n", + "* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation.\n", + "* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint.\n", + "\n", + "The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions:\n", + "\n", + "* `get_tree(solver)` returns the root node of the current (partial) program tree\n", + "* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants.\n", + "* `get_path(solver, node)` returns the path at which the node is located.\n", + "* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations).\n", + "* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2).\n", + "\n", + "To get information about a node, we can use the following getter functions:\n", + "\n", + "* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1.\n", + "* `get_rule(node)`. Get the rule of a filled node.\n", + "* `get_children(node)`. Get the children of a node.\n", + "* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain.\n", + "\n", + "Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.propagate!" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\"\"\"\n", + "Forbids the consecutive application of the specified rule at path `path`.\n", + "\"\"\"\n", + "struct LocalForbidConsecutive <: AbstractLocalConstraint\n", + " path::Vector{Int}\n", + " rule::Int\n", + "end\n", + "\n", + "\"\"\"\n", + "Propagates the constraints by preventing a consecutive application of the specified rule.\n", + "\"\"\"\n", + "function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive)\n", + " node = get_node_at_location(solver, constraint.path)\n", + " if isfilled(node)\n", + " if get_rule(node) == constraint.rule\n", + " #the specified rule is used, make sure the rule will not be used by any of the children\n", + " for (i, child) ∈ enumerate(get_children(node))\n", + " if isfilled(child)\n", + " if get_rule(child) == constraint.rule\n", + " #the specified rule was used twice in a row, which is violating the constraint\n", + " set_infeasible!(solver)\n", + " return\n", + " end\n", + " elseif child.domain[constraint.rule]\n", + " child_path = push!(copy(constraint.path), i)\n", + " remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child\n", + " end\n", + " end\n", + " end\n", + " elseif node.domain[constraint.rule]\n", + " #our node is a hole with the specified rule in its domain\n", + " #we will now check if any of the children already uses the specified rule\n", + " softfail = false\n", + " for (i, child) ∈ enumerate(get_children(node))\n", + " if isfilled(child)\n", + " if get_rule(child) == constraint.rule\n", + " #the child holds the specified rule, so the parent cannot have this rule\n", + " remove!(solver, constraint.path, constraint.rule)\n", + " end\n", + " elseif child.domain[constraint.rule]\n", + " #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail.\n", + " softfail = true\n", + " end\n", + " end\n", + " if softfail\n", + " #we cannot deactivate the constraint, because it needs to be repropagated\n", + " return\n", + " end\n", + " end\n", + "\n", + " #the constraint is satisfied and can be deactivated\n", + " HerbConstraints.deactivate!(solver, constraint)\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation.\n", + "\n", + "Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation.\n", + "\n", + "In our case, we want to repropagate if either:\n", + "* a tree manipulation occured at the `constraint.path`\n", + "* a tree manipulation occured at the child of the `constraint.path`" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "HerbConstraints.shouldschedule" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "\n", + "\"\"\"\n", + "Gets called whenever an tree manipulation occurs at the given `path`.\n", + "Returns true iff the `constraint` should be rescheduled for propagation.\n", + "\"\"\"\n", + "function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool\n", + " return (path == constraint.path) || (path == constraint.path[1:end-1])\n", + "end\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "1\n", + "x\n", + "-1\n", + "-x\n", + "1 * 1\n", + "1x\n", + "x * x\n", + "x * 1\n", + "x + 1\n", + "x + x\n", + "1 + x\n", + "1 + 1\n", + "1 * -1\n", + "1 * -x\n", + "x * -x\n", + "x * -1\n", + "x + -1\n", + "x + -x\n", + "1 + -x\n", + "-(1 * 1)\n", + "1 + -1\n", + "-(1x)\n", + "-(x * x)\n", + "-(x * 1)\n", + "-((x + 1))\n", + "-((x + x))\n", + "-1 * 1\n", + "-((1 + x))\n", + "-1 * x\n", + "-((1 + 1))\n", + "-x * x\n", + "-x * 1\n", + "-x + 1\n", + "-x + x\n", + "-1 + x\n", + "-1 + 1\n", + "(1 + 1) * 1\n", + "-(1 * -1)\n", + "(1 + 1) * x\n", + "-(1 * -x)\n", + "(1 + x) * x\n", + "-(x * -x)\n", + "(1 + x) * 1\n", + "-(x * -1)\n", + "(x + x) * 1\n", + "-((x + -1))\n", + "(x + x) * x\n", + "-((x + -x))\n", + "(x + 1) * x\n", + "-((1 + -x))\n", + "(x + 1) * 1\n", + "-((1 + -1))\n", + "x * 1 + 1\n", + "x * 1 + x\n", + "x * x + x\n", + "x * x + 1\n", + "1x + 1\n", + "1x + x\n", + "-(-1 * 1)\n", + "1 * 1 + x\n", + "-(-1 * x)\n", + "1 * 1 + 1\n", + "-(-x * x)\n", + "-(-x * 1)\n", + "-((-x + 1))\n", + "1 * (1 + 1)\n", + "-((-x + x))\n", + "1 * (1 + x)\n", + "-((-1 + x))\n", + "1 * (x + x)\n", + "-((-1 + 1))\n", + "1 * (x + 1)\n", + "x * (x + 1)\n", + "x * (x + x)\n", + "x * (1 + x)\n", + "x * (1 + 1)\n", + "-1 * -1\n", + "x + 1 * 1\n", + "-1 * -x\n", + "x + 1x\n", + "-x * -x\n", + "x + x * x\n", + "-x * -1\n", + "x + x * 1\n", + "-x + -1\n", + "1 + x * 1\n", + "-x + -x\n", + "1 + x * x\n", + "-1 + -x\n", + "1 + 1x\n", + "-1 + -1\n", + "1 + 1 * 1\n", + "-1 * (1 + 1)\n", + "1 * (1 + -1)\n", + "-1 * (1 + x)\n", + "1 * (1 + -x)\n", + "-1 * (x + x)\n", + "1 * (x + -x)\n", + "-1 * (x + 1)\n", + "1 * (x + -1)\n", + "-x * (x + 1)\n", + "x * (x + -1)\n", + "-x * (x + x)\n", + "x * (x + -x)\n", + "-x * (1 + x)\n", + "x * (1 + -x)\n", + "-x * (1 + 1)\n", + "x * (1 + -1)\n", + "-x + 1 * 1\n", + "x + 1 * -1\n", + "-x + 1x\n", + "x + 1 * -x\n", + "-x + x * x\n", + "x + x * -x\n", + "-x + x * 1\n", + "x + x * -1\n", + "-1 + x * 1\n", + "1 + x * -1\n", + "-1 + x * x\n", + "1 + x * -x\n", + "-1 + 1x\n", + "1 + 1 * -x\n", + "-1 + 1 * 1\n", + "1 + 1 * -1\n", + "1 * -(1 * 1)\n", + "(1 + -1) * 1\n", + "1 * -(1x)\n", + "(1 + -1) * x\n", + "1 * -(x * x)\n", + "(1 + -x) * x\n", + "1 * -(x * 1)\n", + "(1 + -x) * 1\n", + "1 * -((x + 1))\n", + "(x + -x) * 1\n", + "1 * -((x + x))\n", + "(x + -x) * x\n", + "1 * -((1 + x))\n", + "(x + -1) * x\n", + "1 * -((1 + 1))\n", + "(x + -1) * 1\n", + "x * -((1 + 1))\n", + "x * -1 + 1\n", + "x * -((1 + x))\n", + "x * -1 + x\n", + "x * -((x + x))\n", + "x * -x + x\n", + "x * -((x + 1))\n", + "x * -x + 1\n", + "x * -(x * 1)\n", + "1 * -x + 1\n", + "x * -(x * x)\n", + "1 * -x + x\n", + "x * -(1x)\n", + "1 * -1 + x\n", + "x * -(1 * 1)\n", + "1 * -1 + 1\n", + "x + -(1 * 1)\n", + "x + -(1x)\n", + "1 * (-1 + 1)\n", + "x + -(x * x)\n", + "1 * (-1 + x)\n", + "x + -(x * 1)\n", + "1 * (-x + x)\n", + "x + -((x + 1))\n", + "1 * (-x + 1)\n", + "x + -((x + x))\n", + "x * (-x + 1)\n", + "x + -((1 + x))\n", + "x * (-x + x)\n", + "x + -((1 + 1))\n", + "x * (-1 + x)\n", + "1 + -((1 + 1))\n", + "x * (-1 + 1)\n", + "1 + -((1 + x))\n", + "x + -1 * 1\n", + "1 + -((x + x))\n", + "x + -1 * x\n", + "1 + -((x + 1))\n", + "x + -x * x\n", + "1 + -(x * 1)\n", + "x + -x * 1\n", + "1 + -(x * x)\n", + "1 + -x * 1\n", + "1 + -(1x)\n", + "1 + -x * x\n", + "1 + -(1 * 1)\n", + "1 + -1 * x\n", + "1 + -1 * 1\n", + "-(1 * 1) * 1\n", + "-(1 * 1) * x\n", + "-((1 + 1) * 1)\n", + "-(1x) * x\n", + "-((1 + 1) * x)\n", + "-(1x) * 1\n", + "-((1 + x) * x)\n", + "-(x * x) * 1\n", + "-((1 + x) * 1)\n", + "-(x * x) * x\n", + "-((x + x) * 1)\n", + "-(x * 1) * x\n", + "-((x + x) * x)\n", + "-(x * 1) * 1\n", + "-((x + 1) * x)\n", + "-((x + 1)) * 1\n", + "-((x + 1) * 1)\n", + "-((x + 1)) * x\n", + "-((x * 1 + 1))\n", + "-((x + x)) * x\n", + "-((x * 1 + x))\n", + "-((x + x)) * 1\n", + "-((x * x + x))\n", + "-((1 + x)) * 1\n", + "-((x * x + 1))\n", + "-((1 + x)) * x\n", + "-((1x + 1))\n", + "-((1 + 1)) * x\n", + "-((1x + x))\n", + "-((1 + 1)) * 1\n", + "-((1 * 1 + x))\n", + "-((1 + 1)) + 1\n", + "-((1 * 1 + 1))\n", + "-((1 + 1)) + x\n", + "-((1 + x)) + x\n", + "-(-1 * -1)\n", + "-((1 + x)) + 1\n", + "-(-1 * -x)\n", + "-((x + x)) + 1\n", + "-(-x * -x)\n", + "-((x + x)) + x\n", + "-(-x * -1)\n", + "-((x + 1)) + x\n", + "-((-x + -1))\n", + "-((x + 1)) + 1\n", + "-((-x + -x))\n", + "-(x * 1) + 1\n", + "-((-1 + -x))\n", + "-(x * 1) + x\n", + "-((-1 + -1))\n", + "-(x * x) + x\n", + "-(x * x) + 1\n", + "(-1 + 1) * 1\n", + "-(1x) + 1\n", + "(-1 + 1) * x\n", + "-(1x) + x\n", + "(-1 + x) * x\n", + "-(1 * 1) + x\n", + "(-1 + x) * 1\n", + "-(1 * 1) + 1\n", + "(-x + x) * 1\n", + "(-x + x) * x\n", + "(1 + 1) * -1\n", + "(-x + 1) * x\n", + "(1 + 1) * -x\n", + "(-x + 1) * 1\n", + "(1 + x) * -x\n", + "-x * 1 + 1\n", + "(1 + x) * -1\n", + "-x * 1 + x\n", + "(x + x) * -1\n", + "-x * x + x\n", + "(x + x) * -x\n", + "-x * x + 1\n", + "(x + 1) * -x\n", + "-1 * x + 1\n", + "(x + 1) * -1\n", + "-1 * x + x\n", + "x * 1 + -1\n", + "-1 * 1 + x\n", + "x * 1 + -x\n", + "-1 * 1 + 1\n", + "x * x + -x\n", + "x * x + -1\n", + "-(1 * (1 + 1))\n", + "1x + -1\n", + "-(1 * (1 + x))\n", + "1x + -x\n", + "-(1 * (x + x))\n", + "1 * 1 + -x\n", + "-(1 * (x + 1))\n", + "1 * 1 + -1\n", + "-(x * (x + 1))\n", + "-(x * (x + x))\n", + "-(x * (1 + x))\n", + "-(x * (1 + 1))\n", + "-((x + 1 * 1))\n", + "-((x + 1x))\n", + "-((x + x * x))\n", + "-((x + x * 1))\n", + "-((1 + x * 1))\n", + "-((1 + x * x))\n", + "-((1 + 1x))\n", + "-((1 + 1 * 1))\n" + ] + } + ], + "source": [ + "clearconstraints!(grammar)\n", + "\n", + "addconstraint!(grammar, ForbidConsecutive(minus))\n", + "addconstraint!(grammar, ForbidConsecutive(plus))\n", + "addconstraint!(grammar, ForbidConsecutive(times))\n", + "\n", + "iter = BFSIterator(grammar, :Int, max_size=6)\n", + "\n", + "for program ∈ iter\n", + " println(rulenode2expr(program, grammar))\n", + "end" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.8.5", + "language": "julia", + "name": "julia-1.8" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/docs/src/tutorials/constraints.md b/docs/src/tutorials/constraints.md new file mode 100644 index 0000000..08d9265 --- /dev/null +++ b/docs/src/tutorials/constraints.md @@ -0,0 +1,738 @@ +# Getting started with HerbConstraints + +When enumerating programs using a grammar, we will encounter many redundant programs. For example, `x`, `-(-x)` and `1 * x` are syntactically different programs, but they have the same semantics. Grammar constraints aim to speed up synthesis by eliminating such redundant programs and thereby reducing the size of the program space. + +### Setup + +For this tutorial, we need to import the following modules of the Herb.jl framework: + +* `HerbCore` for the necessary data strucutes, like `Hole`s and `RuleNode`s +* `HerbGrammar` to define the grammar +* `HerbConstraints` to define the constraints +* `HerbSearch` to execute a constrained enumeration + +We will also redefine the simple arithmetic grammar from the previous tutorial. + + +```julia +using HerbCore, HerbGrammar, HerbConstraints, HerbSearch + +grammar = @cfgrammar begin + Int = 1 + Int = x + Int = - Int + Int = Int + Int + Int = Int * Int +end +``` + + + 1: Int = 1 + 2: Int = x + 3: Int = -Int + 4: Int = Int + Int + 5: Int = Int * Int + + + +### Working with constraints + +To show the effects of constraints, we will first enumerate all programs without constraints (up to a maximum size of 3 AST nodes). + +(To make sure the grammar doesn't have any constraints, we can clear the constraints using `clearconstraints!`. This is not needed at this point, but could come in handy if your REPL holds a reference to a constrained version of the grammar) + + +```julia +clearconstraints!(grammar) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x * 1 + x + 1 + x + x + 1 + x + 1 + 1 + + +Upon inspection, we can already see some redundant programs, like `1 * 1` and `-(-1)`. To eliminate these redundant programs, we will set up some constraints that prevent these patterns from appearing. Then we will create another iteratator to enumerate all programs that satisfy the defined grammar constraints. + +To make the forbidden pattern constraint general, we will use a special type of rulenode: `VarNode(:A)`. This node matches with any subtree and can be used to forbid multiple forbidden patterns using a single constraint. For example, `Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])])))` forbids: + +* `-(-1)` +* `-(-X)` +* `-(-(1 + 1))` +* `1 + -(-(1 + 1))` +* etc + + +```julia +one = 1 +x = 2 +minus = 3 +plus = 4 +times = 5 + +addconstraint!(grammar, Forbidden(RuleNode(times, [RuleNode(one), VarNode(:A)]))) # forbid 1*A +addconstraint!(grammar, Forbidden(RuleNode(minus, [RuleNode(minus, [VarNode(:A)])]))) # forbid -(-A) + +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + 1 + x + -1 + -x + x * 1 + x * x + x + x + x + 1 + 1 + 1 + 1 + x + + +### Forbidden Constraint + +The `Forbidden` constraint forbids any subtree in the program that matches a given template tree. Such a template tree can consist of 3 node types: +* `RuleNode(1)`. Matches exactly the given rule. +* `DomainRuleNode(BitVector((0, 0, 0, 1, 1)), children)`. Matches any rule in its bitvector domain. In this case, rule 4 and 5. +* `VarNode(:A)`. Matches any subtree. If another VarNode of the same name is used, the subtrees have to be the same. + + +```julia +#this constraint forbids A+A and A*A +constraint = Forbidden(DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:A), VarNode(:A)])) + +# Without this constraint, we encounter 154 programs +clearconstraints!(grammar) +iter = BFSIterator(grammar, :Int, max_size=5) +println(length(iter)) + +# With this constraint, we encounter 106 programs +clearconstraints!(grammar) +addconstraint!(grammar, constraint) +iter = BFSIterator(grammar, :Int, max_size=5) +println(length(iter)) + +``` + + 154 + 106 + + +### Contains Constraint + +The `Contains` constraint enforces that a given rule appears in the program tree at least once. + +In the arithmetic grammar, this constraint can be used to ensure the input symbol `x` is used in the program. Otherwise, the program is just a constant. + + +```julia +clearconstraints!(grammar) +addconstraint!(grammar, Contains(2)) #rule 2 should be used in the program +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + x + -x + -(-x) + 1x + x * x + x * 1 + x + 1 + x + x + 1 + x + + +### Contains Subtree Constraint + +Similarly to the `Contains` constraint, the `ContainsSubtree` can be used to enforce a given template tree is used in the program at least once. + + +```julia +clearconstraints!(grammar) +addconstraint!(grammar, ContainsSubtree(RuleNode(times, [RuleNode(x), RuleNode(x)]))) #x*x should be in the program tree +iter = BFSIterator(grammar, :Int, max_size=4) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + x * x + -(x * x) + + +### Ordered Constraint + +The `Ordered` constraint enforces an `<=` ordering on a provided list of variables. With this constraint, we can break symmetries based on commutativity. For example, `1+x` and `x+1` are semantically equivalent. By imposing an `Ordered` constraint, we can eliminate one of the symmetric variants. + +To define an `Ordered` constraint, we need to provide it with a template tree including at least two differently named `VarNode`s. And additionally, an ordering of the variables in the tree. + +In the upcoming example we will set up a template tree representing `a+b` and `a*b`. +Then, we will impose an ordering `a<=b` on all the subtrees that match the template. + +The result is that our iterator skips the redundant programs `x+1` and `x*1`, as they are already represented by `1+x` and `1*x`. + + + +```julia +clearconstraints!(grammar) + +template_tree = DomainRuleNode(BitVector((0, 0, 0, 1, 1)), [VarNode(:a), VarNode(:b)]) +order = [:a, :b] + +addconstraint!(grammar, Ordered(template_tree, order)) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x + x + 1 + x + 1 + 1 + + +### Forbidden Sequence Constraint + +The `ForbiddenSequence` constraints forbids a given sequence of rule nodes in a vertical path of the tree. + +An optional second argument, `ignore_if`, can be used to overrule the constraint in case any of the rules on the `ignore_if` list are present. + +Below we will define the constraint `ForbiddenSequence([plus, one], ignore_if=[times])`. It forbids an `1` after an `+` unless an `*` disrupts the sequence. + +This constraint will **forbid** the following programs: + +* x + 1 +* x + -1 +* x + -(-1) +* x + (x + 1) +* x * (x + 1) + +But it will **allow** the following program (as * disrupts the sequence): + +* x + (x * 1) + + + +```julia +constraint = ForbiddenSequence([plus, one], ignore_if=[times]) +addconstraint!(grammar, constraint) +iter = BFSIterator(grammar, :Int, max_size=3) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end + +``` + + 1 + x + -1 + -x + 1 * 1 + -(-1) + 1x + -(-x) + x * x + x + x + + +### Custom Constraint + +To implement a new constraint, we need to define two structs: an `AbstractGrammarConstraint` and an `AbstractLocalConstraint`. + +A **grammar constraint** is a high-level constraint on the grammar itself and does not refer to a location in the tree. For example, the `Forbidden` constraint is responsible for forbidding a template tree everywhere in the tree. To divide the work of constraint propagation, the grammar constraint will post several local constraints that are responsible for propagating the constraint at each particular location. + +A **local constraint** is a rooted version of a grammar constraint. Each local constraint holds a `path` field that points to a location in the tree where this constraint applies. + +Suppose we want to implement a simple custom constraint that forbids a given `rule` twice in a row. + +Each time a new AST node is added to a tree, the `on_new_node` function is called to notify that an unseen node has been added to the tree at path `path`. Our grammar constraint has the opportunity to react to this event. In this example, we will post a new local constraint at the new location using the `post!` function. + +(Don't worry about the `HerbConstraints.` prefixes. Normally, constraints are defined within the HerbConstraints repository, so there is no need to specify the namespace) + + +```julia +""" +Forbids the consecutive application of the specified rule. +For example, CustomConstraint(4) forbids the tree 4(1, 4(1, 1)) as it applies rule 4 twice in a row. +""" +struct ForbidConsecutive <: AbstractGrammarConstraint + rule::Int +end + +""" +Post a local constraint on each new node that appears in the tree +""" +function HerbConstraints.on_new_node(solver::Solver, constraint::ForbidConsecutive, path::Vector{Int}) + HerbConstraints.post!(solver, LocalForbidConsecutive(path, constraint.rule)) +end +``` + + + HerbConstraints.on_new_node + + +Next, we will define our local constraint. This constraint is responsible for propagating the constraint at a given path. The `propagate!` method can use several solver functions to manipulate the tree. The following **tree manipulations** can be used to remove rules from the domain of a hole at a given path: + +* `remove!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove!(solver::Solver, path::Vector{Int}, rules::Vector{Int})` +* `remove_all_but!(solver::Solver, path::Vector{Int}, new_domain::BitVector)` +* `remove_above!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `remove_below!(solver::Solver, path::Vector{Int}, rule_index::Int)` +* `make_equal!(solver::Solver, node1::AbstractRuleNode, node2::AbstractRuleNode)` (a high level manipulation that requires `node1` and `node2` to be in the tree) + +In addition to tree manipulations, the following solver functions can be used to communicate new information to the solver: + +* `set_infeasible!(solver)`. If a propagator detects an inconsistency, the solver should be notified and cancel any other scheduled propagators. +* `deactivate!(solver, constraint)`. If a constraint is satisfied, it should deactivate itself to prevent re-propagation. +* `post!(solver, constraint)` A constraint is allowed to post new local constraints. This might be helpful if a constraint can be reduced to a smaller constraint. + +The solver manages all constraints and the program tree we propagate on. Applying tree manipulations might cause a chain reaction of other propagators, so the shape of the tree might update as we propagate. The get the latest information about the tree, we should use the following getter functions: + +* `get_tree(solver)` returns the root node of the current (partial) program tree +* `isfeasible(solver)` returns the a flag indicating if the solver is not violating any (other) constriants. +* `get_path(solver, node)` returns the path at which the node is located. +* `get_node_at_location(solver, path)` returns the node that is currently at the given path (be aware that this instance might be replaced by manipulations). +* `get_hole_at_location(solver, path)` same as get node at location, but asserts the node is a hole (domain size >= 2). + +To get information about a node, we can use the following getter functions: + +* `isfilled(node)`. Returns true if the node is a `RuleNode` or has domain size 1. +* `get_rule(node)`. Get the rule of a filled node. +* `get_children(node)`. Get the children of a node. +* `node.domain[rule]`. Given the node is a hole, return true if `rule` is in the domain. + +Finally, another useful function for propagators is `pattern_match(node1, node2)`. This function compares two trees and returns a `PatternMatchResult` that indicates if the nodes match, and potentially indicate which holes need to be filled to complete the match. + + + +```julia +""" +Forbids the consecutive application of the specified rule at path `path`. +""" +struct LocalForbidConsecutive <: AbstractLocalConstraint + path::Vector{Int} + rule::Int +end + +""" +Propagates the constraints by preventing a consecutive application of the specified rule. +""" +function HerbConstraints.propagate!(solver::Solver, constraint::LocalForbidConsecutive) + node = get_node_at_location(solver, constraint.path) + if isfilled(node) + if get_rule(node) == constraint.rule + #the specified rule is used, make sure the rule will not be used by any of the children + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the specified rule was used twice in a row, which is violating the constraint + set_infeasible!(solver) + return + end + elseif child.domain[constraint.rule] + child_path = push!(copy(constraint.path), i) + remove!(solver, child_path, constraint.rule) # remove the rule from the domain of the child + end + end + end + elseif node.domain[constraint.rule] + #our node is a hole with the specified rule in its domain + #we will now check if any of the children already uses the specified rule + softfail = false + for (i, child) ∈ enumerate(get_children(node)) + if isfilled(child) + if get_rule(child) == constraint.rule + #the child holds the specified rule, so the parent cannot have this rule + remove!(solver, constraint.path, constraint.rule) + end + elseif child.domain[constraint.rule] + #the child is a hole and contains the specified node. since there are 2 holes involved, we will softfail. + softfail = true + end + end + if softfail + #we cannot deactivate the constraint, because it needs to be repropagated + return + end + end + + #the constraint is satisfied and can be deactivated + HerbConstraints.deactivate!(solver, constraint) +end +``` + + + HerbConstraints.propagate! + + +Posting a local constraint will trigger the initial propagation. To re-propagate, the constraint needs to be rescheduled for propagation. + +Whenever the tree is manipulated, we will make a `shouldschedule` check to see if our constraint needs to be rescheduled for propagation based on the manipulation. + +In our case, we want to repropagate if either: +* a tree manipulation occured at the `constraint.path` +* a tree manipulation occured at the child of the `constraint.path` + + +```julia + +""" +Gets called whenever an tree manipulation occurs at the given `path`. +Returns true iff the `constraint` should be rescheduled for propagation. +""" +function HerbConstraints.shouldschedule(solver::Solver, constraint::LocalForbidConsecutive, path::Vector{Int})::Bool + return (path == constraint.path) || (path == constraint.path[1:end-1]) +end + +``` + + + HerbConstraints.shouldschedule + + +With all the components implemented, we can do a constrained enumeration using our new `ForbidConsecutive` constraint. + + +```julia +clearconstraints!(grammar) + +addconstraint!(grammar, ForbidConsecutive(minus)) +addconstraint!(grammar, ForbidConsecutive(plus)) +addconstraint!(grammar, ForbidConsecutive(times)) + +iter = BFSIterator(grammar, :Int, max_size=6) + +for program ∈ iter + println(rulenode2expr(program, grammar)) +end +``` + + 1 + x + -1 + -x + 1 * 1 + 1x + x * x + x * 1 + x + 1 + x + x + 1 + x + 1 + 1 + 1 * -1 + 1 * -x + x * -x + x * -1 + x + -1 + x + -x + 1 + -x + -(1 * 1) + 1 + -1 + -(1x) + -(x * x) + -(x * 1) + -((x + 1)) + -((x + x)) + -1 * 1 + -((1 + x)) + -1 * x + -((1 + 1)) + -x * x + -x * 1 + -x + 1 + -x + x + -1 + x + -1 + 1 + (1 + 1) * 1 + -(1 * -1) + (1 + 1) * x + -(1 * -x) + (1 + x) * x + -(x * -x) + (1 + x) * 1 + -(x * -1) + (x + x) * 1 + -((x + -1)) + (x + x) * x + -((x + -x)) + (x + 1) * x + -((1 + -x)) + (x + 1) * 1 + -((1 + -1)) + x * 1 + 1 + x * 1 + x + x * x + x + x * x + 1 + 1x + 1 + 1x + x + -(-1 * 1) + 1 * 1 + x + -(-1 * x) + 1 * 1 + 1 + -(-x * x) + -(-x * 1) + -((-x + 1)) + 1 * (1 + 1) + -((-x + x)) + 1 * (1 + x) + -((-1 + x)) + 1 * (x + x) + -((-1 + 1)) + 1 * (x + 1) + x * (x + 1) + x * (x + x) + x * (1 + x) + x * (1 + 1) + -1 * -1 + x + 1 * 1 + -1 * -x + x + 1x + -x * -x + x + x * x + -x * -1 + x + x * 1 + -x + -1 + 1 + x * 1 + -x + -x + 1 + x * x + -1 + -x + 1 + 1x + -1 + -1 + 1 + 1 * 1 + -1 * (1 + 1) + 1 * (1 + -1) + -1 * (1 + x) + 1 * (1 + -x) + -1 * (x + x) + 1 * (x + -x) + -1 * (x + 1) + 1 * (x + -1) + -x * (x + 1) + x * (x + -1) + -x * (x + x) + x * (x + -x) + -x * (1 + x) + x * (1 + -x) + -x * (1 + 1) + x * (1 + -1) + -x + 1 * 1 + x + 1 * -1 + -x + 1x + x + 1 * -x + -x + x * x + x + x * -x + -x + x * 1 + x + x * -1 + -1 + x * 1 + 1 + x * -1 + -1 + x * x + 1 + x * -x + -1 + 1x + 1 + 1 * -x + -1 + 1 * 1 + 1 + 1 * -1 + 1 * -(1 * 1) + (1 + -1) * 1 + 1 * -(1x) + (1 + -1) * x + 1 * -(x * x) + (1 + -x) * x + 1 * -(x * 1) + (1 + -x) * 1 + 1 * -((x + 1)) + (x + -x) * 1 + 1 * -((x + x)) + (x + -x) * x + 1 * -((1 + x)) + (x + -1) * x + 1 * -((1 + 1)) + (x + -1) * 1 + x * -((1 + 1)) + x * -1 + 1 + x * -((1 + x)) + x * -1 + x + x * -((x + x)) + x * -x + x + x * -((x + 1)) + x * -x + 1 + x * -(x * 1) + 1 * -x + 1 + x * -(x * x) + 1 * -x + x + x * -(1x) + 1 * -1 + x + x * -(1 * 1) + 1 * -1 + 1 + x + -(1 * 1) + x + -(1x) + 1 * (-1 + 1) + x + -(x * x) + 1 * (-1 + x) + x + -(x * 1) + 1 * (-x + x) + x + -((x + 1)) + 1 * (-x + 1) + x + -((x + x)) + x * (-x + 1) + x + -((1 + x)) + x * (-x + x) + x + -((1 + 1)) + x * (-1 + x) + 1 + -((1 + 1)) + x * (-1 + 1) + 1 + -((1 + x)) + x + -1 * 1 + 1 + -((x + x)) + x + -1 * x + 1 + -((x + 1)) + x + -x * x + 1 + -(x * 1) + x + -x * 1 + 1 + -(x * x) + 1 + -x * 1 + 1 + -(1x) + 1 + -x * x + 1 + -(1 * 1) + 1 + -1 * x + 1 + -1 * 1 + -(1 * 1) * 1 + -(1 * 1) * x + -((1 + 1) * 1) + -(1x) * x + -((1 + 1) * x) + -(1x) * 1 + -((1 + x) * x) + -(x * x) * 1 + -((1 + x) * 1) + -(x * x) * x + -((x + x) * 1) + -(x * 1) * x + -((x + x) * x) + -(x * 1) * 1 + -((x + 1) * x) + -((x + 1)) * 1 + -((x + 1) * 1) + -((x + 1)) * x + -((x * 1 + 1)) + -((x + x)) * x + -((x * 1 + x)) + -((x + x)) * 1 + -((x * x + x)) + -((1 + x)) * 1 + -((x * x + 1)) + -((1 + x)) * x + -((1x + 1)) + -((1 + 1)) * x + -((1x + x)) + -((1 + 1)) * 1 + -((1 * 1 + x)) + -((1 + 1)) + 1 + -((1 * 1 + 1)) + -((1 + 1)) + x + -((1 + x)) + x + -(-1 * -1) + -((1 + x)) + 1 + -(-1 * -x) + -((x + x)) + 1 + -(-x * -x) + -((x + x)) + x + -(-x * -1) + -((x + 1)) + x + -((-x + -1)) + -((x + 1)) + 1 + -((-x + -x)) + -(x * 1) + 1 + -((-1 + -x)) + -(x * 1) + x + -((-1 + -1)) + -(x * x) + x + -(x * x) + 1 + (-1 + 1) * 1 + -(1x) + 1 + (-1 + 1) * x + -(1x) + x + (-1 + x) * x + -(1 * 1) + x + (-1 + x) * 1 + -(1 * 1) + 1 + (-x + x) * 1 + (-x + x) * x + (1 + 1) * -1 + (-x + 1) * x + (1 + 1) * -x + (-x + 1) * 1 + (1 + x) * -x + -x * 1 + 1 + (1 + x) * -1 + -x * 1 + x + (x + x) * -1 + -x * x + x + (x + x) * -x + -x * x + 1 + (x + 1) * -x + -1 * x + 1 + (x + 1) * -1 + -1 * x + x + x * 1 + -1 + -1 * 1 + x + x * 1 + -x + -1 * 1 + 1 + x * x + -x + x * x + -1 + -(1 * (1 + 1)) + 1x + -1 + -(1 * (1 + x)) + 1x + -x + -(1 * (x + x)) + 1 * 1 + -x + -(1 * (x + 1)) + 1 * 1 + -1 + -(x * (x + 1)) + -(x * (x + x)) + -(x * (1 + x)) + -(x * (1 + 1)) + -((x + 1 * 1)) + -((x + 1x)) + -((x + x * x)) + -((x + x * 1)) + -((1 + x * 1)) + -((1 + x * x)) + -((1 + 1x)) + -((1 + 1 * 1)) + From e0d4c5fbea571989cee04312c128d200c260257e Mon Sep 17 00:00:00 2001 From: Sebastijan Date: Wed, 3 Jul 2024 20:02:52 +0200 Subject: [PATCH 43/75] added tutorial about interpreters --- .../tutorials/working_with_interpreters.jl | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 docs/src/tutorials/working_with_interpreters.jl diff --git a/docs/src/tutorials/working_with_interpreters.jl b/docs/src/tutorials/working_with_interpreters.jl new file mode 100644 index 0000000..815eef2 --- /dev/null +++ b/docs/src/tutorials/working_with_interpreters.jl @@ -0,0 +1,164 @@ +### A Pluto.jl notebook ### +# v0.19.43 + +using Markdown +using InteractiveUtils + +# ╔═╡ e0a7076c-9345-40ef-a26e-99e8bad31463 +using HerbGrammar +using HerbInterpret + +# ╔═╡ 55719688-3940-11ef-1f29-f51dea064ff3 +md"# Using the Julia interpreter + +To know how good a candidate program is, program synthesisers execute them. The easiest way to execute a program is to rely on Julia itself. To leverage the Julia interpreter, you only have to ensure that your programs are valid Julia expressions. + +For example, assume the following grammar. +" + +# ╔═╡ 39eaa982-ba88-49b9-ad52-076a169d0439 +g = @cfgrammar begin + Number = |(1:2) + Number = x + Number = Number + Number + Number = Number * Number + end + +# ╔═╡ 2478d5a4-a11e-42aa-87dd-d97a3fa5d378 +md" +Let's construct a program `x+3`, which would correspond to the following `RuleNode` representation +" + +# ╔═╡ 1e15898e-568c-4211-ba00-27de61806aeb +myprog = RuleNode(4,[RuleNode(3),RuleNode(1)]) + +# ╔═╡ d43a2094-b215-4d6c-b6d8-8d32fe8898d6 +md" +To run this program, we have to convert it into a Julia expression, which we can do in the following way: +" + +# ╔═╡ a77621ce-1749-4e16-b3dc-f5312cc5ee73 +myprog_julia = rulenode2expr(myprog, g) + +# ╔═╡ 48997ff5-a492-4bfd-8c64-d935433228c0 +md" +Now we have a valid Julia expression, but we are still missing one key ingredient: we have to inform the interpreter about the special symbols. In our case, these are `:x` and `:+`. To do so, we need to create a symbol table, which is nothing more than a dictionary mapping symbols to their values: +" + +# ╔═╡ 3e4f7ed5-bfba-4aa0-9f43-2978a9082054 +symboltable = Dict{Symbol,Any}(:x => 2, :+ => +) + +# ╔═╡ 2a6f6456-2937-4520-8427-8d7595076ec5 +md" +Now we can execute our program through the defaul interpreter available in `HerbInterpret`: +" + +# ╔═╡ c0dcdbc8-2355-4f4d-85c2-ec37bfeab226 +interpret(symboltable, myprog_julia) + +# ╔═╡ b3ac6903-8513-40cc-91ee-8ae7beb08d1d +md"And that's it!" + +# ╔═╡ f765b471-74f8-4e17-8e1e-e556d88eb84b +md"# Defining a custom interpreter + +A disadvantage of the default Julia interpreter is that it needs to traverse abstract syntax tree twice -- once to convert it into a Julia expression, and the second time to execute that expression. Program execution is regularly the most consuming part of the entire pipeline and, by eliminating one of these steps, we can cut the runtime in half. + +We can define an interpreter that works directly over `RuleNode`s. +Consider the scenario in which we want to write programs for robot navigation: imagine a 2D world in which the robot can move around and pick up a ball. The programs we could write direct the robot to go up, down, left, and right. For convenience, the programming language also offers conditionals and loops: +" + +# ╔═╡ 1b251d0f-3a77-494f-a359-d8dc33ad5d44 +grammar_robots = @csgrammar begin + Start = Sequence #1 + + Sequence = Operation #2 + Sequence = (Operation; Sequence) #3 + Operation = Transformation #4 + Operation = ControlStatement #5 + + Transformation = moveRight() | moveDown() | moveLeft() | moveUp() | drop() | grab() #6 + ControlStatement = IF(Condition, Sequence, Sequence) #12 + ControlStatement = WHILE(Condition, Sequence) #13 + + Condition = atTop() | atBottom() | atLeft() | atRight() | notAtTop() | notAtBottom() | notAtLeft() | notAtRight() #14 +end + +# ╔═╡ aff77be9-365f-4672-bbd4-07f23528e32e + md" + This grammar specifies a simple sequential program with instructions for the robot. A couple of example programs: + - `moveRight(); moveLeft(); drop()` + - WHILE(notAtTop(), moveUp()) + +The idea behind this programming language is that the program specifies a set of transformations over a state of the robot world. Thus, a program can only be executed over a particular state. In this case, the state represents the size of the 2D world, the current position of a robot, the current position of a ball, and whether the robot is currently holding a ball. The execution of a particular instruction acts as a state transformation: each instruction takes a state as an input, transforms it, and passes it to the subsequent instruction. For example, execution of the program `moveRight(); moveLeft(); drop()` would proceed as: + 1. take an input state, + 2. pass it to the `moveRight()` instruction, + 3. pass the output of `moveRight()` to `moveLeft()` instructions, + 4. pass the output of `moveLeft()` to `drop()`, + 5. return the output of `drop()`. + + + The following is only one possible way to implement a custom interpreter, but it demonstrates a general template that can always be followed. + + We want to implement the following function, which would take in a program in the form of a `RuleNode`, a grammar, and a starting state, and return the state obtained after executing the program: + + As `RuleNode`s only store indices + " + +# ╔═╡ c99860ae-644d-4b72-b176-413095cd9b2c + + +# ╔═╡ 4a733a80-e98b-4f87-adf4-f72a16afd0c1 + + +# ╔═╡ b55fa52d-75cc-4311-8387-1e6c2682282f + + +# ╔═╡ 204e7a43-ebc7-4caf-af46-7502be14a314 + + +# ╔═╡ bd2b8c2d-c9a4-40b5-afcd-214daad9eccd + + +# ╔═╡ f7202e23-56e8-4611-9cde-633156e7e4da + + +# ╔═╡ 9db3b242-a14b-4932-80cd-75c54a21946b + + +# ╔═╡ ceb78d01-8e80-4356-95ad-bac889bc3fc1 + + +# ╔═╡ 1f700607-3cdf-43bf-91f2-72de3c9abc85 +md" +The remaining functions follow a similar idea. (You can see the full implementation of this interpreter at [this link]())." + +# ╔═╡ 5f56cdd8-7ede-44e9-b322-36ac7ebb537b + + +# ╔═╡ Cell order: +# ╠═e0a7076c-9345-40ef-a26e-99e8bad31463 +# ╠═55719688-3940-11ef-1f29-f51dea064ff3 +# ╠═39eaa982-ba88-49b9-ad52-076a169d0439 +# ╟─2478d5a4-a11e-42aa-87dd-d97a3fa5d378 +# ╠═1e15898e-568c-4211-ba00-27de61806aeb +# ╟─d43a2094-b215-4d6c-b6d8-8d32fe8898d6 +# ╠═a77621ce-1749-4e16-b3dc-f5312cc5ee73 +# ╠═48997ff5-a492-4bfd-8c64-d935433228c0 +# ╠═3e4f7ed5-bfba-4aa0-9f43-2978a9082054 +# ╠═2a6f6456-2937-4520-8427-8d7595076ec5 +# ╠═c0dcdbc8-2355-4f4d-85c2-ec37bfeab226 +# ╠═b3ac6903-8513-40cc-91ee-8ae7beb08d1d +# ╠═f765b471-74f8-4e17-8e1e-e556d88eb84b +# ╠═1b251d0f-3a77-494f-a359-d8dc33ad5d44 +# ╠═aff77be9-365f-4672-bbd4-07f23528e32e +# ╠═c99860ae-644d-4b72-b176-413095cd9b2c +# ╠═4a733a80-e98b-4f87-adf4-f72a16afd0c1 +# ╠═b55fa52d-75cc-4311-8387-1e6c2682282f +# ╠═204e7a43-ebc7-4caf-af46-7502be14a314 +# ╠═bd2b8c2d-c9a4-40b5-afcd-214daad9eccd +# ╠═f7202e23-56e8-4611-9cde-633156e7e4da +# ╠═9db3b242-a14b-4932-80cd-75c54a21946b +# ╠═ceb78d01-8e80-4356-95ad-bac889bc3fc1 +# ╠═1f700607-3cdf-43bf-91f2-72de3c9abc85 +# ╠═5f56cdd8-7ede-44e9-b322-36ac7ebb537b From 6684255e58631c5479d5cc43e00e5f9daf40fa06 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 4 Jul 2024 09:18:12 +0200 Subject: [PATCH 44/75] Simplify fizzbuzz function --- docs/src/tutorials/syntax-trees.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/syntax-trees.ipynb index 1329ace..13b3931 100644 --- a/docs/src/tutorials/syntax-trees.ipynb +++ b/docs/src/tutorials/syntax-trees.ipynb @@ -159,7 +159,7 @@ "id": "da78a5d9-038e-4d97-ab1a-32efc830c1e8", "metadata": {}, "source": [ - "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.e`." + "We can confirm that our AST is correct by displaying it in a more human-readable way, using `HerbGrammar.rulenode2expr` and by testing it on a few input examples using `HerbInterpret.execute_on_input`." ] }, { From 1ff18d409cdc1d2f5a32f4e943a425f54cb2705a Mon Sep 17 00:00:00 2001 From: LoLo5689 Date: Thu, 4 Jul 2024 09:26:27 +0200 Subject: [PATCH 45/75] Top Down Iterator MarkDown --- docs/make.jl | 1 + docs/src/tutorials/TopDown.md | 114 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100644 docs/src/tutorials/TopDown.md diff --git a/docs/make.jl b/docs/make.jl index 46efd2a..bdb6e05 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -24,6 +24,7 @@ makedocs( "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md" + "Top Down Iterator" => "tutorials/TopDown" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", diff --git a/docs/src/tutorials/TopDown.md b/docs/src/tutorials/TopDown.md new file mode 100644 index 0000000..ed3e84c --- /dev/null +++ b/docs/src/tutorials/TopDown.md @@ -0,0 +1,114 @@ +# Top Down Iterator + +## Base Iterator + +This function describes the iteration for a Top Down Iterator. A priority queue is created to determine the order. The solver is checked for the constraints, if it violates the constraints, it is considered infeasible, and if it is feasible, it is added to the queue. This function then returns the next complete tree, that is a tree without holes. +This function creates a priority queue, and is therefore called once during the initialisation. + +``` julia +function Base.iterate(iter::TopDownIterator) + # Priority queue with `SolverState`s (for variable shaped trees) and `UniformIterator`s (for fixed shaped trees) + pq :: PriorityQueue{Union{SolverState, UniformIterator}, Union{Real, Tuple{Vararg{Real}}}} = PriorityQueue() + + solver = iter.solver + + if isfeasible(solver) + enqueue!(pq, get_state(solver), priority_function(iter, get_grammar(solver), get_tree(solver), 0, false)) + end + return _find_next_complete_tree(iter.solver, pq, iter) +end +``` + +## Base Iterator With a Given Priority Queue + +This function describes the iteration for a Top Down Iterator, and a priority queue is given as an argument. This function then returns the next complete tree, that is a tree without holes. +After a priority queue is created, this is the function that will be called. + +``` julia +function Base.iterate(iter::TopDownIterator, pq::DataStructures.PriorityQueue) + return _find_next_complete_tree(iter.solver, pq, iter) +end +``` + +# Find Next Complete Tree / Program + +This function pops an element from the priority queue whilst it is not empty, and then checks what kind of iterator it is. + +``` julia +function _find_next_complete_tree( + solver::Solver, + pq::PriorityQueue, + iter::TopDownIterator +) + while length(pq) ≠ 0 + (item, priority_value) = dequeue_pair!(pq) + +``` + +If it is a Uniform Iterator, that is an interator where all the holes have the same shape, then it iterates over the solutions. + +``` julia + + if item isa UniformIterator + #the item is a fixed shaped solver, we should get the next solution and re-enqueue it with a new priority value + uniform_iterator = item + solution = next_solution!(uniform_iterator) + if !isnothing(solution) + enqueue!(pq, uniform_iterator, priority_function(iter, get_grammar(solver), solution, priority_value, true)) + return (solution, pq) + end + +``` +If it is not a Uniform Iterator, we find a hole to branch on. If the holes are all uniform, a Uniform Iterator is created, and is enqueued. If iterating on the holes would exceed a maximum depth, nothing new is enqueued. Lastly, if the holes aren't the same shape, we branch / partition on the holes, to create new partial domains to enqueue. + +``` julia + elseif item isa SolverState + #the item is a solver state, we should find a variable shaped hole to branch on + state = item + load_state!(solver, state) + + hole_res = hole_heuristic(iter, get_tree(solver), get_max_depth(solver)) + if hole_res ≡ already_complete + uniform_solver = UniformSolver(get_grammar(solver), get_tree(solver), with_statistics=solver.statistics) + uniform_iterator = UniformIterator(uniform_solver, iter) + solution = next_solution!(uniform_iterator) + if !isnothing(solution) + enqueue!(pq, uniform_iterator, priority_function(iter, get_grammar(solver), solution, priority_value, true)) + return (solution, pq) + end + elseif hole_res ≡ limit_reached + # The maximum depth is reached + continue + elseif hole_res isa HoleReference + # Variable Shaped Hole was found + (; hole, path) = hole_res + + partitioned_domains = partition(hole, get_grammar(solver)) + number_of_domains = length(partitioned_domains) + for (i, domain) ∈ enumerate(partitioned_domains) + if i < number_of_domains + state = save_state!(solver) + end + @assert isfeasible(solver) "Attempting to expand an infeasible tree: $(get_tree(solver))" + remove_all_but!(solver, path, domain) + if isfeasible(solver) + enqueue!(pq, get_state(solver), priority_function(iter, get_grammar(solver), get_tree(solver), priority_value, false)) + end + if i < number_of_domains + load_state!(solver, state) + end + end + end + + +``` +Otherwise, throw an exception, because we came across an unexpected iterator type. + +``` julia + else + throw("BadArgument: PriorityQueue contains an item of unexpected type '$(typeof(item))'") + end + end + return nothing +end +``` \ No newline at end of file From 28b6039824d3749272753ac54918712d4cf21689 Mon Sep 17 00:00:00 2001 From: Pamela Wochner Date: Thu, 4 Jul 2024 09:32:30 +0200 Subject: [PATCH 46/75] Return String in grammar instead of Dict. --- docs/src/tutorials/syntax-trees.ipynb | 123 ++++++++++++-------------- 1 file changed, 56 insertions(+), 67 deletions(-) diff --git a/docs/src/tutorials/syntax-trees.ipynb b/docs/src/tutorials/syntax-trees.ipynb index 13b3931..7220eaf 100644 --- a/docs/src/tutorials/syntax-trees.ipynb +++ b/docs/src/tutorials/syntax-trees.ipynb @@ -54,7 +54,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 16, "id": "69d543a7-3067-4376-b1bb-1a4f9217b786", "metadata": {}, "outputs": [ @@ -76,9 +76,8 @@ "13: Number = Number * Number\n" ] }, - "execution_count": 1, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -135,7 +134,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 17, "id": "9c61d359-1f38-410e-b654-5d36bc9a1d4e", "metadata": {}, "outputs": [ @@ -145,9 +144,8 @@ "13{6,12{11,4}}" ] }, - "execution_count": 2, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -164,7 +162,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 18, "id": "7c933e57-03f8-41ac-a464-70f95b9505c0", "metadata": {}, "outputs": [ @@ -183,7 +181,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 19, "id": "32fbb714-25aa-40d5-9c0b-c20ba8999d0c", "metadata": {}, "outputs": [ @@ -193,9 +191,8 @@ "65" ] }, - "execution_count": 4, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -216,7 +213,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 20, "id": "1f41bec4-d081-48bd-9fee-c30021d4a882", "metadata": {}, "outputs": [ @@ -226,23 +223,22 @@ "fizzbuzz (generic function with 1 method)" ] }, - "execution_count": 5, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ "function fizzbuzz(x)\n", " if x % 5 == 0 && x % 3 == 0\n", - " return Dict(:output1 => \"FizzBuzz\")\n", + " return \"FizzBuzz\"\n", " else\n", " if x % 3 == 0\n", - " return Dict(:output1 => \"Fizz\")\n", + " return \"Fizz\"\n", " else\n", " if x % 5 == 0\n", - " return Dict(:output1 => \"Buzz\")\n", + " return \"Buzz\"\n", " else\n", - " return Dict(:output1 => string(x))\n", + " return string(x)\n", " end\n", " end\n", " end\n", @@ -261,7 +257,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 21, "id": "7b0ea30c-e415-4d7e-ba2f-a1590cd4c8fe", "metadata": {}, "outputs": [ @@ -276,7 +272,7 @@ "6: String = Buzz\n", "7: String = FizzBuzz\n", "8: String = string(Int)\n", - "9: Return = Dict(:output1 => String)\n", + "9: Return = String\n", "10: Int = Int % Int\n", "11: Bool = Int == Int\n", "12: Int = if Bool\n", @@ -287,9 +283,8 @@ "13: Bool = Bool && Bool\n" ] }, - "execution_count": 6, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -298,7 +293,7 @@ " Int = 0 | 3 | 5\n", " String = \"Fizz\" | \"Buzz\" | \"FizzBuzz\"\n", " String = string(Int)\n", - " Return = Dict(:output1 => String)\n", + " Return = String\n", " Int = Int % Int\n", " Bool = Int == Int\n", " Int = Bool ? Int : Int\n", @@ -389,7 +384,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 22, "id": "9c897ebc-4a79-45be-b349-c376bffa03df", "metadata": {}, "outputs": [ @@ -399,9 +394,8 @@ "12{13{11{10{1,4}2}11{10{1,3}2}}9{7}12{11{10{1,3}2}9{5}12{11{10{1,4}2}9{6}9{8{1}}}}}" ] }, - "execution_count": 7, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -468,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 23, "id": "5a4ba50a-ccda-4e20-9301-051ee839fdf5", "metadata": {}, "outputs": [ @@ -477,15 +471,15 @@ "output_type": "stream", "text": [ "if input1 % 5 == 0 && input1 % 3 == 0\n", - " Dict(:output1 => \"FizzBuzz\")\n", + " \"FizzBuzz\"\n", "else\n", " if input1 % 3 == 0\n", - " Dict(:output1 => \"Fizz\")\n", + " \"Fizz\"\n", " else\n", " if input1 % 5 == 0\n", - " Dict(:output1 => \"Buzz\")\n", + " \"Buzz\"\n", " else\n", - " Dict(:output1 => string(input1))\n", + " string(input1)\n", " end\n", " end\n", "end\n" @@ -499,7 +493,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 24, "id": "9e0b4ec1-eab1-49b9-be2d-c923098d1850", "metadata": {}, "outputs": [ @@ -507,15 +501,14 @@ "data": { "text/plain": [ "4-element Vector{Any}:\n", - " Dict{QuoteNode, String}(:(:output1) => \"Fizz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"22\")" + " \"Fizz\"\n", + " \"Buzz\"\n", + " \"FizzBuzz\"\n", + " \"22\"" ] }, - "execution_count": 9, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -544,7 +537,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 25, "id": "65bfb9cc-bbdc-46e5-8ebf-9c15c6424339", "metadata": {}, "outputs": [ @@ -554,9 +547,8 @@ "6," ] }, - "execution_count": 10, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -576,7 +568,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 26, "id": "742f7ee8-c088-4331-b17c-dc5a2078ca6b", "metadata": {}, "outputs": [ @@ -585,15 +577,15 @@ "output_type": "stream", "text": [ "if input1 % 5 == 0 && input1 % 3 == 0\n", - " Dict(:output1 => \"FizzBuzz\")\n", + " \"FizzBuzz\"\n", "else\n", " if input1 % 3 == 0\n", - " Dict(:output1 => \"Buzz\")\n", + " \"Buzz\"\n", " else\n", " if input1 % 5 == 0\n", - " Dict(:output1 => \"Buzz\")\n", + " \"Buzz\"\n", " else\n", - " Dict(:output1 => string(input1))\n", + " string(input1)\n", " end\n", " end\n", "end\n" @@ -607,7 +599,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 27, "id": "59d13ab2-7415-4463-89bf-e09ff55a74d8", "metadata": {}, "outputs": [ @@ -615,15 +607,14 @@ "data": { "text/plain": [ "4-element Vector{Any}:\n", - " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"22\")" + " \"Buzz\"\n", + " \"Buzz\"\n", + " \"FizzBuzz\"\n", + " \"22\"" ] }, - "execution_count": 12, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -642,7 +633,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 28, "id": "74d04a45-1697-4bcd-a5fd-adaa4ef455ff", "metadata": {}, "outputs": [ @@ -652,9 +643,8 @@ "6," ] }, - "execution_count": 13, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ @@ -676,7 +666,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 29, "id": "6f965fe6-f5b1-4059-911c-362f4c1731d6", "metadata": {}, "outputs": [ @@ -685,15 +675,15 @@ "output_type": "stream", "text": [ "if input1 % 5 == 0 && input1 % 3 == 0\n", - " Dict(:output1 => \"FizzBuzz\")\n", + " \"FizzBuzz\"\n", "else\n", " if input1 % 3 == 0\n", - " Dict(:output1 => \"Buzz\")\n", + " \"Buzz\"\n", " else\n", " if input1 % 5 == 0\n", - " Dict(:output1 => \"Buzz\")\n", + " \"Buzz\"\n", " else\n", - " Dict(:output1 => string(input1))\n", + " string(input1)\n", " end\n", " end\n", "end\n" @@ -707,7 +697,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 30, "id": "41395fec-9053-423b-b0fd-7089bb89c2d0", "metadata": {}, "outputs": [ @@ -715,15 +705,14 @@ "data": { "text/plain": [ "4-element Vector{Any}:\n", - " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"Buzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"FizzBuzz\")\n", - " Dict{QuoteNode, String}(:(:output1) => \"22\")" + " \"Buzz\"\n", + " \"Buzz\"\n", + " \"FizzBuzz\"\n", + " \"22\"" ] }, - "execution_count": 15, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ From 1a8ebbfd11beaa24cc7fd13c3fc35553450d12b7 Mon Sep 17 00:00:00 2001 From: Lorenzo <19820958+LoLo5689@users.noreply.github.com> Date: Thu, 4 Jul 2024 09:35:08 +0200 Subject: [PATCH 47/75] fix --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index bdb6e05..b6c685a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -24,7 +24,7 @@ makedocs( "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", "Advanced Search Procedures" => "tutorials/advanced_search.md" - "Top Down Iterator" => "tutorials/TopDown" + "Top Down Iterator" => "tutorials/TopDown.md" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", From 5447cad3b90b8d545766fa48df2b4d354ef62bee Mon Sep 17 00:00:00 2001 From: Sebastijan Date: Thu, 4 Jul 2024 09:49:45 +0200 Subject: [PATCH 48/75] fixed saving issues --- docs/make.jl | 3 +- .../tutorials/working_with_interpreters.html | 17 + .../tutorials/working_with_interpreters.jl | 633 +++++++++++++++++- 3 files changed, 637 insertions(+), 16 deletions(-) create mode 100644 docs/src/tutorials/working_with_interpreters.html diff --git a/docs/make.jl b/docs/make.jl index 46efd2a..e89727b 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -23,7 +23,8 @@ makedocs( "Tutorials" => [ "A more verbose getting started with Herb.jl" => "tutorials/getting_started_with_herb.md", "Defining Grammars in Herb.jl" => "tutorials/defining_grammars.md", - "Advanced Search Procedures" => "tutorials/advanced_search.md" + "Advanced Search Procedures" => "tutorials/advanced_search.md", + "Working with custom interpreters" => "tutorials/working_with_interpreters.html" ], "Sub-Modules" => [ "HerbCore.jl" => "HerbCore/index.md", diff --git a/docs/src/tutorials/working_with_interpreters.html b/docs/src/tutorials/working_with_interpreters.html new file mode 100644 index 0000000..e4a9971 --- /dev/null +++ b/docs/src/tutorials/working_with_interpreters.html @@ -0,0 +1,17 @@ + + + + + + + +