def optimize(instance, max_time=10000, time_limit=100, threads=1):
    model = cp_model.CpModel()
    start_vars = dict()
    end_vars = dict()
    durations = dict()
    interval_vars = dict()

    # Create variables
    for task in instance.tasks:
        start_vars[task] = model.NewIntVar(0, max_time, 'start' + task.name)
        end_vars[task] = model.NewIntVar(0, max_time, 'end' + task.name)
        durations[task] = task.length
        interval_vars[task] = model.NewIntervalVar(start_vars[task],
                                                   durations[task],
                                                   end_vars[task],
                                                   'interval' + task.name)

    # Precedence and blocking constraints
    for task in instance.tasks:
        if task.next_task:
            model.Add(start_vars[task.next_task] >= end_vars[task])

    # No overlap constraints
    for machine in instance.machines:
        for task in machine.tasks:
            model.AddNoOverlap([interval_vars[task] for task in machine.tasks])

    # Minimize the makespan
    obj_var = model.NewIntVar(0, max_time, 'makespan')
    model.AddMaxEquality(
        obj_var,
        [end_vars[task] for job in instance.jobs for task in job.tasks])
    model.Minimize(obj_var)

    # Define solver and solve
    cb = cp_model.ObjectiveSolutionPrinter()
    solver = cp_model.CpSolver()
    #print(dir(solver.parameters)) # I just used this to find the names of the parameters
    solver.parameters.max_time_in_seconds = time_limit
    solver.parameters.num_search_workers = threads
    status = solver.SolveWithSolutionCallback(model, cb)

    solution = Solution(instance)

    # Print out solution and return it
    for job in instance.jobs:
        for task in job.tasks:
            print(task.name)
            print('Start: %f' % solver.Value(start_vars[task]))
            print('End: %f' % solver.Value(end_vars[task]))
            solution.add(task, solver.Value(start_vars[task]),
                         solver.Value(end_vars[task]))
    return solution
Beispiel #2
0
def solve_reified(demands, capacities, cost):
    # solving the problem by applying reified constraints
    # About reified constaints in OR Tools:
    # https://www.cis.upenn.edu/~cis189/files/Lecture8.pdf

    M = len(demands)  # number of customers
    N = len(capacities)  # number of warehouses

    model = cp_model.CpModel()
    # solver = pywraplp.Solver.CreateSolver('SCIP')
    solver = cp_model.CpSolver()

    # define variables
    x = {}
    for i in range(M):
        # x_i = warehouse number from ith customer is served
        x[i] = model.NewIntVar(0, N - 1, f'x_{i}')

    z = {}
    for i in range(M):
        for j in range(N):
            z[i, j] = model.NewBoolVar(f'z_{{{i},{j}}}')
        # model.Add(x[i] > -)

    # z := x_i == j
    for i in range(M):
        for j in range(N):
            model.Add(x[i] == j).OnlyEnforceIf(z[i, j])
            model.Add(x[i] != j).OnlyEnforceIf(z[i, j].Not())

    # capacities cannot been outgrown by demands
    for j in range(N):
        model.Add(sum(demands[i] * z[i, j] for i in range(M)) <= capacities[j])

    model.Minimize(
        sum(cost[i, j] * z[i, j] for j in range(N) for i in range(M)))

    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)
    print(status)

    assignment_vector = np.zeros((M), dtype=int)
    if status == cp_model.OPTIMAL:
        print('Solution:')
        print('Objective value =', solution_printer.ObjectiveValue())
        for i in range(M):
            assignment_vector[i] = solver.Value(x[i])
        print('Assignment vector:', assignment_vector, sep='\n')
    else:
        print('The problem does not have an optimal solution.')
    print()
    return assignment_vector
Beispiel #3
0
def solve_shift_scheduling(params, output_proto):
    """Solves the shift scheduling problem."""
    # Data
    num_employees = 8
    num_weeks = 3
    shifts = ['O', 'M', 'A', 'N']

    # Fixed assignment: (employee, shift, day).
    # This fixes the first 2 days of the schedule.
    fixed_assignments = [
        (0, 0, 0),
        (1, 0, 0),
        (2, 1, 0),
        (3, 1, 0),
        (4, 2, 0),
        (5, 2, 0),
        (6, 2, 3),
        (7, 3, 0),
        (0, 1, 1),
        (1, 1, 1),
        (2, 2, 1),
        (3, 2, 1),
        (4, 2, 1),
        (5, 0, 1),
        (6, 0, 1),
        (7, 3, 1),
    ]

    # Request: (employee, shift, day, weight)
    # A negative weight indicates that the employee desire this assignment.
    requests = [
        # Employee 3 wants the first Saturday off.
        (3, 0, 5, -2),
        # Employee 5 wants a night shift on the second Thursday.
        (5, 3, 10, -2),
        # Employee 2 does not want a night shift on the first Friday.
        (2, 3, 4, 4)
    ]

    # Shift constraints on continuous sequence :
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    shift_constraints = [
        # One or two consecutive days of rest, this is a hard constraint.
        (0, 1, 1, 0, 2, 2, 0),
        # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are
        # possible but penalized.
        (3, 1, 2, 20, 3, 4, 5),
    ]

    # Weekly sum constraints on shifts days:
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    weekly_sum_constraints = [
        # Constraints on rests per week.
        (0, 1, 2, 7, 2, 3, 4),
        # At least 1 night shift per week (penalized). At most 4 (hard).
        (3, 0, 1, 3, 4, 4, 0),
    ]

    # Penalized transitions:
    #     (previous_shift, next_shift, penalty (0 means forbidden))
    penalized_transitions = [
        # Afternoon to night has a penalty of 4.
        (2, 3, 4),
        # Night to morning is forbidden.
        (3, 1, 0),
    ]

    # daily demands for work shifts (morning, afternon, night) for each day
    # of the week starting on Monday.
    weekly_cover_demands = [
        (2, 3, 1),  # Monday
        (2, 3, 1),  # Tuesday
        (2, 2, 2),  # Wednesday
        (2, 3, 1),  # Thursday
        (2, 2, 2),  # Friday
        (1, 2, 3),  # Saturday
        (1, 3, 1),  # Sunday
    ]

    # Penalty for exceeding the cover constraint per shift type.
    excess_cover_penalties = (2, 2, 5)

    num_days = num_weeks * 7
    num_shifts = len(shifts)

    model = cp_model.CpModel()

    work = {}
    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))

    # Linear terms of the objective in a minimization context.
    obj_int_vars = []
    obj_int_coeffs = []
    obj_bool_vars = []
    obj_bool_coeffs = []

    # Exactly one shift per day.
    for e in range(num_employees):
        for d in range(num_days):
            model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)

    # Fixed assignments.
    for e, s, d in fixed_assignments:
        model.Add(work[e, s, d] == 1)

    # Employee requests
    for e, s, d, w in requests:
        obj_bool_vars.append(work[e, s, d])
        obj_bool_coeffs.append(w)

    # Shift constraints
    for ct in shift_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            works = [work[e, shift, d] for d in range(num_days)]
            variables, coeffs = add_soft_sequence_constraint(
                model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
                max_cost,
                'shift_constraint(employee %i, shift %i)' % (e, shift))
            obj_bool_vars.extend(variables)
            obj_bool_coeffs.extend(coeffs)

    # Weekly sum constraints
    for ct in weekly_sum_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            for w in range(num_weeks):
                works = [work[e, shift, d + w * 7] for d in range(7)]
                variables, coeffs = add_soft_sum_constraint(
                    model, works, hard_min, soft_min, min_cost, soft_max,
                    hard_max, max_cost,
                    'weekly_sum_constraint(employee %i, shift %i, week %i)' %
                    (e, shift, w))
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    # Penalized transitions
    for previous_shift, next_shift, cost in penalized_transitions:
        for e in range(num_employees):
            for d in range(num_days - 1):
                transition = [
                    work[e, previous_shift, d].Not(), work[e, next_shift,
                                                           d + 1].Not()
                ]
                if cost == 0:
                    model.AddBoolOr(transition)
                else:
                    trans_var = model.NewBoolVar(
                        'transition (employee=%i, day=%i)' % (e, d))
                    transition.append(trans_var)
                    model.AddBoolOr(transition)
                    obj_bool_vars.append(trans_var)
                    obj_bool_coeffs.append(cost)

    # Cover constraints
    for s in range(1, num_shifts):
        for w in range(num_weeks):
            for d in range(7):
                works = [work[e, s, w * 7 + d] for e in range(num_employees)]
                # Ignore Off shift.
                min_demand = weekly_cover_demands[d][s - 1]
                worked = model.NewIntVar(min_demand, num_employees, '')
                model.Add(worked == sum(works))
                over_penalty = excess_cover_penalties[s - 1]
                if over_penalty > 0:
                    name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,
                                                                         d)
                    excess = model.NewIntVar(0, num_employees - min_demand,
                                             name)
                    model.Add(excess == worked - min_demand)
                    obj_int_vars.append(excess)
                    obj_int_coeffs.append(over_penalty)

    # Objective
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars))) +
        sum(obj_int_vars[i] * obj_int_coeffs[i]
            for i in range(len(obj_int_vars))))

    if output_proto:
        print('Writing proto to %s' % output_proto)
        with open(output_proto, 'w') as text_file:
            text_file.write(str(model))

    # Solve the model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    if params:
        text_format.Merge(params, solver.parameters)
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)

    # Print solution.
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print()
        header = '          '
        for w in range(num_weeks):
            header += 'M T W T F S S '
        print(header)
        for e in range(num_employees):
            schedule = ''
            for d in range(num_days):
                for s in range(num_shifts):
                    if solver.BooleanValue(work[e, s, d]):
                        schedule += shifts[s] + ' '
            print('worker %i: %s' % (e, schedule))
        print()
        print('Penalties:')
        for i, var in enumerate(obj_bool_vars):
            if solver.BooleanValue(var):
                penalty = obj_bool_coeffs[i]
                if penalty > 0:
                    print('  %s violated, penalty=%i' % (var.Name(), penalty))
                else:
                    print('  %s fulfilled, gain=%i' % (var.Name(), -penalty))

        for i, var in enumerate(obj_int_vars):
            if solver.Value(var) > 0:
                print('  %s violated by %i, linear penalty=%i' %
                      (var.Name(), solver.Value(var), obj_int_coeffs[i]))

    print()
    print('Statistics')
    print('  - status          : %s' % solver.StatusName(status))
    print('  - conflicts       : %i' % solver.NumConflicts())
    print('  - branches        : %i' % solver.NumBranches())
    print('  - wall time       : %f s' % solver.WallTime())
Beispiel #4
0
def solve_shift_scheduling(params, output_proto, num_employees, num_weeks, shifts, fixed_assignments, requests, shift_constraints,
                             weekly_sum_constraints, penalized_transitions, weekly_cover_demands, excess_cover_penalties):
    """Solves the shift scheduling problem."""

    num_days = num_weeks * 7
    num_shifts = len(shifts)

    # Solver in the OR-Tools library. This is the key to the whole thing.
    model = cp_model.CpModel()

    # Sets up the data typing and initialization for the matrix used in the solver. 
    work = {}
    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))

    # Linear terms of the objective in a minimization context. 
    obj_int_vars = []
    obj_int_coeffs = []
    obj_bool_vars = []
    obj_bool_coeffs = []

    # Exactly one shift per day. This ensures every employee is assigned one shift per day (including 'off' as a shift).
    for e in range(num_employees):
        for d in range(num_days):
            model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)

    # Fixed assignments. This parses the shifts already scheduled as a base to work off of while generating the schedule.
    for e, s, d in fixed_assignments:
        model.Add(work[e, s, d] == 1)

    # Parses employee requests to pass into the solver.
    for e, s, d, w in requests:
        try:
            obj_bool_vars.append(work[e, s, d])
            obj_bool_coeffs.append(w)
        except:
            continue

    # Takes in the shift constraints and parses them. To be passed into the solver. 
    for ct in shift_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            works = [work[e, shift, d] for d in range(num_days)]
            variables, coeffs = add_soft_sequence_constraint(
                model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
                max_cost, 'shift_constraint(employee %i, shift %i)' % (e,
                                                                       shift))
            obj_bool_vars.extend(variables)
            obj_bool_coeffs.extend(coeffs)

    # Parses the weekly sum constraints to pass into the solver.
    for ct in weekly_sum_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            for w in range(num_weeks):
                works = [work[e, shift, d + w * 7] for d in range(7)]
                variables, coeffs = add_soft_sum_constraint(
                    model, works, hard_min, soft_min, min_cost, soft_max,
                    hard_max, max_cost,
                    'weekly_sum_constraint(employee %i, shift %i, week %i)' %
                    (e, shift, w))
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    # Parses the penalized transitions to pass into the solver.
    for previous_shift, next_shift, cost in penalized_transitions:
        for e in range(num_employees):
            for d in range(num_days - 1):
                transition = [
                    work[e, previous_shift, d].Not(),
                    work[e, next_shift, d + 1].Not()
                ]
                if cost == 0:
                    model.AddBoolOr(transition)
                else:
                    trans_var = model.NewBoolVar(
                        'transition (employee=%i, day=%i)' % (e, d))
                    transition.append(trans_var)
                    model.AddBoolOr(transition)
                    obj_bool_vars.append(trans_var)
                    obj_bool_coeffs.append(cost)

    # Parses the cover constraints to pass them into the solver.
    for s in range(1, num_shifts):
        for w in range(num_weeks):
            for d in range(7):
                works = [work[e, s, w * 7 + d] for e in range(num_employees)]
                # Ignore Off shift.
                min_demand = weekly_cover_demands[d][s - 1]
                worked = model.NewIntVar(min_demand, num_employees, '')
                model.Add(worked == sum(works))
                over_penalty = excess_cover_penalties[s - 1]
                if over_penalty > 0:
                    name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,
                                                                         d)
                    excess = model.NewIntVar(0, num_employees - min_demand,
                                             name)
                    model.Add(excess == worked - min_demand)
                    obj_int_vars.append(excess)
                    obj_int_coeffs.append(over_penalty)

    # Optimizes the model object in preparation for calculation.
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars)))
        + sum(obj_int_vars[i] * obj_int_coeffs[i]
              for i in range(len(obj_int_vars))))
    if output_proto:
        print('Writing proto to %s' % output_proto)
        with open(output_proto, 'w') as text_file:
            text_file.write(str(model))

    # Solve the model.
    solver = cp_model.CpSolver()
    if params:
        text_format.Merge(params, solver.parameters)
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)
    shifts[s]
    # Print solution.
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print()
        header = '          '
        for w in range(num_weeks):
            header += 'M T W T F S S '
        print(header)
        for e in range(num_employees):
            schedule = ''
            for d in range(num_days):
                for s in range(num_shifts):
                    if solver.BooleanValue(work[e, s, d]):
                        schedule += shifts[s] + ' '
            print('worker %i: %s' % (e, schedule))
        print()
        print('Penalties:')
        for i, var in enumerate(obj_bool_vars):
            if solver.BooleanValue(var):
                penalty = obj_bool_coeffs[i]
                if penalty > 0:
                    print('  %s violated, penalty=%i' % (var.Name(), penalty))
                else:
                    print('  %s fulfilled, gain=%i' % (var.Name(), -penalty))

        for i, var in enumerate(obj_int_vars):
            if solver.Value(var) > 0:
                print('  %s violated by %i, linear penalty=%i' %
                      (var.Name(), solver.Value(var), obj_int_coeffs[i]))

    print()
    print('Statistics')
    print('  - status          : %s' % solver.StatusName(status))
    print('  - conflicts       : %i' % solver.NumConflicts())
    print('  - branches        : %i' % solver.NumBranches())
    print('  - wall time       : %f s' % solver.WallTime())
Beispiel #5
0
def solve_shift_scheduling(params, output_proto):
    """Solves the shift scheduling problem."""
    # Data
    num_employees = 5
    num_weeks = 1
    shifts = ["R", "Manager", "Bar", "Restaurant"]

    shift_to_hour = [0, 8, 7, 6]
    tolerated_delta_contract_hours = 15
    employee_to_hour = [40, 30, 35, 26, 16]

    # Fixed assignment: (employee, shift, day).
    # This fixes the first 2 days of the schedule.
    fixed_assignments = []

    # Request: (employee, shift, day, weight)
    # A negative weight indicates that the employee desire this assignment.
    requests = [
        # Employee 3 wants the first Saturday off.
        # (1, 0, 0, -2),
        # (1, 0, 1, -2),
        # (0, 1, 1, 2),
        # Employee 5 wants a night shift on the second Thursday.
        # (5, 3, 10, -2),
        # Employee 2 does not want a night shift on the first Friday.
        # (1, 3, 4, -4),
    ]

    # Shift constraints on continuous sequence :
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    shift_constraints = [
        # One or two consecutive days of rest, this is a hard constraint.
        # (0, 1, 1, 0, 3, 3, 0),
        # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are
        # possible but penalized.
        # (3, 1, 2, 20, 3, 4, 5),
    ]

    # Weekly sum constraints on shifts days:
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    weekly_sum_constraints = [
        # Constraints on rests per week.
        # (0, 1, 2, 7, 2, 3, 4),
        # At least 1 night shift per week (penalized). At most 4 (hard).
        # (3, 0, 1, 3, 2, 2, 0),
    ]

    # Penalized transitions:
    #     (previous_shift, next_shift, penalty (0 means forbidden))
    penalized_transitions = [
        # Afternoon to night has a penalty of 4.
        # (2, 3, 4),
        # Night to morning is forbidden.
        # (1, 3, 0),
    ]

    # daily demands for work shifts (Manager, Bar, Restaurant) for each day
    # of the week starting on Monday.
    weekly_cover_demands = [
        (1, 1, 1),  # Monday
        (1, 1, 1),  # Tuesday
        (1, 1, 1),  # Wednesday
        (1, 1, 1),  # Thursday
        (1, 1, 1),  # Friday
        (1, 1, 1),  # Saturday
        (1, 1, 1),  # Sunday
    ]

    # Employee mastery level for each shift (Manager, Bar, Restaurant)
    # 0 = No ability   1 = Training for this position    2 = Ability
    employee_shift_mastery = [
        (2, 0, 0),
        (0, 0, 2),
        (0, 2, 0),
        (0, 2, 2),
        (2, 0, 0),
    ]

    # Penalty for exceeding the cover constraint per shift type.
    excess_cover_penalties = (10, 5, 1)

    num_days = num_weeks * 7
    num_shifts = len(shifts)

    model = cp_model.CpModel()

    work = {}

    # Create work binary matrice
    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                work[e, s, d] = model.NewBoolVar("work%i_%i_%i" % (e, s, d))

    # Linear terms of the objective in a minimization context.
    obj_int_vars = []
    obj_int_coeffs = []
    obj_bool_vars = []
    obj_bool_coeffs = []

    # Maximum one shift per day.
    for e in range(num_employees):
        for d in range(num_days):
            model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)

    # Fixed assignments.
    for e, s, d in fixed_assignments:
        model.Add(work[e, s, d] == 1)

    # Employee requests
    for e, s, d, w in requests:
        obj_bool_vars.append(work[e, s, d])
        obj_bool_coeffs.append(w)

    # Shift constraints
    for ct in shift_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            works = [work[e, shift, d] for d in range(num_days)]
            variables, coeffs = add_soft_sequence_constraint(
                model,
                works,
                hard_min,
                soft_min,
                min_cost,
                soft_max,
                hard_max,
                max_cost,
                "shift_constraint(employee %i, shift %i)" % (e, shift),
            )
            obj_bool_vars.extend(variables)
            obj_bool_coeffs.extend(coeffs)

    # Weekly sum constraints
    for ct in weekly_sum_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            for w in range(num_weeks):
                works = [work[e, shift, d + w * 7] for d in range(7)]
                variables, coeffs = add_soft_sum_constraint(
                    model,
                    works,
                    hard_min,
                    soft_min,
                    min_cost,
                    soft_max,
                    hard_max,
                    max_cost,
                    "weekly_sum_constraint(employee %i, shift %i, week %i)" %
                    (e, shift, w),
                )
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    # Penalized transitions
    for previous_shift, next_shift, cost in penalized_transitions:
        for e in range(num_employees):
            for d in range(num_days - 1):
                transition = [
                    work[e, previous_shift, d].Not(),
                    work[e, next_shift, d + 1].Not(),
                ]
                if cost == 0:
                    model.AddBoolOr(transition)
                else:
                    trans_var = model.NewBoolVar(
                        "transition (employee=%i, day=%i)" % (e, d))
                    transition.append(trans_var)
                    model.AddBoolOr(transition)
                    obj_bool_vars.append(trans_var)
                    obj_bool_coeffs.append(cost)

    # Cover constraints
    for s in range(1, num_shifts):
        for w in range(num_weeks):
            for d in range(7):
                works = [work[e, s, w * 7 + d] for e in range(num_employees)]
                # Ignore Off shift.
                min_demand = weekly_cover_demands[d][s - 1]
                worked = model.NewIntVar(min_demand, num_employees, "")
                model.Add(worked == sum(works))
                over_penalty = excess_cover_penalties[s - 1]
                if over_penalty > 0:
                    name = "excess_demand(shift=%i, week=%i, day=%i)" % (s, w,
                                                                         d)
                    excess = model.NewIntVar(0, num_employees - min_demand,
                                             name)
                    model.Add(excess == worked - min_demand)
                    obj_int_vars.append(excess)
                    obj_int_coeffs.append(over_penalty)

    # Employee hour contract constraints
    for e in range(num_employees):
        works = [
            sum(work[e, s, d] * shift_to_hour[s] for d in range(num_days))
            for s in range(num_shifts)
        ]
        variables, coeffs = add_soft_sum_constraint(
            model,
            works,
            -tolerated_delta_contract_hours + employee_to_hour[e],
            employee_to_hour[e],
            4,
            employee_to_hour[e],
            tolerated_delta_contract_hours + employee_to_hour[e],
            4,
            "diff_sum_hours_contract(employee %i, week %i)" % (e, w),
        )
        obj_int_vars.extend(variables)
        obj_int_coeffs.extend(coeffs)

    # Employee shift mastery constraints
    for e in range(num_employees):
        for s in range(1, num_shifts):
            coef_mastery = employee_shift_mastery[e][s - 1]
            if coef_mastery < 2:
                num_shifts_worked = model.NewIntVar(0, num_days, "")
                model.Add(num_shifts_worked == sum(work[e, s, d]
                                                   for d in range(num_days)))
                if coef_mastery == 0:
                    # Employee cannot fulfill this role
                    model.Add(num_shifts_worked == 0)
                elif coef_mastery == 1:
                    # Employee can fulfill this role with a penalty
                    variables, coeffs = add_soft_sum_constraint(
                        model,
                        num_shifts_worked,
                        0,
                        0,
                        0,
                        0,
                        num_days,
                        2,
                        "mastery_constraints(employee %i, shift %i)" %
                        (e, shift),
                    )
                    obj_int_vars.extend(variables)
                    obj_int_coeffs.extend(coeffs)

    # Objective
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars))) +
        sum(obj_int_vars[i] * obj_int_coeffs[i]
            for i in range(len(obj_int_vars)))
        # + sum(hours_per_employee)
    )

    # if output_proto:
    #     print("Writing proto to %s" % output_proto)
    #     with open(output_proto, "w") as text_file:
    #         text_file.write(str(model))

    # Solve the model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    if params:
        text_format.Merge(params, solver.parameters)
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)

    # Print solution.
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print()
        header = "          "
        for w in range(num_weeks):
            header += "M T W T F S S "
        print(header)
        for e in range(num_employees):
            schedule = ""
            for d in range(num_days):
                shift_worked = "-"
                for s in range(num_shifts):
                    if solver.BooleanValue(work[e, s, d]):
                        shift_worked = shifts[s] + " "
                schedule += shift_worked + " "
            total_hours = sum(
                sum(
                    solver.Value(work[e, s, d]) * shift_to_hour[s]
                    for s in range(num_shifts)) for d in range(num_days))
            print("worker %i: %s - %i" % (e, schedule, total_hours))
        print()
        print("Penalties:")
        for i, var in enumerate(obj_bool_vars):
            if solver.BooleanValue(var):
                penalty = obj_bool_coeffs[i]
                if penalty > 0:
                    print("  %s violated, penalty=%i" % (var.Name(), penalty))
                else:
                    print("  %s fulfilled, gain=%i" % (var.Name(), -penalty))

        for i, var in enumerate(obj_int_vars):
            if solver.Value(var) > 0:
                print("  %s violated by %i, linear penalty=%i" %
                      (var.Name(), solver.Value(var), obj_int_coeffs[i]))

    print()
    print(solver.ResponseStats())
Beispiel #6
0
def steel_mill_slab_with_column_generation(problem, output_proto):
    """Solves the Steel Mill Slab Problem."""
    ### Load problem.
    (num_slabs, capacities, _, orders) = build_problem(problem)

    num_orders = len(orders)
    num_capacities = len(capacities)
    all_orders = range(len(orders))
    print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
          (num_orders, num_slabs, num_capacities - 1))

    # Compute auxilliary data.
    widths = [x[0] for x in orders]
    colors = [x[1] for x in orders]
    max_capacity = max(capacities)
    loss_array = [
        min(x for x in capacities if x >= c) - c
        for c in range(max_capacity + 1)
    ]

    ### Model problem.

    # Generate all valid slabs (columns)
    unsorted_valid_slabs = collect_valid_slabs_dp(capacities, colors, widths,
                                                  loss_array)

    # Sort slab by descending load/loss. Remove duplicates.
    valid_slabs = sorted(unsorted_valid_slabs,
                         key=lambda c: 1000 * c[-1] + c[-2])
    all_valid_slabs = range(len(valid_slabs))

    # create model and decision variables.
    model = cp_model.CpModel()
    selected = [model.NewBoolVar('selected_%i' % i) for i in all_valid_slabs]

    for order_id in all_orders:
        model.Add(
            sum(selected[i] for i, slab in enumerate(valid_slabs)
                if slab[order_id]) == 1)

    # Redundant constraint (sum of loads == sum of widths).
    model.Add(
        sum(selected[i] * valid_slabs[i][-1]
            for i in all_valid_slabs) == sum(widths))

    # Objective.
    model.Minimize(
        sum(selected[i] * valid_slabs[i][-2] for i in all_valid_slabs))

    print('Model created')

    # Output model proto to file.
    if output_proto:
        output_file = open(output_proto, 'w')
        output_file.write(str(model.ModelProto()))
        output_file.close()

    ### Solve model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)

    ### Output the solution.
    if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        print('Loss = %i, time = %.2f s, %i conflicts' %
              (solver.ObjectiveValue(), solver.WallTime(),
               solver.NumConflicts()))
    else:
        print('No solution')
Beispiel #7
0
def steel_mill_slab(problem, break_symmetries, output_proto):
    """Solves the Steel Mill Slab Problem."""
    ### Load problem.
    (num_slabs, capacities, num_colors, orders) = build_problem(problem)

    num_orders = len(orders)
    num_capacities = len(capacities)
    all_slabs = range(num_slabs)
    all_colors = range(num_colors)
    all_orders = range(len(orders))
    print('Solving steel mill with %i orders, %i slabs, and %i capacities' %
          (num_orders, num_slabs, num_capacities - 1))

    # Compute auxilliary data.
    widths = [x[0] for x in orders]
    colors = [x[1] for x in orders]
    max_capacity = max(capacities)
    loss_array = [
        min(x for x in capacities if x >= c) - c
        for c in range(max_capacity + 1)
    ]
    max_loss = max(loss_array)
    orders_per_color = [[o for o in all_orders if colors[o] == c + 1]
                        for c in all_colors]
    unique_color_orders = [
        o for o in all_orders if len(orders_per_color[colors[o] - 1]) == 1
    ]

    ### Model problem.

    # Create the model and the decision variables.
    model = cp_model.CpModel()
    assign = [[
        model.NewBoolVar('assign_%i_to_slab_%i' % (o, s)) for s in all_slabs
    ] for o in all_orders]
    loads = [
        model.NewIntVar(0, max_capacity, 'load_of_slab_%i' % s)
        for s in all_slabs
    ]
    color_is_in_slab = [[
        model.NewBoolVar('color_%i_in_slab_%i' % (c + 1, s))
        for c in all_colors
    ] for s in all_slabs]

    # Compute load of all slabs.
    for s in all_slabs:
        model.Add(
            sum(assign[o][s] * widths[o] for o in all_orders) == loads[s])

    # Orders are assigned to one slab.
    for o in all_orders:
        model.Add(sum(assign[o]) == 1)

    # Redundant constraint (sum of loads == sum of widths).
    model.Add(sum(loads) == sum(widths))

    # Link present_colors and assign.
    for c in all_colors:
        for s in all_slabs:
            for o in orders_per_color[c]:
                model.AddImplication(assign[o][s], color_is_in_slab[s][c])
                model.AddImplication(color_is_in_slab[s][c].Not(),
                                     assign[o][s].Not())

    # At most two colors per slab.
    for s in all_slabs:
        model.Add(sum(color_is_in_slab[s]) <= 2)

    # Project previous constraint on unique_color_orders
    for s in all_slabs:
        model.Add(sum(assign[o][s] for o in unique_color_orders) <= 2)

    # Symmetry breaking.
    for s in range(num_slabs - 1):
        model.Add(loads[s] >= loads[s + 1])

    # Collect equivalent orders.
    width_to_unique_color_order = {}
    ordered_equivalent_orders = []
    for c in all_colors:
        colored_orders = orders_per_color[c]
        if not colored_orders:
            continue
        if len(colored_orders) == 1:
            o = colored_orders[0]
            w = widths[o]
            if w not in width_to_unique_color_order:
                width_to_unique_color_order[w] = [o]
            else:
                width_to_unique_color_order[w].append(o)
        else:
            local_width_to_order = {}
            for o in colored_orders:
                w = widths[o]
                if w not in local_width_to_order:
                    local_width_to_order[w] = []
                local_width_to_order[w].append(o)
            for w, os in local_width_to_order.items():
                if len(os) > 1:
                    for p in range(len(os) - 1):
                        ordered_equivalent_orders.append((os[p], os[p + 1]))
    for w, os in width_to_unique_color_order.items():
        if len(os) > 1:
            for p in range(len(os) - 1):
                ordered_equivalent_orders.append((os[p], os[p + 1]))

    # Create position variables if there are symmetries to be broken.
    if break_symmetries and ordered_equivalent_orders:
        print('  - creating %i symmetry breaking constraints' %
              len(ordered_equivalent_orders))
        positions = {}
        for p in ordered_equivalent_orders:
            if p[0] not in positions:
                positions[p[0]] = model.NewIntVar(0, num_slabs - 1,
                                                  'position_of_slab_%i' % p[0])
                model.AddMapDomain(positions[p[0]], assign[p[0]])
            if p[1] not in positions:
                positions[p[1]] = model.NewIntVar(0, num_slabs - 1,
                                                  'position_of_slab_%i' % p[1])
                model.AddMapDomain(positions[p[1]], assign[p[1]])
            # Finally add the symmetry breaking constraint.
            model.Add(positions[p[0]] <= positions[p[1]])

    # Objective.
    obj = model.NewIntVar(0, num_slabs * max_loss, 'obj')
    losses = [model.NewIntVar(0, max_loss, 'loss_%i' % s) for s in all_slabs]
    for s in all_slabs:
        model.AddElement(loads[s], loss_array, losses[s])
    model.Add(obj == sum(losses))
    model.Minimize(obj)

    ### Solve model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    objective_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, objective_printer)

    ### Output the solution.
    if status in (cp_model.OPTIMAL, cp_model.FEASIBLE):
        print('Loss = %i, time = %f s, %i conflicts' %
              (solver.ObjectiveValue(), solver.WallTime(),
               solver.NumConflicts()))
    else:
        print('No solution')
Beispiel #8
0
def solve_shift_scheduling(params, output_proto):
    """Solves the shift scheduling problem."""
    # Data
    num_employees = 8
    num_weeks = 3
    shifts = ['O', 'M', 'A', 'N']

    # Fixed assignment: (employee, shift, day).
    # This fixes the first 2 days of the schedule.
    ## 部分已经固定的排班计划表
    ## 一天有四个班次分别为 ['O', 'M', 'A', 'N'],O代表休息,morning,afternoon,night
    ## 8个员工一共需要排三周
    ## 部分员工已经锁定了前三天的部分班次
    fixed_assignments = [
        (0, 0, 0),
        (1, 0, 0),
        (2, 1, 0),
        (3, 1, 0),
        (4, 2, 0),
        (5, 2, 0),
        (6, 2, 3),
        (7, 3, 0),
        (0, 1, 1),
        (1, 1, 1),
        (2, 2, 1),
        (3, 2, 1),
        (4, 2, 1),
        (5, 0, 1),
        (6, 0, 1),
        (7, 3, 1),
    ]

    ## 员工偏好
    # Request: (employee, shift, day, weight)
    ## 需求(员工号,需求班次,哪天需求,喜好)
    # A negative weight indicates that the employee desire this assignment.
    ## 负数代表员工偏好某一天的某个班次,负值越大,代表该员工越喜好这个这个班次
    requests = [
        # Employee 3 wants the first Saturday off.
        ## 员工3好这口上班
        (3, 0, 5, -2),
        # Employee 5 wants a night shift on the second Thursday.
        ## 员工5好这口上班
        (5, 3, 10, -2),
        # Employee 2 does not want a night shift on the first Friday.
        ## 员工2不好这口上班
        (2, 3, 4, 4)
    ]

    # Shift constraints on continuous sequence :
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    shift_constraints = [
        # One or two consecutive days of rest, this is a hard constraint.
        ## 惩罚是0,乃是硬约束
        (0, 1, 1, 0, 2, 2, 0),
        # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are
        # possible but penalized.
        ## 惩罚是大小的惩罚正数,乃是软约束
        (3, 1, 2, 20, 3, 4, 5),
    ]

    # 每周轮班天数的总和限制
    # Weekly sum constraints on shifts days:
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    weekly_sum_constraints = [
        # Constraints on rests per week.
        ## 每周休息两天是可以的,休息三天就要给4的惩罚,休息1天,就要给7的惩罚
        (0, 1, 2, 7, 2, 3, 4),
        # At least 1 night shift per week (penalized). At most 4 (hard).
        ## 夜班的约束,三连夜班可以,但是一次夜班都没有是有惩罚的,四连夜班也是不行
        (
            3, 0, 1, 3, 3, 5, 0
        ),  # (0, 1, 2, 7, 2, 3, 4), (3, 0, 1, 3, 4, 4, 0), 原版,4是4似乎有问题,可到达四,到四了也不允许
    ]

    # Penalized transitions:
    #     (previous_shift, next_shift, penalty (0 means forbidden))
    # 前一个班次,后一个班次,惩罚
    penalized_transitions = [
        # Afternoon to night has a penalty of 4.
        ## 如果2~3班次连上,就给惩罚
        (2, 3, 4),
        # Night to morning is forbidden.
        ## 如果夜班3到白班0,就给硬约束0
        (3, 1, 0),
    ]
    ## O 代表无排班计划,属于休息
    ## M早班,A午班,N晚班
    ## 每一日的排班需求
    # daily demands for work shifts (morning, afternon, night) for each day
    # of the week starting on Monday.
    weekly_cover_demands = [
        (2, 3, 1),  # Monday
        (2, 3, 1),  # Tuesday
        (2, 2, 2),  # Wednesday
        (2, 3, 1),  # Thursday
        (2, 2, 2),  # Friday
        (1, 2, 3),  # Saturday
        (1, 3, 1),  # Sunday
    ]

    # Penalty for exceeding the cover constraint per shift type.
    ## 早中晚超过覆盖限制的惩罚
    excess_cover_penalties = (2, 2, 5)

    num_days = num_weeks * 7
    num_shifts = len(shifts)

    model = cp_model.CpModel()

    ## 工作任务分配的笛卡尔集
    work = {}
    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))

    # Linear terms of the objective in a minimization context.
    ## 线性目标?
    obj_int_vars = []
    obj_int_coeffs = []
    obj_bool_vars = []
    obj_bool_coeffs = []

    # Exactly one shift per day.
    ## 每一个员工,每个班次,最多参与一次
    for e in range(num_employees):
        for d in range(num_days):
            model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)

    # Fixed assignments.
    ## 部门员工的排班已经固定,输入就好
    for e, s, d in fixed_assignments:
        model.Add(work[e, s, d] == 1)

    # Employee requests
    ## 把员工需求添加值obj_bool_vars
    ## 同时把员工需求的衡量值添加至obj_bool_coeffs
    for e, s, d, w in requests:
        obj_bool_vars.append(work[e, s, d])
        obj_bool_coeffs.append(w)

    # Shift constraints
    ## 班次的约束
    # ---> 前一个班次,后一个班次,惩罚 shift_constraints

    for ct in shift_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            works = [work[e, shift, d] for d in range(num_days)]
            variables, coeffs = add_soft_sequence_constraint(
                model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
                max_cost,
                'shift_constraint(employee %i, shift %i)' % (e, shift))
            obj_bool_vars.extend(variables)
            obj_bool_coeffs.extend(coeffs)

    print(("----obj_bool----" * 10 + "\n") * 3)
    ## 前三个是员工偏好,喜欢2个不喜欢1个。
    print("obj_bool_vars:\n", obj_bool_vars)  # 分别是任务 员工做或者不做,
    print("obj_bool_coeffs:\n", obj_bool_coeffs)

    # Weekly sum constraints
    ## 周总和约束
    for ct in weekly_sum_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            for w in range(num_weeks):
                # works,是所有work集中,所有指定日期的周,某个员工在某个班次的所有任务量
                works = [work[e, shift, d + w * 7] for d in range(7)]
                variables, coeffs = add_soft_sum_constraint(
                    model, works, hard_min, soft_min, min_cost, soft_max,
                    hard_max, max_cost,
                    'weekly_sum_constraint(employee %i, shift %i, week %i)' %
                    (e, shift, w))
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    print(("----obj_int----" * 10 + "\n") * 3)
    print("obj_int_vars:\n", obj_int_vars)
    print("obj_int_coeffs:\n", obj_int_coeffs)

    # Penalized transitions
    ## 类似于换模约束
    for previous_shift, next_shift, cost in penalized_transitions:
        for e in range(num_employees):
            for d in range(num_days - 1):  # 总的来说最后一天不存在连续夜白的可能
                transition = [
                    work[e, previous_shift, d].Not(), work[e, next_shift,
                                                           d + 1].Not()
                ]
                if cost == 0:
                    model.AddBoolOr(transition)
                else:
                    trans_var = model.NewBoolVar(
                        'transition (employee=%i, day=%i)' % (e, d))
                    transition.append(trans_var)
                    model.AddBoolOr(transition)
                    obj_bool_vars.append(trans_var)
                    obj_bool_coeffs.append(cost)

    # Cover constraints
    for s in range(1, num_shifts):
        for w in range(num_weeks):
            for d in range(7):
                works = [work[e, s, w * 7 + d] for e in range(num_employees)]
                # Ignore Off shift.
                min_demand = weekly_cover_demands[d][s - 1]
                worked = model.NewIntVar(min_demand, num_employees, '')
                model.Add(worked == sum(works))
                over_penalty = excess_cover_penalties[s - 1]
                if over_penalty > 0:
                    name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,
                                                                         d)
                    excess = model.NewIntVar(0, num_employees - min_demand,
                                             name)
                    model.Add(excess == worked - min_demand)
                    obj_int_vars.append(excess)
                    obj_int_coeffs.append(over_penalty)

    # Objective
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars))) +
        sum(obj_int_vars[i] * obj_int_coeffs[i]
            for i in range(len(obj_int_vars))))

    if output_proto:
        print('Writing proto to %s' % output_proto)
        with open(output_proto, 'w') as text_file:
            text_file.write(str(model))

    # Solve the model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8

    if params:
        text_format.Merge(params, solver.parameters)

    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)

    # Print solution.
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print()
        header = '          '
        for w in range(num_weeks):
            header += 'M T W T F S S '
        print(header)
        for e in range(num_employees):
            schedule = ''
            for d in range(num_days):
                for s in range(num_shifts):
                    if solver.BooleanValue(work[e, s, d]):
                        schedule += shifts[s] + ' '
            print('worker %i: %s' % (e, schedule))
        print()
        print('Penalties:')
        for i, var in enumerate(obj_bool_vars):
            if solver.BooleanValue(var):
                penalty = obj_bool_coeffs[i]
                if penalty > 0:
                    print('  %s violated, penalty=%i' % (var.Name(), penalty))
                else:
                    print('  %s fulfilled, gain=%i' % (var.Name(), -penalty))

        for i, var in enumerate(obj_int_vars):
            if solver.Value(var) > 0:
                print('  %s violated by %i, linear penalty=%i' %
                      (var.Name(), solver.Value(var), obj_int_coeffs[i]))

    print()
    print(solver.ResponseStats())
def solve_shift_scheduling(id_list,weeks):
    # Data
    employees = id_list   
    num_employees = len(employees) 
    num_weeks = weeks
    
    # shifts = ['O', 'M', 'A', 'N']
    shifts = ['0', '1', '2', '3']

    # Fixed assignment: (employee, shift, day).
    fixed_assignments = []

    # Request: (employee, shift, day, weight)
    # A negative weight indicates that the employee desire this assignment.
    # ex.
    # Employee 3 wants the first Saturday off, (3, 0, 5, -2)
    # Employee 5 wants a night shift on the second Thursday, (5, 3, 10, -2)
    # Employee 2 does not want a night shift on the third Friday, (2, 3, 4, 4)
    requests = []

    # Shift constraints on continuous sequence :
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    shift_constraints = [
        # One or two consecutive days of rest, this is a hard constraint.
        (0, 1, 1, 0, 2, 2, 0),
        # betweem 2 and 3 consecutive days of night shifts, 1 and 4 are
        # possible but penalized.
        (3, 1, 2, 20, 3, 4, 5), #!
    ]

    # Weekly sum constraints on shifts days:
    #     (shift, hard_min, soft_min, min_penalty,
    #             soft_max, hard_max, max_penalty)
    weekly_sum_constraints = [
        # Constraints on rests per week.
        (0, 1, 1, 7, 2, 2, 4),
        # At least 1 night shift per week (penalized). At most 4 (hard).
        (3, 0, 1, 3, 4, 4, 0),
    ]
    # Penalized transitions:
    #     (previous_shift, next_shift, penalty (0 means forbidden))
    penalized_transitions = [

        # Afternoon to night has a penalty of 4.
        (2, 3, 4),

        # Night to morning is forbidden.
        (3, 1, 0),
    ] 

    # daily demands for work shifts (morning, afternon, night) for each day
    # of the week starting on Monday.
    weekly_cover_demands = [
        (1, 1, 1),  # Monday
        (1, 1, 1),  # Tuesday
        (1, 1, 1),  # Wednesday
        (1, 1, 1),  # Thursday
        (1, 1, 1),  # Friday
        (1, 1, 1),  # Saturday
        (1, 1, 1),  # Sunday
    ]

    # Penalty for exceeding the cover constraint per shift type.
    excess_cover_penalties = (2, 2, 5)

    num_days = num_weeks * 7
    num_shifts = len(shifts)

    model = cp_model.CpModel()

    work = {}
    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                work[e, s, d] = model.NewBoolVar('work%i_%i_%i' % (e, s, d))


    # Linear terms of the objective in a minimization context.
    obj_int_vars = []
    obj_int_coeffs = []
    obj_bool_vars = []
    obj_bool_coeffs = []

    # Exactly one shift per day.
    for e in range(num_employees):
        for d in range(num_days):
            model.Add(sum(work[e, s, d] for s in range(num_shifts)) == 1)

    # Fixed assignments.
    for e, s, d in fixed_assignments:
        model.Add(work[e, s, d] == 1)

    # Employee requests
    for e, s, d, w in requests:
        obj_bool_vars.append(work[e, s, d])
        obj_bool_coeffs.append(w)

    # Shift constraints
    for ct in shift_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            works = [work[e, shift, d] for d in range(num_days)]
            variables, coeffs = add_soft_sequence_constraint(
                model, works, hard_min, soft_min, min_cost, soft_max, hard_max,
                max_cost, 'shift_constraint(employee %i, shift %i)' % (e,
                                                                       shift))
            obj_bool_vars.extend(variables)
            obj_bool_coeffs.extend(coeffs)

    # Weekly sum constraints
    for ct in weekly_sum_constraints:
        shift, hard_min, soft_min, min_cost, soft_max, hard_max, max_cost = ct
        for e in range(num_employees):
            for w in range(num_weeks):
                works = [work[e, shift, d + w * 7] for d in range(7)]
                variables, coeffs = add_soft_sum_constraint(
                    model, works, hard_min, soft_min, min_cost, soft_max,
                    hard_max, max_cost,
                    'weekly_sum_constraint(employee %i, shift %i, week %i)' %
                    (e, shift, w))
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    # Penalized transitions
    for previous_shift, next_shift, cost in penalized_transitions:
        for e in range(num_employees):
            for d in range(num_days - 1):
                transition = [
                    work[e, previous_shift, d].Not(),
                    work[e, next_shift, d + 1].Not()
                ]
                if cost == 0:
                    model.AddBoolOr(transition)
                else:
                    trans_var = model.NewBoolVar(
                        'transition (employee=%i, day=%i)' % (e, d))
                    transition.append(trans_var)
                    model.AddBoolOr(transition)
                    obj_bool_vars.append(trans_var)
                    obj_bool_coeffs.append(cost)

    # Cover constraints
    for s in range(1, num_shifts):
        for w in range(num_weeks):
            for d in range(7):
                works = [work[e, s, w * 7 + d] for e in range(num_employees)]
                # Ignore Off shift.
                min_demand = weekly_cover_demands[d][s - 1]
                worked = model.NewIntVar(min_demand, num_employees, '')
                model.Add(worked == sum(works))
                over_penalty = excess_cover_penalties[s - 1]
                if over_penalty > 0:
                    name = 'excess_demand(shift=%i, week=%i, day=%i)' % (s, w,
                                                                         d)
                    excess = model.NewIntVar(0, num_employees - min_demand,
                                             name)
                    model.Add(excess == worked - min_demand)
                    obj_int_vars.append(excess)
                    obj_int_coeffs.append(over_penalty)

    # Objective
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars)))
        + sum(obj_int_vars[i] * obj_int_coeffs[i]
              for i in range(len(obj_int_vars))))

    # Solve the model.
    solver = cp_model.CpSolver()
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status = solver.SolveWithSolutionCallback(model, solution_printer)
    
    # Print solution.
    # if status == cp_model.OPTIMAL: 
    #     print('Status: %s' % ("OPTIMAL"))       
    #     str1 = ''
    #     for e in range(num_employees):
    #         str2 = ''
    #         for d in range(num_days):
    #             for s in range(num_shifts):
    #                 if solver.BooleanValue(work[e, s, d]):
    #                     str2 += '{"day":"%s", "shift":"%s"},' % (str(d),shifts[s])
    #         str2 = '[%s]' % str2[:-1]
    #         str1 += '{"employee":"%s","shifts":%s},' % (employees[e],str2)         
    #     json_str = '[%s]' % (str1[:-1])       
    #     # TODO: Add penalties to json.      
    #     return json_str

    if status == cp_model.OPTIMAL: 
        print('Status: %s' % ("OPTIMAL"))       
        str1 = ''
        for d in range(num_days):
            str2 = ''
            for s in range(num_shifts):
                for e in range(num_employees):
                    if solver.BooleanValue(work[e, s, d]):
                        str2 += '"%s",' % (employees[e])
            str2 = '[%s]' % (str2[:-1])
            str1 += '{"day":"%s","shifts":%s},' % (d, str2)
        json_str = '[%s]' % (str1[:-1]) 
        # TODO: Add penalties to json.      
        return json_str

    if status == cp_model.INFEASIBLE:
        return json.dumps({"message":"INFEASIBLE"})
Beispiel #10
0
            num_shifts_per_employee += work[e, h, d] * work_time[h]

    model.Add(num_shifts_per_employee <= max_shifts_per_employee)
    model.Add(num_shifts_per_employee >= min_shifts_per_employee)

# Step 7: Define the objective
model.Minimize(
    sum(obj_bool_vars[i] * obj_bool_coeffs[i]
        for i in range(len(obj_bool_vars))) +
    sum(obj_bool_opd_vars[i] * obj_bool_opd_coeffs[i]
        for i in range(len(obj_bool_opd_vars))))

# Step 8: Create a solver and invoke solve
solver = cp_model.CpSolver()

solution_printer = cp_model.ObjectiveSolutionPrinter()
status = solver.SolveWithSolutionCallback(model, solution_printer)

# Step 9: Call a printer and display the results
if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
    print('worker 0(徐), worker 1(謝), worker 2(胡), worker 3(杰)')
    print()
    different_header = [['S ', 'M ', 'T ', 'W ', 'T ', 'F ', 'S '],
                        ['M ', 'T ', 'W ', 'T ', 'F ', 'S ', 'S '],
                        ['T ', 'W ', 'T ', 'F ', 'S ', 'S ', 'M '],
                        ['W ', 'T ', 'F ', 'S ', 'S ', 'M ', 'T '],
                        ['T ', 'F ', 'S ', 'S ', 'M ', 'T ', 'W '],
                        ['F ', 'S ', 'S ', 'M ', 'T ', 'W ', 'T '],
                        ['S ', 'S ', 'M ', 'T ', 'W ', 'T ', 'F ']]
    header = '          '
    for i in range(num_days - 1):
Beispiel #11
0
def solve_shift_scheduling(livreurs, Creneaux, num_weeks, zones,
                           max_shift_per_day, min_shift_per_day, contrainte_tp,
                           daily_cover_demands, occ_rues, waiting_time,
                           shift_hours):
    num_employees = len(livreurs.keys())
    num_shifts = len(Creneaux)
    num_days = num_weeks * 7

    model = cp_model.CpModel()
    work = {}

    for e in range(num_employees):
        for s in range(num_shifts):
            for d in range(num_days):
                for zone in zones:
                    work[e, s, d, zone] = model.NewBoolVar('work%i_%i_%i_%r' %
                                                           (e, s, d, zone))
    work_wt = {}
    for e in range(num_employees):
        for d in range(num_days):
            for s in range(num_shifts):
                work_wt[e, s,
                        d] = model.NewBoolVar('work_wt%i_%i_%i' % (e, s, d))
                model.Add(sum(work[e, s, d, zone]
                              for zone in zones) == 1).OnlyEnforceIf(
                                  work_wt[e, s, d])
                model.Add(sum(work[e, s, d, zone]
                              for zone in zones) == 0).OnlyEnforceIf(
                                  work_wt[e, s, d].Not())

    for d in range(num_days):
        for s in range(1, num_shifts):
            for zone in zones:
                model.Add(
                    sum(work[e, s, d, zone]
                        for e in range(num_employees)) <= 1)

    # nb min and max of shift per day.
    # for e in range(num_employees):
    #     for d in range(num_days):
    #         model.Add(sum(work_wt[e, s, d] for s in range(1,num_shifts)) <= max_shift_per_day)
    #         model.Add(sum(work_wt[e, s, d] for s in range(1,num_shifts)) >= min_shift_per_day)

    obj_bool_vars = []
    obj_bool_coeffs = []
    obj_int_vars = []
    obj_int_coeffs = []
    # total pourcentage work constraint
    if contrainte_tp != []:
        for e in range(num_employees):
            for w in range(num_weeks):
                works = [
                    work[e, shift, d + w * 7, zone]
                    for zone in contrainte_tp[0] for shift in contrainte_tp[1]
                    for d in range(7)
                ]
                weights = [
                    shift_hours[Creneaux[shift + 1]]
                    for zone in contrainte_tp[0] for shift in contrainte_tp[1]
                    for d in range(7)
                ]
                variables, coeffs = add_soft_scalar_sum_constraint(
                    model, works, weights, int(contrainte_tp[3]),
                    int(contrainte_tp[2]), contrainte_tp[5],
                    int(contrainte_tp[2]), int(contrainte_tp[4]),
                    contrainte_tp[6],
                    'weekly_pourc_work_constraint(employee %i, week %i)' %
                    (e, w))
                obj_int_vars.extend(variables)
                obj_int_coeffs.extend(coeffs)

    # Cover constraints
    for zone in zones:
        for d in range(num_days):
            works = [
                work[e, s, d, zone] for s in range(1, num_shifts)
                for e in range(num_employees)
            ]
            # Ignore Off shift.
            min_demand = daily_cover_demands[d, zone]
            model.Add(sum(works) == min_demand)

    for zone in zones:
        for e in range(num_employees):
            for d in range(num_days):
                for s in range(1, num_shifts):
                    obj_bool_vars.append(work[e, s, d, zone])
                    obj_bool_coeffs.append(occ_rues[s - 1, d, zone])

    # ------------- MODIFICATION -----------------
    for zone in zones:
        for e in range(num_employees):
            for d in range(num_days):
                for s in range(1, num_shifts):
                    if 1 <= s <= 8 or 21 <= s <= 24:
                        obj_bool_vars.append(work[e, s, d, zone])
                        obj_bool_coeffs.append(14)

    # ---------------------------------------------
    # Objective
    model.Minimize(
        sum(obj_bool_vars[i] * obj_bool_coeffs[i]
            for i in range(len(obj_bool_vars))) +
        sum(obj_int_vars[i] * obj_int_coeffs[i]
            for i in range(len(obj_int_vars))))

    # Solve the model.
    solver = cp_model.CpSolver()
    solver.parameters.num_search_workers = 8
    solver.parameters.max_time_in_seconds = waiting_time
    solution_printer = cp_model.ObjectiveSolutionPrinter()
    status_int = solver.SolveWithSolutionCallback(model, solution_printer)
    # print("status : {}".format(status_int))
    # print("INFEASIBLE:{}".format(cp_model.INFEASIBLE))

    return solver, work, status_int