# Copyright 2010 Hakan Kjellerstrand hakank@gmail.com # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Moving furnitures (scheduling) problem in Google CP Solver. Marriott & Stukey: 'Programming with constraints', page 112f The model implements an experimental decomposition of the global constraint cumulative. Compare with the following models: * ECLiPSE: http://www.hakank.org/eclipse/furniture_moving.ecl * MiniZinc: http://www.hakank.org/minizinc/furniture_moving.mzn * Comet: http://www.hakank.org/comet/furniture_moving.co * Choco: http://www.hakank.org/choco/FurnitureMoving.java * Gecode: http://www.hakank.org/gecode/furniture_moving.cpp * JaCoP: http://www.hakank.org/JaCoP/FurnitureMoving.java * SICStus: http://hakank.org/sicstus/furniture_moving.pl * Zinc: http://hakank.org/minizinc/furniture_moving.zinc This model was created by Hakan Kjellerstrand (hakank@gmail.com) Also see my other Google CP Solver models: http://www.hakank.org/google_or_tools/ """ from __future__ import print_function import sys from ortools.constraint_solver import pywrapcp # # Decompositon of cumulative. # # Inspired by the MiniZinc implementation: # http://www.g12.csse.unimelb.edu.au/wiki/doku.php?id=g12:zinc:lib:minizinc:std:cumulative.mzn&s[]=cumulative # The MiniZinc decomposition is discussed in the paper: # A. Schutt, T. Feydy, P.J. Stuckey, and M. G. Wallace. # 'Why cumulative decomposition is not as bad as it sounds.' # Download: # http://www.cs.mu.oz.au/%7Epjs/rcpsp/papers/cp09-cu.pdf # http://www.cs.mu.oz.au/%7Epjs/rcpsp/cumu_lazyfd.pdf # # # Parameters: # # s: start_times assumption: array of IntVar # d: durations assumption: array of int # r: resources assumption: array of int # b: resource limit assumption: IntVar or int # def my_cumulative(solver, s, d, r, b): # tasks = [i for i in range(len(s))] tasks = [i for i in range(len(s)) if r[i] > 0 and d[i] > 0] times_min = min([s[i].Min() for i in tasks]) times_max = max([s[i].Max() + max(d) for i in tasks]) for t in range(times_min, times_max + 1): bb = [] for i in tasks: c1 = solver.IsLessOrEqualCstVar(s[i], t) # s[i] <= t c2 = solver.IsGreaterCstVar(s[i] + d[i], t) # t < s[i] + d[i] bb.append(c1 * c2 * r[i]) solver.Add(solver.Sum(bb) <= b) # Somewhat experimental: # This constraint is needed to contrain the upper limit of b. if not isinstance(b, int): solver.Add(b <= sum(r)) def main(): # Create the solver. solver = pywrapcp.Solver("Furniture moving") # # data # n = 4 duration = [30, 10, 15, 15] demand = [3, 1, 3, 2] upper_limit = 160 # # declare variables # start_times = [ solver.IntVar(0, upper_limit, "start_times[%i]" % i) for i in range(n) ] end_times = [ solver.IntVar(0, upper_limit * 2, "end_times[%i]" % i) for i in range(n) ] end_time = solver.IntVar(0, upper_limit * 2, "end_time") # number of needed resources, to be minimized num_resources = solver.IntVar(0, 10, "num_resources") # # constraints # for i in range(n): solver.Add(end_times[i] == start_times[i] + duration[i]) solver.Add(end_time == solver.Max(end_times)) my_cumulative(solver, start_times, duration, demand, num_resources) # # Some extra constraints to play with # # all tasks must end within an hour # solver.Add(end_time <= 60) # All tasks should start at time 0 # for i in range(n): # solver.Add(start_times[i] == 0) # limitation of the number of people # solver.Add(num_resources <= 3) # # objective # # objective = solver.Minimize(end_time, 1) objective = solver.Minimize(num_resources, 1) # # solution and search # solution = solver.Assignment() solution.Add(start_times) solution.Add(end_times) solution.Add(end_time) solution.Add(num_resources) db = solver.Phase(start_times, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) # # result # solver.NewSearch(db, [objective]) num_solutions = 0 while solver.NextSolution(): num_solutions += 1 print("num_resources:", num_resources.Value()) print("start_times :", [start_times[i].Value() for i in range(n)]) print("duration :", [duration[i] for i in range(n)]) print("end_times :", [end_times[i].Value() for i in range(n)]) print("end_time :", end_time.Value()) print() solver.EndSearch() print() print("num_solutions:", num_solutions) print("failures:", solver.Failures()) print("branches:", solver.Branches()) print("WallTime:", solver.WallTime()) if __name__ == "__main__": main()