Why does ortools set constraint rigidly? - python
I went through google ortools scheduling tutorial and created slightly different constraint.
Say, that we have a df, that indicates the following:
Each nurse can work only on the preassigned shift. According to the table, nurse 0 must work either on shift 0 or 1, the nurse 2 must work only on the shift 2 etc.
Furthermore, I added the following constraint: "If nurse_0 takes shift_0, then she must take shif_1". It's shown by column "is_child" - shift_1 is child_shift for shift_0 for nurse_0.
df_dict = {
"slot_number":[1,1,2,2,3],
"asset_name":['0','1','0','1','2'],
"is_child":["No","No","Yes",'No','No']
}
df = pd.DataFrame.from_dict(df_dict)
Create variables and a model:
num_nurses = 3
num_shifts = 3
all_nurses = range(num_nurses)
all_shifts = range(num_shifts)
model = cp_model.CpModel()
shifts = {}
for n in all_nurses:
for s in all_shifts:
shifts[(n, s)] = model.NewBoolVar('shift_n%is%i' % (n, s))
One nurse can take only one shift:
for s in all_shifts:
model.Add(sum(shifts[(n, s)] for n in all_nurses) == 1)
My constraint:
# whether the slot (key) have any children in other slots
children = {0: [1], 1: [] ,2: []}
# what nurses are considered the children (values) in which slot (key)
nurse_child_sched = {0:[], 1:[0], 2:[]}
# In this case if nurse 0 take slot 0, then she must take slot 1 too.
for s in all_shifts:
for n in all_nurses:
if (children[s]):
for child in children[s]:
if n in nurse_child_sched[child]:
model.Add((shifts[(n,s)] + shifts[(n,child)])==2)
print(f"{s}-parent_slot and {child}-child_slot were connected for nurse {n}")
The code to create schedules and show solutions:
class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):
"""Print intermediate solutions."""
def __init__(self, shifts, num_nurses, num_shifts, sols):
cp_model.CpSolverSolutionCallback.__init__(self)
self._shifts = shifts
self._num_nurses = num_nurses
self._num_shifts = num_shifts
self._solutions = set(sols)
self._solution_count = 0
def on_solution_callback(self):
if self._solution_count in self._solutions:
print('Solution %i' % self._solution_count)
for n in range(self._num_nurses):
is_working = False
for s in range(self._num_shifts):
if self.Value(self._shifts[(n, s)]):
is_working = True
print(' Nurse %i works shift %i' % (n, s))
if not is_working:
print(' Nurse {} does not work'.format(n))
print()
self._solution_count += 1
def solution_count(self):
return self._solution_count
solver = cp_model.CpSolver()
solver.parameters.linearization_level = 0
a_few_solutions = range(5)
solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses, num_shifts,a_few_solutions)
solver.SearchForAllSolutions(model, solution_printer)
And, finally, I have the following result:
Solution 0
Nurse 0 works shift 0
Nurse 0 works shift 1
Nurse 1 works shift 2
Nurse 2 does not work
Solution 1
Nurse 0 works shift 0
Nurse 0 works shift 1
Nurse 1 does not work
Nurse 2 works shift 2
Solution 2
Nurse 0 works shift 0
Nurse 0 works shift 1
Nurse 0 works shift 2
Nurse 1 does not work
Nurse 2 does not work
However, according to my logic, there must be also at least the solution below. I realize, that my constraint says the model to put nurse_0 on shift_0, but I need to just put the relation between two slots in case if nurse_0 is putted on first shift and no constraints otherwise. Thanks in advance.
Solution x
Nurse 1 works shift 0
Nurse 0 works shift 1
Nurse 0 works shift 2
Nurse 1 does not work
Nurse 2 does not work
Side note:
One nurse can take only one shift:
for s in all_shifts:
model.Add(sum(shifts[(n, s)] for n in all_nurses) == 1)
Here you have written "each shift is assigned to exactly one nurse"
If you wan to express "one nurse can take only one shift" you should write:
for n in all_nurses:
model.Add(sum(shifts[(n, s)] for s in all_shifts) == 1)
note: but in this case since nurse exactly work one shift, child shift is impossible so I guess your code is OK and not the comment...
Related
Debugging the solution to a possible Bipartition
I came across this problem We want to split a group of n people (labeled from 1 to n) into two groups of any size. Each person may dislike some other people, and they should not go into the same group. Given the integer n and the array dislikes where dislikes[i] = [ai, bi] indicates that the person labeled ai does not like the person labeled bi, return true if it is possible to split everyone into two groups in this way. Example 1: Input: n = 4, dislikes = [[1,2],[1,3],[2,4]] Output: true Explanation: group1 [1,4] and group2 [2,3]. Example 2: Input: n = 3, dislikes = [[1,2],[1,3],[2,3]] Output: false Example 3: Input: n = 5, dislikes = [[1,2],[2,3],[3,4],[4,5],[1,5]] Output: false Below is my approach to the solution: create two lists, group1 and group2 and initialise group1 with 1 generate all the numbers from 2 to n in a variable called num check if num is enemy with group1 elements, if yes, then check if num is enemy with group2 elements, if yes as well, return False else put num in its respective group and goto step 2 with the next value return True below is the code implementation class Solution(object): def possibleBipartition(self, n, dislikes): """ :type n: int :type dislikes: List[List[int]] :rtype: bool """ group1 = [1] group2 = [] for num in range(2, n+1): put_to_group_1 = 1 for _n in group1: if [_n, num] in dislikes or [num, _n] in dislikes: put_to_group_1 = 0 break put_to_group_2 = 1 for _n in group2: if[_n, num] in dislikes or [num, _n] in dislikes: put_to_group_2 = 0 break if put_to_group_1 == 0 and put_to_group_2 == 0: return False if put_to_group_1 == 1: group1.append(num) else: group2.append(num) return True However for the following input I am getting False, but the expected output isTrue. 50 [[21,47],[4,41],[2,41],[36,42],[32,45],[26,28],[32,44],[5,41],[29,44],[10,46],[1,6],[7,42],[46,49],[17,46],[32,35],[11,48],[37,48],[37,43],[8,41],[16,22],[41,43],[11,27],[22,44],[22,28],[18,37],[5,11],[18,46],[22,48],[1,17],[2,32],[21,37],[7,22],[23,41],[30,39],[6,41],[10,22],[36,41],[22,25],[1,12],[2,11],[45,46],[2,22],[1,38],[47,50],[11,15],[2,37],[1,43],[30,45],[4,32],[28,37],[1,21],[23,37],[5,37],[29,40],[6,42],[3,11],[40,42],[26,49],[41,50],[13,41],[20,47],[15,26],[47,49],[5,30],[4,42],[10,30],[6,29],[20,42],[4,37],[28,42],[1,16],[8,32],[16,29],[31,47],[15,47],[1,5],[7,37],[14,47],[30,48],[1,10],[26,43],[15,46],[42,45],[18,42],[25,42],[38,41],[32,39],[6,30],[29,33],[34,37],[26,38],[3,22],[18,47],[42,48],[22,49],[26,34],[22,36],[29,36],[11,25],[41,44],[6,46],[13,22],[11,16],[10,37],[42,43],[12,32],[1,48],[26,40],[22,50],[17,26],[4,22],[11,14],[26,39],[7,11],[23,26],[1,20],[32,33],[30,33],[1,25],[2,30],[2,46],[26,45],[47,48],[5,29],[3,37],[22,34],[20,22],[9,47],[1,4],[36,46],[30,49],[1,9],[3,26],[25,41],[14,29],[1,35],[23,42],[21,32],[24,46],[3,32],[9,42],[33,37],[7,30],[29,45],[27,30],[1,7],[33,42],[17,47],[12,47],[19,41],[3,42],[24,26],[20,29],[11,23],[22,40],[9,37],[31,32],[23,46],[11,38],[27,29],[17,37],[23,30],[14,42],[28,30],[29,31],[1,8],[1,36],[42,50],[21,41],[11,18],[39,41],[32,34],[6,37],[30,38],[21,46],[16,37],[22,24],[17,32],[23,29],[3,30],[8,30],[41,48],[1,39],[8,47],[30,44],[9,46],[22,45],[7,26],[35,42],[1,27],[17,30],[20,46],[18,29],[3,29],[4,30],[3,46]] Can anyone tell me where I might be going wrong with the implementation?
Consider a scenario: Let's assume that in the dislikes array, we have [1,6],[2,6] among other elements (so 6 hates 1 and 2). Person 1 doesn't hate anybody else After placing everybody into groups, let's say 2 gets placed in group 2. While placing 6, you can't put it in either group, since it conflicts with 1 in group 1 and 2 in group 2. 6 could have been placed in group 1 if you didn't start with the assumption of placing 1 in group 1 (ideally 1 could have been placed in group 2 without conflict). Long story short, don't start with person 1 in group 1. Take the first element in the dislikes array, put either of them in either group, and then continue with the algorithm.
PYTHON - "Love for Mathematics"
I just finished a challenge on Dcoder ("Love for Mathematics") using Python. I failed two test-cases, but got one right. I used somewhat of a lower level of Python for the same as I haven't explored more yet, so I'm sorry if it looks a bit too basic.The Challenge reads: Students of Dcoder school love Mathematics. They love to read a variety of Mathematics books. To make sure they remain happy, their Mathematics teacher decided to get more books for them. A student would become happy if there are at least X Mathematics books in the class and not more than Y books because they know "All work and no play makes Jack a dull boy".The teacher wants to buy a minimum number of books to make the maximum number of students happy. The Input The first line of input contains an integer N indicating the number of students in the class. This is followed up by N lines where every line contains two integers X and Y respectively. #Sample Input 5 3 6 1 6 7 11 2 15 5 8 The Output Output two space-separated integers that denote the minimum number of mathematics books required and the maximum number of happy students. Explanation: The teacher could buy 5 books and keep student 1, 2, 4 and 5 happy. #Sample Output 5 4 Constraints: 1 <= N <= 10000 1 <= X, Y <= 10^9 My code: n = int(input()) l = [] mi = [] ma = [] for i in range(n): x, y = input().split() mi.append(int(x)) ma.append(int(y)) if i == 0: h=ma[0] else: if ma[i]>h: h=ma[i] for i in range(h): c = 0 for j in range(len(mi)): if ma[j]>=i and mi[j]<=i: c+=1 l.append(c) great = max(l) for i in range(1,len(l)+1): if l[i]==great: print(i,l[i]) break My Approach: I first assigned the two minimum and maximum variables to two different lists - one containing the minimum values, and the other, the maximum. Then I created a loop that processes all numbers from 0 to the maximum possible value of the list containing maximum values and increasing the count for each no. by 1 every time it lies within the favorable range of students. In this specific case, I got that count list to be (for the above given input): [1,2,3,3,4,4,3,3,2 ...] and so on. So I could finalize that 4 would be the maximum no. of students and that the first index of 4 in the list would be the minimum no. of textbooks required. But only 1 test-case worked and two failed. I would really appreciate it if anyone could help me out here. Thank You.
This problem is alike minimum platform problem. In that, you need to sort the min and max maths books array in ascending order respectively. Try to understand the problem from the above link (platform problem) then this will be a piece of cake. Here is your solution: n = int(input()) min_books = [] max_books = [] for i in range(n): x, y = input().split() min_books.append(int(x)) max_books.append(int(y)) min_books.sort() max_books.sort() happy_st_result = 1 happy_st = 1 books_needed = min_books[0] i = 1 j = 0 while (i < n and j < n): if (min_books[i] <= max_books[j]): happy_st+= 1 i+= 1 elif (min_books[i] > max_books[j]): happy_st-= 1 j+= 1 if happy_st > happy_st_result: happy_st_result = happy_st books_needed = min_books[i-1] print(books_needed, happy_st_result) Try this, and let me know if you need any clarification.
#Vinay Gupta's logic and explanation is correct. If you think on those lines, the answer should become immediately clear to you. I have implemented the same logic in my code below, except using fewer lines and cool in-built python functions. # python 3.7.1 import itertools d = {} for _ in range(int(input())): x, y = map(int, input().strip().split()) d.setdefault(x, [0, 0])[0] += 1 d.setdefault(y, [0, 0])[1] += 1 a = list(sorted(d.items(), key=lambda x: x[0])) vals = list(itertools.accumulate(list(map(lambda x: x[1][0] - x[1][1], a)))) print(a[vals.index(max(vals))][0], max(vals)) The above answer got accepted in Dcoder too.
Condionally and Randomly update Pandas values?
I want to build a scheduling app in python using pandas. The following DataFrame is initialised where 0 denotes if a person is busy and 1 if a person is available. import pandas as pd df = pd.DataFrame({'01.01.': [1,1,0], '02.01.': [0,1,1], '03.01.': [1,0,1]}, index=['Person A', 'Person B', 'Person C']) >>> df 01.01. 02.01. 03.01. Person A 1 0 1 Person B 1 1 0 Person C 0 1 1 I now want to randomly schedule n number of people per day if they are available. In other words, for every day, if people are available (1), randomly set n number of people to scheduled (2). I tried something as follows: # Required number of people across time / columns required_number = [0, 1, 2] # Iterate through time / columns for col in range(len(df.columns)): # Current number of scheduled people current_number = (df.iloc[:, [col]].values==2).sum() # Iterate through indices / rows / people for ind in range(len(df.index)): # Check if they are available (1) and # if the required number of people has not been met yet if (df.iloc[ind, col]==1 and current_number<required_number[col]): # Change "free" / 1 person to "scheduled" / 2 df.iloc[ind, col] = 2 # Increment scheduled people by one current_number += 1 >>> df 01.01. 02.01. 03.01. Person A 1 0 2 Person B 1 2 0 Person C 0 1 2 This works as intended but – because I'm simply looping, I have no way of adding randomness (ie. that Person A / B / C) are randomly selected so long as they are available. Is there a way of directly doing so in pandas? Thanks. BBQuercus
You can randomly choose proper indices in a series and then change values corresponding to the chosen indices: for i in range(len(df.columns)): if sum(df.iloc[:,i] == 1) >= required_number[i]: column = df.iloc[:,i].reset_index(drop=True) #We are going to store indices in a list a = [j for j in column.index if column[j] == 1] random_indexes = np.random.choice(a, required_number[i], replace = False) df.iloc[:,i] = [column[j] if j not in random_indexes else 2 for j in column.index] Now df is the wanted result.
python Constraints - constraining the amount
I have a constraint problem that I'm trying to solve with python-constraint So let's say I have 3 locations: loc1,...loc3 Also, I have 7 devices: device1,...device7 Max amount of devices in each location: loc1:3, loc2:4, loc3:2 (for example maximum of 3 devices in loc1 and so on...) And some constraints about the locations and the devices: loc1: device1, device3, device7, loc2: device1, device3, device4, device5, device6, device7 loc3: device2, device4, device5, device6 (meaning for example only device1, device3 and device7 can be in loc1.) I'm trying to get a set of possible options for devices in locations. from constraint import * problem = Problem() for key in locations_devices_dict: problem.addVariable(key,locations_devices_dict[key]) # problem.addVariable("loc1", ['device1', 'device3', 'device7']) problem.addConstraint(AllDifferentConstraint()) and I'm stuck on how to do the constrains. I've tried: problem.addConstraint(MaxSumConstraint(3), 'loc1') but it doesn't work, MaxSumConstraint does not sum what I need. All devices must be placed somewhere possible solution: loc1: device1, device3 loc2: device4, device6, device7 loc3: device2, device5 Anyone has an idea? (another python package/not to use any package, is also good idea if someone has any suggestions...)
This is simple assignment-like model: So we have a binary variable indicating if device d is assigned to location L. The linear constraints are just: assign each device to one location each location has a maximum number of devices make sure to use only allowed assignments (modeled above by allowed(L,d)) This problem can be handled by any constraint solver. Enumerating all possible solutions is a bit dangerous. For large instances there are just way too many. Even for this small problem we already have 25 solutions: For large problems this number will be astronomically large. Using the Python constraint package this can look like: from constraint import * D = 7 # number of devices L = 3 # number of locations maxdev = [3,4,2] allowed = [[1,3,7],[1,3,4,5,6,7],[2,4,5,6]] problem = Problem() problem.addVariables(["x_L%d_d%d" %(loc+1,d+1) for loc in range(L) for d in range(D) if d+1 in allowed[loc]],[0,1]) for loc in range(L): problem.addConstraint(MaxSumConstraint(maxdev[loc]),["x_L%d_d%d" %(loc+1,d+1) for d in range(D) if d+1 in allowed[loc]]) for d in range(D): problem.addConstraint(ExactSumConstraint(1),["x_L%d_d%d" %(loc+1,d+1) for loc in range(L) if d+1 in allowed[loc]]) S = problem.getSolutions() n = len(S) n For large problems you may want to use dicts to speed things up.
edit: I wrote this answer before I saw #ErwinKalvelagen's code. So I did not check his solution... So I used #ErwinKalvelagen approach and created a matrix that represented the probelm. for each (i,j), x[i,j]=1 if device i can go to location j, 0 otherwise. Then, I used addConstraint(MaxSumConstraint(maxAmount[i]), row) for each row - this is the constraint that represent the maximum devices in each location. and addConstraint(ExactSumConstraint(1), col) for each column - this is the constraint that each device can be placed only in one location. next, I took all x[i,j]=0 (device i can not be in location j) and for each t(i,j) addConstraint(lambda var, val=0: var == val, (t,)) This problem is similar to the sudoku problem, and I used this example for help The matrix for my example above is: (devices:) 1 2 3 4 5 6 7 loc1: 1 0 1 0 0 0 1 loc2: 1 0 1 1 1 1 1 loc3: 0 1 0 1 1 1 0 My code: problem = Problem() rows = range(locations_amount) cols = range(devices_amount) matrix = [(row, col) for row in rows for col in cols] problem.addVariables(matrix, range(0, 2)) #each cell can get 0 or 1 rowSet = [zip([el] * len(cols), cols) for el in rows] colSet = [zip(rows, [el] * len(rows)) for el in cols] rowsConstrains = getRowConstrains() # list that has the maximum amount in each location(3,4,2) #from my example: loc1:3, loc2:4, loc3:2 for i,row in enumerate(rowSet): problem.addConstraint(MaxSumConstraint(rowsConstrains[i]), row) for col in colSet: problem.addConstraint(ExactSumConstraint(1), col) s = getLocationsSet() # set that has all the tuples that x[i,j] = 1 for i, loc in enumerate(locations_list): for j, iot in enumerate(devices_list): t=(i,j) if t in s: continue problem.addConstraint(lambda var, val=0: var == val, (t,)) # the value in these cells must be 0 solver = problem.getSolution() example for a solution: (devices:) 1 2 3 4 5 6 7 loc1: 1 0 1 0 0 0 1 loc2: 0 0 0 1 1 1 0 loc3: 0 1 0 0 0 0 0
How to compute the number of edges to change to make the undirected graph a single component?
I was trying to solve this problem with the following code. But the answers aren't accurate for all inputs. Problem Statement There are N cities and N one-way bridges in Byteland. There is exactly one incoming and one outgoing bridge for each city. Byteland wants to be "elligible" to host the World Cup. A country is "elligible" for the World Cup, if and only if you can travel from any city to any other city of that country. You are asked to perform the minimum number of steps to help Byteland become an elligible World Cup host. For each step you can swap the destinations of two bridges. For example: We have two bridges A -> B and C -> D (there is a bridge from A to B and from C to D ). If we swap their destinations, we will have two bridges A -> D and C -> B . Input Format First line contains one integer, T denoting the number of test cases. For each test there will be two lines: First line contains one integer, N. Second line contains N integers, where the X th integer denotes city where bridge goes from city X . Output Format Just print the answer to the problem, one line per case. Sample Input 2 4 3 1 2 4 3 2 3 1 Sample Output 1 0 My Code for i in range(input()): n = input() bridges = {} connection = [int(y) for y in raw_input().split(' ')] for j in range(n): bridges[j+1] = connection[j] #print bridges count = 0 swapped = True for k in range(1, n+1): if swapped: swapped = False for j in range(1, n+1): if bridges[bridges[j]] == j: bridges[j], bridges[1 if (j+1)%n == 0 else (j+1)%n] = bridges[1 if (j+1)%n == 0 else (j+1)%n], bridges[j] swapped = True #print bridges count += 1 print count
You have to compute the connected components, then the number of swap is equal to the number of components minus one. for i in range(int(input())): N = int(input()) bridges = {index + 1: int(connection) for index, connection in enumerate(input().split())} visited = set() component_count = 0 towns = list(bridges.keys()) while towns: current = towns[0] component = set() while current not in visited: towns.remove(current) visited.add(current) current = bridges[current] component_count += 1 print(component_count - 1) The code does the following: start at the first town not visited yet (outer loop), and visit as much town as possible followinng the bridges connections (inner loop) each time the inner loop breaks, it means that we met a town we already met which makes a component in the graph. Break the loop and increment the number of component. Start again with a new twon that is not visited This gives the correct result because we are guaranteed that components are cycles, you can break the cycle (swap a connection) to connect a component to another without breaking the component.