Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/dantzig.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ defmodule Dantzig do
Documentation for `Dantzig`.
"""

alias Dantzig.Format.CPLEX
alias Dantzig.HiGHS
alias Dantzig.Problem

Expand All @@ -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
93 changes: 93 additions & 0 deletions lib/dantzig/format/cplex.ex
Original file line number Diff line number Diff line change
@@ -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
100 changes: 8 additions & 92 deletions lib/dantzig/highs.ex
Original file line number Diff line number Diff line change
@@ -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()

Expand Down Expand Up @@ -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
4 changes: 1 addition & 3 deletions lib/dantzig/highs_downloader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
])

Expand Down
4 changes: 2 additions & 2 deletions lib/dantzig/problem.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 4 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
19 changes: 19 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -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"},
}
Loading