diff --git a/Assignment2/A/tsp.P b/Assignment2/A/tsp.P new file mode 100644 index 0000000..bfa0ca7 --- /dev/null +++ b/Assignment2/A/tsp.P @@ -0,0 +1,34 @@ +/*Jacob Besse +Prolog*/ + +/*This is the data set.*/ + +edge(a,b,2). edge(a,c,3). edge(a,d,1). +edge(b,d,2). edge(b,e,2). edge(b,f,4). +edge(c,a,3). edge(c,d,2). edge(c,e,2). +edge(d,a,1). edge(d,b,2). +edge(e,c,2). edge(e,d,2). edge(e,f,1). +edge(f,b,4). edge(f,c,3). edge(f,e,1). + +len([], 0). +len([_|T], N):- len(T, X), N is X+1 . + +best_path(Visited, Total):- path(a, a, Visited, Total). + +path(Start, Fin, Visited, Total) :- path(Start, Fin, [Start], Visited, 0, Total). + +path(Start, Fin, CurrentLoc, Visited, Costn, Total) :- + edge(Start, StopLoc, Distance), NewCostn is Costn + Distance, \+ member(StopLoc, CurrentLoc), + path(StopLoc, Fin, [StopLoc|CurrentLoc], Visited, NewCostn, Total). + +path(Start, Fin, CurrentLoc, Visited, Costn, Total) :- + edge(Start, Fin, Distance), reverse([Fin|CurrentLoc], Visited), len(Visited, Q), + (Q\=7 -> Total is 100000; Total is Costn + Distance). + +shortest_path(Path):-setof(Cost-Path, best_path(Path,Cost), Holder),pick(Holder,Path). + +best(Cost-Holder,Bcost-_,Cost-Holder):- Cost 0; + +var x{1..N,1..N} binary; + +minimize Total: + sum{ i in 1..N,j in 1..N: i<>j} dist[i,j]* x[i,j]; + +C1{k in 1..N}: sum{i in 1..N: i <> k} x[i,k] =1; + +C2{k in 1..N}: sum{i in 1..N: i <> k} x[k,i] =1; + +C3{i in 2..N, j in 2..N: i <> j}: m[i]-m[j]+N*x[i,j]<=N-1; + +C4{i in 2..N}: m[i]<=N-1; + + +data tsp.dat; +option solver cplex; +solve; +display Total; +display x; +display m; diff --git a/Assignment2/A/tsp.py b/Assignment2/A/tsp.py new file mode 100644 index 0000000..414524e --- /dev/null +++ b/Assignment2/A/tsp.py @@ -0,0 +1,80 @@ +"""Simple Travelling Salesperson Problem (TSP) between cities.""" + +from ortools.constraint_solver import routing_enums_pb2 +from ortools.constraint_solver import pywrapcp + + +def create_data_model(): + """Stores the data for the problem.""" + data = {} + data["distance_matrix"] = [ + [99, 2, 3, 1, 99, 99], + [99, 99, 99, 2, 2, 4], + [3, 99, 99, 2, 2, 99], + [1, 2, 99, 99, 99, 99], + [99, 99, 2, 2, 99, 1], + [99, 4, 3, 99, 1, 99], + ] + data["num_vehicles"] = 1 + data["depot"] = 0 + return data + + +def print_solution(manager, routing, solution): + """Prints solution on console.""" + print(f"Objective: {solution.ObjectiveValue()} miles") + index = routing.Start(0) + plan_output = "Route for vehicle 0:\n" + route_distance = 0 + while not routing.IsEnd(index): + plan_output += f" {manager.IndexToNode(index) + 1} ->" + previous_index = index + index = solution.Value(routing.NextVar(index)) + route_distance += routing.GetArcCostForVehicle(previous_index, index, 0) + plan_output += f" {manager.IndexToNode(index)}\n" + plan_output += f"Route distance: {route_distance} miles\n" + print(plan_output) + + +def main(): + """Entry point of the program.""" + # Instantiate the data problem. + data = create_data_model() + + # Create the routing index manager. + manager = pywrapcp.RoutingIndexManager( + len(data["distance_matrix"]), data["num_vehicles"], data["depot"] + ) + + # Create Routing Model. + routing = pywrapcp.RoutingModel(manager) + + + def distance_callback(from_index, to_index): + """Returns the distance between the two nodes.""" + # Convert from routing variable Index to distance matrix NodeIndex. + from_node = manager.IndexToNode(from_index) + to_node = manager.IndexToNode(to_index) + return data["distance_matrix"][from_node][to_node] + + transit_callback_index = routing.RegisterTransitCallback(distance_callback) + + # Define cost of each arc. + routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) + + # Setting first solution heuristic. + search_parameters = pywrapcp.DefaultRoutingSearchParameters() + search_parameters.first_solution_strategy = ( + routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC + ) + + # Solve the problem. + solution = routing.SolveWithParameters(search_parameters) + + # Print solution on console. + if solution: + print_solution(manager, routing, solution) + + +if __name__ == "__main__": + main() diff --git a/Assignment2/B/data.lp b/Assignment2/B/data.lp new file mode 100644 index 0000000..f3c8d1c --- /dev/null +++ b/Assignment2/B/data.lp @@ -0,0 +1,10 @@ +task(0, 2). +task(1, 3). +task(2, 1). +task(3, 9). +task(4, 2). +task(5, 8). +task(6, 9). +task(7, 8). +task(8, 5). +task(9, 8). diff --git a/Assignment2/B/data.py b/Assignment2/B/data.py new file mode 100644 index 0000000..4764e00 --- /dev/null +++ b/Assignment2/B/data.py @@ -0,0 +1 @@ +[2, 3, 1, 9, 2, 8, 9, 8, 5, 8] \ No newline at end of file diff --git a/Assignment2/B/data10.lp b/Assignment2/B/data10.lp new file mode 100644 index 0000000..2cd274c --- /dev/null +++ b/Assignment2/B/data10.lp @@ -0,0 +1,10 @@ +task(0, 4). +task(1, 1). +task(2, 3). +task(3, 9). +task(4, 10). +task(5, 7). +task(6, 5). +task(7, 10). +task(8, 1). +task(9, 3). diff --git a/Assignment2/B/data10.py b/Assignment2/B/data10.py new file mode 100644 index 0000000..987812e --- /dev/null +++ b/Assignment2/B/data10.py @@ -0,0 +1 @@ +[4, 1, 3, 9, 10, 7, 5, 10, 1, 3] \ No newline at end of file diff --git a/Assignment2/B/data20.lp b/Assignment2/B/data20.lp new file mode 100644 index 0000000..91fdede --- /dev/null +++ b/Assignment2/B/data20.lp @@ -0,0 +1,20 @@ +task(0, 7). +task(1, 6). +task(2, 2). +task(3, 17). +task(4, 10). +task(5, 19). +task(6, 20). +task(7, 17). +task(8, 1). +task(9, 12). +task(10, 17). +task(11, 6). +task(12, 5). +task(13, 7). +task(14, 20). +task(15, 17). +task(16, 2). +task(17, 3). +task(18, 19). +task(19, 11). diff --git a/Assignment2/B/data20.py b/Assignment2/B/data20.py new file mode 100644 index 0000000..07aa4f9 --- /dev/null +++ b/Assignment2/B/data20.py @@ -0,0 +1 @@ +[7, 6, 2, 17, 10, 19, 20, 17, 1, 12, 17, 6, 5, 7, 20, 17, 2, 3, 19, 11] \ No newline at end of file diff --git a/Assignment2/B/data30.lp b/Assignment2/B/data30.lp new file mode 100644 index 0000000..a2d5747 --- /dev/null +++ b/Assignment2/B/data30.lp @@ -0,0 +1,30 @@ +task(0, 1). +task(1, 19). +task(2, 21). +task(3, 8). +task(4, 6). +task(5, 24). +task(6, 17). +task(7, 23). +task(8, 15). +task(9, 22). +task(10, 14). +task(11, 2). +task(12, 2). +task(13, 10). +task(14, 3). +task(15, 17). +task(16, 17). +task(17, 19). +task(18, 26). +task(19, 9). +task(20, 28). +task(21, 16). +task(22, 22). +task(23, 19). +task(24, 17). +task(25, 15). +task(26, 18). +task(27, 15). +task(28, 23). +task(29, 27). diff --git a/Assignment2/B/data30.py b/Assignment2/B/data30.py new file mode 100644 index 0000000..e741883 --- /dev/null +++ b/Assignment2/B/data30.py @@ -0,0 +1 @@ +[1, 19, 21, 8, 6, 24, 17, 23, 15, 22, 14, 2, 2, 10, 3, 17, 17, 19, 26, 9, 28, 16, 22, 19, 17, 15, 18, 15, 23, 27] \ No newline at end of file diff --git a/Assignment2/B/randomgen.py b/Assignment2/B/randomgen.py new file mode 100644 index 0000000..75dbd24 --- /dev/null +++ b/Assignment2/B/randomgen.py @@ -0,0 +1,15 @@ +import random +import sys +if len(sys.argv) == 2: x = int(sys.argv[1]) +else: x = 10 +file1 = open("data" + str(x) + ".lp", "w") +file2 = open("data" + str(x) + ".py", "w") +file2.write('[') +for i in range(x): + j = random.randint(1,x) + file1.write("task({}, {}).\n".format(i, j)) + file2.write(str(j)) + if i != x - 1: file2.write(", ") +file2.write(']') +file1.close() +file2.close() \ No newline at end of file diff --git a/Assignment2/B/task.lp b/Assignment2/B/task.lp new file mode 100644 index 0000000..ad8c8ca --- /dev/null +++ b/Assignment2/B/task.lp @@ -0,0 +1,16 @@ +% Input facts +#const deadline = 90. +#const processors = 8. +#include "data30.lp". + +1 { start(T, Time) : Time = 0..deadline } 1 :- task(T, _). +1 { end(T, Time) : Time = 0..deadline } 1 :- task(T, _). + +:- start(T, Time), end(T, End), task(T, Length), End != Time + Length. + +:- Time = 0..deadline, not #count{ T : start(T, StartTime), end(T, EndTime), StartTime <= Time, Time < EndTime } <= processors. + +:- task(T, _), end(T, Time), Time > deadline. + +#show start/2. +#show end/2. diff --git a/Assignment2/B/task.py b/Assignment2/B/task.py new file mode 100644 index 0000000..1cd64e0 --- /dev/null +++ b/Assignment2/B/task.py @@ -0,0 +1,50 @@ +"""Solves a simple assignment problem.""" +from ortools.sat.python import cp_model + + +def main() -> None: + # Data + costs = [10,5,8,10,5,2,1] + num_workers = 2 + deadline = 20 + num_tasks = len(costs) + + # Model + model = cp_model.CpModel() + + # Variables + x = {} + for worker in range(num_workers): + for task in range(num_tasks): + x[worker, task] = model.new_bool_var(f"x[{worker},{task}]") + + # Each task is assigned to exactly one worker. + for task in range(num_tasks): + model.add_exactly_one(x[worker, task] for worker in range(num_workers)) + + # Objective + for worker in range(num_workers): + objective_terms = [] + for task in range(num_tasks): + objective_terms.append(costs[task] * x[worker, task]) + model.AddLinearConstraint(sum(objective_terms), 0, deadline) + + # Solve + solver = cp_model.CpSolver() + status = solver.solve(model) + + # Print solution. + if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: + print(f"Total cost = {solver.objective_value}\n") + for worker in range(num_workers): + for task in range(num_tasks): + if solver.boolean_value(x[worker, task]): + print( + f"Worker {worker} assigned to task {task}." + + f" Cost = {costs[task]}" + ) + else: + print("No solution found.") + +if __name__ == "__main__": + main() diff --git a/Assignment2/C/cut.dat b/Assignment2/C/cut.dat new file mode 100644 index 0000000..f6c9d0e --- /dev/null +++ b/Assignment2/C/cut.dat @@ -0,0 +1,13 @@ +param: WIDTHS: orders:= + 6.77 10 + 7.56 40 + 17.46 33 + 18.76 10 ; + +param nPAT := 9 ; + +param nbr: 1 2 3 4 5 6 7 8 9 := + 6.77 0 1 1 0 3 2 0 1 4 + 7.56 1 0 2 1 1 4 6 5 2 + 17.46 0 1 0 2 1 0 1 1 1 + 18.76 3 2 2 1 1 1 0 0 0 ; diff --git a/Assignment2/C/cut.lp b/Assignment2/C/cut.lp new file mode 100644 index 0000000..74498aa --- /dev/null +++ b/Assignment2/C/cut.lp @@ -0,0 +1,66 @@ +#const max = 10. +#const size = 30. +order(1, size). +order(2, size). +order(3, size). +order(4, size). + +width(1). +width(2). +width(3). +width(4). + +pattern(1, 1, 0). +pattern(1, 2, 1). +pattern(1, 3, 0). +pattern(1, 4, 3). + +pattern(2, 1, 1). +pattern(2, 2, 0). +pattern(2, 3, 1). +pattern(2, 4, 2). + +pattern(3, 1, 1). +pattern(3, 2, 2). +pattern(3, 3, 0). +pattern(3, 4, 2). + +pattern(4, 1, 0). +pattern(4, 2, 1). +pattern(4, 3, 2). +pattern(4, 4, 1). + +pattern(5, 1, 3). +pattern(5, 2, 1). +pattern(5, 3, 1). +pattern(5, 4, 1). + +pattern(6, 1, 2). +pattern(6, 2, 4). +pattern(6, 3, 0). +pattern(6, 4, 1). + +pattern(7, 1, 0). +pattern(7, 2, 6). +pattern(7, 3, 1). +pattern(7, 4, 0). + +pattern(8, 1, 1). +pattern(8, 2, 5). +pattern(8, 3, 1). +pattern(8, 4, 0). + +pattern(9, 1, 4). +pattern(9, 2, 2). +pattern(9, 3, 1). +pattern(9, 4, 0). + + +1 { cut(I, 0..max) } 1:- pattern(I, _, _). +:- order(W, L), #sum { J * K: pattern(I, W, J), cut(I, K)} < L. + +total(N) :- N = #sum {J, I : cut(I, J)}. +#minimize {N: total(N)}. + +#show cut/2. +#show total/1. diff --git a/Assignment2/C/cut.mod b/Assignment2/C/cut.mod new file mode 100644 index 0000000..0e074f5 --- /dev/null +++ b/Assignment2/C/cut.mod @@ -0,0 +1,20 @@ +set WIDTHS; +param orders {WIDTHS} > 0; + +param nPAT integer >= 0; +param nbr {WIDTHS, 1..nPAT} integer >= 0; + +var Cut {1..nPAT} integer >= 0; + +minimize Number: + sum {j in 1..nPAT} Cut[j]; + +subject to Fulfill {i in WIDTHS}: + sum {j in 1..nPAT} nbr[i,j] * Cut[j] >= orders[i]; + +data cut.dat; +option solver cplex; +solve; +option omit_zero_rows 1; +option display_1col 0; +display Cut; diff --git a/Assignment2/README.md b/Assignment2/README.md new file mode 100644 index 0000000..2b53384 --- /dev/null +++ b/Assignment2/README.md @@ -0,0 +1,36 @@ +# Assignment 1 + +## Part A +### clingo + + +## Part B +### clingo +| size | processors | deadline | result | time | +| ---- | ---------- | -------- | +| 10 | 3 | 20 | yes | 0.031 | +| 20 | 6 | 30 | no | 4.498 | +| 20 | 8 | 30 | yes | 0.233 | +| 30 | 6 | 60 | unknown | too long to solve | +| 30 | 8 | 60 | yes | 2.961 | +| 30 | 8 | 80 | yes | 6.701 | +| 30 | 8 | 90 | yes | 9.245 | + +With either too high or too low constrain, the runtime grows exponentially. If the constrain is too low, it must iterate through all answers to find one correct result. If it is too high, then it may waste too much time fill the first processor. + +### Google OR-Tools + +## Part C +### clingo +| size | ans | time | +| ---- | --- | ------ | +| 5 | 5 | 0.678 | +| 10 | 9 | 0.866 | +| 15 | 13 | 1.122 | +| 20 | 17 | 1.902 | +| 25 | 21 | 6.379 | +| 30 | 25 | 34.466 | + +One important factor of the runtime is the max number of cut to check. If we set max = 10 with size = 30, we get the time of 0.914. However, it is impossible to get the max size before running, I believe this time would be a great representation of the runtime of clingo. + +### AMPL