From e0e65fe4c734d0f2be2624548927ac2da1c0b125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Niemier?= <~@hauleth.dev> Date: Wed, 10 Dec 2025 22:25:55 +0100 Subject: [PATCH] Extract problem formatter to separate module This is done in preparation to make Dantzig able to use different solvers, not just HiGHS. --- lib/dantzig.ex | 3 +- lib/dantzig/format/cplex.ex | 93 ++++++++++++ lib/dantzig/highs.ex | 100 ++----------- lib/dantzig/highs_downloader.ex | 4 +- lib/dantzig/problem.ex | 4 +- mix.exs | 7 +- mix.lock | 19 +++ test/dantzig/format/cplex_test.exs | 93 ++++++++++++ .../instances/closed_form_quadratic_test.exs | 2 +- .../instances/layout_examples_test.exs | 2 +- test/dantzig/io_data_test.exs | 132 ------------------ test/dantzig/problem_test.exs | 2 +- test/test_helper.exs | 1 + 13 files changed, 226 insertions(+), 236 deletions(-) create mode 100644 lib/dantzig/format/cplex.ex create mode 100644 test/dantzig/format/cplex_test.exs delete mode 100644 test/dantzig/io_data_test.exs diff --git a/lib/dantzig.ex b/lib/dantzig.ex index bcd2821..172d463 100644 --- a/lib/dantzig.ex +++ b/lib/dantzig.ex @@ -3,6 +3,7 @@ defmodule Dantzig do Documentation for `Dantzig`. """ + alias Dantzig.Format.CPLEX alias Dantzig.HiGHS alias Dantzig.Problem @@ -16,7 +17,7 @@ defmodule Dantzig do end def dump_problem_to_file(%Problem{} = problem, path) do - iodata = HiGHS.to_lp_iodata(problem) + iodata = CPLEX.to_iodata(problem) File.write!(path, iodata) end end diff --git a/lib/dantzig/format/cplex.ex b/lib/dantzig/format/cplex.ex new file mode 100644 index 0000000..c63e0d3 --- /dev/null +++ b/lib/dantzig/format/cplex.ex @@ -0,0 +1,93 @@ +defmodule Dantzig.Format.CPLEX do + alias Dantzig.Constraint + alias Dantzig.Polynomial + alias Dantzig.Problem + alias Dantzig.ProblemVariable + + def to_iodata(%Problem{} = problem) do + constraints = Enum.sort(problem.constraints) + + constraints_iodata = + Enum.map(constraints, fn {_id, constraint} -> + constraint_to_iodata(constraint) + end) + + bounds = all_variable_bounds(Map.values(problem.variables)) + integers = variables_by_type(problem.variables, :integer) + binaries = variables_by_type(problem.variables, :binary) + + [ + direction_to_iodata(problem.direction), + "\n ", + Polynomial.to_lp_iodata_objective(problem.objective), + "\n", + "Subject To\n", + constraints_iodata, + "Bounds\n", + bounds, + "General\n", + list_variables(integers), + "Binary\n", + list_variables(binaries), + "End\n" + ] + end + + defp constraint_to_iodata(constraint = %Constraint{}) do + [ + " ", + constraint.name, + ": ", + Polynomial.to_lp_constraint(constraint.left_hand_side), + " ", + operator_to_iodata(constraint.operator), + " ", + to_string(constraint.right_hand_side), + "\n" + ] + end + + defp operator_to_iodata(operator) do + case operator do + :== -> "=" + other -> to_string(other) + end + end + + defp direction_to_iodata(:maximize), do: "Maximize" + defp direction_to_iodata(:minimize), do: "Minimize" + + + # Bounds have higher priority than variable type. So we need to exclude the :binary type here. + defp variable_bounds(%ProblemVariable{type: :binary}), do: "" + + defp variable_bounds(%ProblemVariable{} = v) do + case {v.min, v.max} do + {nil, nil} -> + " #{v.name} free\n" + + {nil, max} -> + " #{v.name} <= #{max}\n" + + {min, nil} -> + " #{min} <= #{v.name}\n" + + {min, max} -> + " #{min} <= #{v.name}\n #{v.name} <= #{max}\n" + end + end + + defp all_variable_bounds(variables) do + Enum.map(variables, &variable_bounds/1) + end + + defp variables_by_type(variables, type) do + for {name, %{type: ^type}} <- variables, do: name + end + + defp list_variables([]), do: [] + + defp list_variables(variables) do + for name <- variables, do: " #{name}\n" + end +end diff --git a/lib/dantzig/highs.ex b/lib/dantzig/highs.ex index e976bd0..a0852a0 100644 --- a/lib/dantzig/highs.ex +++ b/lib/dantzig/highs.ex @@ -1,17 +1,19 @@ defmodule Dantzig.HiGHS do - @moduledoc false + @moduledoc """ + Find solutions using [HiGHS solver][highs]. + + [highs]: https://highs.dev + """ - require Dantzig.Problem, as: Problem alias Dantzig.Config - alias Dantzig.Constraint - alias Dantzig.ProblemVariable + alias Dantzig.Format.CPLEX + alias Dantzig.Problem alias Dantzig.Solution - alias Dantzig.Polynomial @max_random_prefix 2 ** 32 def solve(%Problem{} = problem) do - iodata = to_lp_iodata(problem) + iodata = CPLEX.to_iodata(problem) command = Config.get_highs_binary_path() @@ -76,90 +78,4 @@ defmodule Dantzig.HiGHS do File.rm_rf(dirpath) end end - - defp constraint_to_iodata(constraint = %Constraint{}) do - [ - " ", - constraint.name, - ": ", - Polynomial.to_lp_constraint(constraint.left_hand_side), - " ", - operator_to_iodata(constraint.operator), - " ", - to_string(constraint.right_hand_side), - "\n" - ] - end - - defp operator_to_iodata(operator) do - case operator do - :== -> "=" - other -> to_string(other) - end - end - - defp direction_to_iodata(:maximize), do: "Maximize" - defp direction_to_iodata(:minimize), do: "Minimize" - - def to_lp_iodata(%Problem{} = problem) do - constraints = Enum.sort(problem.constraints) - - constraints_iodata = - Enum.map(constraints, fn {_id, constraint} -> - constraint_to_iodata(constraint) - end) - - bounds = all_variable_bounds(Map.values(problem.variables)) - integers = variables_by_type(problem.variables, :integer) - binaries = variables_by_type(problem.variables, :binary) - - [ - direction_to_iodata(problem.direction), - "\n ", - Polynomial.to_lp_iodata_objective(problem.objective), - "\n", - "Subject To\n", - constraints_iodata, - "Bounds\n", - bounds, - "General\n", - list_variables(integers), - "Binary\n", - list_variables(binaries), - "End\n" - ] - end - - # Bounds have higher priority than variable type. So we need to exclude the :binary type here. - defp variable_bounds(%ProblemVariable{type: :binary}), do: "" - - defp variable_bounds(%ProblemVariable{} = v) do - case {v.min, v.max} do - {nil, nil} -> - " #{v.name} free\n" - - {nil, max} -> - " #{v.name} <= #{max}\n" - - {min, nil} -> - " #{min} <= #{v.name}\n" - - {min, max} -> - " #{min} <= #{v.name}\n #{v.name} <= #{max}\n" - end - end - - defp all_variable_bounds(variables) do - Enum.map(variables, &variable_bounds/1) - end - - defp variables_by_type(variables, type) do - for {name, %{type: ^type}} <- variables, do: name - end - - defp list_variables([]), do: [] - - defp list_variables(variables) do - for name <- variables, do: " #{name}\n" - end end diff --git a/lib/dantzig/highs_downloader.ex b/lib/dantzig/highs_downloader.ex index efbf547..9fdd9a0 100644 --- a/lib/dantzig/highs_downloader.ex +++ b/lib/dantzig/highs_downloader.ex @@ -2,8 +2,6 @@ defmodule Dantzig.HiGHSDownloader do alias Dantzig.Config require Logger - @external_resource Dantzig.Config.get_highs_binary_path() - def maybe_download_for_target() do downloaded_version = Config.read_downloaded_version() highs_version = Config.get_highs_version() @@ -52,7 +50,7 @@ defmodule Dantzig.HiGHSDownloader do unpacked = :erl_tar.extract({:binary, tar_archive}, [ :compressed, - files: [~c'bin/highs'], + files: [~c"bin/highs"], cwd: to_charlist(tmp_dir) ]) diff --git a/lib/dantzig/problem.ex b/lib/dantzig/problem.ex index 54e04a3..358c746 100644 --- a/lib/dantzig/problem.ex +++ b/lib/dantzig/problem.ex @@ -21,10 +21,10 @@ defmodule Dantzig.Problem do direction: nil, variables: %{}, constraints: %{}, - contraints_metadata: %{} + constraints_metadata: %{} @spec solve_for_all_variables(t()) :: %{ - ProblemVariable.variable_namme() => SolvedConstraint.t() + ProblemVariable.variable_name() => SolvedConstraint.t() } def solve_for_all_variables(%__MODULE__{} = problem) do # There are two ways of solving for all variables: diff --git a/mix.exs b/mix.exs index 946b3a4..a8c1b14 100644 --- a/mix.exs +++ b/mix.exs @@ -17,7 +17,7 @@ defmodule Dantzig.MixProject do elixirc_paths: elixirc_paths(Mix.env()), package: package(), name: "Danztig", - description: "Linear progamming solver for elixir", + description: "Linear programming solver for elixir", source_url: "https://github.com/tmbb/dantzig" ] end @@ -45,9 +45,10 @@ defmodule Dantzig.MixProject do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:nimble_parsec, "~> 1.4"}, + {:nimble_parsec, "~> 1.4", runtime: false}, {:ex_doc, "~> 0.36", only: :dev, runtime: false}, - {:stream_data, "~> 1.1", only: [:test, :dev]} + {:stream_data, "~> 1.1", only: [:test, :dev]}, + {:mneme, "~> 0.10.2", only: :test} ] end diff --git a/mix.lock b/mix.lock index 9b5ad4b..1b9a493 100644 --- a/mix.lock +++ b/mix.lock @@ -1,9 +1,28 @@ %{ "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"}, "ex_doc": {:hex, :ex_doc, "0.37.1", "65ca30d242082b95aa852b3b73c9d9914279fff56db5dc7b3859be5504417980", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "6774f75477733ea88ce861476db031f9399c110640752ca2b400dbbb50491224"}, + "file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"}, + "finch": {:hex, :finch, "0.20.0", "5330aefb6b010f424dcbbc4615d914e9e3deae40095e73ab0c1bb0968933cadf", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.6.2 or ~> 1.7", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2658131a74d051aabfcba936093c903b8e89da9a1b63e430bee62045fa9b2ee2"}, + "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"}, + "hpax": {:hex, :hpax, "1.0.3", "ed67ef51ad4df91e75cc6a1494f851850c0bd98ebc0be6e81b026e765ee535aa", [:mix], [], "hexpm", "8eab6e1cfa8d5918c2ce4ba43588e894af35dbd8e91e6e55c817bca5847df34a"}, + "igniter": {:hex, :igniter, "0.5.52", "18777a36918e3bb91c70f07b69f6a6589d8fa5547a7d210b228d410a2453923f", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "8d75f0f2307e21b53ad96bd746f1806da91859ec0d4a68b203b763da4d5ae567"}, + "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"}, + "mime": {:hex, :mime, "2.0.7", "b8d739037be7cd402aee1ba0306edfdef982687ee7e9859bee6198c1e7e2f128", [:mix], [], "hexpm", "6171188e399ee16023ffc5b76ce445eb6d9672e2e241d2df6050f3c771e80ccd"}, + "mint": {:hex, :mint, "1.7.1", "113fdb2b2f3b59e47c7955971854641c61f378549d73e829e1768de90fc1abf1", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0 or ~> 1.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "fceba0a4d0f24301ddee3024ae116df1c3f4bb7a563a731f45fdfeb9d39a231b"}, + "mneme": {:hex, :mneme, "0.10.2", "f263ff74e993ef9eb160e04b608a149881e601ed6f5508c9afcafb578a184b52", [:mix], [{:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:igniter, "~> 0.3.76 or ~> 0.4.0 or ~> 0.5.0", [hex: :igniter, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "3b9493fc114c4bb0f6232e021620ffd7944819b9b9105a5b286b6dc907f7720a"}, + "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"}, + "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, + "owl": {:hex, :owl, "0.13.0", "26010e066d5992774268f3163506972ddac0a7e77bfe57fa42a250f24d6b876e", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"}, + "req": {:hex, :req, "0.5.16", "99ba6a36b014458e52a8b9a0543bfa752cb0344b2a9d756651db1281d4ba4450", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "974a7a27982b9b791df84e8f6687d21483795882a7840e8309abdbe08bb06f09"}, + "rewrite": {:hex, :rewrite, "1.2.0", "80220eb14010e175b67c939397e1a8cdaa2c32db6e2e0a9d5e23e45c0414ce21", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "a1cd702bbb9d51613ab21091f04a386d750fc6f4516b81900df082d78b2d8c50"}, + "sourceror": {:hex, :sourceror, "1.10.0", "38397dedbbc286966ec48c7af13e228b171332be1ad731974438c77791945ce9", [:mix], [], "hexpm", "29dbdfc92e04569c9d8e6efdc422fc1d815f4bd0055dc7c51b8800fb75c4b3f1"}, + "spitfire": {:hex, :spitfire, "0.2.1", "29e154873f05444669c7453d3d931820822cbca5170e88f0f8faa1de74a79b47", [:mix], [], "hexpm", "6eeed75054a38341b2e1814d41bb0a250564092358de2669fdb57ff88141d91b"}, "stream_data": {:hex, :stream_data, "1.1.3", "15fdb14c64e84437901258bb56fc7d80aaf6ceaf85b9324f359e219241353bfb", [:mix], [], "hexpm", "859eb2be72d74be26c1c4f272905667672a52e44f743839c57c7ee73a1a66420"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, + "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"}, } diff --git a/test/dantzig/format/cplex_test.exs b/test/dantzig/format/cplex_test.exs new file mode 100644 index 0000000..9966edb --- /dev/null +++ b/test/dantzig/format/cplex_test.exs @@ -0,0 +1,93 @@ +defmodule Dantzig.Format.CPLEXTest do + use ExUnit.Case, async: true + use Mneme + + use Dantzig.Polynomial.Operators + + alias Dantzig.Constraint + alias Dantzig.Problem + + @subject Dantzig.Format.CPLEX + + test "generates continuous variables correctly and uses them as default" do + problem = Problem.new(direction: :maximize) + {problem, x} = Problem.new_variable(problem, "x") + {problem, y} = Problem.new_variable(problem, "y", min: 1) + + problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) + problem = Problem.increment_objective(problem, x) + + data = IO.iodata_to_binary(@subject.to_iodata(problem)) + + auto_assert( + """ + Maximize + 1 x00000000_x + Subject To + c00000000: 1 x00000000_x + 2 x00000001_y = 10 + Bounds + x00000000_x free + 1 <= x00000001_y + General + Binary + End + """ <- data + ) + end + + test "generates integer variables correctly" do + problem = Problem.new(direction: :maximize) + {problem, x} = Problem.new_variable(problem, "x", type: :integer) + {problem, y} = Problem.new_variable(problem, "y", min: 1) + {problem, _z} = Problem.new_variable(problem, "z", min: 5, type: :integer) + + problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) + problem = Problem.increment_objective(problem, x) + + data = IO.iodata_to_binary(@subject.to_iodata(problem)) + + auto_assert( + """ + Maximize + 1 x00000000_x + Subject To + c00000000: 1 x00000000_x + 2 x00000001_y = 10 + Bounds + x00000000_x free + 1 <= x00000001_y + 5 <= x00000002_z + General + x00000000_x + x00000002_z + Binary + End + """ <- data + ) + end + + test "generates binary variables correctly" do + problem = Problem.new(direction: :maximize) + {problem, x} = Problem.new_variable(problem, "x", type: :binary) + {problem, y} = Problem.new_variable(problem, "y", min: 1) + + problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) + problem = Problem.increment_objective(problem, x) + + data = IO.iodata_to_binary(@subject.to_iodata(problem)) + + auto_assert( + """ + Maximize + 1 x00000000_x + Subject To + c00000000: 1 x00000000_x + 2 x00000001_y = 10 + Bounds + 1 <= x00000001_y + General + Binary + x00000000_x + End + """ <- data + ) + end +end diff --git a/test/dantzig/instances/closed_form_quadratic_test.exs b/test/dantzig/instances/closed_form_quadratic_test.exs index 4c2146c..06d0da5 100644 --- a/test/dantzig/instances/closed_form_quadratic_test.exs +++ b/test/dantzig/instances/closed_form_quadratic_test.exs @@ -13,7 +13,7 @@ defmodule Dantzig.Instances.ClosedFormQuadraticTest do problem = Problem.increment_objective(problem, x - x * x) end - solution = Dantzig.solve!(problem) + {:ok, solution} = Dantzig.HiGHS.solve(problem) assert solution.model_status == "Optimal" assert solution.feasibility == "Feasible" diff --git a/test/dantzig/instances/layout_examples_test.exs b/test/dantzig/instances/layout_examples_test.exs index e11587c..0969ac3 100644 --- a/test/dantzig/instances/layout_examples_test.exs +++ b/test/dantzig/instances/layout_examples_test.exs @@ -23,7 +23,7 @@ defmodule Danztig.Instances.LayoutExampleTest do |> Problem.increment_objective(center - left_margin - right_margin) end - solution = Dantzig.solve!(problem) + {:ok, solution} = Dantzig.HiGHS.solve(problem) # Test properties of the solution assert solution.model_status == "Optimal" diff --git a/test/dantzig/io_data_test.exs b/test/dantzig/io_data_test.exs deleted file mode 100644 index 8fdbc2f..0000000 --- a/test/dantzig/io_data_test.exs +++ /dev/null @@ -1,132 +0,0 @@ -defmodule Dantzig.IoDataTest do - use ExUnit.Case, async: true - - require Dantzig.Problem, as: Problem - use Dantzig.Polynomial.Operators - alias Dantzig.Constraint - alias Dantzig.HiGHS - - test "generates continuous variables correctly and uses them as default" do - problem = Problem.new(direction: :maximize) - {problem, x} = Problem.new_variable(problem, "x") - {problem, y} = Problem.new_variable(problem, "y", min: 1) - - problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) - problem = Problem.increment_objective(problem, x) - - io_data = HiGHS.to_lp_iodata(problem) - - expected = [ - "Maximize", - "\n ", - [["1", " ", ["x00000000_x"]], ""], - "\n", - "Subject To\n", - [ - [ - " ", - "c00000000", - ": ", - [["1", " ", ["x00000000_x"], " ", ["+ ", "2", " ", ["x00000001_y"]]], ""], - " ", - "=", - " ", - "10", - "\n" - ] - ], - "Bounds\n", - [" x00000000_x free\n", " 1 <= x00000001_y\n"], - "General\n", - [], - "Binary\n", - [], - "End\n" - ] - - assert io_data == expected - end - - test "generates integer variables correctly" do - problem = Problem.new(direction: :maximize) - {problem, x} = Problem.new_variable(problem, "x", type: :integer) - {problem, y} = Problem.new_variable(problem, "y", min: 1) - {problem, _z} = Problem.new_variable(problem, "z", min: 5, type: :integer) - - problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) - problem = Problem.increment_objective(problem, x) - - io_data = HiGHS.to_lp_iodata(problem) - - expected = [ - "Maximize", - "\n ", - [["1", " ", ["x00000000_x"]], ""], - "\n", - "Subject To\n", - [ - [ - " ", - "c00000000", - ": ", - [["1", " ", ["x00000000_x"], " ", ["+ ", "2", " ", ["x00000001_y"]]], ""], - " ", - "=", - " ", - "10", - "\n" - ] - ], - "Bounds\n", - [" x00000000_x free\n", " 1 <= x00000001_y\n", " 5 <= x00000002_z\n"], - "General\n", - [" x00000000_x\n", " x00000002_z\n"], - "Binary\n", - [], - "End\n" - ] - - assert io_data == expected - end - - test "generates binary varialbes correctly" do - problem = Problem.new(direction: :maximize) - {problem, x} = Problem.new_variable(problem, "x", type: :binary) - {problem, y} = Problem.new_variable(problem, "y", min: 1) - - problem = Problem.add_constraint(problem, Constraint.new(x + 2 * y, :==, 10)) - problem = Problem.increment_objective(problem, x) - - io_data = HiGHS.to_lp_iodata(problem) - - expected = [ - "Maximize", - "\n ", - [["1", " ", ["x00000000_x"]], ""], - "\n", - "Subject To\n", - [ - [ - " ", - "c00000000", - ": ", - [["1", " ", ["x00000000_x"], " ", ["+ ", "2", " ", ["x00000001_y"]]], ""], - " ", - "=", - " ", - "10", - "\n" - ] - ], - "Bounds\n", - ["", " 1 <= x00000001_y\n"], - "General\n", - [], - "Binary\n", - [" x00000000_x\n"], - "End\n" - ] - - assert io_data == expected - end -end diff --git a/test/dantzig/problem_test.exs b/test/dantzig/problem_test.exs index 398b6b3..2350a86 100644 --- a/test/dantzig/problem_test.exs +++ b/test/dantzig/problem_test.exs @@ -109,7 +109,7 @@ defmodule Dantzig.ProblemTest do problem = Problem.add_constraint(problem, Constraint.new(x + y, :==, 20)) problem = Problem.increment_objective(problem, x + y + z) - assert {:ok, solution} = Dantzig.solve(problem) + assert {:ok, solution} = Dantzig.HiGHS.solve(problem) assert solution.feasibility == "Feasible" end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 869559e..1e52b80 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -1 +1,2 @@ ExUnit.start() +Mneme.start()