diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ebe0f793 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,68 @@ +name: CI +on: + pull_request: + branches: + - master + push: + branches: + - master + tags: '*' +jobs: + test: + name: Julia ${{ matrix.version }} - ${{ matrix.os }} - ${{ matrix.arch }} - ${{ github.event_name }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + version: + - '1.6' # Replace this with the minimum Julia version that your package supports. E.g. if your package requires Julia 1.5 or higher, change this to '1.5'. + - '1' # Leave this line unchanged. '1' will automatically expand to the latest stable 1.x release of Julia. + - 'nightly' + os: + - ubuntu-latest + arch: + - x64 + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: ${{ matrix.version }} + arch: ${{ matrix.arch }} + - uses: actions/cache@v1 + env: + cache-name: cache-artifacts + with: + path: ~/.julia/artifacts + key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} + restore-keys: | + ${{ runner.os }}-test-${{ env.cache-name }}- + ${{ runner.os }}-test- + ${{ runner.os }}- + - uses: julia-actions/julia-buildpkg@v1 + - uses: julia-actions/julia-runtest@v1 + - uses: julia-actions/julia-processcoverage@v1 + - uses: codecov/codecov-action@v1 + with: + file: lcov.info + docs: + name: Documentation + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: '1' + - run: | + julia --project=docs -e ' + using Pkg + Pkg.develop(PackageSpec(path=pwd())) + Pkg.instantiate()' + - run: | + julia --project=docs -e ' + using Documenter: doctest + using Fridge + doctest(Fridge)' # change MYPACKAGE to the name of your package + - run: julia --project=docs docs/make.jl + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCUMENTER_KEY: ${{ secrets.DOCUMENTER_KEY }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index cfb93320..51c546f4 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,6 @@ docs/site/ # committed for packages, but should be committed for applications that require a static # environment. Manifest.toml + +# Large Data Files +data/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 4f71fa6b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,24 +0,0 @@ -codecov: true -coveralls: true - -language: julia -os: - - linux - - osx - - windows -julia: - - 1.5 -notifications: - email: false -git: - depth: 99999999 -jobs: - include: - - stage: "Documentation" - julia: 1.5 - os: linux - script: - - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); - Pkg.instantiate()' - - julia --project=docs/ docs/make.jl - after_success: skip \ No newline at end of file diff --git a/FridgeLogo.png b/FridgeLogo.png new file mode 100644 index 00000000..aa50b00a Binary files /dev/null and b/FridgeLogo.png differ diff --git a/Project.toml b/Project.toml index 881f78bc..7ba49446 100644 --- a/Project.toml +++ b/Project.toml @@ -1,9 +1,16 @@ -name = "STMOZOO" -uuid = "8e7dc78f-7190-4116-bf88-3d148ae9f088" -authors = ["michielstock "] +name = "Fridge" +uuid = "673b17d6-c7db-4caa-9a54-38a02eba0033" +authors = ["warvbell "] version = "0.1.0" [deps] +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Cascadia = "54eefc05-d75b-58de-a785-1a3403f0919f" +Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterTools = "35a29f4d-8980-5a13-9543-d66fff28ecb8" +Gumbo = "708ec375-b3d6-5a57-a7ce-8257bf98657a" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/README.md b/README.md index e2ced9a6..b73e9abb 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,30 @@ -# STMO-ZOO -Welcome to the STMO zoo! This is your final assignment for the course Selected Topics in Mathematical Optimization. Your goal is to implement an optimization method in Julia and contribute this to this repository. To pass, you have to: +# Fridge.jl +[![](https://img.shields.io/badge/docs-dev-blue)](https://wardvanbelle.github.io/Fridge.jl/dev) [![codecov](https://codecov.io/gh/wardvanbelle/Fridge.jl/branch/master/graph/badge.svg?token=GJ8JXBG5M1)](https://codecov.io/gh/wardvanbelle/Fridge.jl)\ +\ + +by Ward Van Belle -- fork this repo and create a pull request; -- add a module to `src` with **at least one function** -- add at least one unit test to the folder `test`; -- document all your functions and add a page to the documentation page; -- make a notebook in [Pluto](https://github.com/fonsp/Pluto.jl) and add it to `notebooks`; -- perform a small code review of two other students. +A package that optimizes your fridge use while reducing your waste pile!! + +This package tries to find the best recipes for you based on a recipe database. In our eyes (and the eyes of the objective function), the best recipes are the ones that use as much ingredients from your fridge as possible and that don't need extra ingredients from the grocery store. + +## How to use this package? +There are several ways for you to use this package. + +1. First and foremost you should have a **recipe database**. This database should be a .csv file or a .jld2 file containing the recipe names in one column and a list containing the ingredient names in the next column. For instance: + + | recipeName | Ingredients | + |:----------:|:-----------:| + | french fries| potatoes, salt| + | boiled eggs | eggs, salt| + +2. If you just want to find an approximation of the best recipe combo. Then you can use the `greedyFindCombo` function. This performs a greedy search to find a quick solution. However, if you want a chance for a better solution, then you can use the `findBestRecipe` function. This function checks if your ingredients are in the database, and if not it offers possible alternatives. Next it uses **simulated annealing** to find a better recipe combination. + +## Help I don't have a database (recipeWebscraper.jl) + +Don't worry, we got you covered with `recipeWebscraper.jl`. This module includes a function that downloads recipes from [the cosylab recipe database](https://cosylab.iiitd.edu.in/recipedb/) based on their recipe number and exports them to a **.csv** file. + +You can use the `scrapeRecipe` function for this. Using this method it is possible to download around 16 000 recipes before you get kicked from the site. Therefore it is recommended to look at the cuisine you would like to download and start from the earliest recipe number in this cuisine. Recipe numbers can be found by looking at the end of the url of a recipe page eg. [Belgian Chocolate Mousse](https://cosylab.iiitd.edu.in/recipedb/search_recipeInfo/106541) is the first Belgian recipe and has the recipe number **106541**. -Depending on the project you choose some of these individual assignments might be really minimalistic, with other parts larger. For example, if you want to develop an application, say solving the graph coloring problem with Tabu Search, you might have only a single function in the source code (e.g., generating an instance) but have a fairly large notebook with a tutorial. On the other hand, if you work on a method, e.g., implementing Bee Colony Optimization, you might have many functions in the source code, while your notebook is only a demonstration on the test functions. -[![Build Status](https://travis-ci.org/MichielStock/STMOZOO.svg?branch=master)](https://travis-ci.org/MichielStock/STMOZOO)[![Coverage Status](https://coveralls.io/repos/github/MichielStock/STMOZOO/badge.svg?branch=master)](https://coveralls.io/github/MichielStock/STMOZOO?branch=master) \ No newline at end of file diff --git a/docs/Project.toml b/docs/Project.toml index 16656868..dfa65cd1 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,3 +1,2 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" -STMOZOO = "8e7dc78f-7190-4116-bf88-3d148ae9f088" diff --git a/docs/make.jl b/docs/make.jl index 21d4327e..c001f53a 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,17 +1,17 @@ using Documenter -using STMOZOO -using STMOZOO.Example +using Fridge -makedocs(sitename="STMO ZOO", +makedocs(sitename="Fridge.jl", format = Documenter.HTML(), - modules=[Example], # add your module - pages=Any[ - "Example"=> "man/example.md", # add the page to your documentation + modules=[Fridge], # add your module + pages=[ + "index.md", + "main function" => "mainFunction.md", + "search algorithms" => "searchAlgorithms.md", + "recipe webscraper"=> "recipeWebscraper.md", ]) -#= deploydocs( - repo = "github.com/michielstock/STMOZOO.jl.git", - ) -=# \ No newline at end of file + repo = "github.com/wardvanbelle/Fridge.jl", + ) \ No newline at end of file diff --git a/docs/src/assets/logo.png b/docs/src/assets/logo.png new file mode 100644 index 00000000..aa50b00a Binary files /dev/null and b/docs/src/assets/logo.png differ diff --git a/docs/src/index.md b/docs/src/index.md index ad33d99d..a2c5a225 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -1,7 +1,7 @@ -# STMO ZOO documentation +# Fridge +by *Ward Van Belle* -This is the documentation page for the STMO ZOO packages (edition 2020-2021). This is the place for the **documentation** -of your function. So explain the main functionality of your module and list the documentation of your funtions. +This package tries to find the best recipes for you based on a recipe database. In our eyes (and the eyes of the objective function), the best recipes are the ones that use as much ingredients from your fridge as possible and that don't need extra ingredients from the grocery store. ```@contents ``` \ No newline at end of file diff --git a/docs/src/mainFunction.md b/docs/src/mainFunction.md new file mode 100644 index 00000000..e4ed80df --- /dev/null +++ b/docs/src/mainFunction.md @@ -0,0 +1,11 @@ +## Overview Function + +```@docs +findBestRecipe +``` + +This function checks the ingredients by using the `checkIngredients` function: + +```@docs +checkIngredients +``` \ No newline at end of file diff --git a/docs/src/man/example.md b/docs/src/man/example.md deleted file mode 100644 index 08857ecb..00000000 --- a/docs/src/man/example.md +++ /dev/null @@ -1,14 +0,0 @@ -## Example - -This is the example code, written by Michiel Stock. It contains some basic functionality -to solve quadratic systems of the form: - -``\min_{\mathbf{x}} \frac{1}{2} \mathbf{x}^\intercal P\mathbf{x} + \mathbf{q} \cdot \mathbf{x} + r\,,`` - -It contains a function to solve this optimization problem and a helper function that generates a quadratic function from -the parameters in their canonical form. - -```@docs -solve_quadratic_system -quadratic_function -``` \ No newline at end of file diff --git a/docs/src/recipeWebscraper.md b/docs/src/recipeWebscraper.md new file mode 100644 index 00000000..02daffba --- /dev/null +++ b/docs/src/recipeWebscraper.md @@ -0,0 +1,14 @@ +```@docs + +``` + + +# recipeWebscraper + +This is a supporting module that allows the user to download recipes from [the cosylab recipe database](https://cosylab.iiitd.edu.in/recipedb/) based on their recipe number and exports them to a **.csv** file. + +## scrapeRecipe + +```@docs +scrapeRecipe +``` diff --git a/docs/src/searchAlgorithms.md b/docs/src/searchAlgorithms.md new file mode 100644 index 00000000..e1bdfa4d --- /dev/null +++ b/docs/src/searchAlgorithms.md @@ -0,0 +1,22 @@ +## Search Algorithms + +This page includes some more information on the two search methods used. + +### Greedy Search + +```@docs +greedyFindCombo +``` + +### Simulated Annealing + +```@docs +SAFindCombo +``` + +### Neighbour Functions + +```@docs +randomCombo +Neighbour +``` \ No newline at end of file diff --git a/instructions.md b/instructions.md deleted file mode 100644 index bb96327c..00000000 --- a/instructions.md +++ /dev/null @@ -1,100 +0,0 @@ -# Assignments - -Edition 2021-2022 - -This file gives a detailed overview of what you have to do for this project. - -## In brief - -For the exam project, you pick a optimization related topic of your interest that is *not* covered in detail in class. This can be an algorithm, an application you solve with methods seen the course or some theoretical aspect you want to study. You write some code that you add to the STMOZOO codebase (including documentation, tests etc.) and illustrate you application in a notebook. - -## Getting started - -- [ ] pick a project (take a look at `project ideas.md` or discuss with Michiel) -- [ ] [fork](https://docs.github.com/en/enterprise-server@2.20/github/getting-started-with-github/fork-a-repo) this repo -- [ ] rename your repo using a short indicative name, e.g., `GeneticProgramming.jl`. Add `.jl` to indicate this is a Julia package. **Don't use spaces in the name!** -- [ ] make a local clone of the repository -- [ ] add the repo with your project to the project sheet -- [ ] update the `readme.md` - - [ ] add title - - [ ] add your names - - [ ] add a small abstract/example of what the code should do - -## Source code - -> **This part is optional!** Most of you will work in a Pluto notebook, so you don't have to add source code as well (it is hard to import in the notebook). Just put your functions tidily in an appendix at the bottom of a notebook. - -Every project needs to have some source code, at least one function! You have to decide which parts belong in the source code (and can hence be readily loaded by other users) and which parts of your project will be in the notebook where people can see and interact with your code. - -Developing code can be done in any text editor, though we highly recommend [Visual Studio Code](https://code.visualstudio.com/), with Juno the environment for Julia. [Atom](https://atom.io/) is an alternative but is not supported anymore. When developing, you have to activate your project. Assuming that the location of the REPL is the project folder, open the Pkg manager (typing `]`) and type `activate .`. The dot indicated the current directory. If you use external packages in your project, for example, Zygote or LinearAlgebra, you have to add them using `add PACKAGE` in the package manager. This action will create a dependency and update the `Project.toml` file. - -Importantly, all your code should be in a [module](https://docs.julialang.org/en/v1/manual/modules/), where you export only the functions useful for the user. - -- [ ] In the `src` folder, add a new Julia file with your source code, for example `geneticprogramming.jl`. Don't use spaces or capitals in the file name. -- [ ] Link your file in `STMOZOO.jl` using `include(filename)`, running the code. -- [ ] Create a module environment in your file for all your code. Use [camel case](https://en.wikipedia.org/wiki/Camel_case) for the name. - - use `module GeneticProgramming begin ... end` to wrap your code; - - import everything you need from external packages: `using LinearAlgebra: norm`; - - export your functions using `export` -- [ ] write awesome code! -- [ ] take a look at your code regarding the [Julia style guide](https://docs.julialang.org/en/v1/manual/style-guide/) -- [ ] check the [Julia performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/) -- [ ] document *every* function! Make sure that an external user can understand everything! Be liberal with comments in your code. Take a look at the [guidelines](https://docs.julialang.org/en/v1/manual/documentation/) - -## Unit tests - -> **This part is optional!** Again, you only need to provide a notebook. You are encouraged to add some unit tests in the notebook (make it a hidden cell). You can do this by importing `Test` and add an `@testset` as decribed below. - -Great, we have written some code. The question is, does it work? Likely you have experimented in the REPL. For a larger project, we would like to have guarantees that it works, though. Luckily, this is very easy in Julia, where we can readily use [Unit testing](https://docs.julialang.org/en/v1/stdlib/Test/). - -You will have to write a file with some unit tests, ideally testing every function you have written! The fraction of functions that are tested is called [code coverage](https://en.wikipedia.org/wiki/Code_coverage). This project is monitored automatically using Travis (check the button on the readme page!). Currently, coverage is 100%, so help to keep this as high as possible! - -Tests can be executed using the `@test` macro. You evaluate some functions and check their results. The result should evaluate to `true`. For example: `@test 1+1 == 2` or `@test √(9) ≈ 3.0`. - -It makes sense to group several tests, which can be done using `@testset "names of tests" begin ... end`. - -Your assignments: -- [ ] add a source file to the `test/` folder, the same name as your source code file. -- [ ] add an `include(...)` with the filename in `runtests.jl` -- [ ] in your file, add a block `@testset "MyModule" begin ... end` with a series of sensible unit tests. Use subblocks of `@testset` if needed. -- [ ] run your tests, in the package manager, type `test`. It will run all tests and generate a report. - -Travis will automatically run your unit tests online when you push to the origin repo. - -## Documentation - -> **This part is optional!** If you just provide a couple of examples how to use your code in the readme, it is fine. - -Hopefully, you have already documented all your functions, so this should be a breeze! We will generate a documentation page using the [Documenter](https://juliadocs.github.io/Documenter.jl/stable/man/guide/) package. Since we will not put the project in the package manager, we won't host the documentation, though we generate HTML pages anyway. - -- [ ] add markdown file to `docs/src/man` with the documentation. -- [ ] write a general introduction explaining the rationale of your code. -- [ ] use a `@docs` block to add your functions with their documentation. -- [ ] update the `make.jl` file, linking your page. -- [ ] run the `make.jl` file to generate the documentation, an HTML file, not added to the repo. - -## Notebook - -> **This is the most important part!** Add all your functionality and explanation to the Pluto notebook so that it is stand-alone. Try to make sure that your initial examples are sufficiently small so it does not take too long to run the notebook. - -Finally, you have to add a [Pluto](https://github.com/fonsp/Pluto.jl) notebook to the `notebook` folder. Again use the same name you used for your source code. Depending on the nature of your project, this will be the most extensive task! Make full use of Pluto's interactivity to illustrate your code. In contrast to the documentation page, this is not the place to explain your functions but rather show what you can do with your software or explain a concept. - -Alternatively, you may use [Literate](https://fredrikekre.github.io/Literate.jl/v2/) to have script with text annotation to explain your code/package. Up to you what you feel most comfortable with. - -## Code review - -Each of you will have to perform a code review of two other projects. You have till noon 13h of the exam date to do this, though it should not take too long. The aim is to **help** the other groups to make each other's project even better. - -- [ ] make a fork or local clone of the repo of the person you are reviewing; -- [ ] (if relevant) check the source code, is the documentation clear? Anything obvious that can be improved. -- [ ] (if relevant) run the tests. Do they work? Anything that could be tested but is not done so? -- [ ] Is the documentation clear? Do you find any typos? Could an example be added? -- [ ] Take a good look at the notebook. Any suggestions there to improve this? - -Big things can be addressed by opening an issue. Small fixes and suggestions to the other person's code can be done immediately and via a pull request. - -Afterwards, you have the rest of the day to: -- [ ] merge the entire request and fix any issues you find meaningful. -- [ ] fill in a small questionnaire on Ufora about your project and the projects you have reviewed. - -When your code is final, you can [tag](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-commits/managing-tags) your latest commit and mention Michiel you are finished (mention `@michielstock`). diff --git a/notebook/example.jl b/notebook/example.jl deleted file mode 100644 index 950417f0..00000000 --- a/notebook/example.jl +++ /dev/null @@ -1,159 +0,0 @@ -### A Pluto.jl notebook ### -# v0.14.8 - -using Markdown -using InteractiveUtils - -# ╔═╡ 5ad5c202-20f8-11eb-23f1-4f38b687c285 -using STMOZOO.Example - -# ╔═╡ d2d007b8-20f8-11eb-0ddd-1181d4565a85 -using Plots - -# ╔═╡ 45189a82-20fa-11eb-0423-05ce1b84639d -using Zygote - -# ╔═╡ 171fee18-20f6-11eb-37e5-2d04caea8c35 -md""" -# Example: solving quadratic systems - -By Michiel Stock - -## Introduction - -In this notebook, I have chosen to write code so minimize *quadratic functions* of the form: - -$$f(\mathbf{x}) = \frac{1}{2} \mathbf{x}^\intercal P\mathbf{x} + \mathbf{q} \cdot \mathbf{x} + r\,,$$ - -where $P \succ 0$. - -Hence, solving - -$$\min_\mathbf{x}\, f(\mathbf{x})\,.$$ -""" - -# ╔═╡ fb4aeb8c-20f7-11eb-0444-259de7b76883 -md""" - -## Derivation - -The gradient of the quadratic function is - -$$\nabla f(\mathbf{x})=P\mathbf{x} +\mathbf{q}\,.$$ - -Setting this to zero gives - -$$\mathbf{x}^\star=-P^{-1}\mathbf{q}\,.$$ - -We also know that $\nabla^2f(\mathbf{x}) = P \succ 0$, so $\mathbf{x}^\star$ is a minimizer. -""" - -# ╔═╡ 52e87238-20f8-11eb-2ea0-27ee1208d3c3 -md""" -## Illustration - -We will use a simple 2D example to illustrate this code. -""" - -# ╔═╡ e9a9d69e-20f8-11eb-0d9d-330ee5e9cf25 -P = [7 -1; -1 2] - -# ╔═╡ fdd4e550-20f8-11eb-227b-25f36708484d -q = [-14.0, -2.0] - -# ╔═╡ 025fd6e8-20f9-11eb-3e7d-3519f3c4b58f -r = 1 - -# ╔═╡ 096eff98-20f9-11eb-1e61-99d5714895ba -md"We have a function to generate the quadratic function." - -# ╔═╡ 165509ca-20f9-11eb-107c-550cbba0f0e9 -f_quadr = quadratic_function(P, q, r) - -# ╔═╡ 1fffc82a-20f9-11eb-198c-c160d7dac87d -f_quadr([2, 1]) - -# ╔═╡ 26ab6ce2-20f9-11eb-1836-1756b290e5e3 -md"No more need to remember the formulla for the minimizer! Just use `solve_quadratic_system`!" - -# ╔═╡ 49832a8e-20f9-11eb-0841-19a40a12db18 -x_star = solve_quadratic_system(P, q, r) - -# ╔═╡ 55e0e274-20f9-11eb-36c0-753f228f7e9b -begin - contourf(-20:0.1:20, -20:0.1:20, (x, y) -> f_quadr([x,y]), color=:speed) - scatter!([x_star[1]], [x_star[2]], label="minimizer") -end - -# ╔═╡ b1551758-20f9-11eb-3e8f-ff9a7127d7f8 -md""" -## Approximating non-quadratic functions - -We can approximate non-quadratic functions by a quadratic function: The second order Taylor approximation $\hat{f}$ of a function $f$ at $\mathbf{x}$ is -$$f(\mathbf{x}+\mathbf{v})\approx\hat{f}(\mathbf{x}+\mathbf{v}) = f(\mathbf{x}) + \nabla f(\mathbf{x})^\top \mathbf{v} + \frac{1}{2} \mathbf{v}^\top \nabla^2 f(\mathbf{x}) \mathbf{v}\,.$$ - -Let us use this idea for the Rosenbrock function. -""" - -# ╔═╡ 41d8f1dc-20fa-11eb-3586-a989427c1fd6 -f_nonquadr((x1, x2); a=1, b=5) = (a-x1)^2 + b * (x2 - x1^2)^2 - -# ╔═╡ 4ed4215e-20fa-11eb-11ee-f7741591163c -x = [0.0, 0.0] - -# ╔═╡ 56af99ee-20fa-11eb-0240-69c675efb78c -fx = f_nonquadr(x) - -# ╔═╡ 6c5473b4-20fa-11eb-327b-51ac560530eb -∇fx = f_nonquadr'(x) - -# ╔═╡ 7518c2c0-20fa-11eb-32c0-a9db2a91cbc5 -∇²fx = Zygote.hessian(f_nonquadr, x) - -# ╔═╡ 34027942-20fb-11eb-261e-3b991ce4c9f8 -v = solve_quadratic_system(∇²fx, ∇fx, fx) - -# ╔═╡ 3bbeb85c-20fc-11eb-04d0-fb12d8ace50a -f̂(x′) = quadratic_function(∇²fx, ∇fx, fx)(x′ .- x) - -# ╔═╡ 8623ac1a-20fa-11eb-2d45-49cce0fdac86 -begin - plot_nonquadr = contourf(-2:0.01:2, -2:0.01:2, (x, y) -> f_nonquadr([x,y]), color=:speed, title="non-quadratic function") - scatter!(plot_nonquadr, [x[1]], [x[2]], label="x") - scatter!(plot_nonquadr, [x[1]+v[1]], [x[2]+v[2]], label="x + v") - - plot_approx = contourf(-2:0.01:2, -2:0.01:2, (x, y) -> f̂([x,y]), color=:speed, - title="quadratic approximation") - scatter!(plot_approx, [x[1]], [x[2]], label="x") - scatter!(plot_approx, [x[1]+v[1]], [x[2]+v[2]], label="x + v") - - plot(plot_nonquadr, plot_approx, layout=(2,1), size=(600, 800)) - -end - - -# ╔═╡ Cell order: -# ╟─171fee18-20f6-11eb-37e5-2d04caea8c35 -# ╠═5ad5c202-20f8-11eb-23f1-4f38b687c285 -# ╠═d2d007b8-20f8-11eb-0ddd-1181d4565a85 -# ╟─fb4aeb8c-20f7-11eb-0444-259de7b76883 -# ╟─52e87238-20f8-11eb-2ea0-27ee1208d3c3 -# ╠═e9a9d69e-20f8-11eb-0d9d-330ee5e9cf25 -# ╠═fdd4e550-20f8-11eb-227b-25f36708484d -# ╠═025fd6e8-20f9-11eb-3e7d-3519f3c4b58f -# ╟─096eff98-20f9-11eb-1e61-99d5714895ba -# ╠═165509ca-20f9-11eb-107c-550cbba0f0e9 -# ╠═1fffc82a-20f9-11eb-198c-c160d7dac87d -# ╟─26ab6ce2-20f9-11eb-1836-1756b290e5e3 -# ╠═49832a8e-20f9-11eb-0841-19a40a12db18 -# ╠═55e0e274-20f9-11eb-36c0-753f228f7e9b -# ╟─b1551758-20f9-11eb-3e8f-ff9a7127d7f8 -# ╠═41d8f1dc-20fa-11eb-3586-a989427c1fd6 -# ╠═45189a82-20fa-11eb-0423-05ce1b84639d -# ╠═4ed4215e-20fa-11eb-11ee-f7741591163c -# ╠═56af99ee-20fa-11eb-0240-69c675efb78c -# ╠═6c5473b4-20fa-11eb-327b-51ac560530eb -# ╠═7518c2c0-20fa-11eb-32c0-a9db2a91cbc5 -# ╠═34027942-20fb-11eb-261e-3b991ce4c9f8 -# ╠═3bbeb85c-20fc-11eb-04d0-fb12d8ace50a -# ╟─8623ac1a-20fa-11eb-2d45-49cce0fdac86 diff --git a/notebook/fridgeNotebook.jl b/notebook/fridgeNotebook.jl new file mode 100644 index 00000000..1cb5c12d --- /dev/null +++ b/notebook/fridgeNotebook.jl @@ -0,0 +1,1030 @@ +### A Pluto.jl notebook ### +# v0.17.1 + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end +end + +# ╔═╡ ae625e40-7f56-11ec-3cd5-d1244e23a3ba +begin +using PlutoUI, HTTP, Gumbo, Cascadia, CSV, JLD2 + +md"""# Fridge.jl +by *Ward Van Belle* +""" + +end + +# ╔═╡ 06db3cc2-1680-49b9-a054-281a6b35205a +md"""$(Resource("https://github.com/wardvanbelle/Fridge.jl/raw/master/FridgeLogo.png", :width => 150, :align => "right")) + +A package that optimizes your fridge use while reducing your waste pile!! + +This package tries to find the best recipes for you based on a recipe database. In our eyes (and the eyes of the objective function), the best recipes are the ones that use as much ingredients from your fridge as possible and that don't need extra ingredients from the grocery store.""" + +# ╔═╡ 3ad1af72-7096-4106-a3c7-47326695e2cb +md""" +## Checking The Ingredients +If you want to optimize fridge usage. It's best to know which ingredients are in the fridge. By checking all the ingredients and offering possible alternatives, it is easier for the algorithm to give better results. For instance, cheese may be replaced by swiss cheese.\ +\ +It takes two inputs: +- The fridge list: A list containing the different foods in your fridge as a string. +- The ingredient list: A list containing all the different ingredients that are used in the recipe database. + +After searching for the different ingredients, it outputs an adapted fridge list. +""" + +# ╔═╡ 8e58f807-1db9-495e-8679-430f953292ea +function checkIngredients(fridgeList,ingredientList) + print("Checking if the food in your fridge is found in our database.\n\n") + + # check if the ingredients in the fridge are found in the database + for food in fridgeList + if food in ingredientList + print("Found $food in the ingredient database.\n") + else + print("Did not find $food in the ingredient database.\n\n") + if any(occursin.(food,ingredientList)) + print("Possible alternatives in database:\n") + alternatives = ingredientList[occursin.(food,ingredientList)] + for (indexNum, alternative) in enumerate(alternatives) + print("[$indexNum] $alternative\n") + end + print("If you want to take an alternative type its number, else type no.\n") + answer = readline() + if answer != "no" + correctInput = false + while !correctInput + try + answer = parse(Int64,answer) + correctInput = true + catch + print("Please only type the number of the alternative, eg. 1\n") + answer = readline() + end + end + fridgeList[fridgeList .== food] .= alternatives[answer] + print("Replaced $food with $(alternatives[answer])\n") + end + else + print("Did not find any alternative in the ingredient database.\n") + end + end + end + print("Done checking ingredients.\n\n") + + return fridgeList +end + +# ╔═╡ 264b1eb5-c421-4a69-a55c-8fbbbb7715d1 +md""" +## Objective Functions +Of course, we want to be able to score how good our objective function will be. This objective function is based on the following formula where ``u`` is the number of ingredients used from the fridge, ``r`` is the number of ingredients remaining in the fridge and ``e`` are the extra ingredients needed. ``w_1``, ``w_2`` and ``w_3`` are a set of weights. + +``score = w_1 * u + w_2 * r + w_3*e`` +""" + +# ╔═╡ 1752889a-513a-4620-add4-a9ca7fa92c8e +md""" +## The Search Algorithm +Let's now take a look at the different search algorithms that you can use in the Fridge.jl module. +""" + +# ╔═╡ a65c0925-7a67-4da3-9a6d-2ade8d3529e9 +md"""### Greedy Algorithm +This algorithm works like the one seen in class. +It ranks all recipes based on the previously discussed objective function. Picks the best scoring recipe. Adapts the recipe options and picks the new best recipe. +It keeps repeating this until the amount of chosen recipes is equal to `numRecipes` or if there are no recipes left to pick.\ +\ +As an input, it takes the earlier mentioned `fridgeList`, a dictionary containing all the recipes and their ingredients (`recipeDict`) and, the max number of recipes that a combination should have (`numRecipes`). \ +\ +After searching, it outputs the best-found combination. +""" + +# ╔═╡ 954fe2b3-171b-43ad-a1df-cbb3414988de +md""" +### Simulated Annealing +To improve our earlier result, we can use simulated annealing. The algorithm below is based on the one seen in class. The biggest difference is the fact that we also use a Tabu list to block recipes for a certain number of cycles. + +To start searching, it needs the earlier mentioned `fridgeList`, `recipeDict` and `numRecipes` but also the following things: +- `initSolution`: The initial solution from which it should start looking. +- `tabuList`: A list of recipes that should not be used in the found neighbour. +- `randRecipe`: A Boolean `true` or `false` value. When `true`, random recipes are used to create the neighbour. + +After searching it also outputs the best-found combination of recipes. +""" + +# ╔═╡ eee53924-fd20-44d8-a3eb-a2af9f9112b9 +md""" +To optimize this searching method, there are a lot of parameters that can be tweaked. +The following options are available: +- `kT`: repetitions per temperature +- `r`: cooling rate +- `Tmax`: maximal temperature to start +- `Tmin`: minimal temperature to end +- `tabuLength`: Number of cycles that recipe needs to be blocked + +You can experiment with them further on below! +""" + +# ╔═╡ 7315f676-0d0e-43ae-8231-b6274dc5c8de +md""" +### Neighbours +There are two types of neighbours to use in the module. One uses a random combination of recipes, the other is based on the Greedy Search algorithm. Both are implemented in the `Neighbour()` function. +""" + +# ╔═╡ 134a2dd7-69d2-449e-82ab-5bfff90ede1b +md"""A neighbour is created by first removing a recipe from the current combination `curSolution`. Next, the Neighbour function looks for other recipes that are compatible with the other recipes that are in the current combination.""" + +# ╔═╡ b6205bd3-41da-45e0-81f1-a480a3f0e737 +md""" +## Overview Function +Finally, we want to bring all the functions together in one clean overview function. +The only things that need to be provided are: +- A list containing the different foods in your fridge as a string. `fridgeList` +- A relative or absolute path to a .csv or .jld2 file containing the recipe database. `dataPath` + +Optionally you can also adapt the max amount of recipes in a combination (`numRecipes`) and wether or not random recipes should be used to create a neighbour (`randRecipe`). + +""" + +# ╔═╡ 6af2a6dd-ba7e-470e-8915-12ae6eff8357 +md"""## Example Time +To start of we'll need to get our hands on a recipe database. Don't fear if you don't have one laying around. By using the `scrapeRecipe()` function provided in this module, you can create a .csv file containing recipes from [the cosylab recipe database](https://cosylab.iiitd.edu.in/recipedb/). + +You just need to provide the following things: +- a starting recipe number +- a stopping recipe number +- the path to where the .csv file should be saved (you can copy this from your file explorer) + +Next al that is left to do is to check the `Download database now` box. Since this will take a few minutes we provide you with a 🍪 and some ☕, enjoy! +""" + +# ╔═╡ 656f211d-d1e8-4325-a12b-b89eca0351da +md""" +#### recipe database selection menu +The standard inputs are 106 541 to 106 866 since this downloads all the Belgian recipes. If you would like to download recipes from another country, please take a look at the explanation provided in the readme on the [GitHub repository](https://github.com/wardvanbelle/Fridge.jl). + +Starting recipe number: $(@bind startNumber TextField(default="106541"))\ +Stopping recipe number: $(@bind endNumber TextField(default="106866"))\ +Filepath: $(@bind filePath TextField(default="../data/BelgianRecipeDB.csv"))\ +Download database now: $(@bind downloadDB CheckBox()) + +It is recommended to only use this once. Otherwise, you will have copies of some recipes in your database. So you better uncheck the box above! +""" + +# ╔═╡ bf2725c1-55e6-4c6a-9b83-53f5984cae75 +md"Next it is time to look at the things we have left in our kitchen:" + +# ╔═╡ fd02d76a-0ae6-4869-96bb-516c536fb91d +fridgeList = ["chocolate", "potato", "salt", "cheese"] + +# ╔═╡ 34544e10-e4a1-4cfe-b0bc-43f8927bb19c +md""" +Now that we have a small database and the content of our fridge, we can start looking for a recipe combination. Keep an eye on your terminal since it is possible that you need to give some input! If you are ready, check the box below! +""" + +# ╔═╡ 312fabf7-7d11-4bac-b52b-14de82d13e73 +md"Ready to start searching? $(@bind readyToSearch CheckBox())" + +# ╔═╡ 15a97461-7096-4ea0-acbc-edbc5db79cde +md""" +Congratulations! You made it through the notebook! +You can now continue to the **experimental section** below to play with the different parameters used in the algorithms. Enjoy the food! 🍔 +""" + +# ╔═╡ d49cf674-088f-47a1-9795-7641b3f01cd1 +md"""## Experimental Section +We will now load the .csv file and save it in a dictionary so that it doesn't need to load every time you run the simulated annealing algorithm. +""" + +# ╔═╡ 86f03d01-4f97-4487-9683-38f8dc9d826a +md""" +### Parameter Options: +**General**\ +`numRecipes` : $(@bind numRecipesExp Slider(1:10, default=3, show_value=true))\ +`tabuLength` : $(@bind tabuLengthExp Slider(1:15, default=5, show_value=true))\ +`randRecipe` : $(@bind randRecipeExp CheckBox())\ +\ +**Simulated Annealing specific**\ +`kT` : $(@bind kTExp Slider(20:10:150, default=100, show_value=true))\ +`r` : $(@bind rExp Slider(0.1:0.05:0.95, default=0.75, show_value=true))\ +`Tmax` : $(@bind TmaxExp Slider(2:10, default=4, show_value=true))\ +`Tmin` : $(@bind TminExp Slider(1:9, default=1, show_value=true))\ +\ +**Objective specific**\ +``w_1``: $(@bind w_1 Slider(0:10, default=1, show_value=true))\ +``w_2``: $(@bind w_2 Slider(0:10, default=6, show_value=true))\ +``w_3``: $(@bind w_3 Slider(0:10, default=2, show_value=true))\ +""" + +# ╔═╡ 70ffadd9-80d4-4eb9-92d9-e3d5ffa50074 +md"Start experimenting: $(@bind startExperimenting CheckBox())" + +# ╔═╡ c51f2b76-fe4c-4c29-87dc-c7784e5200c2 +md""" +## Supporting Functions +Below you can find all the supporting functions used in this notebook. These can also be found in the src folder in the [GitHub repository](https://github.com/wardvanbelle/Fridge.jl). Please do not adapt them. +""" + +# ╔═╡ 09d6393c-9b14-4401-babe-708a88ef0a16 +function scrapeRecipe(scrapeBegin,scrapeEnd,csvPath) +""" + scrapeRecipe(scrapeBegin,scrapeEnd,csvPath) + +Download recipetitles and their corresponding ingredients from the recipe database of cosylab as a dictionary. +The recipes are downloaded based on their recipe number. This number can range from 2610 to 149191. +To get a recipenumber one should look at the last number of the url of a certain recipe. +For example, the recipe for 'Speculoosbavarois' is number 106585. +The recipes get automatically saved in a csv file where the first column is the recipetitle and the second column +is the list of ingredients. + +## Input: +- scrapeBegin: The recipe number where the iteration should begin. +- scrapeEnd: The recipe number after which the iteration should and. +- csvPath: The path where the csv file is stored. + +## Examples: + +The example below downloads the recipes 2700 to 2702 and +stores them in the csv file 'recipedb.csv' in the current folder. + +```julia-repl +julia> scrapeRecipe(2700,2702,"./recipedb.csv") +``` +""" + for i = scrapeBegin:scrapeEnd + # get the webpage + htmlText = HTTP.request("GET","https://cosylab.iiitd.edu.in/recipedb/search_recipeInfo/$i") + htmlBody = parsehtml(String(htmlText.body)) + + # get the needed elements out of the webpage + recipeTitle = eachmatch(Selector("h3"),htmlBody.root)[1].children[1].text + print("recipe number $i : $recipeTitle\n") + ingredientTab = eachmatch(Selector("#ingredient_nutri"),htmlBody.root) + ingredientTabel = eachmatch(Selector("#myTable"),ingredientTab[1]) + ingredientLinks = eachmatch(Selector("td a"),ingredientTabel[1]) + ingredientList = [] + for ingredientLink in ingredientLinks + try + append!(ingredientList, [ingredientLink.children[1].text]) + catch y + end + end + + # write the data to the DB + if isfile(csvPath) + CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = true) + else + CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = false) + end + + end + print("done") +end + +# ╔═╡ cd5458a5-2136-4559-b4e5-2d91abea870c +if downloadDB + scrapeRecipe(parse(Int64,startNumber), parse(Int64,endNumber), filePath) +end + +# ╔═╡ 87b72838-260f-4ffb-ac79-03860165afa1 +function loadRecipeDBCSV(csvPath) + # read the dictionary from the csv file + print("Loading the recipe database.\n") + tempDict = CSV.File(csvPath) |> Dict + recipeDict = Dict() + + # parse the ingredient list to the right format + print("Parsing ingredients to right format.\n\n") + for recipe in keys(tempDict) + ingredientList = tempDict[recipe] + recipeDict[recipe] = eval(Meta.parse(ingredientList)) + end + return recipeDict +end + +# ╔═╡ 11b589ff-3a09-4395-921a-872feb190022 +if isfile(filePath) + recipeDictExp = loadRecipeDBCSV(filePath) +end + +# ╔═╡ adde7105-4a4a-453b-a7a8-9c9471dc29d3 +function createIngredientDatabase(recipeDict) + # create an unique vector of all ingredients used in the recipe database + ingredientList = [] + for ingredients in values(recipeDict) + append!(ingredientList,ingredients) + end + ingredientList = unique(ingredientList) + + return ingredientList +end + +# ╔═╡ 89e58385-4db9-4ae3-9495-d8a2e63f2d72 +function recipeToNumVector(fridgeList,ingredientList) +""" + recipeToNumVector(fridgeList,ingredientList) + +This function changes an ingredients list of a recipe to a numeric vector. This vector has a length equal +to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe, that position in the vector is a 1. +If not it is a 0. The last index of the vector contains the amount of extra ingredients needed. + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- ingredientList: A list containing all the ingredients used in a specific recipe. + +## Output: +- numVector: This vector has a length equal to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe, +that position in the vector is a 1. If not it is a 0. The last index of the vector contains the amount of extra ingredients needed. + +""" + numVector = zeros(Int64,length(fridgeList)+1) + for i in 1:length(fridgeList) + numVector[i] = fridgeList[i] in ingredientList ? 1 : 0 + end + numVector[end] = length(ingredientList) - sum(numVector) + return numVector +end + +# ╔═╡ 3ed830c9-e3f7-4632-9ff3-cdd48c00f315 +function randomCombo(fridgeList, recipeDict, numRecipes) + + randCombo = Dict() + ingredientsArray = [] + namesArray = [] + + for (name,ingredients) in recipeDict + push!(namesArray,name) + push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients)) + end + + for i = 1:numRecipes + + randIndex = rand(1:length(namesArray)) + tempRecipeName = namesArray[randIndex] + randCombo[tempRecipeName] = ingredientsArray[randIndex] + + if all(isone.(sum(values(randCombo)))) + break + end + + namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0] + ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0] + + if isempty(namesArray) + break + end + + end + + return randCombo +end + +# ╔═╡ a6e77624-606a-49ea-941d-8180cbd94a27 +compatible(x) = !any(sum(x)[1:end-1] .>= 2) # checks if two recipes use a same ingredient + +# ╔═╡ 644c2809-5b2b-4426-8deb-f952372a8438 +begin + +# objective function used to score a single recipe +fridgeObjective(x::Array{Int64}) = w_1 * sum(x[1:end-1]) + w_2 *sum(x[1:end-1] .== 0) + w_3 *x[end] + +# objective function used to score a combination of recipes +fridgeObjective(x) = compatible(x) ? sum(sum(x) .== 1)*w_1 + sum(sum(x) .== 0)*w_2 + sum(x)[end]*w_3 : Inf + +end + +# ╔═╡ ace6ecfa-319b-48f3-a675-51b4bc26abd0 +function greedyFindCombo(fridgeList, recipeDict, numRecipes) + + bestCombo = Dict() + ingredientsArray = [] + namesArray = [] + + for (name,ingredients) in recipeDict + push!(namesArray,name) + push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients)) + end + + for i = 1:numRecipes + + bestOrder = sortperm(ingredientsArray, by=fridgeObjective) + ingredientsArray = ingredientsArray[bestOrder] + namesArray = namesArray[bestOrder] + + # break if all recipes that are left, don't use anything from the fridge + if all(ingredientsArray[1][1:end-1] .== 0) + break + end + + tempRecipeName = namesArray[1] + bestCombo[tempRecipeName] = ingredientsArray[1] + + # break if all ingredients are used + if all(isone.(sum(values(bestCombo)))) + break + end + + namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0] + ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0] + + # break if there are no recipes left + if isempty(namesArray) + break + end + + end + + return bestCombo +end + +# ╔═╡ 7365d57b-fd33-46af-9dcd-2c07908f64a3 +function Neighbour(curSolution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe) + + toRemove = rand(curSolution)[1] + # adapt the fridgeList so that only ingredients from the removed ingredient are available + tempFridgeList = copy(fridgeList) + for recipe in keys(curSolution) + if recipe != toRemove + tempFridgeList = [i for i in fridgeList if !in(i,recipeDict[recipe])] + numRecipes -= 1 + end + end + + # adapt the recipeDict and use greedy search to find a new solution + tempRecipeDict = copy(recipeDict) + for recipe in keys(curSolution) + delete!(tempRecipeDict,recipe) + end + + for recipe in tabuList + try delete!(tempRecipeDict,recipe) + catch e + end + end + + if randRecipe + neighbour = randomCombo(tempFridgeList, tempRecipeDict, numRecipes) + else + neighbour = greedyFindCombo(tempFridgeList, tempRecipeDict, numRecipes) + end + + # correct recipe vectors + for recipe in keys(neighbour) + neighbour[recipe] = recipeToNumVector(fridgeList,recipeDict[recipe]) + end + + # here combine the two dictionaries + tempCurSolution = copy(curSolution) + delete!(tempCurSolution,toRemove) + neighbour = merge(neighbour,tempCurSolution) + + return neighbour +end + +# ╔═╡ 6f1d6e44-30a1-42ee-b888-4cbb105b5348 +function SAFindCombo(initSolution, fridgeList, recipeDict, numRecipes, randRecipe; + kT=100, # repetitions per temperature + r=0.75, # cooling rate + Tmax=4, # maximal temperature to start + Tmin=1, # minimal temperature to end + tabuLength=3) # number of cycli that recipe needs to be blocked + + @assert 0 < Tmin < Tmax "Temperatures should be positive" + @assert 0 < r < 1 "cooling rate is between 0 and 1" + solution = initSolution + obj = fridgeObjective([i for i in values(solution)]) + tabuList = String[i for i in keys(initSolution)] + + # current temperature + T = Tmax + while T > Tmin + # repeat kT times + for i in 1:kT + sn = Neighbour(solution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe) # random neighbor + obj_sn = fridgeObjective([i for i in values(sn)]) + # if the neighbor improves the solution, keep it + # otherwise accept with a probability determined by the + # Metropolis heuristic + if obj_sn < obj || rand() < exp(-(obj_sn-obj)/T) + solution = sn + obj = obj_sn + end + + for recipe in keys(sn) + if !in(recipe, tabuList) + if length(tabuList) < tabuLength + pushfirst!(tabuList,recipe) + else + pop!(tabuList) + pushfirst!(tabuList,recipe) + end + end + end + end + + # decay temperature + T *= r + end + + return solution +end + +# ╔═╡ 9bfc9f3d-bc05-4c15-8b4c-2cfce3507afc +function findBestRecipe(fridgeList, dataPath; numRecipes=3, randRecipe=false) + + # load the recipe dictionary from the db file + recipeDict = dataPath[end-3:end] == ".csv" ? loadRecipeDBCSV(dataPath) : load(dataPath) + + # create a list of all ingredients in your database + ingredientList = createIngredientDatabase(recipeDict) + + # check for every food in your fridge if it's in the database. If not check if there are alternatives. + fridgeList = checkIngredients(fridgeList, ingredientList) + + # find the best greedy recipe + greedySolution = greedyFindCombo(fridgeList, recipeDict, numRecipes) + + # find the best recipe with SA + SASolution = SAFindCombo(greedySolution, fridgeList, recipeDict, numRecipes, randRecipe) + + # print the solution + print("The best recipes to make are:\n\n") + for recipeName in keys(SASolution) + print("$(recipeName) : $(recipeDict[recipeName])\n") + end + + return SASolution +end + +# ╔═╡ 3569550e-0157-4d7a-8cda-4df327c92b81 +if readyToSearch + findBestRecipe(fridgeList, filePath) +end + +# ╔═╡ e10c7ddf-aef0-470a-be22-ac250d84fb7e +begin +if startExperimenting + greedySolutionExp = greedyFindCombo(fridgeList, recipeDictExp, numRecipesExp) + SASolutionExp = SAFindCombo(greedySolutionExp, fridgeList, recipeDictExp, numRecipesExp, randRecipeExp, kT=kTExp, r=rExp, Tmax=TmaxExp, Tmin=TminExp, tabuLength=tabuLengthExp) + + md""" + greedy output: $([recipe * ", " for recipe in keys(greedySolutionExp)])\ + SA output: $([recipe * ", " for recipe in keys(SASolutionExp)]) + """ +else + md"Check the `Start experimenting` checkbox to see the output of your parameter combination." +end +end + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +Cascadia = "54eefc05-d75b-58de-a785-1a3403f0919f" +Gumbo = "708ec375-b3d6-5a57-a7ce-8257bf98657a" +HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" +JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +PlutoUI = "7f904dfe-b85e-4ff6-b463-dae2292396a8" + +[compat] +CSV = "~0.10.2" +Cascadia = "~1.0.1" +Gumbo = "~0.8.0" +HTTP = "~0.9.17" +JLD2 = "~0.4.18" +PlutoUI = "~0.7.32" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +[[AbstractPlutoDingetjes]] +deps = ["Pkg"] +git-tree-sha1 = "8eaf9f1b4921132a4cff3f36a1d9ba923b14a481" +uuid = "6e696c72-6542-2067-7265-42206c756150" +version = "1.1.4" + +[[AbstractTrees]] +git-tree-sha1 = "03e0550477d86222521d254b741d470ba17ea0b5" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.3.4" + +[[ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" + +[[Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" + +[[Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" + +[[CSV]] +deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings"] +git-tree-sha1 = "9519274b50500b8029973d241d32cfbf0b127d97" +uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" +version = "0.10.2" + +[[Cascadia]] +deps = ["AbstractTrees", "Gumbo"] +git-tree-sha1 = "95629728197821d21a41778d0e0a49bc2d58ab9b" +uuid = "54eefc05-d75b-58de-a785-1a3403f0919f" +version = "1.0.1" + +[[CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.0" + +[[ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "024fe24d83e4a5bf5fc80501a314ce0d1aa35597" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.11.0" + +[[Compat]] +deps = ["Base64", "Dates", "DelimitedFiles", "Distributed", "InteractiveUtils", "LibGit2", "Libdl", "LinearAlgebra", "Markdown", "Mmap", "Pkg", "Printf", "REPL", "Random", "SHA", "Serialization", "SharedArrays", "Sockets", "SparseArrays", "Statistics", "Test", "UUIDs", "Unicode"] +git-tree-sha1 = "44c37b4636bc54afac5c574d2d02b625349d6582" +uuid = "34da2185-b29b-5c13-b0c7-acf172513d20" +version = "3.41.0" + +[[DataAPI]] +git-tree-sha1 = "cc70b17275652eb47bc9e5f81635981f13cea5c8" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.9.0" + +[[DataStructures]] +deps = ["Compat", "InteractiveUtils", "OrderedCollections"] +git-tree-sha1 = "3daef5523dd2e769dad2365274f760ff5f282c7d" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.18.11" + +[[DataValueInterfaces]] +git-tree-sha1 = "bfc1187b79289637fa0ef6d4436ebdfe6905cbd6" +uuid = "e2d170a0-9d28-54be-80f0-106bbe20a464" +version = "1.0.0" + +[[Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" + +[[DelimitedFiles]] +deps = ["Mmap"] +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" + +[[Distributed]] +deps = ["Random", "Serialization", "Sockets"] +uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b" + +[[Downloads]] +deps = ["ArgTools", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" + +[[FileIO]] +deps = ["Pkg", "Requires", "UUIDs"] +git-tree-sha1 = "67551df041955cc6ee2ed098718c8fcd7fc7aebe" +uuid = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" +version = "1.12.0" + +[[FilePathsBase]] +deps = ["Compat", "Dates", "Mmap", "Printf", "Test", "UUIDs"] +git-tree-sha1 = "04d13bfa8ef11720c24e4d840c0033d145537df7" +uuid = "48062228-2e41-5def-b9a4-89aafe57970f" +version = "0.9.17" + +[[FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "335bfdceacc84c5cdf16aadc768aa5ddfc5383cc" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.4" + +[[Future]] +deps = ["Random"] +uuid = "9fa8497b-333b-5362-9e8d-4d0656e87820" + +[[Gumbo]] +deps = ["AbstractTrees", "Gumbo_jll", "Libdl"] +git-tree-sha1 = "e711d08d896018037d6ff0ad4ebe675ca67119d4" +uuid = "708ec375-b3d6-5a57-a7ce-8257bf98657a" +version = "0.8.0" + +[[Gumbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"] +git-tree-sha1 = "29070dee9df18d9565276d68a596854b1764aa38" +uuid = "528830af-5a63-567c-a44a-034ed33b8444" +version = "0.10.2+0" + +[[HTTP]] +deps = ["Base64", "Dates", "IniFile", "Logging", "MbedTLS", "NetworkOptions", "Sockets", "URIs"] +git-tree-sha1 = "0fa77022fe4b511826b39c894c90daf5fce3334a" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "0.9.17" + +[[Hyperscript]] +deps = ["Test"] +git-tree-sha1 = "8d511d5b81240fc8e6802386302675bdf47737b9" +uuid = "47d2ed2b-36de-50cf-bf87-49c2cf4b8b91" +version = "0.0.4" + +[[HypertextLiteral]] +git-tree-sha1 = "2b078b5a615c6c0396c77810d92ee8c6f470d238" +uuid = "ac1192a8-f4b3-4bfe-ba22-af5b92cd3ab2" +version = "0.9.3" + +[[IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "f7be53659ab06ddc986428d3a9dcc95f6fa6705a" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.2" + +[[IniFile]] +deps = ["Test"] +git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8" +uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f" +version = "0.5.0" + +[[InlineStrings]] +deps = ["Parsers"] +git-tree-sha1 = "8d70835a3759cdd75881426fced1508bb7b7e1b6" +uuid = "842dd82b-1e85-43dc-bf29-5d0ee9dffc48" +version = "1.1.1" + +[[InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" + +[[IteratorInterfaceExtensions]] +git-tree-sha1 = "a3f24677c21f5bbe9d2a714f95dcd58337fb2856" +uuid = "82899510-4779-5014-852e-03e436cf321d" +version = "1.0.0" + +[[JLD2]] +deps = ["DataStructures", "FileIO", "MacroTools", "Mmap", "Pkg", "Printf", "Reexport", "TranscodingStreams", "UUIDs"] +git-tree-sha1 = "39f22411266cdd1621092c762a3f0648dbdc8433" +uuid = "033835bb-8acc-5ee8-8aae-3f567f8a3819" +version = "0.4.18" + +[[JLLWrappers]] +deps = ["Preferences"] +git-tree-sha1 = "22df5b96feef82434b07327e2d3c770a9b21e023" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.4.0" + +[[JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "8076680b162ada2a031f707ac7b4953e30667a37" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.2" + +[[LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" + +[[LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "MbedTLS_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" + +[[LibGit2]] +deps = ["Base64", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" + +[[LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "MbedTLS_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" + +[[Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" + +[[LinearAlgebra]] +deps = ["Libdl"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" + +[[Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" + +[[MacroTools]] +deps = ["Markdown", "Random"] +git-tree-sha1 = "3d3e902b31198a27340d0bf00d6ac452866021cf" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.9" + +[[Markdown]] +deps = ["Base64"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" + +[[MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "Random", "Sockets"] +git-tree-sha1 = "1c38e51c3d08ef2278062ebceade0e46cefc96fe" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.0.3" + +[[MbedTLS_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" + +[[Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" + +[[MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" + +[[NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" + +[[OrderedCollections]] +git-tree-sha1 = "85f8e6578bf1f9ee0d11e7bb1b1456435479d47c" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.4.1" + +[[Parsers]] +deps = ["Dates"] +git-tree-sha1 = "92f91ba9e5941fc781fecf5494ac1da87bdac775" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.2.0" + +[[Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" + +[[PlutoUI]] +deps = ["AbstractPlutoDingetjes", "Base64", "ColorTypes", "Dates", "Hyperscript", "HypertextLiteral", "IOCapture", "InteractiveUtils", "JSON", "Logging", "Markdown", "Random", "Reexport", "UUIDs"] +git-tree-sha1 = "ae6145ca68947569058866e443df69587acc1806" +uuid = "7f904dfe-b85e-4ff6-b463-dae2292396a8" +version = "0.7.32" + +[[PooledArrays]] +deps = ["DataAPI", "Future"] +git-tree-sha1 = "db3a23166af8aebf4db5ef87ac5b00d36eb771e2" +uuid = "2dfb63ee-cc39-5dd5-95bd-886bf059d720" +version = "1.4.0" + +[[Preferences]] +deps = ["TOML"] +git-tree-sha1 = "2cf929d64681236a2e074ffafb8d568733d2e6af" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.2.3" + +[[Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" + +[[REPL]] +deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" + +[[Random]] +deps = ["Serialization"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" + +[[Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "838a3a4188e2ded87a4f9f184b4b0d78a1e91cb7" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.0" + +[[SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" + +[[SentinelArrays]] +deps = ["Dates", "Random"] +git-tree-sha1 = "15dfe6b103c2a993be24404124b8791a09460983" +uuid = "91c51154-3ec4-41a3-a24f-3f23e20d615c" +version = "1.3.11" + +[[Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" + +[[SharedArrays]] +deps = ["Distributed", "Mmap", "Random", "Serialization"] +uuid = "1a1011a3-84de-559e-8e89-a11a2f7dc383" + +[[Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" + +[[SparseArrays]] +deps = ["LinearAlgebra", "Random"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + +[[Statistics]] +deps = ["LinearAlgebra", "SparseArrays"] +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[[TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" + +[[TableTraits]] +deps = ["IteratorInterfaceExtensions"] +git-tree-sha1 = "c06b2f539df1c6efa794486abfb6ed2022561a39" +uuid = "3783bdb8-4a98-5b6b-af9a-565f29a5fe9c" +version = "1.0.1" + +[[Tables]] +deps = ["DataAPI", "DataValueInterfaces", "IteratorInterfaceExtensions", "LinearAlgebra", "TableTraits", "Test"] +git-tree-sha1 = "bb1064c9a84c52e277f1096cf41434b675cd368b" +uuid = "bd369af6-aec1-5ad0-b16a-f7cc5008161c" +version = "1.6.1" + +[[Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" + +[[Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" + +[[TranscodingStreams]] +deps = ["Random", "Test"] +git-tree-sha1 = "216b95ea110b5972db65aa90f88d8d89dcb8851c" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.9.6" + +[[URIs]] +git-tree-sha1 = "97bbe755a53fe859669cd907f2d96aee8d2c1355" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.3.0" + +[[UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" + +[[Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" + +[[WeakRefStrings]] +deps = ["DataAPI", "InlineStrings", "Parsers"] +git-tree-sha1 = "c69f9da3ff2f4f02e811c3323c22e5dfcb584cfa" +uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5" +version = "1.4.1" + +[[Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" + +[[nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" + +[[p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +""" + +# ╔═╡ Cell order: +# ╟─ae625e40-7f56-11ec-3cd5-d1244e23a3ba +# ╟─06db3cc2-1680-49b9-a054-281a6b35205a +# ╟─3ad1af72-7096-4106-a3c7-47326695e2cb +# ╠═8e58f807-1db9-495e-8679-430f953292ea +# ╟─264b1eb5-c421-4a69-a55c-8fbbbb7715d1 +# ╠═644c2809-5b2b-4426-8deb-f952372a8438 +# ╟─1752889a-513a-4620-add4-a9ca7fa92c8e +# ╟─a65c0925-7a67-4da3-9a6d-2ade8d3529e9 +# ╠═ace6ecfa-319b-48f3-a675-51b4bc26abd0 +# ╟─954fe2b3-171b-43ad-a1df-cbb3414988de +# ╠═6f1d6e44-30a1-42ee-b888-4cbb105b5348 +# ╟─eee53924-fd20-44d8-a3eb-a2af9f9112b9 +# ╟─7315f676-0d0e-43ae-8231-b6274dc5c8de +# ╠═3ed830c9-e3f7-4632-9ff3-cdd48c00f315 +# ╟─134a2dd7-69d2-449e-82ab-5bfff90ede1b +# ╠═7365d57b-fd33-46af-9dcd-2c07908f64a3 +# ╟─b6205bd3-41da-45e0-81f1-a480a3f0e737 +# ╠═9bfc9f3d-bc05-4c15-8b4c-2cfce3507afc +# ╟─6af2a6dd-ba7e-470e-8915-12ae6eff8357 +# ╟─656f211d-d1e8-4325-a12b-b89eca0351da +# ╠═cd5458a5-2136-4559-b4e5-2d91abea870c +# ╟─bf2725c1-55e6-4c6a-9b83-53f5984cae75 +# ╠═fd02d76a-0ae6-4869-96bb-516c536fb91d +# ╟─34544e10-e4a1-4cfe-b0bc-43f8927bb19c +# ╟─312fabf7-7d11-4bac-b52b-14de82d13e73 +# ╠═3569550e-0157-4d7a-8cda-4df327c92b81 +# ╟─15a97461-7096-4ea0-acbc-edbc5db79cde +# ╟─d49cf674-088f-47a1-9795-7641b3f01cd1 +# ╠═11b589ff-3a09-4395-921a-872feb190022 +# ╟─86f03d01-4f97-4487-9683-38f8dc9d826a +# ╟─70ffadd9-80d4-4eb9-92d9-e3d5ffa50074 +# ╟─e10c7ddf-aef0-470a-be22-ac250d84fb7e +# ╟─c51f2b76-fe4c-4c29-87dc-c7784e5200c2 +# ╟─09d6393c-9b14-4401-babe-708a88ef0a16 +# ╟─87b72838-260f-4ffb-ac79-03860165afa1 +# ╟─adde7105-4a4a-453b-a7a8-9c9471dc29d3 +# ╟─89e58385-4db9-4ae3-9495-d8a2e63f2d72 +# ╟─a6e77624-606a-49ea-941d-8180cbd94a27 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002 diff --git a/project ideas.md b/project ideas.md deleted file mode 100644 index 1c74e8dc..00000000 --- a/project ideas.md +++ /dev/null @@ -1,59 +0,0 @@ -# Project ideas - -Here are some suggestions for projects to do. Either pick something from this list or use it as inspiration. - -If you choose a project use strikthrought to remove it -> ~~crossed~~ - -## Teaching 👨‍🏫 - -Explain an optimization method not covered in the course though an interactive notebook. Gold standard is [Distill](https://distill.pub/2017/momentum/) - -- Conjugate gradient descent -- [Cross-entropy method](https://en.wikipedia.org/wiki/Cross-Entropy_Method) -- Levenberg–Marquardt algorithm (for nonlinear regression and inverse kinematics) -- training adverserial neural networks -- Q-learning -- link between [fractals and the Newton method](https://www.youtube.com/watch?v=-RdOwhmqP5s) -- [MAP-elites](https://arxiv.org/abs/1504.04909) -- [information-geometric opitmization algorithms](http://www.cmap.polytechnique.fr/~anne.auger/teaching-slides/05_DFO-IGO.pdf) -- the big deal with multi-objective optimization -- Thompson sampling: optimal gambling 🎲 -- Christofides' algorithm for solving the TSP -- ... - -## Tools 🔧 - -Solve a problem using a method from the course or a new one. - -- time table scheduling -- my own RNA folder! -- color transfer using optimal transportation done right -- analytic solution of a ODE using genetic programming -- graph matching using simulated annealing -- optimal task for every person using the Hungarian algorithm -- finding negative cycles using the Bellman–Ford algorithm -- multiple sequence alignment using Genetic Algorithms -- using MaxEnt to find the best probability distribution -- finding design patterns in phage lytic proteins using evolutionary algorithms -- homebrew SVM -- recovering images from a few pixels using [compressed sensing](https://www.youtube.com/watch?v=SbU1pahbbkc) (L1-norm minimization) -- visalizing complex networks using quadratic optimization -- ... - -## Research projects ⚗️ - -Experiment and learn. Reproduce the results of a paper (in simplified form). Show me something exciting about optimization! Use [visuals](https://www.youtube.com/watch?v=gvck7ssg9dE) to tell a story. - -- [fitting a neural network by optimizing one weigth at a time](http://arxiv.org/abs/2005.05955) -- search paths of Hill Climbing: an exhaustive search -- [convex restrictions in physical design](https://stanford.edu/~boyd/papers/pdf/cvx_restrictions_physical_design.pdf) -- non-covergence as a regularization mechanism in machine learning -- improving gradient descent or learning using [gradient startvation](https://mohammadpz.github.io/GradientStarvation.html) -- improving the convergence of evolutionary algorithms by varying the objective function -- the optimization landscape of ANNs -- comparision of optimizers for deep learning -- Newton methods for lazy bums: is the diagonal sufficient? -- utilitarian ethics: optimization for the Good Place? -- [Maze solver using bacteria](https://www.technologyreview.com/2021/11/09/1039107/e-coli-maze-solving-biocomputer/?s=03) (simulation of an experiment) -- [discovering natural laws](https://www.science.org/doi/abs/10.1126/science.1165893) -- ... diff --git a/src/Fridge.jl b/src/Fridge.jl new file mode 100644 index 00000000..f4f4695b --- /dev/null +++ b/src/Fridge.jl @@ -0,0 +1,447 @@ +module Fridge + +include("recipeWebscraper.jl") + +using .recipeWebscraper, JLD2 + +export checkIngredients, greedyFindCombo, findBestRecipe, randomCombo, Neighbour, SAFindCombo, scrapeRecipe, loadRecipeDBCSV + +#================================================== + CHECK INGREDIENTS FUNCTIONS +==================================================# + +function createIngredientDatabase(recipeDict) + # create an unique vector of all ingredients used in the recipe database + ingredientList = [] + for ingredients in values(recipeDict) + append!(ingredientList,ingredients) + end + ingredientList = unique(ingredientList) + + return ingredientList +end + +""" + checkIngredients(fridgeList, ingredientList) + +This function checks if the foods in your fridge are also found in the ingredient overview +of the recipe database. If they are not found in the database, regex is used to find possible alternatives. +For instance cheese may be replaced by swiss cheese. + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- ingredientList: A list containing all the different ingredients that are used in the recipe database. +- test: An optional Boolean argument. When true the function simulates user input. + +## Output: +- fridgeList: The (adapted) given fridgeList + +""" +function checkIngredients(fridgeList,ingredientList; test=false) + + + print("Checking if the food in your fridge is found in our database.\n\n") + + # check if the ingredients in the fridge are found in the database + for food in fridgeList + if food in ingredientList + print("Found $food in the ingredient database.\n") + else + print("Did not find $food in the ingredient database.\n\n") + if any(occursin.(food,ingredientList)) + print("Possible alternatives in database:\n") + alternatives = ingredientList[occursin.(food,ingredientList)] + for (indexNum, alternative) in enumerate(alternatives) + print("[$indexNum] $alternative\n") + end + print("If you want to take an alternative type its number, else type no.\n") + if !test + # read wanted alternative number + answer = readline() + else + # simulate user input + answer = string(rand(1:length(alternatives))) + print("The computer typed $answer.") + end + if answer != "no" + correctInput = false + while !correctInput + try + answer = parse(Int64,answer) + correctInput = true + catch + print("Please only type the number of the alternative, eg. 1\n") + answer = readline() + end + end + fridgeList[fridgeList .== food] .= alternatives[answer] + print("Replaced $food with $(alternatives[answer])\n") + end + else + print("Did not find any alternative in the ingredient database.\n") + end + end + end + print("Done checking ingredients.\n\n") + + return fridgeList +end + +#================================================== + OBJECTIVE FUNCTIONS +==================================================# + +# objective function used to score a single recipe +fridgeObjective(x::Array{Int64}) = sum(x[1:end-1]) + 6*sum(x[1:end-1] .== 0) + 2*x[end] + +# objective function used to score a combination of recipes +fridgeObjective(x) = compatible(x) ? sum(sum(x) .== 0)*6 + sum(x)[end]*2 : Inf + +#================================================== + GREEDY ALGORITHM +==================================================# + +""" + greedyFindCombo(fridgeList, recipeDict, numRecipes) + +This function uses greedy search to find a good combination of recipes that match your fridge content. +It ranks all recipes based on the following formula + +``score = (ingredients from fridge used) + 6*(ingredients in fridge remaining) + 2*(extra ingredients needed)`` + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- recipeDict: A dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- numRecipes: The max amount of recipes that a combo should contain. + +## Output: +- bestCombo: A dictionary containing the best found combination of recipes. + +""" +function greedyFindCombo(fridgeList, recipeDict, numRecipes) + + bestCombo = Dict() + ingredientsArray = [] + namesArray = [] + + for (name,ingredients) in recipeDict + push!(namesArray,name) + push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients)) + end + + for i = 1:numRecipes + + bestOrder = sortperm(ingredientsArray, by=fridgeObjective) + ingredientsArray = ingredientsArray[bestOrder] + namesArray = namesArray[bestOrder] + + # break if all recipes that are left, don't use anything from the fridge + if all(ingredientsArray[1][1:end-1] .== 0) + break + end + + tempRecipeName = namesArray[1] + bestCombo[tempRecipeName] = ingredientsArray[1] + + # break if all ingredients are used + if all(isone.(sum(values(bestCombo)))) + break + end + + namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0] + ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& bestCombo[tempRecipeName][1:end-1] ) == 0] + + # break if there are no recipes left + if isempty(namesArray) + break + end + + end + + return bestCombo +end + +#================================================== + NEIGBOURHOODS +==================================================# + +""" + randomCombo(fridgeList, recipeDict, numRecipes) + +This function gives a random combination of recipes from the provided recipe dictionary. + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- recipeDict: A dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- numRecipes: The max amount of recipes that a combo should contain. + +## Output: +- randCombo: A dictionary containing a random combination of recipes. +""" +function randomCombo(fridgeList, recipeDict, numRecipes) + + randCombo = Dict() + ingredientsArray = [] + namesArray = [] + + for (name,ingredients) in recipeDict + push!(namesArray,name) + push!(ingredientsArray, recipeToNumVector(fridgeList, ingredients)) + end + + for i = 1:numRecipes + + randIndex = rand(1:length(namesArray)) + tempRecipeName = namesArray[randIndex] + randCombo[tempRecipeName] = ingredientsArray[randIndex] + + # break if all ingredients are used + if all(isone.(sum(values(randCombo)))) + break + end + + namesArray = [name for (name, ingredientList) in zip(namesArray, ingredientsArray) if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0] + ingredientsArray = [ingredientList for ingredientList in ingredientsArray if sum(ingredientList[1:end-1] .& randCombo[tempRecipeName][1:end-1] ) == 0] + + # break if there are no recipes left + if isempty(namesArray) + break + end + + end + + return randCombo +end + +""" + Neighbour(curSolution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe) + +This is a function that looks for a neighbour of the current solution. This function is used in the simulated annealing algorithm. +It also uses a tabulist to stimulate the use of new solutions. + +## Input: +- curSolution: The current best combination of recipes, given as a dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- fridgeList: A list containing the different foods in your fridge as a string. +- recipeDict: A dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- numRecipes: The max amount of recipes that a combo should contain. +- tabuList: A list of recipes that should not be used in the found neighbour. +- randRecipe: A Boolean `true` or `false` value. When `true`, random recipes are used for the neighbour. + +## Output: +- neighbour: A dictionary containing a combination of recipes. + +""" +function Neighbour(curSolution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe) + + toRemove = rand(curSolution)[1] + # adapt the fridgeList so that only ingredients from the removed ingredient are available + tempFridgeList = copy(fridgeList) + for recipe in keys(curSolution) + if recipe != toRemove + tempFridgeList = [i for i in fridgeList if !in(i,recipeDict[recipe])] + numRecipes -= 1 + end + end + # adapt the recipeDict and use greedy search to find a new solution + tempRecipeDict = copy(recipeDict) + for recipe in keys(curSolution) + delete!(tempRecipeDict,recipe) + end + + for recipe in tabuList + try delete!(tempRecipeDict,recipe) + catch e + end + end + + if randRecipe + neighbour = randomCombo(tempFridgeList, tempRecipeDict, numRecipes) + else + neighbour = greedyFindCombo(tempFridgeList, tempRecipeDict, numRecipes) + end + + # correct recipe vectors + for recipe in keys(neighbour) + neighbour[recipe] = recipeToNumVector(fridgeList,recipeDict[recipe]) + end + + # here combine the two dictionaries + tempCurSolution = copy(curSolution) + delete!(tempCurSolution,toRemove) + neighbour = merge(neighbour,tempCurSolution) + + return neighbour +end + +#================================================== + SIMULATED ANNEALING ALGORITHM +==================================================# + +""" + SAFindCombo(curSolution, fridgeList, recipeDict, numRecipes, randRecipe; kT=100, r=0.75, Tmax=4, Tmin=1, tabuLength=3) + +This function uses simulated annealing to find a better combination of recipes that match your fridge content. +It starts with the current solution and tries to improve this. + +## Input: +- curSolution: The current best combination of recipes, given as a dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- fridgeList: A list containing the different foods in your fridge as a string. +- recipeDict: A dictionary in which the keys are the recipe names and the responding values are a list of the needed ingredients. +- numRecipes: The max amount of recipes that a combo should contain. +- tabuList: A list of recipes that should not be used in the found neighbour. +- randRecipe: A Boolean `true` or `false` value. When `true`, random recipes are used for the neighbour. + +## Optional Inputs: +- kT: repetitions per temperature +- r: cooling rate +- Tmax: maximal temperature to start +- Tmin: minimal temperature to end +- tabuLength: Number of cycli that recipe needs to be blocked + +## Output: +- solution: A dictionary containing the best found combination of recipes. + +""" +function SAFindCombo(curSolution, fridgeList, recipeDict, numRecipes, randRecipe; + kT=100, # repetitions per temperature + r=0.75, # cooling rate + Tmax=4, # maximal temperature to start + Tmin=1, # minimal temperature to end + tabuLength=3) # number of cycli that recipe needs to be blocked + + @assert 0 < Tmin < Tmax "Temperatures should be positive" + @assert 0 < r < 1 "cooling rate is between 0 and 1" + + + solution = curSolution + if isempty(solution) + print("There are no recipes in the database matching your fridge.") + T = Tmin-1 + else + obj = fridgeObjective([i for i in values(solution)]) + T = Tmax + end + + tabuList = String[i for i in keys(curSolution)] + + while T > Tmin + if isempty(solution) + print("There are no recipes in the database matching your fridge.") + break + end + print("T = $T \n") + # repeat kT times + for i in 1:kT + sn = Neighbour(solution, fridgeList, recipeDict, numRecipes, tabuList, randRecipe) # random neighbor + obj_sn = fridgeObjective([i for i in values(sn)]) + # if the neighbor improves the solution, keep it + # otherwise accept with a probability determined by the + # Metropolis heuristic + if obj_sn < obj || rand() < exp(-(obj_sn-obj)/T) + solution = sn + obj = obj_sn + end + + for recipe in keys(sn) + if !in(recipe, tabuList) + if length(tabuList) < tabuLength + pushfirst!(tabuList,recipe) + else + pop!(tabuList) + pushfirst!(tabuList,recipe) + end + end + end + end + + # decay temperature + T *= r + end + + return solution +end + +#================================================== + OVERVIEW FUNCTION +==================================================# + +""" + findBestRecipe(fridgeList, csvPath; numRecipes=3, randRecipe=false) + +This function combines all other functions. This function checks if your ingredients are in the database, +if not it offers possible alternatives. Next it uses simulated annealing to find a better recipe combination. + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- dataPath: A relative or absolute path to a .csv or .jld2 file containing the recipe database. + +## Optional Inputs: +- numRecipes: The max amount of recipes that a combo should contain. +- randRecipe: A Boolean `true` or `false` value. When `true`, random recipes are used to find the neighbour in simulated annealing. +- testmode: An optional Boolean argument. When true the function simulates user input. + +## Output: +- SASolution: A dictionary containing the best found combination of recipes. + +""" +function findBestRecipe(fridgeList, dataPath; numRecipes=3, randRecipe=false, testmode=false) + + + # load the recipe dictionary from the db file + recipeDict = dataPath[end-3:end] == ".csv" ? loadRecipeDBCSV(dataPath) : load(dataPath) + + # create a list of all ingredients in your database + ingredientList = createIngredientDatabase(recipeDict) + + # check for every food in your fridge if it's in the database. If not check if their are alternatives. + fridgeList = checkIngredients(fridgeList, ingredientList, test=testmode) + + # find the best greedy recipe + greedySolution = greedyFindCombo(fridgeList, recipeDict, numRecipes) + print("greedySolution = $greedySolution\n") + + # find the best recipe with SA + SASolution = SAFindCombo(greedySolution, fridgeList, recipeDict, numRecipes, randRecipe) + + # print the solution + print("The best recipes to make are:\n\n") + for recipeName in keys(SASolution) + print("$(recipeName) : $(recipeDict[recipeName])\n") + end + + return SASolution +end + +#================================================== + SUPPORTING FUNCTIONS +==================================================# + +""" + recipeToNumVector(fridgeList,ingredientList) + +This function changes an ingredients list of a recipe to a numeric vector. This vector has a length equal +to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe, that position in the vector is a 1. +If not it is a 0. The last index of the vector contains the amount of extra ingredients needed. + +## Input: +- fridgeList: A list containing the different foods in your fridge as a string. +- ingredientList: A list containing all the ingredients used in a specific recipe. + +## Output: +- numVector: This vector has a length equal to the number of foods in your fridge plus one. If a certain food from the fridge is used in the recipe, +that position in the vector is a 1. If not it is a 0. The last index of the vector contains the amount of extra ingredients needed. + +""" +function recipeToNumVector(fridgeList,ingredientList) + + numVector = zeros(Int64,length(fridgeList)+1) + for i in 1:length(fridgeList) + numVector[i] = fridgeList[i] in ingredientList ? 1 : 0 + end + numVector[end] = length(ingredientList) - sum(numVector) + return numVector +end + +compatible(x) = !any(sum(x)[1:end-1] .>= 2) # checks if two recipes use a same ingredient + +end # module \ No newline at end of file diff --git a/src/STMOZOO.jl b/src/STMOZOO.jl deleted file mode 100644 index 34b94d12..00000000 --- a/src/STMOZOO.jl +++ /dev/null @@ -1,8 +0,0 @@ -module STMOZOO - - -# execute your source file and export the module you made -include("example.jl") -export Example - -end # module diff --git a/src/example.jl b/src/example.jl deleted file mode 100644 index 7fc131da..00000000 --- a/src/example.jl +++ /dev/null @@ -1,61 +0,0 @@ -# Michiel Stock -# Example of a source code file implementing a module. - - -# all your code is part of the module you are implementing -module Example - -# you have to import everything you need for your module to work -# if you use a new package, don't forget to add it in the package manager -using LinearAlgebra - -# export all functions that are relevant for the user -export solve_quadratic_system, quadratic_function - -""" - solve_quadratic_system(P, q, r=0; testPD=false) - -Find the minimizer of a canonical quadratic system: - -``f(x) = 0.5xᵀ P x + x ⋅ q + r`` - -with `P` a positive scalar or a positive-definite n x n matrix and `q` a scalar or -a n x n vector. The intercept `r` can optionally be given but does not influence -the minimizer. One can use the keyword argument `testPD` to test if `P` is positive definite, -though this comes at a computational cost. - -## Examples - -Scalar case: - -```julia-repl -julia> solve_quadratic_system(8, -4, 3) -0.5 -``` - -Vector case: - -```julia-repl -julia> P = [3 1; 1 2]; - -julia> q = [0.5, -2]; - -julia> solve_quadratic_system(P, q) -2-element Array{Float64,1}: - -0.6 - 1.2999999999999998 -``` -""" -function solve_quadratic_system(P, q, r=0; testPD=false) - testPD && @assert isposdef(P) "Provided P is not positive definite" - return - P \ q -end - -""" - quadratic_function(P, q, r) - -Give the paramters of a canonical quatratic function and returns a function. -""" -quadratic_function(P, q, r) = x -> 0.5x' * P * x + q ⋅ x + r - -end \ No newline at end of file diff --git a/src/recipeWebscraper.jl b/src/recipeWebscraper.jl new file mode 100644 index 00000000..13d7525b --- /dev/null +++ b/src/recipeWebscraper.jl @@ -0,0 +1,83 @@ + +module recipeWebscraper + +using HTTP, Gumbo, Cascadia, CSV + +export scrapeRecipe, loadRecipeDBCSV + +# recipe number goes from 2610 to 149191 +# downloaded everything until 18788 + +""" + scrapeRecipe(scrapeBegin,scrapeEnd,csvPath) + +Download recipetitles and their corresponding ingredients from the recipe database of cosylab as a dictionary. +The recipes are downloaded based on their recipe number. This number can range from 2610 to 149191. +To get a recipenumber one should look at the last number of the url of a certain recipe. +For example, the recipe for 'Speculoosbavarois' is number 106585. +The recipes get automatically saved in a csv file where the first column is the recipetitle and the second column +is the list of ingredients. + +## Input: +- scrapeBegin: The recipe number where the iteration should begin. +- scrapeEnd: The recipe number after which the iteration should and. +- csvPath: The path where the csv file is stored. + +## Examples: + +The example below downloads the recipes 2700 to 2702 and +stores them in the csv file 'recipedb.csv' in the current folder. + +```julia-repl +julia> scrapeRecipe(2700,2702,"./recipedb.csv") +``` +""" +function scrapeRecipe(scrapeBegin,scrapeEnd,csvPath) + + for i = scrapeBegin:scrapeEnd + # get the webpage + htmlText = HTTP.request("GET","https://cosylab.iiitd.edu.in/recipedb/search_recipeInfo/$i") + htmlBody = parsehtml(String(htmlText.body)) + + # get the needed elements out of the webpage + recipeTitle = eachmatch(Selector("h3"),htmlBody.root)[1].children[1].text + print("recipe number $i : $recipeTitle\n") + ingredientTab = eachmatch(Selector("#ingredient_nutri"),htmlBody.root) + ingredientTabel = eachmatch(Selector("#myTable"),ingredientTab[1]) + ingredientLinks = eachmatch(Selector("td a"),ingredientTabel[1]) + ingredientList = [] + for ingredientLink in ingredientLinks + try + append!(ingredientList, [ingredientLink.children[1].text]) + catch y + end + end + + # write the data to the DB + if isfile(csvPath) + CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = true) + else + CSV.write(csvPath, Dict(recipeTitle => ingredientList), append = false) + end + + end + print("done") +end + + +function loadRecipeDBCSV(csvPath) + # read the dictionary from the csv file + print("Loading the recipe database.\n") + tempDict = CSV.File(csvPath) |> Dict + recipeDict = Dict() + + # parse the ingredient list to the right format + print("Parsing ingredients to right format.\n\n") + for recipe in keys(tempDict) + ingredientList = tempDict[recipe] + recipeDict[recipe] = eval(Meta.parse(ingredientList)) + end + return recipeDict +end + +end \ No newline at end of file diff --git a/test/example.jl b/test/example.jl deleted file mode 100644 index d554036c..00000000 --- a/test/example.jl +++ /dev/null @@ -1,51 +0,0 @@ -# example of a unit test in Julia -# runs a test for a certain module -# to run the tests, first open the package manager (`]` in REPL), -# activate the project if not done so and then enter `test` - -# wrap all your tests and subgroups in a `@testset` block -@testset "Example" begin - using STMOZOO.Example # load YOUR module - - @testset "solve quadratic" begin - # test some cases - @test solve_quadratic_system(8.0, -2.0, 3.0) == 0.25 - - # test for type - @test solve_quadratic_system(7.0, 27.4, 3.0) isa Number - - # test for type stability - @test solve_quadratic_system(1//2 , -2//1, 3//1) isa Rational - - P = [10 1; 1 5] - q = [100, -7] - - # test for a result - # use ≈ (`\approx`) to check approximate equality - # useful for rounding errors - # NOTE: does NOT work on ≈ 0! - @test solve_quadratic_system(P, q) ≈ - P \ q - @test solve_quadratic_system(P, q, testPD=true) isa Vector - - # test if a certain error is thrown - @test_throws AssertionError solve_quadratic_system(-P, q, testPD=true) - end - - @testset " quadratic function" begin - f_scalar = quadratic_function(3, 4, 8) - - @test f_scalar isa Function - @test f_scalar(2) isa Number - @test f_scalar(2) ≈ 0.5 * 3 * 2^2 + 4 * 2 + 8 - - P = [9.0 -2; -2 3] - q = [-3, -4] - r = -π - x = [-2.0, 2.8] - - f_vect = quadratic_function(P, q, r) - @test f_vect isa Function - @test f_vect(x) isa Number - @test f_vect(x) ≈ 0.5x' * P * x + q' * x + r - end -end \ No newline at end of file diff --git a/test/fridgeTest.jl b/test/fridgeTest.jl new file mode 100644 index 00000000..2344f1f5 --- /dev/null +++ b/test/fridgeTest.jl @@ -0,0 +1,97 @@ +# testset for the Fridge.jl package +@testset "Fridge" begin + using Fridge + + testList = ["cheese","potato","tomato","cabbage"] + testDict = Dict("fries" => ["potato","salt"], + "macaroni" => ["cheese", "salt", "jambon"], + "salad" => ["cabbage","tomato","oil","horseradish"], + "chocolatemilk" => ["chocolate","milk"]) + + testDict2 = Dict("fries" => ["potato","salt"], + "macaroni" => ["cheese", "salt", "jambon"]) + + testDict3 = Dict("fries" => ["potato","salt"], + "macaroni" => ["cheese", "salt", "jambon"], + "salad" => ["cabbage","tomato","oil","horseradish"]) + + testDict4 = Dict("fries" => ["potato","salt"], + "macaroni" => ["cheese", "salt", "jambon"], + "salad" => ["cabbage","tomato","oil","horseradish"], + "chocolatemilk" => ["chocolate","milk"], + "waffle" => ["sugar", "eggs", "milk"], + "thea" => ["herbs", "water"], + "coffee" => ["coffeebeans", "water"]) + + testSolution = Dict("salad" => [0,0,1,1,2], "fries" => [0,1,0,0,1]) + + @testset "checkIngredients" begin + # check if all ingredients are passed if no replacement is needed + @test checkIngredients(testList,["cheese","salt","tomato","chocolate"]) == testList + + # test for type + @test checkIngredients(testList,["cheese","salt","tomato","chocolate"]) isa Vector{String} + end + + @testset "greedyFindCombo" begin + # test max number of recipes end + @test greedyFindCombo(testList, testDict, 2) == Dict("salad" => [0,0,1,1,2], "fries" => [0,1,0,0,1]) + + # test all ingredients used end + @test greedyFindCombo(testList, testDict, 3) == Dict("macaroni" => [1,0,0,0,2], "salad" => [0,0,1,1,2], "fries" => [0,1,0,0,1]) + + # test no recipes left end + @test greedyFindCombo(testList, testDict2, 3) == Dict("macaroni" => [1,0,0,0,2], "fries" => [0,1,0,0,1]) + + # test no recipes left that use ingredients from the fridge + @test greedyFindCombo(testList, testDict, 4) == Dict("macaroni" => [1,0,0,0,2], "salad" => [0,0,1,1,2], "fries" => [0,1,0,0,1]) + end + + @testset "randomCombo" begin + # test all ingredients used end + @test randomCombo(testList, testDict3, 3) == Dict("macaroni" => [1,0,0,0,2], "salad" => [0,0,1,1,2], "fries" => [0,1,0,0,1]) + + # test no recipes left end + @test randomCombo(testList, testDict2, 3) == Dict("macaroni" => [1,0,0,0,2], "fries" => [0,1,0,0,1]) + + # test length of max number recipes end + @test length(values(randomCombo(testList, testDict, 2))) == 2 + end + + @testset "Neighbour" begin + # test not random with empty tabuList + @test "macaroni" in keys(Neighbour(testSolution, testList, testDict, 2, [], false)) + + # test not random with tabuList + @test !in("macaroni",keys(Neighbour(testSolution, testList, testDict, 2, ["macaroni"], false))) + + # test length of random with empty tabuList + @test length(values(Neighbour(testSolution, testList, testDict, 2, [], true))) == 2 + end + + @testset "SAFindCombo" begin + # test output type + @test typeof(SAFindCombo(testSolution, testList, testDict4, 2, true, tabuLength=1)) == Dict{Any, Any} + + # test output length + @test length(values(SAFindCombo(testSolution, testList, testDict4, 2, true, tabuLength=1))) == 2 + end + + @testset "scrapeRecipe" begin + # check if .csv file is produced + scrapeRecipe(106541,106542,"./testDB.csv") + @test isfile("./testDB.csv") + end + + @testset "loadRecipeDBCSV" begin + # check if downloaded recipes are correct + testDictDB = loadRecipeDBCSV("./testDB.csv") + @test keys(testDictDB) == keys(Dict("Belgian Chocolate Mousse" => ["test"], "Belgian Buns" => ["test"])) + end + + @testset "findBestRecipe" begin + scrapeRecipe(106543,106555,"./testDB.csv") + # check if the combination of all recipes works + @test length(values(findBestRecipe(testList, "./testDB.csv", numRecipes=2, randRecipe=true, testmode=true))) == 0 + end +end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index ff9b0f7e..b7676937 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ using Test -include("example.jl") +include("fridgeTest.jl") # add here the file with your unit tests \ No newline at end of file