Current section

Files

Jump to
fixpoint lib examples bin_packing search.ex
Raw

lib/examples/bin_packing/search.ex

defmodule CPSolver.Examples.BinPacking.Search do
alias CPSolver.Variable.Interface
alias CPSolver.Search
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)
item_assignment_ids = MapSet.new(Map.keys(item_assignment_map))
choose_variable_fun = fn variables ->
## get all (unfixed) item assignment vars
_item_vars =
Enum.reduce_while(variables, nil, fn v, item_vars_acc ->
item_var? = v.name in item_assignment_ids
cond do
#Interface.fixed?(v) ->
# {:cont, item_vars_acc}
is_nil(item_vars_acc) ->
(item_var? && {:cont, [v]}) || {:cont, nil}
true ->
(item_var? && {:cont, [v | item_vars_acc]}) || {:halt, item_vars_acc}
end
end)
end
choose_value_fun = fn var ->
value_branching(var, bin_load_vars, item_assignment_map, capacity)
end
fn
:init, _, _ ->
:ok
:branch, variables, _data ->
case choose_variable_fun.(variables) do
nil ->
[]
item_variables ->
## By construction (choose_variable_fun), item vars are in increasing order of weights
selected_variable = List.last(item_variables)
{bins, slack, num_loads} = choose_value_fun.(selected_variable)
domain_partitions =
partitions(selected_variable, bins, slack, num_loads)
List.wrap(Search.partition_record(selected_variable, domain_partitions))
end
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 = Map.get(item_assignment_map, var.name)
## Find bin with minimal slack
## TODO: advanced branching, as described by Gecode docs ("two alternatives" case)
##
{bins, bin_slack, num_loads} =
Enum.reduce_while(Enum.with_index(bin_load_vars, 1), {[], nil, 0}, fn
{load_var, bin_idx},
{min_bins, min_slack, load_count} = slack_acc ->
cond do
Interface.contains?(var, bin_idx) ->
slack = capacity - Interface.min(load_var) - item_weight
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, nil}}
slack == 0 ->
## Perfect fit
{:halt, {[bin_idx], nil, nil}}
slack < 0 ->
## No fit
{:cont, slack_acc}
slack < min_slack ->
## Better fit
{:cont, {[bin_idx], slack, load_count + 1}}
true ->
## Keep current min, add bin to the list of current min bins
{:cont, {[bin_idx | min_bins], min_slack, load_count + 1}}
end
true ->
{:cont, slack_acc}
end
end)
## TODO:
## We have not implemented the branching
## as suggested by Gecode docs for 2-alternative branching:
###
### – 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
###
## What we currently do for branching (see CPSolver.Search):
## - For the first branch we fix the variable with the chosen value;
## - For the second branch, we remove the value from the variable.
##
## - To match Gecode, we will have to remove values for several variables (the ones with the same slack)
##
## May be possible by implementing custom value selector:
## (see CPSolver.Search.ValueSelector.Split for an example).
##
##
## Also, return length of bin loads (to be used when building partitions)
{bins, bin_slack, num_loads}
end
defp partitions(variable, bins, slack, _num_loads) do
bin = Enum.random(bins)
if slack in [nil, 0] do ##|| (length(bins) == num_loads) do
Partition.fixed_partition(bin, variable)
else
Partition.partition_by_fix(bin, variable)
end
|> List.wrap()
end
end