Python Column Generation in Python mit Gurobi

Registriert
Feb. 2024
Beiträge
1
Hallo, ich möchte folgendes Scheduling in Python mit Gurobi mit Column Generation lösen.

Die Indizes sind i ∈ I Krankenschwestern, s ∈ S Schichten und t ∈ T Tage. Die Entscheidungsvariablen sind:
  • x_{its}= 1, wenn die Krankenschwester i die Schicht s am Tag t arbeitet
  • slack_{ts}: Slack für Schicht s am Tag t
  • motivation_{its}: für Krankenschwester i in Schicht s am Tag t
  • mood_{it} für Krankenschwester i am Tag t

Die Parameter / Inputs sind:
  • Demand_{ts}: Bedarf pro Schicht s am Tag t
  • α: Zufallszahl ∈[ 0,1]
  • M: Große Zahl
  • Max_W: Anzahl der maximal erlaubten aufeinanderfolgenden Arbeitstage

Nun zum Teil der Spaltengenerierung:
  • r: Index für Dienstpläne/Raster (eine machbare Zuordnung von Schichten zu einer Pflegekraft für den Planungshorizont)
  • R(i): Menge der alternativen Dienstpläne für Krankenschwester i
  • motivation^r_{its}: Motivation der Krankenschwester i wird der Schicht s im Plan r am Tag d zugewiesen.
Dies führt zu folgendem Hauptproblem (MP) und den einzelnen Teilproblemen (SP i): https://turquoise-demetria-16.tiiny.site/

Das ist mein Code bisher:

Code:
from gurobipy import *
import gurobipy as gu
import pandas as pd
import itertools

# Create DF out of Sets
I_list = [1,2,3]
T_list = [1,2,3,4,5,6,7]
K_list = [1,2,3]
I_list1 = pd.DataFrame(I_list, columns=['I'])
T_list1 = pd.DataFrame(T_list, columns=['T'])
K_list1 = pd.DataFrame(K_list, columns=['K'])
DataDF = pd.concat([I_list1, T_list1, K_list1], axis=1)
Demand_Dict = {(1, 1): 2, (1, 2): 1, (1, 3): 0, (2, 1): 1, (2, 2): 2, (2, 3): 0, (3, 1): 1, (3, 2): 1, (3, 3): 1,
          (4, 1): 1, (4, 2): 2, (4, 3): 0, (5, 1): 2, (5, 2): 0, (5, 3): 1, (6, 1): 1, (6, 2): 1, (6, 3): 1,
          (7, 1): 0, (7, 2): 3, (7, 3): 0}

class MasterProblem:
    def __init__(self, dfData, DemandDF):
        self.physicians = dfData['I'].dropna().astype(int).unique().tolist()
        self.days = dfData['T'].dropna().astype(int).unique().tolist()
        self.shifts = dfData['K'].dropna().astype(int).unique().tolist()
        self.demand = DemandDF
        self.model = gu.Model("MasterProblem")

    def buildModel(self):
        self.generateVaraibles()
        self.generateConstraints()
        self.generateObjective()
        self.setStartSolution()
        self.model.update()

    def generateVaraibles(self):
        self.slack = self.model.addVars(self.days, self.shifts, vtype=gu.GRB.CONTINUOUS, lb=0, name='slack')
        self.motivation_i = self.model.addVars(self.physicians, self.days, self.shifts, self.roster, vtype=gu.GRB.CONTINUOUS, name = 'motivation_i')
        self.lmbda = self.model.addVars(self.physicians, self.roster, vtype=gu.GRB.INTEGER, lb = 0, name = 'lmbda')
    def generateConstraints(self):
        self.cons_lmbda = {}
        for i in self.physicians:
            self.cons_lmbda[i] = self.model.addLConstr(gu.quicksum(self.lmbda[i, r] for r in self.roster) == 1)
        return self.cons_lmbda
        self.cons_demand = {}
        for t in self.days:
            for s in self.shifts:
                self.cons_demand[t,s] = self.model.addLConstr(gu.quicksum(self.motivation_i[i, t, s, r] for r in self.roster for i in self.physicians) + self.slack[t, s] >= self.demand[t, s])
        return self.cons_demand
    def generateObjective(self):
        self.model.setObjective(gu.quicksum(self.slack[t, s] for t in self.days for s in self.shifts), sense = gu.GRB.MINIMIZE)

    def solveRelaxModel(self):
        self.relaxedModel = self.model.relax()
        self.relaxedModel.optimize()

    def getDuals_i(self):
        Pi_cons_lmbda = self.model.getAttr("Pi", self.cons_lmbda)
        return Pi_cons_lmbda

    def getDuals_ts(self):
        Pi_cons_demand = self.model.getAttr("Pi", self.cons_demand)
        return Pi_cons_demand

    def addColumn(self, objective, newSchedule):
        ctName = ('ScheduleUseVar[%s]' %len(self.model.getVars()))
        newColumn = gu.column(newSchedule, self.model.getConstrs())
        self.model.addVar(vtype = gu.GBR.INTEGER, lb = 0, obj = objective, column = newColumn, name = ctName)
        self.model.update()

    def setStartSolution(self):
        startValues = {}
        for i, t, s in itertools.product(self.physicians, self.days, self.shifts):
            startValues[(i, t, s)] = 0
        for i, t, s in startValues:
            self.motivation_i[i, t, s].Start = startValues[i, t, s]
class Subproblem:
    def __init__(self, duals_i, duals_ts, dfData):
        self.days = dfData['T'].dropna().astype(int).unique().tolist()
        self.shifts = dfData['K'].dropna().astype(int).unique().tolist()
        self.duals_i = duals_i
        self.duals_ts = duals_ts
        self.Max = 5
        self.M = 100
        self.alpha = 0.5
        self.model = gu.Model("Subproblem")

    def buildModel(self):
        self.generateVariables()
        self.generateConstraints()
        self.generateObjective()
        self.model.update()

    def generateVariables(self):
        self.x = self.model.addVars(self.days, self.shifts, vtype=GRB.BINARY, name='x')
        self.mood = self.model.addVars(self.days, vtype=GRB.CONTINUOUS, lb = 0, ub = 1, name='mood')
        self.motivation = self.model.addVars(self.days, self.shifts, vtype=GRB.CONTINUOUS, lb = 0, ub = 1, name='motivation')

    def generateConstraints(self):
        for t in self.days:
            self.model.addLConstr(self.mood[t] == 1 - self.alpha * quicksum(self.x[t, s] for s in self.shifts))
        for t in self.days:
            self.model.addLConstr(gu.quicksum(self.x[t, s] for s in self.shifts) <= 1)
        for t in range(1, len(self.days) - self.Max + 2):
            self.model.addLConstr(gu.quicksum(self.x[i, u, s] for s in self.shifts for u in range(t, t + 1 + self.Max)) <= self.Max)
        for t in self.days:
            for s in self.shifts:
                self.model.addLConstr(self.mood[t] + self.M*(1-self.x[t, s]) >= self.motivation[t, s])
                self.model.addLConstr(self.motivation[t, s] >= self.mood[t] - self.M * (1 - self.x[t, s]))
                self.model.addLConstr(self.motivation[t, s] <= self.x[t, s])

    def generateObjective(self):
        self.model.setObjective(0-gu.quicksum(self.motivation[t,s]*self.duals_ts[t,s] for s in self.shifts for t in self.days)-self.duals_i[i], sense = gu.GRB.MINIMIZE)

    def getNewSchedule(self):
        return self.model.getAttr("X", self.model.getVars())


#class InitialScheduleGenerator:
#    def __init__(self, nbPhysicians, nbDays, nbShifts):
#        columns = ['Value', 'Physician', 'Days', 'Shifts']
#        patterns = pd.DataFrame(columns=columns)
#        self.patternDf = patterns
#        self.nbPhysicians = nbPhysicians
#        self.nbDays = nbDays
#        self.nbShifts = nbShifts

#    def generateBasicInitialSchedule(self):
#        self.patternDf['Value'] = 0
#        combinations = list(itertools.product(range(1, self.nbPhysicians + 1),
#                                              range(1, self.nbDays + 1),
#                                              range(1, self.nbShifts + 1)))

#        self.patternDf['Physician'], self.patternDf['Days'], self.patternDf['Shifts'] = zip(*combinations)
#        return self.patternDf


def solveModel(self, timeLimit, EPS):
    self.model.setParam('TimeLimit', timeLimit)
    self.model.setParam('MIPGap', EPS)
    self.model.optimize()

### CG Sequence
#Initizalie Basic Solution
scheduleGenerator = InitialScheduleGenerator(3,7, 3)
scheduleDf = scheduleGenerator.generateBasicInitialSchedule()

#Build MP
master = MasterProblem(DataDF, Demand_Dict)
master.buildModel()

modelImprovable = True

while (modelImprovable):
    master.solveRelaxModel()
    duals_i = master.getDuals_i()
    duals_ts = master.getDuals_ts()
    for i in I:
        subproblem = Subproblem(duals_i, duals_ts, DataDF)
        subproblem.buildModel()
        subproblem.solveModel(3600, 1e-6)
        modelImprovable = (subproblem.getObjectiveValue) < -1e-6
        newScheduleCost = 1
        newScheduleCuts = subproblem.getNewSchedule()
        master.addColumn(newScheduleCost, newScheduleCuts)

master.solveModel(3600, 0.01)


Leider habe ich noch ein paar Probleme. Es geht mir vor allem um die folgenden Dinge.

1) Wie führe ich den Index R richtig ein und wie modelliere ich die Interaktion mit λ und perf im MP?

2) Ist die Berechnung der reduzierten Kosten korrekt?

3) Wie füge ich neue Spalten (in diesem Fall Dienstpläne) in das Masterproblem ein?

Ich wäre wirklich dankbar für jede Hilfe!
 

Anhänge

  • model.pdf
    83,6 KB · Aufrufe: 35
Klatsch Deinen Beitrag einfach mal in Chat GPT rein. Die Code einfach in Anführungszeichen setzen.
 
Zurück
Oben