3
$\begingroup$

Could someone please check this code. I modified constraints as :

  • No. of nurses - 10
  • Max No. of nurses in Morning/Afternoon/Evening Shifts - 3/4/2
  • Max no. of shifts in a week per nurse - 5
    from ortools.sat.python import cp_model    
    
    class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):
        """Print intermediate solutions."""
    
        def __init__(self, shifts, num_nurses, num_days, num_shifts, sols):
            cp_model.CpSolverSolutionCallback.__init__(self)
            self._shifts = shifts
            self._num_nurses = num_nurses
            self._num_days = num_days
            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 d in range(self._num_days):
                    print('Day %i' % d)
                    for n in range(self._num_nurses):
                        is_working = False
                        for s in range(self._num_shifts):
                            if self.Value(self._shifts[(n, d, 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
    
    
    
    def main():
        # Data.
        num_nurses = 10
        num_shifts = 3
        num_days = 7
        all_nurses = range(num_nurses)
        all_shifts = range(num_shifts)
        all_days = range(num_days)
        # Creates the model.
        model = cp_model.CpModel()
    
        # Creates shift variables.
        # shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'.
        shifts = {}
        for n in all_nurses:
            for d in all_days:
                for s in all_shifts:
                    shifts[(n, d,
                            s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))
    
        # Each shift is assigned to exactly one nurse in the schedule period.
        for d in all_days:
            for s in all_shifts:
                if s==0:
                    model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 3)
                if s==1:
                    model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 4)
                if s==2:
                    model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 2)
     
    
            
        # Each nurse works at most one shift per day.
        for n in all_nurses:
            for d in all_days:
                model.Add(sum(shifts[(n, d, s)] for s in all_shifts) <= 1)
    
        # Try to distribute the shifts evenly, so that each nurse works
        # min_shifts_per_nurse shifts. If this is not possible, because the total
        # number of shifts is not divisible by the number of nurses, some nurses will
        # be assigned one more shift.
        min_shifts_per_nurse = (num_shifts * num_days) // num_nurses
        if num_shifts * num_days % num_nurses == 0:
            max_shifts_per_nurse = min_shifts_per_nurse
        else:
            max_shifts_per_nurse = 5
        for n in all_nurses:
            num_shifts_worked = 0
            for d in all_days:
                for s in all_shifts:
                    num_shifts_worked += shifts[(n, d, s)]
            model.Add(min_shifts_per_nurse <= num_shifts_worked)
            model.Add(num_shifts_worked <= max_shifts_per_nurse)
    
        # Creates the solver and solve.
        solver = cp_model.CpSolver()
        solver.parameters.linearization_level = 0
        # Display the first five solutions.
        a_few_solutions = range(5)
        solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses,
                                                        num_days, num_shifts,
                                                        a_few_solutions)
        solver.SearchForAllSolutions(model, solution_printer)
    
        # Statistics.
        print()
        print('Statistics')
        print('  - conflicts       : %i' % solver.NumConflicts())
        print('  - branches        : %i' % solver.NumBranches())
        print('  - wall time       : %f s' % solver.WallTime())
        print('  - solutions found : %i' % solution_printer.solution_count())
    
    
    if __name__ == '__main__':
        main()

It's not showing correct solutions as per constraints:

Solution 0
Day 0
  Nurse 0 works shift 1
  Nurse 1 works shift 0
  Nurse 2 works shift 0
  Nurse 3 does not work
  Nurse 4 works shift 0
  Nurse 5 works shift 1
  Nurse 6 works shift 1
  Nurse 7 works shift 1
  Nurse 8 works shift 2
  Nurse 9 works shift 2
Day 1
  Nurse 0 works shift 2
  Nurse 1 does not work
  Nurse 2 works shift 1
  Nurse 3 does not work
  Nurse 4 does not work
  Nurse 5 works shift 1
  Nurse 6 works shift 1
  Nurse 7 works shift 0
  Nurse 8 works shift 0
  Nurse 9 works shift 0
Day 2
  Nurse 0 does not work
  Nurse 1 does not work
  Nurse 2 does not work
  Nurse 3 does not work
  Nurse 4 does not work
  Nurse 5 does not work
  Nurse 6 does not work
  Nurse 7 does not work
  Nurse 8 does not work
  Nurse 9 does not work
Day 3
  Nurse 0 does not work
  Nurse 1 does not work
  Nurse 2 does not work
  Nurse 3 works shift 1
  Nurse 4 works shift 1
  Nurse 5 does not work
  Nurse 6 does not work
  Nurse 7 does not work
  Nurse 8 does not work
  Nurse 9 does not work
Day 4
  Nurse 0 does not work
  Nurse 1 does not work
  Nurse 2 does not work
  Nurse 3 does not work
  Nurse 4 does not work
  Nurse 5 does not work
  Nurse 6 does not work
  Nurse 7 does not work
  Nurse 8 does not work
  Nurse 9 does not work
Day 5
  Nurse 0 does not work
  Nurse 1 does not work
  Nurse 2 does not work
  Nurse 3 does not work
  Nurse 4 does not work
  Nurse 5 does not work
  Nurse 6 does not work
  Nurse 7 does not work
  Nurse 8 does not work
  Nurse 9 does not work
Day 6
  Nurse 0 does not work
  Nurse 1 works shift 2
  Nurse 2 does not work
  Nurse 3 works shift 2
  Nurse 4 does not work
  Nurse 5 does not work
  Nurse 6 does not work
  Nurse 7 does not work
  Nurse 8 does not work
  Nurse 9 does not work
$\endgroup$

1 Answer 1

3
$\begingroup$

I didn't added lower bound for shift requirements so here's working code:

    from ortools.sat.python import cp_model

    class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):
    """Print intermediate solutions."""

    def __init__(self, shifts, num_nurses, num_days, num_shifts, sols):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self._shifts = shifts
        self._num_nurses = num_nurses
        self._num_days = num_days
        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 d in range(self._num_days):
                print('Day %i' % d)
                for n in range(self._num_nurses):
                    is_working = False
                    for s in range(self._num_shifts):
                        if self.Value(self._shifts[(n, d, 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



def main():
    # Data.
    num_nurses = 10
    num_shifts = 3
    num_days = 7
    all_nurses = range(num_nurses)
    all_shifts = range(num_shifts)
    all_days = range(num_days)
    # Creates the model.
    model = cp_model.CpModel()

    # Creates shift variables.
    # shifts[(n, d, s)]: nurse 'n' works shift 's' on day 'd'.
    shifts = {}
    for n in all_nurses:
        for d in all_days:
            for s in all_shifts:
                shifts[(n, d,
                        s)] = model.NewBoolVar('shift_n%id%is%i' % (n, d, s))

    # Each shift is assigned to exactly one nurse in the schedule period.
    for d in all_days:
        for s in all_shifts:
            if s==0:
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 3)
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) >= 2)
            if s==1:
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 4)
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) >= 2)
            if s==2:
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) <= 2)
                model.Add(sum(shifts[(n, d, s)] for n in all_nurses) >= 1)



    # Each nurse works at most one shift per day.
    for n in all_nurses:
        for d in all_days:
            model.Add(sum(shifts[(n, d, s)] for s in all_shifts) <= 1)

    # Try to distribute the shifts evenly, so that each nurse works
    # min_shifts_per_nurse shifts. If this is not possible, because the total
    # number of shifts is not divisible by the number of nurses, some nurses will
    # be assigned one more shift.
    min_shifts_per_nurse = (num_shifts * num_days) // num_nurses
    if num_shifts * num_days % num_nurses == 0:
        max_shifts_per_nurse = min_shifts_per_nurse
    else:
        max_shifts_per_nurse = 5
    for n in all_nurses:
        num_shifts_worked = 0
        for d in all_days:
            for s in all_shifts:
                num_shifts_worked += shifts[(n, d, s)]
        model.Add(min_shifts_per_nurse <= num_shifts_worked)
        model.Add(num_shifts_worked <= max_shifts_per_nurse)

    # Creates the solver and solve.
    solver = cp_model.CpSolver()
    solver.parameters.linearization_level = 0
    # Display the first five solutions.
    a_few_solutions = range(20)
    solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses,
                                                    num_days, num_shifts,
                                                    a_few_solutions)
    solver.SearchForAllSolutions(model, solution_printer)

    # Statistics.
    print()
    print('Statistics')
    print('  - conflicts       : %i' % solver.NumConflicts())
    print('  - branches        : %i' % solver.NumBranches())
    print('  - wall time       : %f s' % solver.WallTime())
    print('  - solutions found : %i' % solution_printer.solution_count())


if __name__ == '__main__':
    main()
  
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.