From f1469aa95ee8d55611d0a931fd0e6ce88abcc30e Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 10:02:35 +1300 Subject: [PATCH 01/10] [docs] add design patterns tutorial --- docs/make.jl | 1 + .../getting_started/data/knapsack.json | 10 + .../data/knapsack_infeasible.json | 10 + .../design_patterns_for_larger_models.jl | 593 ++++++++++++++++++ 4 files changed, 614 insertions(+) create mode 100644 docs/src/tutorials/getting_started/data/knapsack.json create mode 100644 docs/src/tutorials/getting_started/data/knapsack_infeasible.json create mode 100644 docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl diff --git a/docs/make.jl b/docs/make.jl index e6e0c081773..72ec4d320a2 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -101,6 +101,7 @@ const _PAGES = [ "tutorials/getting_started/getting_started_with_sets_and_indexing.md", "tutorials/getting_started/getting_started_with_data_and_plotting.md", "tutorials/getting_started/performance_tips.md", + "tutorials/getting_started/design_patterns_for_larger_models.md", ], "Linear programs" => [ "tutorials/linear/tips_and_tricks.md", diff --git a/docs/src/tutorials/getting_started/data/knapsack.json b/docs/src/tutorials/getting_started/data/knapsack.json new file mode 100644 index 00000000000..f1c7c54fed5 --- /dev/null +++ b/docs/src/tutorials/getting_started/data/knapsack.json @@ -0,0 +1,10 @@ +{ + "objects": { + "apple": {"profit": 5.0, "weight": 2.0}, + "banana": {"profit": 3.0, "weight": 8.0}, + "cherry": {"profit": 2.0, "weight": 4.0}, + "date": {"profit": 7.0, "weight": 2.0}, + "eggplant": {"profit": 4.0, "weight": 5.0} + }, + "capacity": 10.0 +} diff --git a/docs/src/tutorials/getting_started/data/knapsack_infeasible.json b/docs/src/tutorials/getting_started/data/knapsack_infeasible.json new file mode 100644 index 00000000000..4572fe7813d --- /dev/null +++ b/docs/src/tutorials/getting_started/data/knapsack_infeasible.json @@ -0,0 +1,10 @@ +{ + "objects": { + "apple": {"profit": 5.0, "weight": 2.0}, + "banana": {"profit": 3.0, "weight": 8.0}, + "cherry": {"profit": 2.0, "weight": 4.0}, + "date": {"profit": 7.0, "weight": 2.0}, + "eggplant": {"profit": 4.0, "weight": 5.0} + }, + "capacity": -10.0 +} diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl new file mode 100644 index 00000000000..9b95567c7bd --- /dev/null +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -0,0 +1,593 @@ +# Copyright (c) 2021 Oscar Dowson and contributors #src +# #src +# Permission is hereby granted, free of charge, to any person obtaining a copy #src +# of this software and associated documentation files (the "Software"), to deal #src +# in the Software without restriction, including without limitation the rights #src +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell #src +# copies of the Software, and to permit persons to whom the Software is #src +# furnished to do so, subject to the following conditions: #src +# #src +# The above copyright notice and this permission notice shall be included in all #src +# copies or substantial portions of the Software. #src +# #src +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR #src +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, #src +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE #src +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER #src +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, #src +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #src +# SOFTWARE. #src + +# # Design partterns for larger models + +# JuMP makes it easy to build and solve optimization models. However, once you +# start to construct larger models, and especially ones that interact with +# external data sources or have customizable sets of variables and constraints +# based on client choices, you may find that your scripts become unwieldy. This +# tutorial demonstrates a variety of ways in which you can structure larger JuMP +# models to improve their readability and maintainability. + +# !!! tip +# This tutorial is more advanced than the other "Getting started" tutorials. +# It's in the "Getting started" section to give you an early preview of how +# JuMP makes it easy to structure larger models. However, if you are new to +# JuMP you may want to briefly skim the tutorial, and come back to it once +# you have written a few JuMP models. + +# ## Overview + +# This tutorial uses explanation-by-example. We're going to start with a simple +# knapsack model, and then expand it to add various features and structure. + +# ## A simple script + +# Your first prototype of a JuMP model is probably a script that uses a small +# set of hard-coded data. + +using JuMP, GLPK +profit = [5, 3, 2, 7, 4] +weight = [2, 8, 4, 2, 5] +capacity = 10 +N = 5 +model = Model(GLPK.Optimizer) +@variable(model, x[1:N], Bin) +@objective(model, Max, sum(profit[i] * x[i] for i in 1:N)) +@constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity) +optimize!(model) +value.(x) + +# The benefit of this approach are: +# * it is quick to code +# * it is quick to make changes. + +# The downsides include: +# * all variables are global (read [Performance tips](https://docs.julialang.org/en/v1/manual/performance-tips/)) +# * it is easy to introduce errors, e.g., having `profit` and `weight` be +# vectors of different lengths, or not match `N` +# * the solution, `x[i]`, is hard to interpret without knowing the order in +# which we provided the data. + +# ## Wrap the model in a fuction + +# A good next step is to wrap your model in a function. This is useful for a few +# reasons: +# * it removes global variables +# * it encapsulates the JuMP model and forces you to clarify your inputs and +# outputs +# * we can add some error checking. + +function solve_knapsack_1(profit::Vector, weight::Vector, capacity::Real) + @assert length(profit) == length(weight) + N = length(weight) + model = Model(GLPK.Optimizer) + @variable(model, x[1:N], Bin) + @objective(model, Max, sum(profit[i] * x[i] for i in 1:N)) + @constraint(model, sum(weight[i] * x[i] for i in 1:N) <= capacity) + optimize!(model) + return value.(x) +end + +solve_knapsack_1([5, 3, 2, 7, 4], [2, 8, 4, 2, 5], 10) + +# ## Create better data structures + +# Although we can check for errors like mis-matched vector lengths, if you start +# to develop models with a lot of data, keeping track of vectors and lengths and +# indices is fragile and a common source of bugs. A good solution is to use +# Julia's type system to create an abstraction over your data. + +# For example, we can create a struct that represents a single object: + +struct KnapsackObject + profit::Float64 + weight::Float64 +end + +# as well as a struct that holds a dictionary of objects and the knapsack's +# capacity: + +struct KnapsackData + objects::Dict{String,KnapsackObject} + capacity::Float64 +end + +# Here's what our data might look like now: + +objects = Dict( + "apple" => KnapsackObject(5.0, 2.0), + "banana" => KnapsackObject(3.0, 8.0), + "cherry" => KnapsackObject(2.0, 4.0), + "date" => KnapsackObject(7.0, 2.0), + "eggplant" => KnapsackObject(4.0, 5.0), +) +data = KnapsackData(objects, 10.0) + +# If you want, you can add custom printing to make it easier to visualize: + +function Base.show(io::IO, data::KnapsackData) + println(io, "A knapsack with capacity $(data.capacity) and possible items:") + for (k, v) in data.objects + println(io, " $(rpad(k, 8)) : profit = $(v.profit), weight = $(v.weight)") + end + return +end + +data + +# Then, we can re-write our `solve_knapsack` function to take our `KnapsackData` +# as input: + +function solve_knapsack_2(data::KnapsackData) + model = Model(GLPK.Optimizer) + @variable(model, x[keys(data.objects)], Bin) + @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) + @constraint( + model, + sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity, + ) + optimize!(model) + return value.(x) +end + +solve_knapsack_2(data) + +# ## Read in data from files + +# Having a datastructure is a good step. But it is still annoying that we have +# to hard-code the data into Julia. A good next step is to separate the data +# into an external file format--JSON is a common choice. + +import JSON + +function read_data(filename) + d = JSON.parsefile(filename) + return KnapsackData( + Dict( + k => KnapsackObject(v["profit"], v["weight"]) for + (k, v) in d["objects"] + ), + d["capacity"], + ) +end + +data = read_data(joinpath(@__DIR__, "data", "knapsack.json")) + +# ## Add options via if-else + +# At this point, we have data in a file format which we can load and solve a +# single problem. For many users, this might be sufficient. However, at some +# point you may be asked to add features like "but what if I want to take more +# than one of a particular item?" + +# If this is the first time that you've been asked to add a feature, adding +# options via `if-else` statements is a good approach. For example, we might +# write: + +function solve_knapsack_3(data::KnapsackData; binary_knapsack::Bool) + model = Model(GLPK.Optimizer) + if binary_knapsack + @variable(model, x[keys(data.objects)], Bin) + else + @variable(model, x[keys(data.objects)] >= 0, Int) + end + @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) + @constraint( + model, + sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity, + ) + optimize!(model) + return value.(x) +end + +# Now we can solve the binary knapsack: + +solve_knapsack_3(data; binary_knapsack = true) + +# And an integer knapsack where we can take more than one copy of each item: + +solve_knapsack_3(data; binary_knapsack = false) + +# ## Add options via dispatch + +# If you get repeated requests to add different options, you'll quickly find +# yourself in a mess of different flags and `if-else` statements. It's hard to +# write, hard to read, and hard to ensure you haven't introduced any bugs. +# A good solution is to use Julia's type dispatch. The easiest way to explain +# this is by example. + +# First, start by defining a new abstract type, as well as new subtypes for each +# of our options. + +abstract type AbstractKnapsack end + +struct BinaryKnapsack <: AbstractKnapsack end + +struct IntegerKnapsack <: AbstractKnapsack end + +# Then, we rewrite our `solve_knapsack` function to take a `config` argument, +# and we introduce an `add_knapsack_variables` function to abstract the creation +# of our variables. + +function solve_knapsack_4(data::KnapsackData, config::AbstractKnapsack) + model = Model(GLPK.Optimizer) + x = add_knapsack_variables(model, data, config) + @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) + @constraint( + model, + sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity, + ) + optimize!(model) + return value.(x) +end + +# For the binary knapsack problem, `add_knapsack_variables` looks like this: + +function add_knapsack_variables( + model::Model, + data::KnapsackData, + ::BinaryKnapsack, +) + return @variable(model, x[keys(data.objects)], Bin) +end + +# For the integer knapsack problem, `add_knapsack_variables` looks like this: + +function add_knapsack_variables( + model::Model, + data::KnapsackData, + ::IntegerKnapsack, +) + return @variable(model, x[keys(data.objects)] >= 0, Int) +end + +# Now we can solve the binary knapsack: + +solve_knapsack_4(data, BinaryKnapsack()) + +# and the integer knapsack problem: + +solve_knapsack_4(data, IntegerKnapsack()) + +# The main benefit of the dispatch approach is that you can quickly add new +# options without needing to modify the existing code. For example: + +struct UpperBoundedKnapsack <: AbstractKnapsack + limit::Int +end + +function add_knapsack_variables( + model::Model, + data::KnapsackData, + config::UpperBoundedKnapsack, +) + return @variable(model, 0 <= x[keys(data.objects)] <= config.limit, Int) +end + +solve_knapsack_4(data, UpperBoundedKnapsack(3)) + +# ## Generalize constraints and objectives + +# It's easy to extend the dispatch approach to constraints and objectives as +# well. The key points to notice in the next two functions are that: +# * we can access registered variables via `model[:x]` +# * we can define generic functions which accept any `AbstractKnapsack` as a +# configuration argument. That means we can implement a single method and +# have it apply to multiple configuration types. + +function add_knapsack_constraints( + model::Model, + data::KnapsackData, + ::AbstractKnapsack, +) + x = model[:x] + @constraint( + model, + capacity_constraint, + sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity, + ) + return +end + +function add_knapsack_objective( + model::Model, + data::KnapsackData, + ::AbstractKnapsack, +) + x = model[:x] + @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) + return +end + +function solve_knapsack_5(data::KnapsackData, config::AbstractKnapsack) + model = Model(GLPK.Optimizer) + add_knapsack_variables(model, data, config) + add_knapsack_constraints(model, data, config) + add_knapsack_objective(model, data, config) + optimize!(model) + return value.(model[:x]) +end + +solve_knapsack_5(data, BinaryKnapsack()) + +# ## Remove solver dependence, add error checks + +# Compared to where we started, our knapsack model is now significantly +# different. We've wrapped it in a function, defined some data types, and +# introduced configuration options to control the variables and constraints that +# get added. There's a few other steps we can do to further improve things. They +# are: +# * remove the dependence on `GLPK` +# * add checks that we found an optimal solution +# * add a helper function to avoid the need to explicitly construct the data. + +function solve_knapsack_6( + optimizer, + data::KnapsackData, + config::AbstractKnapsack, +) + model = Model(optimizer) + _add_knapsack_variables(model, data, config) + _add_knapsack_constraints(model, data, config) + _add_knapsack_objective(model, data, config) + optimize!(model) + if termination_status(model) != OPTIMAL + @warn("Model not solved to optimality") + return nothing + end + return value.(model[:x]) +end + +function solve_knapsack_6(optimizer, data::String, config::AbstractKnapsack) + return solve_knapsack_6(optimizer, read_data(data), config) +end + +data_filename = joinpath(@__DIR__, "data", "knapsack.json") +solution = solve_knapsack_6(GLPK.Optimizer, data_filename, BinaryKnapsack()) + +# ## Create a module + +# Now we're ready to expose our model to the wider-world. That might be as part +# of a larger Julia project that we're contributing to, or as a stand-alone +# script that we can run on-demand. In either case, it's good practice to wrap +# everything in a module. This further encapsulates our code into a single +# namespace, and we can add documentation in the form of docstrings. + +# Some good rules to follow whe creating a module are: +# * Use `import` in a module instead of `using` to make it clear which functions +# are from which packages +# * Use `_` to start function and type names that are considered private +# * Add docstrings to all public variables and functions. + +module KnapsackModel + +import JuMP +import JSON + +struct _KnapsackObject + profit::Float64 + weight::Float64 +end + +struct _KnapsackData + objects::Dict{String,_KnapsackObject} + capacity::Float64 +end + +function _read_data(filename) + d = JSON.parsefile(filename) + return _KnapsackData( + Dict( + k => _KnapsackObject(v["profit"], v["weight"]) for + (k, v) in d["objects"] + ), + d["capacity"], + ) +end + +abstract type _AbstractKnapsack end + +""" + BinaryKnapsack() + +Create a binary knapsack problem where each object can be taken 0 or 1 times. +""" +struct BinaryKnapsack <: _AbstractKnapsack end + +""" + IntegerKnapsack() + +Create an integer knapsack problem where each object can be taken any amount of +times. +""" +struct IntegerKnapsack <: _AbstractKnapsack end + +function _add_knapsack_variables( + model::JuMP.Model, + data::_KnapsackData, + ::BinaryKnapsack, +) + return JuMP.@variable(model, x[keys(data.objects)], Bin) +end + +function _add_knapsack_variables( + model::JuMP.Model, + data::_KnapsackData, + ::IntegerKnapsack, +) + return JuMP.@variable(model, x[keys(data.objects)] >= 0, Int) +end + +function _add_knapsack_constraints( + model::JuMP.Model, + data::_KnapsackData, + ::_AbstractKnapsack, +) + x = model[:x] + JuMP.@constraint( + model, + capacity_constraint, + sum(v.weight * x[k] for (k, v) in data.objects) <= data.capacity, + ) + return +end + +function _add_knapsack_objective( + model::JuMP.Model, + data::_KnapsackData, + ::_AbstractKnapsack, +) + x = model[:x] + JuMP.@objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) + return +end + +function _solve_knapsack( + optimizer, + data::_KnapsackData, + config::_AbstractKnapsack, +) + model = JuMP.Model(optimizer) + _add_knapsack_variables(model, data, config) + _add_knapsack_constraints(model, data, config) + _add_knapsack_objective(model, data, config) + JuMP.optimize!(model) + if JuMP.termination_status(model) != JuMP.OPTIMAL + @warn("Model not solved to optimality") + return nothing + end + return JuMP.value.(model[:x]) +end + +""" + solve_knapsack(optimizer, data_filename::String, config::_AbstractKnapsack) + +Solve the knapsack problem and return the optimal primal solution + +## Arguments + + * `optimizer` : an object that can be passed to `JuMP.Model` to construct a new + JuMP model. + * `data_filename` : the filename of a JSON file containing the data for the + problem. + * `config` : an object to control the type of knapsack model constructed. + Valid options are: + * `BinaryKnapsack()` + * `IntegerKnapsack()` + +## Returns + + * If an optimal solution exists: a `JuMP.DenseAxisArray` that maps the `String` + name of each object to the number of objects to pack into the knapsack. + * Otherwise, `nothing`, indicating that the problem does not have an optimal + solution. + +## Examples + +```julia +solution = solve_knapsack(GLPK.Optimizer, "path/to/data.json", BinaryKnapsack()) +``` + +```julia +solution = solve_knapsack( + MOI.OptimizerWithAttributes(GLPK.Optimizer, "msg_lev" => 0), + "path/to/data.json", + IntegerKnapsack(), +) +``` +""" +function solve_knapsack( + optimizer, + data_filename::String, + config::_AbstractKnapsack, +) + return _solve_knapsack(optimizer, _read_data(data_filename), config) +end + +end + +# Finally, you can call your model: + +import .KnapsackModel + +KnapsackModel.solve_knapsack( + GLPK.Optimizer, + joinpath(@__DIR__, "data", "knapsack.json"), + KnapsackModel.BinaryKnapsack(), +) + +# !!! note +# The `.` in `.KnapsackModel` denotes that it is a submodule and not a +# separate package that we installed with `Pkg.add`. If you put the +# `KnapsackModel` in a separate file, load it with: +# ```julia +# include("path/to/KnapsackModel.jl") +# import .KnapsackModel +# ``` + +# ## Add tests + +# As a final step, you should add tests for your model. This often means testing +# on a small problem for which you can work out the optimal solution by hand. +# The Julia standard library `Test` has good unit-testing functionality. + +import .KnapsackModel +using Test + +@testset "KnapsackModel" begin + @testset "feasible_binary_knapsack" begin + x = KnapsackModel.solve_knapsack( + GLPK.Optimizer, + joinpath(@__DIR__, "data", "knapsack.json"), + KnapsackModel.BinaryKnapsack(), + ) + @test isapprox(x["apple"], 1, atol=1e-5) + @test isapprox(x["banana"], 0, atol=1e-5) + @test isapprox(x["cherry"], 0, atol=1e-5) + @test isapprox(x["date"], 1, atol=1e-5) + @test isapprox(x["eggplant"], 1, atol=1e-5) + end + @testset "feasible_integer_knapsack" begin + x = KnapsackModel.solve_knapsack( + GLPK.Optimizer, + joinpath(@__DIR__, "data", "knapsack.json"), + KnapsackModel.IntegerKnapsack(), + ) + @test isapprox(x["apple"], 0, atol=1e-5) + @test isapprox(x["banana"], 0, atol=1e-5) + @test isapprox(x["cherry"], 0, atol=1e-5) + @test isapprox(x["date"], 5, atol=1e-5) + @test isapprox(x["eggplant"], 0, atol=1e-5) + end + @testset "feasible_binary_knapsack" begin + x = KnapsackModel.solve_knapsack( + GLPK.Optimizer, + joinpath(@__DIR__, "data", "knapsack_infeasible.json"), + KnapsackModel.BinaryKnapsack(), + ) + @test x === nothing + end +end + +# !!! tip +# Place these tests in a separate file `test_knapsack_model.jl` so that you +# can run the tests by `include`ing the file. From 24fd9ac0f3b75a81a39e4c5503c8f59339f6dc41 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 10:08:27 +1300 Subject: [PATCH 02/10] Fix formatting --- .../design_patterns_for_larger_models.jl | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 9b95567c7bd..e1ad662ea3a 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -18,7 +18,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE #src # SOFTWARE. #src -# # Design partterns for larger models +# # Design patterns for larger models # JuMP makes it easy to build and solve optimization models. However, once you # start to construct larger models, and especially ones that interact with @@ -127,7 +127,10 @@ data = KnapsackData(objects, 10.0) function Base.show(io::IO, data::KnapsackData) println(io, "A knapsack with capacity $(data.capacity) and possible items:") for (k, v) in data.objects - println(io, " $(rpad(k, 8)) : profit = $(v.profit), weight = $(v.weight)") + println( + io, + " $(rpad(k, 8)) : profit = $(v.profit), weight = $(v.weight)", + ) end return end @@ -560,11 +563,11 @@ using Test joinpath(@__DIR__, "data", "knapsack.json"), KnapsackModel.BinaryKnapsack(), ) - @test isapprox(x["apple"], 1, atol=1e-5) - @test isapprox(x["banana"], 0, atol=1e-5) - @test isapprox(x["cherry"], 0, atol=1e-5) - @test isapprox(x["date"], 1, atol=1e-5) - @test isapprox(x["eggplant"], 1, atol=1e-5) + @test isapprox(x["apple"], 1, atol = 1e-5) + @test isapprox(x["banana"], 0, atol = 1e-5) + @test isapprox(x["cherry"], 0, atol = 1e-5) + @test isapprox(x["date"], 1, atol = 1e-5) + @test isapprox(x["eggplant"], 1, atol = 1e-5) end @testset "feasible_integer_knapsack" begin x = KnapsackModel.solve_knapsack( @@ -572,11 +575,11 @@ using Test joinpath(@__DIR__, "data", "knapsack.json"), KnapsackModel.IntegerKnapsack(), ) - @test isapprox(x["apple"], 0, atol=1e-5) - @test isapprox(x["banana"], 0, atol=1e-5) - @test isapprox(x["cherry"], 0, atol=1e-5) - @test isapprox(x["date"], 5, atol=1e-5) - @test isapprox(x["eggplant"], 0, atol=1e-5) + @test isapprox(x["apple"], 0, atol = 1e-5) + @test isapprox(x["banana"], 0, atol = 1e-5) + @test isapprox(x["cherry"], 0, atol = 1e-5) + @test isapprox(x["date"], 5, atol = 1e-5) + @test isapprox(x["eggplant"], 0, atol = 1e-5) end @testset "feasible_binary_knapsack" begin x = KnapsackModel.solve_knapsack( From 266002b509d2e9c51b791d1731de7466ebf5105d Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 10:13:48 +1300 Subject: [PATCH 03/10] Fix typo in names --- .../getting_started/design_patterns_for_larger_models.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index e1ad662ea3a..4c704538e32 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -349,9 +349,9 @@ function solve_knapsack_6( config::AbstractKnapsack, ) model = Model(optimizer) - _add_knapsack_variables(model, data, config) - _add_knapsack_constraints(model, data, config) - _add_knapsack_objective(model, data, config) + add_knapsack_variables(model, data, config) + add_knapsack_constraints(model, data, config) + add_knapsack_objective(model, data, config) optimize!(model) if termination_status(model) != OPTIMAL @warn("Model not solved to optimality") From 499f2605f3d5c649ac43007c78633da9fe83b882 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 14:35:43 +1300 Subject: [PATCH 04/10] Updates from jd-foster --- .../design_patterns_for_larger_models.jl | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 4c704538e32..037601a6671 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -37,7 +37,8 @@ # ## Overview # This tutorial uses explanation-by-example. We're going to start with a simple -# knapsack model, and then expand it to add various features and structure. +# [knapsack model](https://en.wikipedia.org/wiki/Knapsack_problem), and then +# expand it to add various features and structure. # ## A simple script @@ -56,7 +57,7 @@ model = Model(GLPK.Optimizer) optimize!(model) value.(x) -# The benefit of this approach are: +# The benefits of this approach are: # * it is quick to code # * it is quick to make changes. @@ -96,14 +97,21 @@ solve_knapsack_1([5, 3, 2, 7, 4], [2, 8, 4, 2, 5], 10) # indices is fragile and a common source of bugs. A good solution is to use # Julia's type system to create an abstraction over your data. -# For example, we can create a struct that represents a single object: +# For example, we can create a `struct` that represents a single object, with a +# constructor that lets us validate assumptions on the input data: struct KnapsackObject profit::Float64 weight::Float64 + function KnapsackObject(profit::Float64, weight::Float64) + if weight < 0 + throw(DomainError("Weight of object cannot be negative")) + end + return new(profit, weight) + end end -# as well as a struct that holds a dictionary of objects and the knapsack's +# as well as a `struct` that holds a dictionary of objects and the knapsack's # capacity: struct KnapsackData @@ -156,9 +164,9 @@ solve_knapsack_2(data) # ## Read in data from files -# Having a datastructure is a good step. But it is still annoying that we have +# Having a data structure is a good step. But it is still annoying that we have # to hard-code the data into Julia. A good next step is to separate the data -# into an external file format--JSON is a common choice. +# into an external file format; JSON is a common choice. import JSON From f284848a99c2fd072f433e3cdd67b6f0bfadedb2 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 14:37:27 +1300 Subject: [PATCH 05/10] Add constructor to module code --- .../getting_started/design_patterns_for_larger_models.jl | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 037601a6671..2f014654ecb 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -397,6 +397,12 @@ import JSON struct _KnapsackObject profit::Float64 weight::Float64 + function _KnapsackObject(profit::Float64, weight::Float64) + if weight < 0 + throw(DomainError("Weight of object cannot be negative")) + end + return new(profit, weight) + end end struct _KnapsackData From 0e2b040664ead962df0702950af2238217c60c66 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 16:50:27 +1300 Subject: [PATCH 06/10] More changes --- .../design_patterns_for_larger_models.jl | 90 +++++++++++++------ 1 file changed, 62 insertions(+), 28 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 2f014654ecb..59f01ee5c8b 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -168,6 +168,18 @@ solve_knapsack_2(data) # to hard-code the data into Julia. A good next step is to separate the data # into an external file format; JSON is a common choice. +# The JuMP repository [has a file](https://github.com/jump-dev/JuMP.jl/blob/master/docs/src/tutorials/getting_started/data) +# we're going to use for this tutorial. It is found here: + +data_filename = joinpath(@__DIR__, "data", "knapsack.json") + +# with these contents: + +println(read(data_filename, String)) + +# Now let's write a function that reads this file and builds a `KnapsackData` +# object: + import JSON function read_data(filename) @@ -181,7 +193,7 @@ function read_data(filename) ) end -data = read_data(joinpath(@__DIR__, "data", "knapsack.json")) +data = read_data(data_filename) # ## Add options via if-else @@ -218,28 +230,29 @@ solve_knapsack_3(data; binary_knapsack = true) solve_knapsack_3(data; binary_knapsack = false) -# ## Add options via dispatch +# ## Add configuation options via dispatch # If you get repeated requests to add different options, you'll quickly find # yourself in a mess of different flags and `if-else` statements. It's hard to # write, hard to read, and hard to ensure you haven't introduced any bugs. -# A good solution is to use Julia's type dispatch. The easiest way to explain -# this is by example. +# A good solution is to use Julia's type dispatch to control the configuration +# of the model. The easiest way to explain this is by example. # First, start by defining a new abstract type, as well as new subtypes for each -# of our options. +# of our options. These types are going to control the configuration of the +# knapsack model. -abstract type AbstractKnapsack end +abstract type AbstractConfiguration end -struct BinaryKnapsack <: AbstractKnapsack end +struct BinaryKnapsack <: AbstractConfiguration end -struct IntegerKnapsack <: AbstractKnapsack end +struct IntegerKnapsack <: AbstractConfiguration end # Then, we rewrite our `solve_knapsack` function to take a `config` argument, # and we introduce an `add_knapsack_variables` function to abstract the creation # of our variables. -function solve_knapsack_4(data::KnapsackData, config::AbstractKnapsack) +function solve_knapsack_4(data::KnapsackData, config::AbstractConfiguration) model = Model(GLPK.Optimizer) x = add_knapsack_variables(model, data, config) @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) @@ -282,7 +295,7 @@ solve_knapsack_4(data, IntegerKnapsack()) # The main benefit of the dispatch approach is that you can quickly add new # options without needing to modify the existing code. For example: -struct UpperBoundedKnapsack <: AbstractKnapsack +struct UpperBoundedKnapsack <: AbstractConfiguration limit::Int end @@ -301,14 +314,14 @@ solve_knapsack_4(data, UpperBoundedKnapsack(3)) # It's easy to extend the dispatch approach to constraints and objectives as # well. The key points to notice in the next two functions are that: # * we can access registered variables via `model[:x]` -# * we can define generic functions which accept any `AbstractKnapsack` as a +# * we can define generic functions which accept any `AbstractConfiguration` as a # configuration argument. That means we can implement a single method and # have it apply to multiple configuration types. function add_knapsack_constraints( model::Model, data::KnapsackData, - ::AbstractKnapsack, + ::AbstractConfiguration, ) x = model[:x] @constraint( @@ -322,14 +335,14 @@ end function add_knapsack_objective( model::Model, data::KnapsackData, - ::AbstractKnapsack, + ::AbstractConfiguration, ) x = model[:x] @objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) return end -function solve_knapsack_5(data::KnapsackData, config::AbstractKnapsack) +function solve_knapsack_5(data::KnapsackData, config::AbstractConfiguration) model = Model(GLPK.Optimizer) add_knapsack_variables(model, data, config) add_knapsack_constraints(model, data, config) @@ -354,7 +367,7 @@ solve_knapsack_5(data, BinaryKnapsack()) function solve_knapsack_6( optimizer, data::KnapsackData, - config::AbstractKnapsack, + config::AbstractConfiguration, ) model = Model(optimizer) add_knapsack_variables(model, data, config) @@ -368,7 +381,11 @@ function solve_knapsack_6( return value.(model[:x]) end -function solve_knapsack_6(optimizer, data::String, config::AbstractKnapsack) +function solve_knapsack_6( + optimizer, + data::String, + config::AbstractConfiguration, +) return solve_knapsack_6(optimizer, read_data(data), config) end @@ -383,11 +400,11 @@ solution = solve_knapsack_6(GLPK.Optimizer, data_filename, BinaryKnapsack()) # everything in a module. This further encapsulates our code into a single # namespace, and we can add documentation in the form of docstrings. -# Some good rules to follow whe creating a module are: -# * Use `import` in a module instead of `using` to make it clear which functions +# Some good rules to follow when creating a module are: +# * use `import` in a module instead of `using` to make it clear which functions # are from which packages -# * Use `_` to start function and type names that are considered private -# * Add docstrings to all public variables and functions. +# * use `_` to start function and type names that are considered private +# * add docstrings to all public variables and functions. module KnapsackModel @@ -421,14 +438,14 @@ function _read_data(filename) ) end -abstract type _AbstractKnapsack end +abstract type _AbstractConfiguration end """ BinaryKnapsack() Create a binary knapsack problem where each object can be taken 0 or 1 times. """ -struct BinaryKnapsack <: _AbstractKnapsack end +struct BinaryKnapsack <: _AbstractConfiguration end """ IntegerKnapsack() @@ -436,7 +453,7 @@ struct BinaryKnapsack <: _AbstractKnapsack end Create an integer knapsack problem where each object can be taken any amount of times. """ -struct IntegerKnapsack <: _AbstractKnapsack end +struct IntegerKnapsack <: _AbstractConfiguration end function _add_knapsack_variables( model::JuMP.Model, @@ -457,7 +474,7 @@ end function _add_knapsack_constraints( model::JuMP.Model, data::_KnapsackData, - ::_AbstractKnapsack, + ::_AbstractConfiguration, ) x = model[:x] JuMP.@constraint( @@ -471,7 +488,7 @@ end function _add_knapsack_objective( model::JuMP.Model, data::_KnapsackData, - ::_AbstractKnapsack, + ::_AbstractConfiguration, ) x = model[:x] JuMP.@objective(model, Max, sum(v.profit * x[k] for (k, v) in data.objects)) @@ -481,7 +498,7 @@ end function _solve_knapsack( optimizer, data::_KnapsackData, - config::_AbstractKnapsack, + config::_AbstractConfiguration, ) model = JuMP.Model(optimizer) _add_knapsack_variables(model, data, config) @@ -496,7 +513,11 @@ function _solve_knapsack( end """ - solve_knapsack(optimizer, data_filename::String, config::_AbstractKnapsack) + solve_knapsack( + optimizer, + data_filename::String, + config::_AbstractConfiguration, + ) Solve the knapsack problem and return the optimal primal solution @@ -535,7 +556,7 @@ solution = solve_knapsack( function solve_knapsack( optimizer, data_filename::String, - config::_AbstractKnapsack, + config::_AbstractConfiguration, ) return _solve_knapsack(optimizer, _read_data(data_filename), config) end @@ -598,6 +619,7 @@ using Test @testset "feasible_binary_knapsack" begin x = KnapsackModel.solve_knapsack( GLPK.Optimizer, + ## This file contains data that makes the problem infeasible. joinpath(@__DIR__, "data", "knapsack_infeasible.json"), KnapsackModel.BinaryKnapsack(), ) @@ -608,3 +630,15 @@ end # !!! tip # Place these tests in a separate file `test_knapsack_model.jl` so that you # can run the tests by `include`ing the file. + +# ## Next steps + +# We've only briefly scratched the surface of ways to create and structure large +# JuMP models, so consider this tutorial a starting point, rather than a +# comprehensive list of all the possible ways to structure JuMP models. If you +# are embarking on a large project that uses JuMP, a good next step is to +# look at ways people have written large JuMP projects "in the wild". Here are +# some examples: +# * [PowerSimulations.jl](https://github.com/NREL-SIIP/PowerSimulations.jl/) +# * [PowerModels.jl](https://github.com/lanl-ansi/PowerModels.jl) +# * [AnyMod.jl](https://github.com/leonardgoeke/AnyMOD.jl) From 66af96ca5ac6e0cba7d6d03f0b95a54967eae940 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 26 Nov 2021 16:57:52 +1300 Subject: [PATCH 07/10] More updates --- .../design_patterns_for_larger_models.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 59f01ee5c8b..6625534eefd 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -358,8 +358,7 @@ solve_knapsack_5(data, BinaryKnapsack()) # Compared to where we started, our knapsack model is now significantly # different. We've wrapped it in a function, defined some data types, and # introduced configuration options to control the variables and constraints that -# get added. There's a few other steps we can do to further improve things. They -# are: +# get added. There are a few other steps we can do to further improve things: # * remove the dependence on `GLPK` # * add checks that we found an optimal solution # * add a helper function to avoid the need to explicitly construct the data. @@ -394,11 +393,12 @@ solution = solve_knapsack_6(GLPK.Optimizer, data_filename, BinaryKnapsack()) # ## Create a module -# Now we're ready to expose our model to the wider-world. That might be as part +# Now we're ready to expose our model to the wider world. That might be as part # of a larger Julia project that we're contributing to, or as a stand-alone # script that we can run on-demand. In either case, it's good practice to wrap # everything in a module. This further encapsulates our code into a single -# namespace, and we can add documentation in the form of docstrings. +# namespace, and we can add documentation in the form of +# [docstrings](https://docs.julialang.org/en/v1/manual/documentation/). # Some good rules to follow when creating a module are: # * use `import` in a module instead of `using` to make it clear which functions @@ -450,7 +450,7 @@ struct BinaryKnapsack <: _AbstractConfiguration end """ IntegerKnapsack() -Create an integer knapsack problem where each object can be taken any amount of +Create an integer knapsack problem where each object can be taken any number of times. """ struct IntegerKnapsack <: _AbstractConfiguration end @@ -629,7 +629,8 @@ end # !!! tip # Place these tests in a separate file `test_knapsack_model.jl` so that you -# can run the tests by `include`ing the file. +# can run the tests by adding `include("test_knapsack_model.jl")` to any +# file where needed. # ## Next steps From b278bbc0035a9af2cc6ca721ab64569ea3f66df9 Mon Sep 17 00:00:00 2001 From: odow Date: Sat, 27 Nov 2021 17:01:04 +1300 Subject: [PATCH 08/10] Updates --- .../design_patterns_for_larger_models.jl | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 6625534eefd..df8f56c39a8 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -78,7 +78,9 @@ value.(x) # * we can add some error checking. function solve_knapsack_1(profit::Vector, weight::Vector, capacity::Real) - @assert length(profit) == length(weight) + if length(profit) != length(weight) + throw(DimensionMismatch("profit and weight are different sizes")) + end N = length(weight) model = Model(GLPK.Optimizer) @variable(model, x[1:N], Bin) @@ -168,12 +170,16 @@ solve_knapsack_2(data) # to hard-code the data into Julia. A good next step is to separate the data # into an external file format; JSON is a common choice. -# The JuMP repository [has a file](https://github.com/jump-dev/JuMP.jl/blob/master/docs/src/tutorials/getting_started/data) -# we're going to use for this tutorial. It is found here: +# The JuMP repository [has a file](https://github.com/jump-dev/JuMP.jl/blob/master/docs/src/tutorials/getting_started/data/knapsack.json) +# we're going to use for this tutorial. To run this tutorial locally, download +# the file and then update `data_filename` as appropriate. -data_filename = joinpath(@__DIR__, "data", "knapsack.json") +# To build this version of the JuMP documentation, we needed to set the +# filename: -# with these contents: +data_filename = joinpath(@__DIR__, "data", "knapsack.json"); + +# `knapsack.json` has the following contents: println(read(data_filename, String)) @@ -616,7 +622,7 @@ using Test @test isapprox(x["date"], 5, atol = 1e-5) @test isapprox(x["eggplant"], 0, atol = 1e-5) end - @testset "feasible_binary_knapsack" begin + @testset "infeasible_binary_knapsack" begin x = KnapsackModel.solve_knapsack( GLPK.Optimizer, ## This file contains data that makes the problem infeasible. @@ -638,8 +644,18 @@ end # JuMP models, so consider this tutorial a starting point, rather than a # comprehensive list of all the possible ways to structure JuMP models. If you # are embarking on a large project that uses JuMP, a good next step is to -# look at ways people have written large JuMP projects "in the wild". Here are -# some examples: -# * [PowerSimulations.jl](https://github.com/NREL-SIIP/PowerSimulations.jl/) -# * [PowerModels.jl](https://github.com/lanl-ansi/PowerModels.jl) -# * [AnyMod.jl](https://github.com/leonardgoeke/AnyMOD.jl) +# look at ways people have written large JuMP projects "in the wild". + +# Here are some good examples (all co-incidentally related to energy): +# * AnyMOD.jl +# * [JuMP-dev 2021 talk](https://www.youtube.com/watch?v=QE_tNDER0F4) +# * [source code](https://github.com/leonardgoeke/AnyMOD.jl) +# * PowerModels.jl +# * [JuMP-dev 2021 talk](https://www.youtube.com/watch?v=POOt1FCA8LI) +# * [source code](https://github.com/lanl-ansi/PowerModels.jl) +# * PowerSimulations.jl +# * [JuliaCon 2021 talk](https://www.youtube.com/watch?v=-ZoO3npjwYU) +# * [source code](https://github.com/NREL-SIIP/PowerSimulations.jl) +# * UnitCommitment.jl +# * [JuMP-dev 2021 talk](https://www.youtube.com/watch?v=rYUZK9kYeIY) +# * [source code](https://github.com/ANL-CEEESA/UnitCommitment.jl) From 31046c3a7bb9244cd4dbf04152d3d4f5201c48ca Mon Sep 17 00:00:00 2001 From: odow Date: Sun, 28 Nov 2021 11:03:32 +1300 Subject: [PATCH 09/10] Update --- .../design_patterns_for_larger_models.jl | 55 +++++++++++-------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index df8f56c39a8..3d923abb286 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -250,9 +250,9 @@ solve_knapsack_3(data; binary_knapsack = false) abstract type AbstractConfiguration end -struct BinaryKnapsack <: AbstractConfiguration end +struct BinaryKnapsackConfig <: AbstractConfiguration end -struct IntegerKnapsack <: AbstractConfiguration end +struct IntegerKnapsackConfig <: AbstractConfiguration end # Then, we rewrite our `solve_knapsack` function to take a `config` argument, # and we introduce an `add_knapsack_variables` function to abstract the creation @@ -285,35 +285,35 @@ end function add_knapsack_variables( model::Model, data::KnapsackData, - ::IntegerKnapsack, + ::IntegerKnapsackConfig, ) return @variable(model, x[keys(data.objects)] >= 0, Int) end # Now we can solve the binary knapsack: -solve_knapsack_4(data, BinaryKnapsack()) +solve_knapsack_4(data, BinaryKnapsackConfig()) # and the integer knapsack problem: -solve_knapsack_4(data, IntegerKnapsack()) +solve_knapsack_4(data, IntegerKnapsackConfig()) # The main benefit of the dispatch approach is that you can quickly add new # options without needing to modify the existing code. For example: -struct UpperBoundedKnapsack <: AbstractConfiguration +struct UpperBoundedKnapsackConfig <: AbstractConfiguration limit::Int end function add_knapsack_variables( model::Model, data::KnapsackData, - config::UpperBoundedKnapsack, + config::UpperBoundedKnapsackConfig, ) return @variable(model, 0 <= x[keys(data.objects)] <= config.limit, Int) end -solve_knapsack_4(data, UpperBoundedKnapsack(3)) +solve_knapsack_4(data, UpperBoundedKnapsackConfig(3)) # ## Generalize constraints and objectives @@ -357,7 +357,7 @@ function solve_knapsack_5(data::KnapsackData, config::AbstractConfiguration) return value.(model[:x]) end -solve_knapsack_5(data, BinaryKnapsack()) +solve_knapsack_5(data, BinaryKnapsackConfig()) # ## Remove solver dependence, add error checks @@ -394,8 +394,11 @@ function solve_knapsack_6( return solve_knapsack_6(optimizer, read_data(data), config) end -data_filename = joinpath(@__DIR__, "data", "knapsack.json") -solution = solve_knapsack_6(GLPK.Optimizer, data_filename, BinaryKnapsack()) +solution = solve_knapsack_6( + GLPK.Optimizer, + data_filename, + BinaryKnapsackConfig(), +) # ## Create a module @@ -447,24 +450,24 @@ end abstract type _AbstractConfiguration end """ - BinaryKnapsack() + BinaryKnapsackConfig() Create a binary knapsack problem where each object can be taken 0 or 1 times. """ -struct BinaryKnapsack <: _AbstractConfiguration end +struct BinaryKnapsackConfig <: _AbstractConfiguration end """ - IntegerKnapsack() + IntegerKnapsackConfig() Create an integer knapsack problem where each object can be taken any number of times. """ -struct IntegerKnapsack <: _AbstractConfiguration end +struct IntegerKnapsackConfig <: _AbstractConfiguration end function _add_knapsack_variables( model::JuMP.Model, data::_KnapsackData, - ::BinaryKnapsack, + ::BinaryKnapsackConfig, ) return JuMP.@variable(model, x[keys(data.objects)], Bin) end @@ -472,7 +475,7 @@ end function _add_knapsack_variables( model::JuMP.Model, data::_KnapsackData, - ::IntegerKnapsack, + ::IntegerKnapsackConfig, ) return JuMP.@variable(model, x[keys(data.objects)] >= 0, Int) end @@ -535,7 +538,7 @@ Solve the knapsack problem and return the optimal primal solution problem. * `config` : an object to control the type of knapsack model constructed. Valid options are: - * `BinaryKnapsack()` + * `BinaryKnapsackConfig()` * `IntegerKnapsack()` ## Returns @@ -548,14 +551,18 @@ Solve the knapsack problem and return the optimal primal solution ## Examples ```julia -solution = solve_knapsack(GLPK.Optimizer, "path/to/data.json", BinaryKnapsack()) +solution = solve_knapsack( + GLPK.Optimizer, + "path/to/data.json", + BinaryKnapsackConfig(), +) ``` ```julia solution = solve_knapsack( MOI.OptimizerWithAttributes(GLPK.Optimizer, "msg_lev" => 0), "path/to/data.json", - IntegerKnapsack(), + IntegerKnapsackConfig(), ) ``` """ @@ -576,7 +583,7 @@ import .KnapsackModel KnapsackModel.solve_knapsack( GLPK.Optimizer, joinpath(@__DIR__, "data", "knapsack.json"), - KnapsackModel.BinaryKnapsack(), + KnapsackModel.BinaryKnapsackConfig(), ) # !!! note @@ -602,7 +609,7 @@ using Test x = KnapsackModel.solve_knapsack( GLPK.Optimizer, joinpath(@__DIR__, "data", "knapsack.json"), - KnapsackModel.BinaryKnapsack(), + KnapsackModel.BinaryKnapsackConfig(), ) @test isapprox(x["apple"], 1, atol = 1e-5) @test isapprox(x["banana"], 0, atol = 1e-5) @@ -614,7 +621,7 @@ using Test x = KnapsackModel.solve_knapsack( GLPK.Optimizer, joinpath(@__DIR__, "data", "knapsack.json"), - KnapsackModel.IntegerKnapsack(), + KnapsackModel.IntegerKnapsackConfigConfig(), ) @test isapprox(x["apple"], 0, atol = 1e-5) @test isapprox(x["banana"], 0, atol = 1e-5) @@ -627,7 +634,7 @@ using Test GLPK.Optimizer, ## This file contains data that makes the problem infeasible. joinpath(@__DIR__, "data", "knapsack_infeasible.json"), - KnapsackModel.BinaryKnapsack(), + KnapsackModel.BinaryKnapsackConfig(), ) @test x === nothing end From cd3e929dbe2585243475aa4badc29253d23806e5 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Sun, 28 Nov 2021 15:35:21 +1300 Subject: [PATCH 10/10] Update design_patterns_for_larger_models.jl --- .../design_patterns_for_larger_models.jl | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl index 3d923abb286..d4f5e0a5473 100644 --- a/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl +++ b/docs/src/tutorials/getting_started/design_patterns_for_larger_models.jl @@ -275,7 +275,7 @@ end function add_knapsack_variables( model::Model, data::KnapsackData, - ::BinaryKnapsack, + ::BinaryKnapsackConfig, ) return @variable(model, x[keys(data.objects)], Bin) end @@ -394,11 +394,8 @@ function solve_knapsack_6( return solve_knapsack_6(optimizer, read_data(data), config) end -solution = solve_knapsack_6( - GLPK.Optimizer, - data_filename, - BinaryKnapsackConfig(), -) +solution = + solve_knapsack_6(GLPK.Optimizer, data_filename, BinaryKnapsackConfig()) # ## Create a module @@ -539,7 +536,7 @@ Solve the knapsack problem and return the optimal primal solution * `config` : an object to control the type of knapsack model constructed. Valid options are: * `BinaryKnapsackConfig()` - * `IntegerKnapsack()` + * `IntegerKnapsackConfig()` ## Returns @@ -621,7 +618,7 @@ using Test x = KnapsackModel.solve_knapsack( GLPK.Optimizer, joinpath(@__DIR__, "data", "knapsack.json"), - KnapsackModel.IntegerKnapsackConfigConfig(), + KnapsackModel.IntegerKnapsackConfig(), ) @test isapprox(x["apple"], 0, atol = 1e-5) @test isapprox(x["banana"], 0, atol = 1e-5)