Skip to content

Commit

Permalink
Evolog Modules: module-based implementation of bin-packing problem in…
Browse files Browse the repository at this point in the history
…cluding unit tests
  • Loading branch information
madmike200590 committed Aug 7, 2024
1 parent 3540762 commit 1dcdbf0
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class ListEncoder extends AbstractAggregateEncoder {
// Now build the list as a recursively nested function term
"$id$_lst_element(ARGS, IDX, lst(N, lst_empty)) :- $id$_element(ARGS, N), not $id$_element_has_successor(ARGS, N), IDX = 0. " +
"$id$_lst_element(ARGS, IDX, lst(N, lst(K, TAIL))) :- $id$_element(ARGS, N), $id$_element_successor(ARGS, K, N), $id$_lst_element(ARGS, PREV_IDX, lst(K, TAIL)), IDX = PREV_IDX + 1. " +
"has_next_$id$_element(ARGS, IDX) :- $id$_lst_element(ARGS, IDX, _), NEXT_IDX = IDX + 1, $id$_lst_element(ARGS, NEXT_IDX, _). " +
"$aggregate_result$(ARGS, LIST) :- $id$_lst_element(ARGS, IDX, LIST), not has_next_$id$_element(ARGS, IDX).");
"$id$_has_next_element(ARGS, IDX) :- $id$_lst_element(ARGS, IDX, _), NEXT_IDX = IDX + 1, $id$_lst_element(ARGS, NEXT_IDX, _). " +
"$aggregate_result$(ARGS, LIST) :- $id$_lst_element(ARGS, IDX, LIST), not $id$_has_next_element(ARGS, IDX).");

private final ProgramParser parser;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,9 @@ Stream<DynamicTest> alphaEnd2EndTests() {
return Stream.of(
alphaEnd2EndTest("3-Coloring", E2E_TESTS_DIR + "3col.asp"),
alphaEnd2EndTest("modules-basic", E2E_TESTS_DIR + "modules-basic.evl"),
alphaEnd2EndTest("neighboring-vertices-list", E2E_TESTS_DIR + "neighboring-vertices-list.evl")
alphaEnd2EndTest("neighboring-vertices-list", E2E_TESTS_DIR + "neighboring-vertices-list.evl"),
alphaEnd2EndTest("bin-packing", E2E_TESTS_DIR + "bin-packing.evl", E2E_TESTS_DIR + "bin-packing.test.evl")
);
}



}
66 changes: 66 additions & 0 deletions alpha-solver/src/test/resources/e2e-tests/bin-packing.evl
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
%%% Module Bin-Packing %%%
%
% This module solves instances of the bin-packing decision problem:
% Given a set of bins, described as terms bin(B, SIZE), and items, described as terms item(I, SIZE),
% the module calculates all assignments of items to bins, such that all items are packed and no
% bin size is exceeded. Each answer set corresponds to one valid assignment.
%
% Input: a fact "instance(BIN_LST, ITEM_LST)", where
% - BIN_LST is a list of terms of form bin(B, SIZE),
% - and ITEM_LST is a list of terms of form item(I, SIZE)
%
%%%
#module bin_packing(instance/2 => {item_packed/2}) {
% Unpack input lists.
bin_element(E, TAIL) :- instance(lst(E, TAIL), _).
bin_element(E, TAIL) :- bin_element(_, lst(E, TAIL)).
bin(B, S) :- bin_element(bin(B, S), _).

item_element(E, TAIL) :- instance(_, lst(E, TAIL)).
item_element(E, TAIL) :- item_element(_, lst(E, TAIL)).
item(I, S) :- item_element(item(I, S), _).

% For every item, guess an assignment to each bin.
{ item_packed(I, B) : bin(B, _) } :- item(I, _).
% An item may only be assigned to one bin at a time
:- item_packed(I, B1), item_packed(I, B2), B1 != B2.

% We must not exceed the capacity of any bin.
capacity_used(B, C) :- C = #sum{S : item(I, S), item_packed(I, B)}, bin(B, _).
:- capacity_used(B, C), C > S, bin(B, S).

% Every item must be packed.
item_packed_somewhere(I) :- item_packed(I, _).
:- item(I, _), not item_packed_somewhere(I).
}


%%% Bin-Packing Valuation %%%
%
% Given a Bin-Packing instance and a satisfying assignment,
% calculates a numeric value for the given assignment.
% The value is the sum of the capacities of all bins used in the assignment.
% Intuitively, we calculate the cost in total storage capacity of a given bin assignment.
%
%%%
#module bin_packing_valuation(instance_with_assignment/3 => {assignment_value/1}) {
%% Unpack input
% Extract bins from bin list
bin_element(E, TAIL) :- instance_with_assignment(lst(E, TAIL), _, _).
bin_element(E, TAIL) :- bin_element(_, lst(E, TAIL)).
bin(B, S) :- bin_element(bin(B, S), _).

% Extract items from item list
item_element(E, TAIL) :- instance_with_assignment(_, lst(E, TAIL), _).
item_element(E, TAIL) :- item_element(_, lst(E, TAIL)).
item(I, S) :- item_element(item(I, S), _).

% Extract individual item assignments
assignment_element(E, TAIL) :- instance_with_assignment(_, _, lst(E, TAIL)).
assignment_element(E, TAIL) :- assignment_element(_, lst(E, TAIL)).
assigned(I, B) :- assignment_element(item_packed(I, B), _).

% Get all bins that are used in the assignment
bin_used(B) :- assigned(_, B).
assignment_value(V) :- V = #sum{ S : bin(B, S), bin_used(B)}.
}
89 changes: 89 additions & 0 deletions alpha-solver/src/test/resources/e2e-tests/bin-packing.test.evl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
%%% Unit tests for bin_packing module

instance(BINS, ITEMS) :- BINS = #list{bin(B, S) : bin(B, S)}, ITEMS = #list{item(I, S) : item(I, S)}.
instance_with_assignment(BINS, ITEMS, ASSGN) :- instance(BINS, ITEMS), #bin_packing[BINS, ITEMS](ASSGN).
bin_assignment(A) :- instance_with_assignment(_, _, A).
assignment_valuation_result(A, V) :- instance_with_assignment(B, I, A), #bin_packing_valuation[B, I, A](V).
assignment_value(A, V) :- assignment_valuation_result(A, lst(assignment_value(V), lst_empty)).

#test singleBinPositiveCase(expect: 1) {
given {
bin(a, 10).

item(x, 3).
item(y, 2).
item(z, 4).

}
assertForAll {
% There is excatly one assignment
assignment_found :- bin_assignment(_).
:- not assignment_found.
:- bin_assignment(A1), bin_assignment(A2), A1 != A2.

% Every item is in a bin
assignment_element(lst(E, TAIL), E, TAIL) :- bin_assignment(lst(E, TAIL)).
assignment_element(ASSGN, E, TAIL) :- assignment_element(ASSGN, _, lst(E, TAIL)).
assigned(I, B, A) :- assignment_element(A, item_packed(I, B), _).
item_assigned(A, I) :- item(I, _), assigned(I, _, A).
:- bin_assignment(A), item(I, _), not item_assigned(A, I).

% In no assignment, any bin capacity is exceeded
capacity_used(A, B, C) :- C = #sum{S : item(I, S), item_assigned(I, B, A)}, bin_assignment(A), bin(B, _).
:- bin_assignment(A), bin(B, S), capacity_used(A, B, C), C > S.

% The single assignment has value 10
:- bin_assignment(A), not assignment_value(A, 10).
}
}

#test singleBinNegativeCase(expect: 1) {
given {
bin(a, 7).

item(x, 3).
item(y, 2).
item(z, 4).
}
assertForAll {
% There is no assignment
assignment_found :- bin_assignment(_).
:- assignment_found.
}
}

#test twoBinsTwoAssignments(expect: 1) {
given {
bin(a, 6).
bin(b, 4).

item(x, 3).
item(y, 2).
item(z, 4).

}
assertForAll {
% There are exactly two assignments
num_assignments(N) :- N = #count{ A : bin_assignment(A)}.
assignment_found :- bin_assignment(_).
:- num_assignments(N), N != 2.

% Every item is in a bin
assignment_element(lst(E, TAIL), E, TAIL) :- bin_assignment(lst(E, TAIL)).
assignment_element(ASSGN, E, TAIL) :- assignment_element(ASSGN, _, lst(E, TAIL)).
assigned(I, B, A) :- assignment_element(A, item_packed(I, B), _).
item_assigned(A, I) :- item(I, _), assigned(I, _, A).
:- bin_assignment(A), item(I, _), not item_assigned(A, I).

% In no assignment, any bin capacity is exceeded
capacity_used(A, B, C) :- C = #sum{S : item(I, S), item_assigned(I, B, A)}, bin_assignment(A), bin(B, _).
:- bin_assignment(A), bin(B, S), capacity_used(A, B, C), C > S.

% There is an assignment in which items y and z are in bin a.
y_and_z_in_a :- bin_assignment(A), assigned(y, a, A), assigned(z, a, A).
:- not y_and_z_in_a.

% Both assignments have value 10
:- bin_assignment(A), not assignment_value(A, 10).
}
}

0 comments on commit 1dcdbf0

Please sign in to comment.