diff --git a/LICENSE b/LICENSE
index 497106c..c63d87e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 2020, UChicago Argonne, LLC
+Copyright © 2020-2021, UChicago Argonne, LLC
All Rights Reserved
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 6fa28d6..0000000
--- a/Makefile
+++ /dev/null
@@ -1,13 +0,0 @@
-JULIA := julia --color=yes
-JULIA_SYSIMAGE_ARGS := $(JULIA_ARGS) --sysimage build/sysimage.so
-
-all: test
-
-build/sysimage.so: Manifest.toml Project.toml
- mkdir -p build
- $(JULIA) --project=test test/sysimage.jl
-
-test: build/sysimage.so
- $(JULIA) --sysimage build/sysimage.so --project=test test/runtests.jl
-
-.PHONY: test test-python test-julia test-watch docs install
diff --git a/Manifest.toml b/Manifest.toml
index fa6f596..15f7a1c 100644
--- a/Manifest.toml
+++ b/Manifest.toml
@@ -1,10 +1,10 @@
# This file is machine-generated - editing it directly is not advised
+[[ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+
[[Artifacts]]
-deps = ["Pkg"]
-git-tree-sha1 = "c30985d8821e0cd73870b17b0ed0ce6dc44cb744"
uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
-version = "1.3.0"
[[Base64]]
uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
@@ -53,15 +53,13 @@ version = "0.3.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 = "4fecfd5485d3c5de4003e19f00c6898cccd40667"
+git-tree-sha1 = "ac4132ad78082518ec2037ae5770b6e796f7f956"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
-version = "3.26.0"
+version = "3.27.0"
[[CompilerSupportLibraries_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "8e695f735fca77e9708e795eda62afdb869cbb70"
+deps = ["Artifacts", "Libdl"]
uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
-version = "0.3.4+0"
[[Conda]]
deps = ["JSON", "VersionParsing"]
@@ -99,6 +97,10 @@ version = "1.0.2"
deps = ["Random", "Serialization", "Sockets"]
uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+[[Downloads]]
+deps = ["ArgTools", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+
[[ForwardDiff]]
deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6"
@@ -132,12 +134,6 @@ git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
version = "0.21.1"
-[[JSON2]]
-deps = ["Dates", "Parsers", "Test"]
-git-tree-sha1 = "66397cc6c08922f98a28ab05a8d3002f9853b129"
-uuid = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
-version = "0.3.2"
-
[[JSONSchema]]
deps = ["HTTP", "JSON", "ZipFile"]
git-tree-sha1 = "b84ab8139afde82c7c65ba2b792fe12e01dd7307"
@@ -150,10 +146,22 @@ git-tree-sha1 = "e952f49e2242fa21edcf27bbd6c67041685bee5d"
uuid = "4076af6c-e467-56ae-b986-b466b2749572"
version = "0.21.6"
+[[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 = ["Printf"]
+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"
@@ -187,19 +195,20 @@ uuid = "739be429-bea8-5141-9913-cc70e7f3736d"
version = "1.0.3"
[[MbedTLS_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "0eef589dd1c26a3ac9d753fe1a8bcad63f956fa6"
+deps = ["Artifacts", "Libdl"]
uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1"
-version = "2.16.8+1"
[[Mmap]]
uuid = "a63ad114-7e13-5084-954f-fe012c677804"
+[[MozillaCACerts_jll]]
+uuid = "14a3606d-f60d-562e-9121-12d972cd8159"
+
[[MutableArithmetics]]
deps = ["LinearAlgebra", "SparseArrays", "Test"]
-git-tree-sha1 = "6b6bb8f550dc38310afd4a0af0786dc3222459e2"
+git-tree-sha1 = "ff3aa3e4dbc837f80c2031de2f90125c8b3793f3"
uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
-version = "0.2.14"
+version = "0.2.15"
[[NaNMath]]
git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
@@ -207,9 +216,7 @@ uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
version = "0.3.5"
[[NetworkOptions]]
-git-tree-sha1 = "ed3157f48a05543cce9b241e1f2815f7e843d96e"
uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
-version = "1.2.0"
[[OpenSpecFun_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
@@ -222,12 +229,6 @@ git-tree-sha1 = "4fa2ba51070ec13fcc7517db714445b4ab986bdf"
uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
version = "1.4.0"
-[[PackageCompiler]]
-deps = ["Libdl", "Pkg", "UUIDs"]
-git-tree-sha1 = "d448727c4b86be81b225b738c88d30334fda6779"
-uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
-version = "1.2.5"
-
[[Parsers]]
deps = ["Dates"]
git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
@@ -235,7 +236,7 @@ uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
version = "1.1.0"
[[Pkg]]
-deps = ["Dates", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "UUIDs"]
+deps = ["Artifacts", "Dates", "Downloads", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "REPL", "Random", "SHA", "Serialization", "TOML", "Tar", "UUIDs", "p7zip_jll"]
uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f"
[[Printf]]
@@ -249,7 +250,7 @@ uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
version = "1.92.2"
[[REPL]]
-deps = ["InteractiveUtils", "Markdown", "Sockets"]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
[[Random]]
@@ -289,8 +290,16 @@ version = "1.1.0"
deps = ["LinearAlgebra", "SparseArrays"]
uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+[[TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+
+[[Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+
[[Test]]
-deps = ["Distributed", "InteractiveUtils", "Logging", "Random"]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
[[TimerOutputs]]
@@ -329,7 +338,13 @@ uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
version = "0.9.3"
[[Zlib_jll]]
-deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
-git-tree-sha1 = "320228915c8debb12cb434c59057290f0834dbf6"
+deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
-version = "1.2.11+18"
+
+[[nghttp2_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d"
+
+[[p7zip_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0"
diff --git a/Project.toml b/Project.toml
index b1937c6..7f2d60c 100644
--- a/Project.toml
+++ b/Project.toml
@@ -5,21 +5,17 @@ version = "0.2.0"
[deps]
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
-JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
-PackageCompiler = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
PyCall = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
[compat]
Conda = "1.4"
-JSON2 = "0.3"
JuMP = "0.21"
MathOptInterface = "0.9"
-PackageCompiler = "1"
PyCall = "1"
TimerOutputs = "0.5"
julia = "1"
diff --git a/README.md b/README.md
index 3949252..c141a63 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,21 @@
-# MIPLearn.jl
+
MIPLearn.jl
+
+
+
+
+
+
+
+
+
+
+
-This package is a work-in-progress Julia interface for [MIPLearn, an extensible framework for
-Learning-Enhanced Mixed-Integer Optimization](https://github.com/ANL-CEEESA/MIPLearn).
+**MIPLearn** is an extensible open-source framework for solving discrete optimization problems using a combination of Mixed-Integer Linear Programming (MIP) and Machine Learning (ML). See the [main repository](miplearn) for more information. This repository holds an experimental Julia interface for the package.
## License
Released under the modified BSD license. See `LICENSE` for more details.
+
+
+[miplearn]: https://github.com/ANL-CEEESA/MIPLearn
\ No newline at end of file
diff --git a/src/MIPLearn.jl b/src/MIPLearn.jl
index 8e1cb2c..fa636ba 100644
--- a/src/MIPLearn.jl
+++ b/src/MIPLearn.jl
@@ -1,41 +1,25 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
__precompile__(false)
module MIPLearn
using PyCall
-miplearn = pyimport("miplearn")
-Instance = miplearn.Instance
-BenchmarkRunner = miplearn.BenchmarkRunner
-macro pycall(expr)
- quote
- err_msg = nothing
- result = nothing
- try
- result = $(esc(expr))
- catch err
- args = err.val.args[1]
- if (err isa PyCall.PyError) && (args isa String) && startswith(args, "Julia")
- err_msg = replace(args, r"Stacktrace.*" => "")
- else
- rethrow(err)
- end
- end
- if err_msg != nothing
- error(err_msg)
- end
- result
- end
-end
+export JuMPInstance
+export LearningSolver
+export @feature
+export @category
-include("log.jl")
-include("jump_solver.jl")
-include("learning_solver.jl")
-include("instance.jl")
+miplearn = pyimport("miplearn")
-export Instance, BenchmarkRunner
+include("utils/log.jl")
+include("utils/pycall.jl")
+include("modeling/jump_instance.jl")
+include("modeling/jump_solver.jl")
+include("modeling/learning_solver.jl")
+include("modeling/macros.jl")
+include("problems/knapsack.jl")
end # module
diff --git a/src/instance.jl b/src/instance.jl
deleted file mode 100644
index 3917ef9..0000000
--- a/src/instance.jl
+++ /dev/null
@@ -1,63 +0,0 @@
-# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
-# Released under the modified BSD license. See COPYING.md for more details.
-
-using JSON2
-import Base: dump
-
-to_model(instance) =
- error("not implemented: to_model")
-
-get_instance_features(instance) = [0.0]
-
-get_variable_features(instance, varname) = [0.0]
-
-get_variable_category(instance, varname) = "default"
-
-find_violated_lazy_constraints(instance, model) = []
-
-build_lazy_constraint(instance, model, v) = nothing
-
-macro Instance(klass)
- quote
- @pydef mutable struct Wrapper <: Instance
- function __init__(self, args...; kwargs...)
- self.data = $(esc(klass))(args...; kwargs...)
- self.training_data = []
- self.features = miplearn.Features()
- end
-
- to_model(self) =
- $(esc(:to_model))(self.data)
-
- get_instance_features(self) =
- $(esc(:get_instance_features))(self.data)
-
- get_variable_features(self, varname) =
- $(esc(:get_variable_features))(self.data, varname)
-
- get_variable_category(self, varname) =
- $(esc(:get_variable_category))(self.data, varname)
-
- find_violated_lazy_constraints(self, model) =
- find_violated_lazy_constraints(self.data, model)
-
- build_lazy_constraint(self, model, v) =
- build_lazy_constraint(self.data, model, v)
-
- load(self) = nothing
-
- flush(self) = nothing
- end
- end
-end
-
-export get_instance_features,
- get_variable_features,
- get_variable_category,
- find_violated_lazy_constraints,
- build_lazy_constraint,
- to_model,
- dump,
- load!,
- @Instance
\ No newline at end of file
diff --git a/src/learning_solver.jl b/src/learning_solver.jl
deleted file mode 100644
index d419238..0000000
--- a/src/learning_solver.jl
+++ /dev/null
@@ -1,27 +0,0 @@
-# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
-# Released under the modified BSD license. See COPYING.md for more details.
-
-struct LearningSolver
- py::PyCall.PyObject
-end
-
-function LearningSolver(;
- optimizer,
- kwargs...,
- )::LearningSolver
- py = miplearn.LearningSolver(
- ;
- kwargs...,
- solver=JuMPSolver(optimizer=optimizer),
- )
- return LearningSolver(py)
-end
-
-solve!(solver::LearningSolver, instance; kwargs...) =
- solver.py.solve(instance; kwargs...)
-
-fit!(solver::LearningSolver, instances; kwargs...) =
- solver.py.fit(instances; kwargs...)
-
-export LearningSolver
\ No newline at end of file
diff --git a/src/modeling/jump_instance.jl b/src/modeling/jump_instance.jl
new file mode 100644
index 0000000..7cbd2c8
--- /dev/null
+++ b/src/modeling/jump_instance.jl
@@ -0,0 +1,52 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+@pydef mutable struct JuMPInstance <: miplearn.Instance
+ function __init__(self, model)
+ init_miplearn_ext(model)
+ features = model.ext[:miplearn][:features]
+ self.model = model
+
+ # Copy training data
+ training_data = []
+ for sample in self.model.ext[:miplearn][:training_samples]
+ pysample = miplearn.TrainingSample()
+ pysample.__dict__ = sample
+ push!(training_data, pysample)
+ end
+ self.training_data = training_data
+
+ # Copy features to data classes
+ self.features = miplearn.Features(
+ instance=miplearn.InstanceFeatures(
+ user_features=PyCall.array2py(
+ features[:instance][:user_features],
+ ),
+ lazy_constraint_count=0,
+ ),
+ variables=Dict(
+ varname => miplearn.VariableFeatures(
+ category=vfeatures[:category],
+ user_features=PyCall.array2py(
+ vfeatures[:user_features],
+ ),
+ )
+ for (varname, vfeatures) in features[:variables]
+ ),
+ constraints=Dict(
+ cname => miplearn.ConstraintFeatures(
+ category=cfeat[:category],
+ user_features=PyCall.array2py(
+ cfeat[:user_features],
+ ),
+ )
+ for (cname, cfeat) in features[:constraints]
+ ),
+ )
+ end
+
+ function to_model(self)
+ return self.model
+ end
+end
diff --git a/src/jump_solver.jl b/src/modeling/jump_solver.jl
similarity index 99%
rename from src/jump_solver.jl
rename to src/modeling/jump_solver.jl
index fcb3590..f80981b 100644
--- a/src/jump_solver.jl
+++ b/src/modeling/jump_solver.jl
@@ -1,5 +1,5 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using JuMP
diff --git a/src/modeling/learning_solver.jl b/src/modeling/learning_solver.jl
new file mode 100644
index 0000000..f8b6b27
--- /dev/null
+++ b/src/modeling/learning_solver.jl
@@ -0,0 +1,32 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+struct LearningSolver
+ py::PyCall.PyObject
+end
+
+function LearningSolver(
+ ;
+ optimizer,
+)::LearningSolver
+ py = miplearn.LearningSolver(solver=JuMPSolver(optimizer=optimizer))
+ return LearningSolver(py)
+end
+
+function solve!(solver::LearningSolver, model::Model)
+ instance = JuMPInstance(model)
+ mip_stats = solver.py.solve(instance)
+ push!(
+ model.ext[:miplearn][:training_samples],
+ instance.training_data[1].__dict__,
+ )
+ return mip_stats
+end
+
+function fit!(solver::LearningSolver, models::Array{Model})
+ instances = [JuMPInstance(m) for m in models]
+ solver.py.fit(instances)
+end
+
+export LearningSolver
diff --git a/src/modeling/macros.jl b/src/modeling/macros.jl
new file mode 100644
index 0000000..78eedf2
--- /dev/null
+++ b/src/modeling/macros.jl
@@ -0,0 +1,84 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+function init_miplearn_ext(model)::Dict
+ if :miplearn ∉ keys(model.ext)
+ model.ext[:miplearn] = Dict{Symbol,Any}(
+ :features => Dict(
+ :variables => Dict{String,Dict}(),
+ :constraints => Dict{String,Dict}(),
+ :instance => Dict{Symbol,Any}(),
+ ),
+ :training_samples => [],
+ )
+ end
+ return model.ext[:miplearn]
+end
+
+
+function init_miplearn_ext(v::VariableRef)::Dict
+ ext = init_miplearn_ext(v.model)
+ if name(v) ∉ keys(ext[:features][:variables])
+ ext[:features][:variables][name(v)] = Dict{Symbol,Any}()
+ end
+ return ext
+end
+
+
+function init_miplearn_ext(c::ConstraintRef)::Dict
+ ext = init_miplearn_ext(c.model)
+ if name(c) ∉ keys(ext[:features][:constraints])
+ ext[:features][:constraints][name(c)] = Dict{Symbol,Any}()
+ end
+ return ext
+end
+
+
+function set_features!(m::Model, f::Array{Float64})::Nothing
+ ext = init_miplearn_ext(m)
+ ext[:features][:instance][:user_features] = f
+ return
+end
+
+
+function set_features!(v::VariableRef, f::Array{Float64})::Nothing
+ ext = init_miplearn_ext(v)
+ ext[:features][:variables][name(v)][:user_features] = f
+ return
+end
+
+
+function set_category!(v::VariableRef, category::String)::Nothing
+ ext = init_miplearn_ext(v)
+ ext[:features][:variables][name(v)][:category] = category
+ return
+end
+
+
+function set_features!(c::ConstraintRef, f::Array{Float64})::Nothing
+ ext = init_miplearn_ext(c)
+ ext[:features][:constraints][name(c)][:user_features] = f
+ return
+end
+
+
+function set_category!(c::ConstraintRef, category::String)::Nothing
+ ext = init_miplearn_ext(c)
+ ext[:features][:constraints][name(c)][:category] = category
+ return
+end
+
+
+macro feature(obj, features)
+ quote
+ set_features!($(esc(obj)), $(esc(features)))
+ end
+end
+
+
+macro category(obj, category)
+ quote
+ set_category!($(esc(obj)), $(esc(category)))
+ end
+end
diff --git a/src/problems/knapsack.jl b/src/problems/knapsack.jl
new file mode 100644
index 0000000..ac3b6c1
--- /dev/null
+++ b/src/problems/knapsack.jl
@@ -0,0 +1,25 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+using JuMP
+
+function knapsack_model(
+ weights::Array{Float64, 1},
+ prices::Array{Float64, 1},
+ capacity::Float64,
+)
+ model = Model()
+ n = length(weights)
+ @variable(model, x[0:(n-1)], Bin)
+ @objective(model, Max, sum(x[i] * prices[i+1] for i in 0:(n-1)))
+ @constraint(
+ model,
+ eq_capacity,
+ sum(
+ x[i] * weights[i+1]
+ for i in 0:(n-1)
+ ) <= capacity,
+ )
+ return model
+end
diff --git a/src/log.jl b/src/utils/log.jl
similarity index 95%
rename from src/log.jl
rename to src/utils/log.jl
index 005fce6..28d2f2c 100644
--- a/src/log.jl
+++ b/src/utils/log.jl
@@ -1,5 +1,5 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
import Logging: min_enabled_level, shouldlog, handle_message
diff --git a/src/utils/pycall.jl b/src/utils/pycall.jl
new file mode 100644
index 0000000..473f468
--- /dev/null
+++ b/src/utils/pycall.jl
@@ -0,0 +1,24 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+macro pycall(expr)
+ quote
+ err_msg = nothing
+ result = nothing
+ try
+ result = $(esc(expr))
+ catch err
+ args = err.val.args[1]
+ if (err isa PyCall.PyError) && (args isa String) && startswith(args, "Julia")
+ err_msg = replace(args, r"Stacktrace.*" => "")
+ else
+ rethrow(err)
+ end
+ end
+ if err_msg != nothing
+ error(err_msg)
+ end
+ result
+ end
+end
\ No newline at end of file
diff --git a/test/Manifest.toml b/test/Manifest.toml
new file mode 100644
index 0000000..c1717b5
--- /dev/null
+++ b/test/Manifest.toml
@@ -0,0 +1,421 @@
+# This file is machine-generated - editing it directly is not advised
+
+[[ArgTools]]
+uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f"
+
+[[Artifacts]]
+uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33"
+
+[[Base64]]
+uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
+
+[[BenchmarkTools]]
+deps = ["JSON", "Logging", "Printf", "Statistics", "UUIDs"]
+git-tree-sha1 = "9e62e66db34540a0c919d72172cc2f642ac71260"
+uuid = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
+version = "0.5.0"
+
+[[BinaryProvider]]
+deps = ["Libdl", "Logging", "SHA"]
+git-tree-sha1 = "ecdec412a9abc8db54c0efc5548c64dfce072058"
+uuid = "b99e7846-7c00-51b0-8f62-c81ae34c0232"
+version = "0.5.10"
+
+[[Bzip2_jll]]
+deps = ["Artifacts", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "c3598e525718abcc440f69cc6d5f60dda0a1b61e"
+uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0"
+version = "1.0.6+5"
+
+[[CEnum]]
+git-tree-sha1 = "215a9aa4a1f23fbd05b92769fdd62559488d70e9"
+uuid = "fa961155-64e5-5f13-b03f-caf6b980ea82"
+version = "0.4.1"
+
+[[Calculus]]
+deps = ["LinearAlgebra"]
+git-tree-sha1 = "f641eb0a4f00c343bbc32346e1217b86f3ce9dad"
+uuid = "49dc2e85-a5d0-5ad3-a950-438e2897f1b9"
+version = "0.5.1"
+
+[[Cbc]]
+deps = ["BinaryProvider", "CEnum", "Cbc_jll", "Libdl", "MathOptInterface", "SparseArrays"]
+git-tree-sha1 = "8da071e2f96de95decbd551d54db9ac82c0bf4f6"
+uuid = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
+version = "0.8.0"
+
+[[Cbc_jll]]
+deps = ["Artifacts", "Cgl_jll", "Clp_jll", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"]
+git-tree-sha1 = "5d24ea46edebb4f067b180cfc092a45d4d2c48b3"
+uuid = "38041ee0-ae04-5750-a4d2-bb4d0d83d27d"
+version = "2.10.5+2"
+
+[[Cgl_jll]]
+deps = ["Artifacts", "Clp_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "0c41f887cca7e95e7ca1cda5bd6e82e6f43b317d"
+uuid = "3830e938-1dd0-5f3e-8b8e-b3ee43226782"
+version = "0.60.2+6"
+
+[[ChainRulesCore]]
+deps = ["Compat", "LinearAlgebra", "SparseArrays"]
+git-tree-sha1 = "44e9f638aa9ed1ad58885defc568c133010140aa"
+uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
+version = "0.9.37"
+
+[[Clp_jll]]
+deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Osi_jll", "Pkg"]
+git-tree-sha1 = "d9eca9fa2435959b5542b13409a8ec5f64c947c8"
+uuid = "06985876-5285-5a41-9fcb-8948a742cc53"
+version = "1.17.6+7"
+
+[[CodecBzip2]]
+deps = ["Bzip2_jll", "Libdl", "TranscodingStreams"]
+git-tree-sha1 = "2e62a725210ce3c3c2e1a3080190e7ca491f18d7"
+uuid = "523fee87-0ab8-5b00-afb7-3ecf72e48cfd"
+version = "0.7.2"
+
+[[CodecZlib]]
+deps = ["TranscodingStreams", "Zlib_jll"]
+git-tree-sha1 = "ded953804d019afa9a3f98981d99b33e3db7b6da"
+uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
+version = "0.7.0"
+
+[[CoinUtils_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"]
+git-tree-sha1 = "5186155a8609b71eae7e104fa2b8fbf6ecd5d9bb"
+uuid = "be027038-0da8-5614-b30d-e42594cb92df"
+version = "2.11.3+4"
+
+[[CommonSubexpressions]]
+deps = ["MacroTools", "Test"]
+git-tree-sha1 = "7b8a93dba8af7e3b42fecabf646260105ac373f7"
+uuid = "bbf7d656-a473-5ed7-a52c-81e309532950"
+version = "0.3.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 = "ac4132ad78082518ec2037ae5770b6e796f7f956"
+uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
+version = "3.27.0"
+
+[[CompilerSupportLibraries_jll]]
+deps = ["Artifacts", "Libdl"]
+uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae"
+
+[[Conda]]
+deps = ["JSON", "VersionParsing"]
+git-tree-sha1 = "6231e40619c15148bcb80aa19d731e629877d762"
+uuid = "8f4d0f93-b110-5947-807f-2305c1781a2d"
+version = "1.5.1"
+
+[[DataStructures]]
+deps = ["Compat", "InteractiveUtils", "OrderedCollections"]
+git-tree-sha1 = "4437b64df1e0adccc3e5d1adbc3ac741095e4677"
+uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
+version = "0.18.9"
+
+[[Dates]]
+deps = ["Printf"]
+uuid = "ade2ca70-3891-5945-98fb-dc099432e06a"
+
+[[DelimitedFiles]]
+deps = ["Mmap"]
+uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab"
+
+[[DiffResults]]
+deps = ["StaticArrays"]
+git-tree-sha1 = "c18e98cba888c6c25d1c3b048e4b3380ca956805"
+uuid = "163ba53b-c6d8-5494-b064-1a9d43ac40c5"
+version = "1.0.3"
+
+[[DiffRules]]
+deps = ["NaNMath", "Random", "SpecialFunctions"]
+git-tree-sha1 = "214c3fcac57755cfda163d91c58893a8723f93e9"
+uuid = "b552c78f-8df3-52c6-915a-8e097449b14b"
+version = "1.0.2"
+
+[[Distributed]]
+deps = ["Random", "Serialization", "Sockets"]
+uuid = "8ba89e20-285c-5b6f-9357-94700520ee1b"
+
+[[Downloads]]
+deps = ["ArgTools", "LibCURL", "NetworkOptions"]
+uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6"
+
+[[ForwardDiff]]
+deps = ["CommonSubexpressions", "DiffResults", "DiffRules", "LinearAlgebra", "NaNMath", "Printf", "Random", "SpecialFunctions", "StaticArrays"]
+git-tree-sha1 = "e2af66012e08966366a43251e1fd421522908be6"
+uuid = "f6369f11-7733-5829-9624-2563aa707210"
+version = "0.10.18"
+
+[[HTTP]]
+deps = ["Base64", "Dates", "IniFile", "MbedTLS", "NetworkOptions", "Sockets", "URIs"]
+git-tree-sha1 = "c9f380c76d8aaa1fa7ea9cf97bddbc0d5b15adc2"
+uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3"
+version = "0.9.5"
+
+[[IniFile]]
+deps = ["Test"]
+git-tree-sha1 = "098e4d2c533924c921f9f9847274f2ad89e018b8"
+uuid = "83e8ac13-25f8-5344-8a64-a9f2b223428f"
+version = "0.5.0"
+
+[[InteractiveUtils]]
+deps = ["Markdown"]
+uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
+
+[[JLLWrappers]]
+git-tree-sha1 = "a431f5f2ca3f4feef3bd7a5e94b8b8d4f2f647a0"
+uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210"
+version = "1.2.0"
+
+[[JSON]]
+deps = ["Dates", "Mmap", "Parsers", "Unicode"]
+git-tree-sha1 = "81690084b6198a2e1da36fcfda16eeca9f9f24e4"
+uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
+version = "0.21.1"
+
+[[JSON2]]
+deps = ["Dates", "Parsers", "Test"]
+git-tree-sha1 = "66397cc6c08922f98a28ab05a8d3002f9853b129"
+uuid = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
+version = "0.3.2"
+
+[[JSONSchema]]
+deps = ["HTTP", "JSON", "ZipFile"]
+git-tree-sha1 = "b84ab8139afde82c7c65ba2b792fe12e01dd7307"
+uuid = "7d188eb4-7ad8-530c-ae41-71a32a6d4692"
+version = "0.3.3"
+
+[[JuMP]]
+deps = ["Calculus", "DataStructures", "ForwardDiff", "JSON", "LinearAlgebra", "MathOptInterface", "MutableArithmetics", "NaNMath", "Random", "SparseArrays", "SpecialFunctions", "Statistics"]
+git-tree-sha1 = "e952f49e2242fa21edcf27bbd6c67041685bee5d"
+uuid = "4076af6c-e467-56ae-b986-b466b2749572"
+version = "0.21.6"
+
+[[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"
+
+[[MIPLearn]]
+deps = ["Conda", "JSON2", "JuMP", "Logging", "MathOptInterface", "PackageCompiler", "Printf", "PyCall", "TimerOutputs"]
+git-tree-sha1 = "86a0bef09e21ec65ee5becf7d662315bb325b1c6"
+uuid = "2b1277c3-b477-4c49-a15e-7ba350325c68"
+version = "0.1.0"
+
+[[MacroTools]]
+deps = ["Markdown", "Random"]
+git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0"
+uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
+version = "0.5.6"
+
+[[Markdown]]
+deps = ["Base64"]
+uuid = "d6f4376e-aef5-505a-96c1-9c027394607a"
+
+[[MathOptInterface]]
+deps = ["BenchmarkTools", "CodecBzip2", "CodecZlib", "JSON", "JSONSchema", "LinearAlgebra", "MutableArithmetics", "OrderedCollections", "SparseArrays", "Test", "Unicode"]
+git-tree-sha1 = "606efe4246da5407d7505265a1ead72467528996"
+uuid = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
+version = "0.9.20"
+
+[[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"
+
+[[MutableArithmetics]]
+deps = ["LinearAlgebra", "SparseArrays", "Test"]
+git-tree-sha1 = "ff3aa3e4dbc837f80c2031de2f90125c8b3793f3"
+uuid = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
+version = "0.2.15"
+
+[[NaNMath]]
+git-tree-sha1 = "bfe47e760d60b82b66b61d2d44128b62e3a369fb"
+uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3"
+version = "0.3.5"
+
+[[NetworkOptions]]
+uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908"
+
+[[OpenBLAS32_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "ba4a8f683303c9082e84afba96f25af3c7fb2436"
+uuid = "656ef2d0-ae68-5445-9ca0-591084a874a2"
+version = "0.3.12+1"
+
+[[OpenSpecFun_jll]]
+deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Pkg"]
+git-tree-sha1 = "9db77584158d0ab52307f8c04f8e7c08ca76b5b3"
+uuid = "efe28fd5-8261-553b-a9e1-b2916fc3738e"
+version = "0.5.3+4"
+
+[[OrderedCollections]]
+git-tree-sha1 = "4fa2ba51070ec13fcc7517db714445b4ab986bdf"
+uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d"
+version = "1.4.0"
+
+[[Osi_jll]]
+deps = ["Artifacts", "CoinUtils_jll", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "OpenBLAS32_jll", "Pkg"]
+git-tree-sha1 = "ef540e28c9b82cb879e33c0885e1bbc9a1e6c571"
+uuid = "7da25872-d9ce-5375-a4d3-7a845f58efdd"
+version = "0.108.5+4"
+
+[[PackageCompiler]]
+deps = ["Libdl", "Pkg", "UUIDs"]
+git-tree-sha1 = "d448727c4b86be81b225b738c88d30334fda6779"
+uuid = "9b87118b-4619-50d2-8e1e-99f35a4d4d9d"
+version = "1.2.5"
+
+[[Parsers]]
+deps = ["Dates"]
+git-tree-sha1 = "c8abc88faa3f7a3950832ac5d6e690881590d6dc"
+uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0"
+version = "1.1.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"
+
+[[Printf]]
+deps = ["Unicode"]
+uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7"
+
+[[PyCall]]
+deps = ["Conda", "Dates", "Libdl", "LinearAlgebra", "MacroTools", "Serialization", "VersionParsing"]
+git-tree-sha1 = "dd1a970b543bd02efce2984582e996af28cab27f"
+uuid = "438e738f-606a-5dbb-bf0a-cddfbfd45ab0"
+version = "1.92.2"
+
+[[REPL]]
+deps = ["InteractiveUtils", "Markdown", "Sockets", "Unicode"]
+uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
+
+[[Random]]
+deps = ["Serialization"]
+uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
+
+[[SHA]]
+uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce"
+
+[[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"
+
+[[SpecialFunctions]]
+deps = ["ChainRulesCore", "OpenSpecFun_jll"]
+git-tree-sha1 = "5919936c0e92cff40e57d0ddf0ceb667d42e5902"
+uuid = "276daf66-3868-5448-9aa4-cd146d93841b"
+version = "1.3.0"
+
+[[StaticArrays]]
+deps = ["LinearAlgebra", "Random", "Statistics"]
+git-tree-sha1 = "2f01a51c23eed210ff4a1be102c4cc8236b66e5b"
+uuid = "90137ffa-7385-5640-81b9-e52037218182"
+version = "1.1.0"
+
+[[Statistics]]
+deps = ["LinearAlgebra", "SparseArrays"]
+uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
+
+[[TOML]]
+deps = ["Dates"]
+uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76"
+
+[[Tar]]
+deps = ["ArgTools", "SHA"]
+uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e"
+
+[[Test]]
+deps = ["InteractiveUtils", "Logging", "Random", "Serialization"]
+uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
+
+[[TimerOutputs]]
+deps = ["Printf"]
+git-tree-sha1 = "32cdbe6cd2d214c25a0b88f985c9e0092877c236"
+uuid = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f"
+version = "0.5.8"
+
+[[TranscodingStreams]]
+deps = ["Random", "Test"]
+git-tree-sha1 = "7c53c35547de1c5b9d46a4797cf6d8253807108c"
+uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
+version = "0.9.5"
+
+[[URIs]]
+git-tree-sha1 = "7855809b88d7b16e9b029afd17880930626f54a2"
+uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4"
+version = "1.2.0"
+
+[[UUIDs]]
+deps = ["Random", "SHA"]
+uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4"
+
+[[Unicode]]
+uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5"
+
+[[VersionParsing]]
+git-tree-sha1 = "80229be1f670524750d905f8fc8148e5a8c4537f"
+uuid = "81def892-9a0e-5fdd-b105-ffc91e053289"
+version = "1.2.0"
+
+[[ZipFile]]
+deps = ["Libdl", "Printf", "Zlib_jll"]
+git-tree-sha1 = "c3a5637e27e914a7a445b8d0ad063d701931e9f7"
+uuid = "a5390f91-8eb1-5f08-bee0-b1d1ffed6cea"
+version = "0.9.3"
+
+[[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"
diff --git a/test/Project.toml b/test/Project.toml
index 9ec49cc..463b9a1 100644
--- a/test/Project.toml
+++ b/test/Project.toml
@@ -1,6 +1,10 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
[deps]
+Cbc = "9961bab8-2fa3-5c5a-9d89-47fab24efd76"
Conda = "8f4d0f93-b110-5947-807f-2305c1781a2d"
-Gurobi = "2e9cd046-0924-5485-92f1-d5272153d98b"
JSON2 = "2535ab7d-5cd8-5a07-80ac-9b1792aadce3"
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
MIPLearn = "2b1277c3-b477-4c49-a15e-7ba350325c68"
diff --git a/test/jump_solver_test.jl b/test/jump_solver_test.jl
deleted file mode 100644
index ec5d330..0000000
--- a/test/jump_solver_test.jl
+++ /dev/null
@@ -1,23 +0,0 @@
-# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
-# Released under the modified BSD license. See COPYING.md for more details.
-
-using Test
-using MIPLearn
-using Gurobi
-using PyCall
-
-miplearn_tests = pyimport("miplearn.solvers.tests")
-
-@testset "JuMPSolver" begin
- for optimizer in [Gurobi.Optimizer]
- instance = KnapsackInstance(
- [23., 26., 20., 18.],
- [505., 352., 458., 220.],
- 67.0,
- )
- model = instance.to_model()
- solver = JuMPSolver(optimizer=optimizer)
- miplearn_tests.test_internal_solver(solver, instance, model)
- end
-end
\ No newline at end of file
diff --git a/test/knapsack.jl b/test/knapsack.jl
deleted file mode 100644
index 13535c2..0000000
--- a/test/knapsack.jl
+++ /dev/null
@@ -1,41 +0,0 @@
-# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
-# Released under the modified BSD license. See COPYING.md for more details.
-
-import MIPLearn: to_model,
- get_instance_features,
- get_variable_features
- find_violated_lazy_constraints
-using JuMP
-
-struct KnapsackData
- weights
- prices
- capacity
-end
-
-function to_model(data::KnapsackData)
- model = Model()
- n = length(data.weights)
- @variable(model, x[0:(n-1)], Bin)
- @objective(model, Max, sum(x[i] * data.prices[i+1] for i in 0:(n-1)))
- @constraint(
- model,
- eq_capacity,
- sum(
- x[i] * data.weights[i+1]
- for i in 0:(n-1)
- ) <= data.capacity,
- )
- return model
-end
-
-function get_instance_features(data::KnapsackData)
- return [0.]
-end
-
-function get_variable_features(data::KnapsackData, var, index)
- return [0.]
-end
-
-KnapsackInstance = @Instance(KnapsackData)
diff --git a/test/learning_solver_test.jl b/test/learning_solver_test.jl
deleted file mode 100644
index 6091ba0..0000000
--- a/test/learning_solver_test.jl
+++ /dev/null
@@ -1,25 +0,0 @@
-# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
-# Released under the modified BSD license. See COPYING.md for more details.
-
-using Test
-using MIPLearn
-using Gurobi
-
-
-@testset "LearningSolver" begin
- for optimizer in [Gurobi.Optimizer]
- instance = KnapsackInstance(
- [23., 26., 20., 18.],
- [505., 352., 458., 220.],
- 67.0,
- )
- solver = LearningSolver(
- optimizer=optimizer,
- mode="heuristic",
- )
- stats = solve!(solver, instance)
- fit!(solver, [instance])
- solve!(solver, instance)
- end
-end
diff --git a/test/modeling/jump_solver_test.jl b/test/modeling/jump_solver_test.jl
new file mode 100644
index 0000000..c6c3fac
--- /dev/null
+++ b/test/modeling/jump_solver_test.jl
@@ -0,0 +1,21 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+using Test
+using MIPLearn
+using Cbc
+using PyCall
+
+miplearn_tests = pyimport("miplearn.solvers.tests")
+
+@testset "JuMPSolver" begin
+ model = MIPLearn.knapsack_model(
+ [23., 26., 20., 18.],
+ [505., 352., 458., 220.],
+ 67.0,
+ )
+ instance = JuMPInstance(model)
+ solver = JuMPSolver(optimizer=Cbc.Optimizer)
+ miplearn_tests.test_internal_solver(solver, instance, model)
+end
\ No newline at end of file
diff --git a/test/modeling/learning_solver_test.jl b/test/modeling/learning_solver_test.jl
new file mode 100644
index 0000000..e4ef448
--- /dev/null
+++ b/test/modeling/learning_solver_test.jl
@@ -0,0 +1,75 @@
+# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
+# Released under the modified BSD license. See COPYING.md for more details.
+
+using JuMP
+using MIPLearn
+using Cbc
+
+@testset "macros" begin
+ weights = [1.0, 2.0, 3.0]
+ prices = [5.0, 6.0, 7.0]
+ capacity = 3.0
+
+ # Create standard JuMP model
+ model = Model()
+ n = length(weights)
+ @variable(model, x[1:n], Bin)
+ @objective(model, Max, sum(x[i] * prices[i] for i in 1:n))
+ @constraint(model, c1, sum(x[i] * weights[i] for i in 1:n) <= capacity)
+
+ # Add machine-learning information
+ @feature(model, [5.0])
+ @feature(c1, [1.0, 2.0, 3.0])
+ @category(c1, "c1")
+ for i in 1:n
+ @feature(x[i], [weights[i]; prices[i]])
+ @category(x[i], "type-$i")
+ end
+
+ # Should store variable features
+ @test model.ext[:miplearn][:features][:variables] == Dict(
+ "x[1]" => Dict(
+ :user_features => [1.0, 5.0],
+ :category => "type-1",
+
+ ),
+ "x[2]" => Dict(
+ :user_features => [2.0, 6.0],
+ :category => "type-2",
+ ),
+ "x[3]" => Dict(
+ :user_features => [3.0, 7.0],
+ :category => "type-3",
+ ),
+ )
+
+ # Should store constraint features
+ @test model.ext[:miplearn][:features][:constraints] == Dict(
+ "c1" => Dict(
+ :user_features => [1.0, 2.0, 3.0],
+ :category => "c1",
+ )
+ )
+
+ # Should store instance features
+ @test model.ext[:miplearn][:features][:instance] == Dict(
+ :user_features => [5.0],
+ )
+
+ solver = LearningSolver(optimizer=Cbc.Optimizer)
+
+ # Should return correct stats
+ stats = solve!(solver, model)
+ @test stats["Lower bound"] == 11.0
+
+ # Should add a sample to the training data
+ @test length(model.ext[:miplearn][:training_samples]) == 1
+ sample = model.ext[:miplearn][:training_samples][1]
+ @test sample["lower_bound"] == 11.0
+ @test sample["solution"]["x[1]"] == 1.0
+
+ fit!(solver, [model])
+
+ solve!(solver, model)
+end
diff --git a/test/runtests.jl b/test/runtests.jl
index 6630ccf..3a564cd 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -1,5 +1,5 @@
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
-# Copyright (C) 2020, UChicago Argonne, LLC. All rights reserved.
+# Copyright (C) 2020-2021, UChicago Argonne, LLC. All rights reserved.
# Released under the modified BSD license. See COPYING.md for more details.
using Test
@@ -8,7 +8,6 @@ using MIPLearn
MIPLearn.setup_logger()
@testset "MIPLearn" begin
- include("knapsack.jl")
- include("jump_solver_test.jl")
- include("learning_solver_test.jl")
+ #include("modeling/jump_solver_test.jl")
+ include("modeling/learning_solver_test.jl")
end
\ No newline at end of file
diff --git a/test/sysimage.jl b/test/sysimage.jl
deleted file mode 100644
index 93c942f..0000000
--- a/test/sysimage.jl
+++ /dev/null
@@ -1,19 +0,0 @@
-using PackageCompiler
-
-using JSON2
-using CPLEX
-using Gurobi
-using JuMP
-using MathOptInterface
-using PyCall
-using TimerOutputs
-
-pkg = [:JSON2
- :CPLEX
- :Gurobi
- :JuMP
- :MathOptInterface
- :PyCall
- :TimerOutputs]
-
-create_sysimage(pkg, sysimage_path="build/sysimage.so")
\ No newline at end of file