def initialize(self): model = CpoModel() self.model = model instance = self.instance T = len(instance.intervals) first_on = instance.earliest_on_interval_idx last_on = instance.latest_on_interval_idx init_start_times = self.get_init_start_times(sort_starts=True) ub = min(instance.get_ub(), self.get_upper_bound(init_start_times)) total_proc = sum(j.processing_time for j in instance.jobs) # -------------------------------------- Construct the model. job_vars = dict() # Dict[Job, CpoIntervalVar] self.job_vars = job_vars gap_vars = dict() # Dict[Tuple[int, int], CpoIntervalVar] obj = 0 # objective value seq_vars = [] # variables for no-overlap constraint stp = model.create_empty_solution( ) # starting point of the solving procedure step_fns = CpGeneral.get_step_functions( instance, first_on, last_on) # Generate seqment function for each processing time. first_job_var = model.interval_var(length=0, optional=False, name="first_job_var", start=1) seq_vars.append( first_job_var) # dummy job with 0 length, first in the sequence last_job_var = model.interval_var(length=0, optional=False, name="last_job_var", end=T - 1) seq_vars.append( last_job_var) # dummy job with 0 length, last in the sequence # variables for jobs ------------------------------------------------------------------------------------------- for job in instance.jobs: var = model.interval_var(length=job.processing_time, optional=False, name="j[{:d}]".format(job.id)) model.add(cpmod.start_of(var) >= first_on) # earliest possible start time is first_on model.add(cpmod.end_of(var) <= last_on + 1) # latest possible end time is last_on job_vars[job] = var seq_vars.append(var) if self.specialized_solver_config[ "JobInObjectiveModelling"] == 0: # Optional : alternatives = [] for t in range(first_on, T): if t <= last_on - ( job.processing_time - 1 ): # (-1) because unit job can be processed in last_on var = model.interval_var(start=t, length=job.processing_time, optional=True, name="j[{:d},{:d}]".format( t, job.id)) alternatives.append(var) obj += cpmod.presence_of( var) * instance.cumulative_energy_cost[t][ t + job.processing_time - 1] * instance.on_power_consumption # add a present variable for the job model.add(cpmod.alternative(job_vars[job], alternatives)) elif self.specialized_solver_config[ "JobInObjectiveModelling"] == 1: # Logical : for t in range(T): if first_on <= t <= last_on - (job.processing_time - 1): obj += (cpmod.start_of(var, absentValue=-1) == t) * instance.cumulative_energy_cost[t][ t + job.processing_time - 1] * instance.on_power_consumption elif self.specialized_solver_config[ "JobInObjectiveModelling"] == 2: # Element : energy = [ instance.cumulative_energy_cost[t][t + job.processing_time - 1] * instance.on_power_consumption if (first_on <= t <= last_on - (job.processing_time - 1)) else 0 for t in range(T) ] obj += cpmod.element(energy, cpmod.start_of(var)) elif self.specialized_solver_config[ "JobInObjectiveModelling"] == 3: # Overlap : for t in range(T): # add overlaps to objective if first_on <= t <= last_on: obj += cpmod.overlap_length( var, (t, t + 1)) * instance.intervals[ t].energy_cost * instance.on_power_consumption elif self.specialized_solver_config[ "JobInObjectiveModelling"] == 4: # Step function : obj += cpmod.start_eval(var, step_fns[job.processing_time]) else: raise Exception( "Given JobInObjectiveModelling method {0} is not supported." .format( self. specialized_solver_config["JobInObjectiveModelling"])) # add variables for gaps --------------------------------------------------------------------------------------- if self.specialized_solver_config[ "GapsInObjectiveModelling"] == 0: # Fixed : for t_s in range(1, T): for t_e in range(t_s + 1, T): if instance.has_switching_cost(t_s, t_e): if instance.get_gap_lower_bound( t_s, t_e) > ub: # skip gaps that are too costly continue sw_cost = instance.optimal_switching_costs[t_s][t_e] var = model.interval_var(start=t_s, end=t_e, optional=True, name="gap[{:d},{:d}]".format( t_s, t_e)) gap_vars[t_s, t_e] = var seq_vars.append(var) obj += cpmod.presence_of( var ) * sw_cost # if the gap is present, add cost to objective elif self.specialized_solver_config[ "GapsInObjectiveModelling"] == 1: # Free : gaps_by_lengths = {i: [] for i in range(1, T - 1)} for gap_len in range(1, T - total_proc - 1): costs = [ instance.optimal_switching_costs[t][t + gap_len] if instance.optimal_switching_costs[t][t + gap_len] is not None else len(instance.intervals) * instance.on_power_consumption + 1 # TODO : max value for t in range(T) if t + gap_len < T ] costs[0] = 0 # TODO: for absent value for i in range(int(T / gap_len)): var = model.interval_var(optional=True, length=gap_len, name="gap[{:d},{:d}]".format( gap_len, i)) model.add(cpmod.start_of(var, absentValue=1) >= 1) model.add(cpmod.end_of(var) <= T - 1) obj += cpmod.element(costs, cpmod.start_of(var)) gaps_by_lengths[gap_len].append(var) seq_vars.append(var) # force order on the gaps for cur, nxt in zip(gaps_by_lengths[gap_len], gaps_by_lengths[gap_len][1:]): model.add(cpmod.presence_of(cur) >= cpmod.presence_of(nxt)) model.add(cpmod.end_before_start(cur, nxt)) elif self.specialized_solver_config[ "GapsInObjectiveModelling"] == 2: # No : # Gaps will be added to the objective after introducing the sequence variable pass else: raise Exception( "Given GapsInObjectiveModelling method {0} is not supported.". format(self. specialized_solver_config["GapsInObjectiveModelling"])) # add no overlap constraint ------------------------------------------------------------------------------------ seq = model.sequence_var(seq_vars, name="seq") model.add(cpmod.no_overlap(seq)) model.add(cpmod.first(seq, first_job_var)) model.add(cpmod.last(seq, last_job_var)) if self.specialized_solver_config[ "GapsInObjectiveModelling"] == 2: # No : gap_costs = ( [0 for _ in range(T)] + # gap_len == 0 [ instance.optimal_switching_costs[gap_s][gap_s + gap_len] if gap_s + gap_len <= T - 1 and instance.optimal_switching_costs[gap_s][gap_s + gap_len] is not None else INT_MAX for gap_len in range(1, T) for gap_s in range(T) ]) for job in instance.jobs: job_var = self.job_vars[job] obj += cpmod.element( gap_costs, (cpmod.start_of_next(seq, job_var, T - 1) - cpmod.end_of(job_var)) * T + cpmod.end_of(job_var)) obj += cpmod.element(gap_costs, cpmod.start_of_next(seq, first_job_var) * T) # constrain lengths to fill the whole horizon ------------------------------------------------------------------ if self.specialized_solver_config[ "GapsInObjectiveModelling"] != 2: # gaps are modelled if self.specialized_solver_config[ "FillAllModelling"] == 0: # SumLengths : model.add( sum([cpmod.length_of(var) for var in seq_vars]) == T - 2) elif self.specialized_solver_config[ "FillAllModelling"] == 1: # Pulse : cumul_func = 0 for var in seq_vars: if var is not first_job_var and var is not last_job_var: cumul_func += cpmod.pulse(var, 1) model.add(cpmod.always_in(cumul_func, (1, T - 1), 1, 1)) elif self.specialized_solver_config[ "FillAllModelling"] == 2: # StartOfNext : for var in seq_vars: if var is not last_job_var: model.add( cpmod.start_of_next( seq, var, lastValue=T, absentValue=0) == cpmod.end_of(var, absentValue=0)) else: raise Exception( "Given FillAllModelling method {0} is not supported.". format(self.specialized_solver_config["FillAllModelling"])) # set objective model.minimize(obj) # - init start times. # if init_start_times is not None: # for job in instance.jobs: # stp.add_interval_var_solution(job_vars[job], presence=True, start=init_start_times[job]) # # # gaps in the schedule # gaps = self.get_gaps_in_schedule(init_start_times) # # for g_s, g_e in gaps: # stp.add_interval_var_solution(gap_vars[g_s, g_e], presence=True, start=g_s) # # # set starting point # model.set_starting_point(stp) # force ordering of the jobs jobs_by_lengths = self.get_job_by_lengths() for lst in jobs_by_lengths.values(): for j_cur, j_nxt in zip(lst, lst[1:]): model.add( cpmod.end_before_start(job_vars[j_cur], job_vars[j_nxt]))
from docplex.cp.model import CpoModel mdl = CpoModel(name='buses') nbbus40 = mdl.integer_var(0,1000,name='nbBus40') nbbus30 = mdl.integer_var(0,1000,name='nbBus30') mdl.add(nbbus40*40 + nbbus30*30 >= 300) mdl.minimize(nbbus40*500 + nbbus30*400) sol=mdl.create_empty_solution() sol[nbbus40]=8 sol[nbbus30]=0 mdl.set_starting_point(sol) msol=mdl.solve() print(msol[nbbus40]," buses 40 seats") print(msol[nbbus30]," buses 30 seats") """ which gives ! ! Starting point is complete and consistent with constraints. * 4000 0 0.01s 1 (gap is 100.0%) 4000 0 2 1 - + New bound is 3800 (gap is 5.00%) * 3800 0 0.01s 1 (gap is 0.00%) ! ---------------------------------------------------------------------------- ! Search completed, 2 solutions found. ! Best objective : 3800 (optimal - effective tol. is 0)