Packages
fixpoint
0.19.1
0.22.1
0.21.5
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.6
0.20.5
0.20.4
0.20.3
0.20.2
0.20.1
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.18.2
0.18.1
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.9
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.13.5
0.13.4
0.13.2
0.13.1
0.12.9
0.12.8
0.12.7
0.12.6
0.12.5
0.12.4
0.12.2
0.12.1
0.11.8
0.11.7
0.11.6
0.11.5
0.11.4
0.11.3
0.11.2
0.11.1
0.10.7
0.10.6
0.10.5
0.10.4
0.10.3
0.10.2
0.10.1
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.46
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.28
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.12
0.5.11
0.5.10
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.3
0.4.2
0.4.1
0.4.0
0.3.6
0.3.5
0.3.4
0.3.3
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.1.3
0.1.2
0.1.1
0.1.0
Constraint Programming Solver
Current section
Files
Jump to
Current section
Files
lib/examples/bin_packing/search.ex
defmodule CPSolver.Examples.BinPacking.Search do
alias CPSolver.Variable.Interface
alias CPSolver.Search.Partition
@doc """
Complete decreasing best fit branching,
roughly as per https://www.gecode.dev/doc-latest/MPG.pdf, chapter 20
"""
def cdbf(item_weights, item_assignment_vars, bin_load_vars, capacity) do
## Create a list [{item_assignment_index, item_weight}]
## (will be used for matching the item assignment variables with items' weights)
##
## Note: item weights are sorted in decreasing order
item_assignment_map =
Enum.zip(item_assignment_vars, item_weights)
|> Map.new(fn {var, weight} -> {var.name, weight} end)
fn
:init, _, _ ->
:ok
:branch, variables, _data ->
case get_item_variables(variables, item_assignment_map) do
nil ->
[]
item_variables ->
selected_variable = List.first(item_variables)
{bins, slack, no_fit_bins} =
value_branching(selected_variable, bin_load_vars, item_assignment_map, capacity)
partitions(
item_variables,
bins,
slack,
no_fit_bins,
bin_load_vars,
item_assignment_map,
capacity
)
end
end
end
## Get the (unfixed) variables with the largest item weight.
defp get_item_variables(variables, item_assignment_map) do
## We rely on:
## - item weights sorted in descending order
## - the item assignment variables are adjacent to each other within the variable list;
## that is, all of them are located in the single block.
Enum.reduce_while(variables, {nil, nil}, fn v, {last_item_weight, item_vars_acc} = acc ->
## Is variable an 'item assignment' variable?
item_weight = Map.get(item_assignment_map, v.name)
cond do
is_nil(item_vars_acc) ->
(item_weight && {:cont, {item_weight, [v]}}) || {:cont, {nil, nil}}
true ->
if item_weight do
## Found another item assignment var
## The weight is different?
## We are only interested in the vars with the identical weights
if last_item_weight == item_weight do
{:cont, {item_weight, [v | item_vars_acc]}}
else
{:halt, acc}
end
else
## The end of item assignment variables' block
## (these variables have to be adjacent in the list of all variables)
{:halt, acc}
end
end
end)
|> then(fn {_, res} -> if res, do: Enum.reverse(res) end)
end
defp value_branching(var, bin_load_vars, item_assignment_map, capacity) do
## The variable is quaranteed to be unfixed `item assignment`,
## (see `choose_variable_fun`)
item_weight = get_item_weight(var, item_assignment_map)
## Find bins with minimal slack
##
{_bins, _bin_slack, _no_fit_bins} =
Enum.reduce_while(Enum.with_index(bin_load_vars, 1), {[], nil, []}, fn
{load_var, bin_idx}, {min_bins, min_slack, no_fit_bins} = slack_acc ->
cond do
Interface.contains?(var, bin_idx) ->
slack = slack(item_weight, capacity, load_var)
cond do
Interface.fixed?(load_var) ->
## The bin load has already been fixed,
## so the item has to be there (no choice).
## TODO: this is the case where further branching doesn't make sense.
## The related issue: https://github.com/bokner/fixpoint/issues/96
{:halt, {[bin_idx], nil, []}}
slack == 0 ->
## Perfect fit
{:halt, {[bin_idx], 0, []}}
slack < 0 ->
## No fit
{:cont, {min_bins, min_slack, [bin_idx | no_fit_bins]}}
slack < min_slack ->
## Better fit
{:cont, {[bin_idx], slack, no_fit_bins}}
true ->
## Keep current min, add bin to the list of current min bins
{:cont, {[bin_idx | min_bins], min_slack, no_fit_bins}}
end
true ->
{:cont, slack_acc}
end
end)
end
defp partitions(
[selected_variable | other_item_variables] = _item_variables,
bins,
slack,
no_fit_bins,
bin_load_vars,
item_assignment_map,
capacity
) do
cond do
Enum.empty?(bins) ->
throw(:fail)
slack in [0, nil] ->
## Perfect fit or the bin being fixed.
## We only need a single partition with the fixed value.
[
Partition.fixed_value_partition(selected_variable, List.first(bins)),
]
true ->
## As suggested by Gecode docs for 2-alternative branching
## (https://www.gecode.dev/doc-latest/MPG.pdf, chapter 20):
###
### – Not only prune bin b from the potential bins for item i,
## but also prune all bins with the same slack as b
## from the potential bins for all items with the same size as i
##
##
## Note: at this point, all item variables are assocated with the same item size as the first item variable.
##
## For each item variable, we will iterate over bin load vars to
## identify the ones with the same slack as computed for the first variable.
##
selected_bin = List.first(bins)
## We will also prune the selected variable, if there are no-fit bins
selected_variable_prun_partition =
Enum.empty?(no_fit_bins) &&
Partition.removed_value_partition(selected_variable, selected_bin) ||
Partition.remove_multiple_values_partition(selected_variable, no_fit_bins)
prune_partition =
Enum.reduce(other_item_variables, selected_variable_prun_partition,
fn variable, acc ->
w = get_item_weight(variable, item_assignment_map)
{_idx, acc} =
Enum.reduce(bin_load_vars, {1, acc}, fn load_var, {bin, acc2} ->
{bin + 1,
cond do
!Interface.contains?(variable, bin) ->
acc2
Interface.fixed?(load_var) ->
Map.put(acc2, variable.id, fn variable ->
Interface.fix(variable, bin)
end)
slack(w, capacity, load_var) == slack ->
Map.put(acc2, variable.id, fn variable ->
Interface.remove(variable, bin)
end)
true ->
acc2
end}
end)
acc
end
)
[
Partition.fixed_value_partition(selected_variable, selected_bin),
prune_partition
]
end
end
defp slack(item_weight, capacity, load_variable) do
capacity - Interface.min(load_variable) - item_weight
end
defp get_item_weight(variable, item_assignment_map) do
Map.get(item_assignment_map, variable.name)
end
end