Resolvendo Problemas Complexos de Agendamento com Ferramentas OR: Um Estudo de Caso Prático


prefácio

O problema de agendamento é um problema de otimização combinatória importante e comum, que envolve organizar razoavelmente a sequência e o tempo de tarefas ou atividades para otimizar alguma função objetivo sob as restrições de recursos e tempo limitados. Os problemas de agendamento são amplamente utilizados na fabricação, logística e transporte, gerenciamento de projetos, agendamento de tarefas de computador, agendamento de tarefas e outros campos.

1. Introdução aos Problemas de Agendamento

O problema de agendamento é um problema de otimização combinatória importante e comum, que envolve organizar razoavelmente a sequência e o tempo de tarefas ou atividades para otimizar alguma função objetivo sob as restrições de recursos e tempo limitados. Os problemas de agendamento são amplamente utilizados na fabricação, logística e transporte, gerenciamento de projetos, agendamento de tarefas de computador, agendamento de tarefas e outros campos.

Problemas comuns de agendamento incluem:

  • Job Shop Scheduling Problema : Dado um conjunto de tarefas e um conjunto de máquinas que podem ser executadas em paralelo, cada tarefa precisa ser concluída em uma ordem específica em uma máquina diferente. O objetivo é minimizar o tempo necessário para concluir todos os trabalhos ou maximizar a eficiência da produção.
  • Problema de agendamento de flow shop : Semelhante ao problema de agendamento de job shop, mas no problema de agendamento de flow shop, cada tarefa precisa passar por um conjunto de máquinas na mesma ordem.
  • Problema de roteamento de veículos : Dado um conjunto de pontos de clientes e um conjunto de veículos de entrega, os veículos precisam partir de um centro, visitar cada ponto de cliente e retornar ao centro para minimizar o custo total de entrega ou minimizar o custo total de entrega. o veículo. Distância de viagem.
  • Problema de agendamento de projeto com restrição de recursos (Problema de agendamento de projeto com restrição de recursos) : Existem várias tarefas em um projeto e cada tarefa requer recursos e tempo específicos para ser concluída. O objetivo é minimizar a duração total do projeto ou maximizar a utilização de recursos enquanto satisfaz as restrições de recursos e tempo.
  • Problema de escalonamento de tarefas de multiprocessador : atribua um conjunto de tarefas a vários processadores e tente equilibrar a carga para minimizar o tempo de conclusão da tarefa ou maximizar a utilização do processador.
  • Problema de Alocação de Tarefas : Crie uma correspondência entre trabalhadores e tarefas para maximizar o valor total das tarefas concluídas ou minimizar o tempo total de trabalho dos trabalhadores.

Os métodos para resolver problemas de agendamento geralmente incluem algoritmos gulosos, algoritmos heurísticos, algoritmos de otimização exata (como programação linear inteira e programação de restrição) e algoritmos metaheurísticos (como algoritmos genéticos e algoritmos de recozimento simulados). A complexidade dos problemas de escalonamento geralmente depende das restrições entre tarefas e recursos, e alguns problemas de escalonamento são problemas NP-difíceis, o que significa que encontrar a solução ótima pode levar um tempo exponencial.

2. Análise de Caso

2-1. Problema de agendamento de enfermagem

Problema de Agendamento de Enfermeiras : Organizações com funcionários que trabalham em vários turnos precisam agendar trabalhadores suficientes para cada turno por dia. Normalmente, o horário terá algumas restrições como "nenhum funcionário deve trabalhar dois turnos seguidos em um turno". Encontrar um horário que satisfaça todas as restrições pode ser computacionalmente difícil.
Dúvidas sobre Agendamento de Enfermeiras:

No próximo exemplo, o diretor do hospital precisa criar uma escala para 4 enfermeiras com base no critério de 3 dias :

  • Mudanças todos os dias, três vezes ao dia.
  • Todos os dias, cada turno é atribuído a uma enfermeira, e nenhuma enfermeira trabalha mais de um turno.
  • Durante os 3 dias, cada enfermeira foi designada para pelo menos dois turnos.

A seguir, mostramos a designação de enfermeiros para seus turnos sujeitos às seguintes restrições :

  • Cada turno é atribuído a uma enfermeira por dia.
  • Uma enfermeira só trabalhará um turno por dia, no máximo.

Restrições gráficas no código :
insira a descrição da imagem aqui
insira a descrição da imagem aqui

O código específico é o seguinte :

#导入所需库,定义护士数量、轮班次数、天数
from ortools.sat.python import cp_model
num_nurses = 4
num_shifts = 3
num_days = 3
all_nurses = range(num_nurses)
all_shifts = range(num_shifts)
all_days = range(num_days)

# 创建模型、创建Bool类型变量数组(n, d, s)-->(护士编号,哪一天,哪一个轮班),定义了针对护士的轮班分配。
#即如果将 shift s 分配给 d 天中的护士 n,shifts[(n, d, s)] 的值为 1,否则为 0。
model = cp_model.CpModel()
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))

# 确保每一天的每一个轮班只有一个护士
for d in all_days:
    for s in all_shifts:
        model.AddExactlyOne(shifts[(n, d, s)] for n in all_nurses)

# 确保每一名护士每天最多轮班一次。
for n in all_nurses:
    for d in all_days:
        model.AddAtMostOne(shifts[(n, d, s)] for s in all_shifts)
        
# 尽量分配均匀,设定每个护士应该上的最小轮班次数和最大轮班次数。
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 = min_shifts_per_nurse + 1
    
# 指定每一个护士的最小轮班数为2,最大轮班数为3for n in all_nurses:
    shifts_worked = []
    for d in all_days:
        for s in all_shifts:
            shifts_worked.append(shifts[(n, d, s)])
    model.Add(min_shifts_per_nurse <= sum(shifts_worked))
    model.Add(sum(shifts_worked) <= max_shifts_per_nurse)

solver = cp_model.CpSolver()
solver.parameters.linearization_level = 0
# Enumerate all solutions.
solver.parameters.enumerate_all_solutions = True

# 在求解器上注册一个回调,系统会在每个解决方案中调用该回调函数。
class NursesPartialSolutionPrinter(cp_model.CpSolverSolutionCallback):
    """Print intermediate solutions."""

    def __init__(self, shifts, num_nurses, num_days, num_shifts, limit):
        cp_model.CpSolverSolutionCallback.__init__(self)
        self._shifts = shifts
        self._num_nurses = num_nurses
        self._num_days = num_days
        self._num_shifts = num_shifts
        self._solution_count = 0
        self._solution_limit = limit

    def on_solution_callback(self):
        self._solution_count += 1
        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))
        if self._solution_count >= self._solution_limit:
            print('Stop search after %i solutions' % self._solution_limit)
            self.StopSearch()

    def solution_count(self):
        return self._solution_count

# Display the first five solutions.
solution_limit = 5
solution_printer = NursesPartialSolutionPrinter(shifts, num_nurses,
                                                num_days, num_shifts,
                                                solution_limit)
#调用求解器,并显示前五个解决方案
solver.Solve(model, solution_printer)

saída :
insira a descrição da imagem aqui

2-2. Problema de agendamento de job shop

Job Shop Scheduling Problema: Cada tarefa consiste em uma sequência de tarefas que devem ser executadas em uma determinada ordem e cada tarefa deve ser processada em uma máquina específica. Por exemplo, o cargo pode ser um fabricante de um único produto de consumo, como um automóvel. O problema é escalonar as tarefas nas máquinas de modo a minimizar a duração do escalonamento, ou seja, o tempo necessário para concluir todas as tarefas.

Este problema tem as seguintes restrições:

  • Nenhuma tarefa para este trabalho pode ser iniciada até que a tarefa do trabalho anterior seja concluída.
  • Uma máquina só pode lidar com uma tarefa por vez.
  • Deve ser executado após o início da tarefa para ser concluído.

A seguir está um exemplo de um problema simples de agendamento de job shop, onde cada tarefa é rotulada por um par de números (m, p), onde m é o número de máquinas nas quais a tarefa deve ser processada e p é o tempo de processamento da tarefa (ou seja, leva tempo). (Os números do trabalho e da máquina começam em 0.)

  • Trabalho 0 = [(0, 3), (1, 2), (2, 2)]
  • Trabalho 1 = [(0, 2), (2, 1), (1, 4)]
  • Trabalho 2 = [(1, 4), (2, 3)]

No exemplo, o trabalho 0 tem 3 tarefas. O primeiro (0, 3) deve ser processado na máquina 0 em 3 unidades de tempo. O segundo (1, 2) deve ser processado na máquina 1 em 2 unidades de tempo, e assim sucessivamente. Há oito missões no total.

Abaixo está uma solução possível :
insira a descrição da imagem aqui
Variáveis ​​e restrições do problema
Esta seção descreve como configurar as variáveis ​​e restrições do problema. Primeiro, deixe task(i, j) denotar a j-ésima tarefa na tarefa i. Por exemplo, task(0, 2) representa a segunda tarefa da tarefa 0, correspondente ao par (1, 2) na declaração do problema.

Em seguida, ti , j t_{i, j}teu , jDefinido como a hora de início da tarefa (i, j). ti , j t_{i, j}teu , jé a variável na resolução do problema do dever de casa. A solução requer determinar os valores dessas variáveis ​​que satisfaçam os requisitos do problema.

O problema de escalonamento de job shop tem duas restrições :

  • Restrição de prioridade - Uma condição derivada de um trabalho que, para quaisquer duas tarefas consecutivas no mesmo trabalho, a primeira tarefa deve ser concluída antes que a segunda possa começar. Por exemplo, task(0, 2) e task(0, 3) são tarefas consecutivas para o trabalho 0. Como a tarefa(0, 2) tem um tempo de processamento de 2, a hora de início da tarefa(0, 3) deve ser pelo menos 2 unidades posterior à hora de início da tarefa 2. (A Tarefa 2 pode ser pintar uma porta, que leva 2 horas para ser pintada.) Portanto, você está limitado por:
    t 0 , 2 t_{0, 2}t0 , 2+ 2 <= t 0 , 3 t_{0, 3}t0 , 3
  • No Overlap Constraint - Há uma restrição de que a máquina não pode lidar com duas tarefas ao mesmo tempo. Por exemplo, tarefas (0, 2) e tarefas (2, 1) são ambas processadas na máquina 1. Como os tempos de processamento são 2 e 4 respectivamente, uma das seguintes restrições deve ser satisfeita:
    t 0 , 2 t_{0, 2}t0 , 2+ 2 <= t 2 , 1 t_{2, 1}t2 , 1(se a tarefa(0, 2) for agendada antes da tarefa(2, 1)) ou
    t 2 , 1 t_{2, 1}t2 , 1+ 4 <= t 0 , 2 t_{0, 2}t0 , 2(se a tarefa(2, 1) for agendada antes da tarefa(0, 2)).

Objetivo do problema
O objetivo do problema de escalonamento de job shop é minimizar o makespan: o período de tempo desde o horário de início mais cedo do trabalho até o horário de término mais tarde do trabalho.

O código fica assim :

"""Minimal jobshop example."""
import collections
from ortools.sat.python import cp_model


def main():
    """Minimal jobshop problem."""
    # Data.
    # # 定义每一个作业的所有任务,(m,p)m代表的是在哪个机器上处理,p代表的是任务的处理时间(即所需要的时间)
    jobs_data = [  # task = (machine_id, processing_time).
        [(0, 3), (1, 2), (2, 2)],  # Job0
        [(0, 2), (2, 1), (1, 4)],  # Job1
        [(1, 4), (2, 3)]  # Job2
    ]
    # 机器数量,得出一共需要的机器数量是多少。
    machines_count = 1 + max(task[0] for job in jobs_data for task in job)
    all_machines = range(machines_count)
    # Computes horizon dynamically as the sum of all durations.
    # 处理所有任务的总的时长并实例化模型
    horizon = sum(task[1] for job in jobs_data for task in job)

    # Create the model.
    model = cp_model.CpModel()
    
    # 使用collection来命名元组,详细使用看下边内容
    # Named tuple to store information about created variables.
    task_type = collections.namedtuple('task_type', 'start end interval')
    # Named tuple to manipulate solution information.
    assigned_task_type = collections.namedtuple('assigned_task_type',
                                                'start job index duration')

    # Creates job intervals and add to the corresponding machine lists.
    all_tasks = {
    
    }
    # 使用collections来创建字典。
    machine_to_intervals = collections.defaultdict(list)
    
    # job_id:第几个作业, job: [(0, 3), (1, 2), (2, 2)]
    for job_id, job in enumerate(jobs_data):
        # task_id: 第几个任务, task: (0, 3)
        for task_id, task in enumerate(job):
            machine = task[0]
            duration = task[1]
            # 拼接后缀
            suffix = '_%i_%i' % (job_id, task_id)
            start_var = model.NewIntVar(0, horizon, 'start' + suffix)
            end_var = model.NewIntVar(0, horizon, 'end' + suffix)
            # 创建时间区间变量来存储每个任务
            interval_var = model.NewIntervalVar(start_var, duration, end_var,
                                                'interval' + suffix)
            
            # 
            all_tasks[job_id, task_id] = task_type(start=start_var,
                                                   end=end_var,
                                                   interval=interval_var)
            # 在机器上添加每个时间区间变量
            machine_to_intervals[machine].append(interval_var)

    # Create and add disjunctive constraints.
    # 创建每个机器的限制
    # # 添加一个“无重叠”约束的方法,通常用于安排一组任务或者活动在时间上不会发生重叠,这可以保证在特定时间段内,只有一个任务或活动会被安排。
    for machine in all_machines:
        model.AddNoOverlap(machine_to_intervals[machine])

    # Precedences inside a job.
    # 添加限制条件:后一个任务的起始时间大于等于前一个任务的结束时间
    for job_id, job in enumerate(jobs_data):
        for task_id in range(len(job) - 1):
            model.Add(all_tasks[job_id, task_id +
                                1].start >= all_tasks[job_id, task_id].end)

    # Makespan objective.
    # 目标变量:所有作业的最后一个任务的结束时间,作为目标变量的最大值约束
    obj_var = model.NewIntVar(0, horizon, 'makespan')
    model.AddMaxEquality(obj_var, [
        all_tasks[job_id, len(job) - 1].end
        for job_id, job in enumerate(jobs_data)
    ])
    
    # 最小化目标变量
    model.Minimize(obj_var)

    # Creates the solver and solve.
    solver = cp_model.CpSolver()
    status = solver.Solve(model)
    
    # 
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        print('Solution:')
        # 创建每个机器被分配的任务列表
        # Create one list of assigned tasks per machine.
        assigned_jobs = collections.defaultdict(list)
        for job_id, job in enumerate(jobs_data):
            for task_id, task in enumerate(job):
                machine = task[0]
                assigned_jobs[machine].append(
                    assigned_task_type(start=solver.Value(
                        all_tasks[job_id, task_id].start),
                                       job=job_id,
                                       index=task_id,
                                       duration=task[1]))

        # Create per machine output lines.
        output = ''
        for machine in all_machines:
            # Sort by starting time.
            assigned_jobs[machine].sort()
            sol_line_tasks = 'Machine ' + str(machine) + ': '
            sol_line = '           '

            for assigned_task in assigned_jobs[machine]:
                name = 'job_%i_task_%i' % (assigned_task.job,
                                           assigned_task.index)
                # Add spaces to output to align columns.
                sol_line_tasks += '%-15s' % name

                start = assigned_task.start
                duration = assigned_task.duration
                sol_tmp = '[%i,%i]' % (start, start + duration)
                # Add spaces to output to align columns.
                sol_line += '%-15s' % sol_tmp

            sol_line += '\n'
            sol_line_tasks += '\n'
            output += sol_line_tasks
            output += sol_line

        # Finally print the solution found.
        print(f'Optimal Schedule Length: {
    
    solver.ObjectiveValue()}')
        print(output)
    else:
        print('No solution found.')

    # Statistics.
    print('\nStatistics')
    print('  - conflicts: %i' % solver.NumConflicts())
    print('  - branches : %i' % solver.NumBranches())
    print('  - wall time: %f s' % solver.WallTime())


if __name__ == '__main__':
    main()

saída :

*Solução:
Comprimento ideal da programação: 11,0
Máquina 0: job_1_task_0 job_0_task_0
[0,2] [2,5]
Máquina 1: job_2_task_0 job_0_task_1 job_1_task_2
[0,4] [5,7] [7,11]
Máquina 2: job_1_task_1 job_2_task_1 job_0_task_2
[2,3] [4,7] [7,9]
Estatísticas

  • conflitos: 0
  • ramos: 0
  • tempo de parede: 0,018851 s*

3. Base de conhecimento

3-1, coleção

Em Python, collections é um módulo de biblioteca padrão que fornece algumas estruturas de dados úteis e ferramentas para estender a funcionalidade de tipos de dados integrados. O módulo de coleções contém vários tipos de coleção que podem ser usados ​​para gerenciar e processar dados com mais eficiência. Alguns tipos de coleção comumente usados ​​incluem :

  • namedtuple (named tuple) : namedtuple é uma função de fábrica usada para criar uma subclasse de tupla nomeada. Cada elemento de uma tupla nomeada tem seu próprio nome de campo, o que permite que cada posição da tupla tenha um nome legível por humanos. Pode tornar o código mais legível.
  • deque (fila bidirecional) : deque é uma fila bidirecional que oferece suporte a operações eficientes de inserção e exclusão em ambas as extremidades da fila. Comparado a uma lista, a complexidade de tempo de inserção e remoção de elementos no cabeçalho de um deque é O(1), enquanto a complexidade de tempo de uma lista é O(n).
  • Counter : Counter é um utilitário de contador simples para contar ocorrências de objetos com hash. Pode ser usado para contar a frequência de elementos em listas, strings, etc.
  • OrderedDict (dicionário ordenado) : OrderedDict é um tipo de dicionário ordenado que mantém a ordem na qual os elementos são inseridos, e o dicionário pode ser percorrido na ordem de inserção.
  • defaultdict (dicionário padrão) : defaultdict é um tipo de dicionário que pode especificar um valor padrão.Ao acessar uma chave que não existe no dicionário, o valor padrão especificado será retornado em vez de gerar uma exceção.
  • ChainMap (mapeamento em cadeia) : ChainMap pode vincular vários dicionários ou objetos de mapeamento para formar uma cadeia de mapeamento lógico.
  • Counter : Counter é um utilitário de contador simples para contar ocorrências de objetos com hash. Pode ser usado para contar a frequência de elementos em listas, strings, etc.

O seguinte usa namedtuple (named tuple como exemplo) : uma função de fábrica para criar uma subclasse de tupla nomeada e gerar uma subclasse de tupla que pode usar nomes para acessar o conteúdo do elemento
Introdução do parâmetro
namedtuple(typename,field_names,*,verbose=False, rename =Falso, módulo=Nenhum)

  • typename: Este parâmetro especifica o nome da classe da subclasse tupla criada, que é equivalente ao usuário definir uma nova classe.

  • field_names: Este parâmetro é uma sequência de strings, como ['x', 'y']. Além disso, field_names também pode usar diretamente uma única string para representar todos os nomes de campo, e vários nomes de campo são separados por espaços ou vírgulas, como 'x y' ou 'x,y'. Qualquer identificador Python válido pode ser usado como um nome de campo (sem sublinhado à esquerda). Os identificadores válidos podem consistir em letras, números e sublinhados, mas não podem começar com números, sublinhados ou palavras-chave (como return, global, pass, raise etc.).

  • renomear: Se este parâmetro for definido como True, os nomes de campo inválidos serão automaticamente substituídos por nomes posicionais. Por exemplo, se você especificar ['abc', 'def', 'ghi', 'abc'], ele será substituído por ['abc', '_1', 'ghi', '_3'], porque o def nome do campo é uma palavra-chave e o nome do campo abc é repetido.

  • verbose: Se este parâmetro for definido como True, quando a subclasse for criada, a definição da classe será impressa imediatamente.

  • módulo: Se este parâmetro for definido, a classe estará localizada sob este módulo, então o atributo do módulo desta classe personalizada será definido para este valor de parâmetro.

Estudo de caso :

from collections import namedtuple

# 定义命名元组类型
task_type = namedtuple('task_type', 'start end interval')

# 创建一个命名元组实例
start_var = 0
end_var = 10
interval_var = 5
task_instance = task_type(start=start_var, end=end_var, interval=interval_var)

# 访问命名元组的字段值
print(task_instance.start)    # 输出: 0
print(task_instance.end)      # 输出: 10
print(task_instance.interval) # 输出: 5

# 定义了一个命名元组 task_type,它有三个字段:start、end 和 interval。
# 接下来,task_type(start=start_var, end=end_var, interval=interval_var) 这行代码实际上是在创建一个 task_type 命名元组的实例,并将参数 start_var、end_var 和 interval_var 分别赋值给相应的字段。
# 这样的命名元组实例可以像普通的元组一样被访问和操作,但是由于每个字段都有一个名字,所以可以通过字段名来获取元组的值,使得代码更加清晰易读

3-2、CpModel().AddNoOverlap()

Significado : É o método usado para adicionar uma restrição "sem sobreposição". Uma restrição "sem sobreposição" geralmente é usada para agendar um conjunto de tarefas ou atividades para que não se sobreponham no tempo. Isso garante que apenas uma tarefa ou atividade seja agendada durante um período de tempo específico.

Estudo de caso :

from ortools.sat.python import cp_model
model = cp_model.CpModel()
# 任务1,开始时间为0,持续时间为2,结束时间为2
start_1 = model.NewIntVar(0, 10, 'start_1')
duration_1 = 2
end_1 = model.NewIntVar(0, 10, 'end_1')
interval_1 = model.NewIntervalVar(start_1, duration_1, end_1, 'interval_1')

# 任务2,开始时间为0,持续时间为2,结束时间为2
start_2 = model.NewIntVar(0, 10, 'start_2')
duration_2 = 2
end_2 = model.NewIntVar(0, 10, 'end_2')
interval_2 = model.NewIntervalVar(start_2, duration_2, end_2, 'interval_2')

# 任务3,开始时间为0,持续时间为2,结束时间为2
start_3 = model.NewIntVar(0, 10, 'start_3')
duration_3 = 2
end_3 = model.NewIntVar(0, 10, 'end_3')
interval_3 = model.NewIntervalVar(start_3, duration_3, end_3, 'interval_3')
model.AddNoOverlap([interval_1, interval_2, interval_3])
solver = cp_model.CpSolver()
status = solver.Solve(model)

if status == cp_model.OPTIMAL:
    print('任务1开始于:', solver.Value(start_1))
    print('任务2开始于:', solver.Value(start_2))
    print('任务3开始于:', solver.Value(start_3))

saída :
insira a descrição da imagem aqui

3-3、CpModel().AddMaxEquality()

Significado : model.AddMaxEquality() é um método fornecido no módulo Constraint Programming (cp_model), que é usado para adicionar uma restrição de que o valor máximo de um conjunto de variáveis ​​é igual ao modelo.

gramática

model.AddMaxEquality(target_var, variable_array)
  • target_var : Indica a variável de destino, que é uma variável inteira (IntVar) usada para armazenar o valor máximo no array de variáveis.
  • variable_array : Indica um array de variáveis, ou seja, um conjunto de variáveis ​​inteiras que precisam encontrar o valor máximo.
  • O método AddMaxEquality () definirá o valor da variável de destino target_var para o valor máximo na matriz variável variable_array.

O caso é o seguinte :

# Import the required module
from ortools.sat.python import cp_model

# Instantiate the CP-SAT model.
model = cp_model.CpModel()

# Define the capacities
br1_cap = 5
br2_cap = 4
br3_cap = 6

# Given tasks
tasks = 10

# Define variables for the tasks assigned to each branch
br1_tasks = model.NewIntVar(0, br1_cap, 'br1')
br2_tasks = model.NewIntVar(0, br2_cap, 'br2')
br3_tasks = model.NewIntVar(0, br3_cap, 'br3')

# Define the variable for the maximum tasks
max_tasks = model.NewIntVar(0, max(br1_cap, br2_cap, br3_cap), 'max_tasks')

# Tell the model that max_tasks is the maximum number of tasks assigned to any branch
model.AddMaxEquality(max_tasks, [br1_tasks, br2_tasks, br3_tasks])

# Add constraints that the tasks assigned to all branches should equal the given tasks
model.Add(br1_tasks + br2_tasks + br3_tasks == tasks)

# Create a solver
solver = cp_model.CpSolver()
status = solver.Solve(model)

# Print the assignments
if status == cp_model.OPTIMAL:
    print("Branch 1 tasks: ", solver.Value(br1_tasks))
    print("Branch 2 tasks: ", solver.Value(br2_tasks))
    print("Branch 3 tasks: ", solver.Value(br3_tasks))
    print("Maximum tasks assigned to a branch: ", solver.Value(max_tasks))

输出如下
Tarefas da filial 1: 0
Tarefas da filial 2: 4
Tarefas da filial 3: 6
Máximo de tarefas atribuídas a uma filial: 6

3-4、cp_model.CpModel().NewIntVar()

Significado : cp_model.CpModel().NewIntVar() é um método para criar uma nova variável inteira (IntVar). Variáveis ​​inteiras são usadas para modelar incógnitas do tipo inteiro que podem representar estados, variáveis ​​de decisão ou outros valores que precisam ser resolvidos em um problema.
Gramática :

NewIntVar(lb, ub, name)

Descrição do parâmetro :

  • lb (limite inferior): O limite inferior da variável inteira, indicando o intervalo de valor mínimo da variável inteira.
  • ub (limite superior): O limite superior da variável inteira, indicando o valor máximo do intervalo de valores da variável inteira.
  • name: O nome da variável inteira (opcional), usado para identificar e identificar a variável na saída.

Estudo de caso :

from ortools.sat.python import cp_model

def simple_integer_variable_example():
    # 创建约束编程模型
    model = cp_model.CpModel()

    # 创建整数变量 x,范围为 [0, 10]
    x = model.NewIntVar(0, 10, 'x')

    # 创建约束条件:x >= 5
    model.Add(x >= 5)

    # 创建求解器并求解模型
    solver = cp_model.CpSolver()
    status = solver.Solve(model)

    if status == cp_model.OPTIMAL:
        print('x 的值:', solver.Value(x))
    else:
        print('找不到可行解。')

if __name__ == '__main__':
    simple_integer_variable_example()

Valor da saída :
Valor de x: 5

3-5、cp_model.CpModel().NewIntervalVar

Significado : cp_model.CpModel().NewIntervalVar() é um método para criar uma variável de intervalo de tempo (IntervalVar). As variáveis ​​de intervalo de tempo são usadas para representar o início e o fim de uma tarefa ou atividade no tempo.

A sintaxe do método NewIntervalVar() é a seguinte :

NewIntervalVar(start_var, duration, end_var, name)

Descrição do parâmetro :

  • start_var: Variável inteira (IntVar) ou valor inteiro representando a hora de início da tarefa. Geralmente uma variável inteira, mas também pode ser um valor inteiro fixo representando a hora de início da tarefa.
  • duração: Um valor inteiro ou variável inteira representando a duração da tarefa. A duração da tarefa pode ser um valor inteiro fixo ou uma variável inteira cujo valor é determinado no tempo de execução.
  • end_var: Variável inteira (IntVar) ou valor inteiro representando a hora de término da tarefa. Geralmente uma variável inteira, mas também pode ser um valor inteiro fixo que representa o horário de término da tarefa.
  • name: O nome da variável de intervalo de tempo (opcional), usado para identificar e identificar a variável na saída.

A característica das variáveis ​​de intervalo de tempo é que elas permitem que os horários de início e término das tarefas sejam ajustados de forma flexível no tempo da solução para satisfazer determinadas restrições, como não sobreposição entre tarefas, duração das tarefas, etc.

Estudo de caso :

from ortools.sat.python import cp_model

def simple_interval_variable_example():
    # 创建约束编程模型
    model = cp_model.CpModel()

    # 创建整数变量表示任务的开始时间和结束时间
    start_var = model.NewIntVar(0, 100, 'start_var')
    end_var = model.NewIntVar(0, 100, 'end_var')

    # 创建整数变量表示任务的持续时间
    duration = model.NewIntVar(10, 50, 'duration')

    # 创建时间区间变量
    task_interval = model.NewIntervalVar(start_var, duration, end_var, 'task_interval')

    # 创建约束条件:任务的开始时间和结束时间之间存在关系
    model.Add(end_var == start_var + duration)

    # 创建求解器并求解模型
    solver = cp_model.CpSolver()
    status = solver.Solve(model)

    if status == cp_model.OPTIMAL:
        print('任务开始时间:', solver.Value(start_var))
        print('任务结束时间:', solver.Value(end_var))
        print('任务持续时间:', solver.Value(duration))
    else:
        print('找不到可行解。')

if __name__ == '__main__':
    simple_interval_variable_example()

saída :

Hora de início da tarefa: 0
Hora de término da tarefa: 10
Duração da tarefa: 10

Artigo de referência:
Site oficial do OR-Tools [
Explicação do texto longo de 10.000 palavras] As coleções da biblioteca Python permitem que você supere 99% do Pythoner .


Resumir

Quantas vezes você tem que experimentá-lo antes de entrar no mesmo rio repetidamente.

Acho que você gosta

Origin blog.csdn.net/weixin_42475060/article/details/131959306
Recomendado
Clasificación