From 2efff7fbef42329e88d970e7b03c10996b15854a Mon Sep 17 00:00:00 2001 From: Viu-Long Kong Date: Wed, 2 Jun 2021 14:57:09 +0200 Subject: [PATCH] sync with release --- examples/cp/visu/flow_shop.py | 43 +- examples/cp/visu/flow_shop_permutation.py | 40 +- examples/cp/visu/house_building_basic.py | 64 +- examples/cp/visu/house_building_calendar.py | 169 ++--- examples/cp/visu/house_building_cumul.py | 143 ++-- examples/cp/visu/house_building_optional.py | 138 ++-- examples/cp/visu/house_building_state.py | 134 ++-- examples/cp/visu/house_building_time.py | 146 ++-- examples/cp/visu/job_shop_basic.py | 40 +- examples/cp/visu/job_shop_flexible.py | 83 +-- examples/cp/visu/job_shop_stochastic.py | 64 +- examples/cp/visu/open_shop.py | 38 +- .../plant_location_with_solver_listener.py | 33 +- examples/cp/visu/rcpsp.py | 44 +- examples/cp/visu/rcpsp_multi_mode.py | 66 +- examples/cp/visu/rcpsp_multi_mode_json.py | 75 +- examples/cp/visu/setup_costs.py | 46 +- examples/cp/visu/setup_times.py | 39 +- examples/cp/visu/squaring_square.py | 36 +- examples/mp/jupyter/boxes.ipynb | 70 +- examples/mp/jupyter/green_truck.ipynb | 36 +- .../mp/jupyter/incremental_modeling.ipynb | 69 +- examples/mp/jupyter/infeasible.ipynb | 14 +- examples/mp/jupyter/load_balancing.ipynb | 211 +++++- examples/mp/jupyter/marketing_campaign.ipynb | 667 ++++++++++++------ .../mp/jupyter/sktrans/transformers.ipynb | 4 +- 26 files changed, 1377 insertions(+), 1135 deletions(-) diff --git a/examples/cp/visu/flow_shop.py b/examples/cp/visu/flow_shop.py index 0c92359..4095dfd 100644 --- a/examples/cp/visu/flow_shop.py +++ b/examples/cp/visu/flow_shop.py @@ -15,11 +15,9 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os - #----------------------------------------------------------------------------- # Initialize the problem data #----------------------------------------------------------------------------- @@ -30,11 +28,10 @@ # The rest of the file consists of one line per job that contains the list of # operations given as durations for each machines. -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/flowshop_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/flowshop_default.data' +with open(filename, 'r') as file: NB_JOBS, NB_MACHINES = [int(v) for v in file.readline().split()] - JOB_DURATIONS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] - + OP_DURATIONS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] #----------------------------------------------------------------------------- # Build the model @@ -44,40 +41,40 @@ mdl = CpoModel() # Create one interval variable per job operation -job_operations = [[mdl.interval_var(size=JOB_DURATIONS[j][m], name="J{}-M{}".format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] +operations = [[interval_var(size=OP_DURATIONS[j][m], name='J{}-M{}'.format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] # Force each operation to start after the end of the previous for j in range(NB_JOBS): - for m in range(1, NB_MACHINES): - mdl.add(mdl.end_before_start(job_operations[j][m - 1], job_operations[j][m])) + for m in range(1,NB_MACHINES): + mdl.add(end_before_start(operations[j][m-1], operations[j][m])) # Force no overlap for operations executed on a same machine for m in range(NB_MACHINES): - mdl.add(mdl.no_overlap([job_operations[j][m] for j in range(NB_JOBS)])) + mdl.add(no_overlap(operations[j][m] for j in range(NB_JOBS))) # Minimize termination date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(job_operations[i][NB_MACHINES - 1]) for i in range(NB_JOBS)]))) - +mdl.add(minimize(max(end_of(operations[i][NB_MACHINES-1]) for i in range(NB_JOBS)))) #----------------------------------------------------------------------------- # Solve the model and display the result #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=10000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=10,LogPeriod=1000000) +print('Solution:') +res.print_solution() # Display solution -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for flow-shop " + filename) - visu.panel("Jobs") +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution for flow-shop ' + filename) + visu.panel('Jobs') for i in range(NB_JOBS): visu.sequence(name='J' + str(i), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) - visu.panel("Machines") + intervals=[(res.get_var_solution(operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) + visu.panel('Machines') for j in range(NB_MACHINES): visu.sequence(name='M' + str(j), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) + intervals=[(res.get_var_solution(operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) visu.show() diff --git a/examples/cp/visu/flow_shop_permutation.py b/examples/cp/visu/flow_shop_permutation.py index 21e903b..f5bbbae 100644 --- a/examples/cp/visu/flow_shop_permutation.py +++ b/examples/cp/visu/flow_shop_permutation.py @@ -16,8 +16,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os @@ -30,10 +29,10 @@ # First line contains the number of jobs, and the number of machines. # The rest of the file consists of one line per job that contains the list of # operations given as durations for each machines. -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/flowshop_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/flowshop_default.data' +with open(filename, 'r') as file: NB_JOBS, NB_MACHINES = [int(v) for v in file.readline().split()] - JOB_DURATIONS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] + OP_DURATIONS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] #----------------------------------------------------------------------------- @@ -44,26 +43,26 @@ mdl = CpoModel() # Create one interval variable per job operation -job_operations = [[mdl.interval_var(size=JOB_DURATIONS[j][m], name="J{}-M{}".format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] +operations = [[interval_var(size=OP_DURATIONS[j][m], name='J{}-M{}'.format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] # Create sequence of operation for each machine -op_sequences = [mdl.sequence_var([job_operations[i][j] for i in range(NB_JOBS)], name="M{}".format(j)) for j in range(NB_MACHINES)] +op_sequences = [sequence_var([operations[i][j] for i in range(NB_JOBS)], name='M{}'.format(j)) for j in range(NB_MACHINES)] # Force each operation to start after the end of the previous for j in range(NB_JOBS): for m in range(1, NB_MACHINES): - mdl.add(mdl.end_before_start(job_operations[j][m - 1], job_operations[j][m])) + mdl.add(end_before_start(operations[j][m-1], operations[j][m])) # Force no overlap for operations executed on a same machine for m in range(NB_MACHINES): - mdl.add(mdl.no_overlap(op_sequences[m])) + mdl.add(no_overlap(op_sequences[m])) # Force sequences to be all identical on all machines for m in range(1, NB_MACHINES): - mdl.add(mdl.same_sequence(op_sequences[0], op_sequences[m])) + mdl.add(same_sequence(op_sequences[0], op_sequences[m])) # Minimize termination date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(job_operations[i][NB_MACHINES - 1]) for i in range(NB_JOBS)]))) +mdl.add(minimize(max([end_of(operations[i][NB_MACHINES-1]) for i in range(NB_JOBS)]))) #----------------------------------------------------------------------------- @@ -71,20 +70,19 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=10000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=10000, TimeLimit=10) # Draw solution -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for permutation flow-shop " + filename) - visu.panel("Jobs") +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution for permutation flow-shop ' + filename) + visu.panel('Jobs') for i in range(NB_JOBS): visu.sequence(name='J' + str(i), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) - visu.panel("Machines") + intervals=[(res.get_var_solution(operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) + visu.panel('Machines') for j in range(NB_MACHINES): visu.sequence(name='M' + str(j), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) + intervals=[(res.get_var_solution(operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) visu.show() diff --git a/examples/cp/visu/house_building_basic.py b/examples/cp/visu/house_building_basic.py index ef02233..5d8726b 100644 --- a/examples/cp/visu/house_building_basic.py +++ b/examples/cp/visu/house_building_basic.py @@ -13,8 +13,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * #----------------------------------------------------------------------------- @@ -24,32 +23,32 @@ # Create model mdl = CpoModel() -masonry = mdl.interval_var(name='masonry', size=35) -carpentry = mdl.interval_var(name='carpentry', size=15) -plumbing = mdl.interval_var(name='plumbing', size=40) -ceiling = mdl.interval_var(name='ceiling', size=15) -roofing = mdl.interval_var(name='roofing', size=5) -painting = mdl.interval_var(name='painting', size=10) -windows = mdl.interval_var(name='windows', size=5) -facade = mdl.interval_var(name='facade', size=10) -garden = mdl.interval_var(name='garden', size=5) -moving = mdl.interval_var(name='moving', size=5) +masonry = interval_var(name='masonry', size=35) +carpentry = interval_var(name='carpentry', size=15) +plumbing = interval_var(name='plumbing', size=40) +ceiling = interval_var(name='ceiling', size=15) +roofing = interval_var(name='roofing', size=5) +painting = interval_var(name='painting', size=10) +windows = interval_var(name='windows', size=5) +facade = interval_var(name='facade', size=10) +garden = interval_var(name='garden', size=5) +moving = interval_var(name='moving', size=5) # Add precedence constraints -mdl.add(mdl.end_before_start(masonry, carpentry)) -mdl.add(mdl.end_before_start(masonry, plumbing)) -mdl.add(mdl.end_before_start(masonry, ceiling)) -mdl.add(mdl.end_before_start(carpentry, roofing)) -mdl.add(mdl.end_before_start(ceiling, painting)) -mdl.add(mdl.end_before_start(roofing, windows)) -mdl.add(mdl.end_before_start(roofing, facade)) -mdl.add(mdl.end_before_start(plumbing, facade)) -mdl.add(mdl.end_before_start(roofing, garden)) -mdl.add(mdl.end_before_start(plumbing, garden)) -mdl.add(mdl.end_before_start(windows, moving)) -mdl.add(mdl.end_before_start(facade, moving)) -mdl.add(mdl.end_before_start(garden, moving)) -mdl.add(mdl.end_before_start(painting, moving)) +mdl.add(end_before_start(masonry, carpentry)) +mdl.add(end_before_start(masonry, plumbing)) +mdl.add(end_before_start(masonry, ceiling)) +mdl.add(end_before_start(carpentry, roofing)) +mdl.add(end_before_start(ceiling, painting)) +mdl.add(end_before_start(roofing, windows)) +mdl.add(end_before_start(roofing, facade)) +mdl.add(end_before_start(plumbing, facade)) +mdl.add(end_before_start(roofing, garden)) +mdl.add(end_before_start(plumbing, garden)) +mdl.add(end_before_start(windows, moving)) +mdl.add(end_before_start(facade, moving)) +mdl.add(end_before_start(garden, moving)) +mdl.add(end_before_start(painting, moving)) #----------------------------------------------------------------------------- @@ -57,11 +56,12 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=10) +print('Solution:') +res.print_solution() # Draw solution -if msol and visu.is_visu_enabled(): - visu.show(msol) +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.show(res) diff --git a/examples/cp/visu/house_building_calendar.py b/examples/cp/visu/house_building_calendar.py index f4ca786..c18e951 100644 --- a/examples/cp/visu/house_building_calendar.py +++ b/examples/cp/visu/house_building_calendar.py @@ -17,52 +17,49 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction -import docplex.cp.utils_visu as visu - +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data #----------------------------------------------------------------------------- -# House building task descriptor -class BuildingTask(object): - def __init__(self, name, duration): - self.name = name - self.duration = duration +# List of available workers together with their holidays as list of tuples (start_day, end_day) +WORKERS = { + 'Joe' : [ (5, 12), (124, 131), (215, 236), (369, 376), (495, 502), (579, 600) ], + 'Jim' : [ (26, 40), (201, 225), (306, 313), (397, 411), (565, 579) ] +} # List of tasks to be executed for each house -MASONRY = BuildingTask('masonry', 35) -CARPENTRY = BuildingTask('carpentry', 15) -PLUMBING = BuildingTask('plumbing', 40) -CEILING = BuildingTask('ceiling', 15) -ROOFING = BuildingTask('roofing', 5) -PAINTING = BuildingTask('painting', 10) -WINDOWS = BuildingTask('windows', 5) -FACADE = BuildingTask('facade', 10) -GARDEN = BuildingTask('garden', 5) -MOVING = BuildingTask('moving', 5) +TASKS = { + 'masonry' : (35 , 'Joe', 1), + 'carpentry' : (15 , 'Joe', 2), + 'plumbing' : (40 , 'Jim', 3), + 'ceiling' : (15 , 'Jim', 4), + 'roofing' : ( 5 , 'Joe', 5), + 'painting' : (10 , 'Jim', 6), + 'windows' : ( 5 , 'Jim', 7), + 'facade' : (10 , 'Joe', 8), + 'garden' : ( 5 , 'Joe', 9), + 'moving' : ( 5 , 'Jim', 10) +} # Tasks precedence constraints (each tuple (X, Y) means X ends before start of Y) -PRECEDENCES = ( (MASONRY, CARPENTRY), - (MASONRY, PLUMBING), - (MASONRY, CEILING), - (CARPENTRY, ROOFING), - (CEILING, PAINTING), - (ROOFING, WINDOWS), - (ROOFING, FACADE), - (PLUMBING, FACADE), - (ROOFING, GARDEN), - (PLUMBING, GARDEN), - (WINDOWS, MOVING), - (FACADE, MOVING), - (GARDEN, MOVING), - (PAINTING, MOVING), - ) - -# Tasks assigned to Joe and Jim -JOE_TASKS = (MASONRY, CARPENTRY, ROOFING, FACADE, GARDEN) -JIM_TASKS = (PLUMBING, CEILING, PAINTING, WINDOWS, MOVING) +PRECEDENCES = [ + ('masonry', 'carpentry'), + ('masonry', 'plumbing'), + ('masonry', 'ceiling'), + ('carpentry', 'roofing'), + ('ceiling', 'painting'), + ('roofing', 'windows'), + ('roofing', 'facade'), + ('plumbing', 'facade'), + ('roofing', 'garden'), + ('plumbing', 'garden'), + ('windows', 'moving'), + ('facade', 'moving'), + ('garden', 'moving'), + ('painting', 'moving'), +] # Total number of houses to build NUMBER_OF_HOUSES = 5 @@ -70,37 +67,21 @@ def __init__(self, name, duration): # Max number of calendar years MAX_YEARS = 2 -# Holydays for Joe and Jim as list of tuples (start_day_index, end_day_index) -JOE_HOLYDAYS = ((5, 12), (124, 131), (215, 236), (369, 376), (495, 502), (579, 600)) -JIM_HOLYDAYS = ((26, 40), (201, 225), (306, 313), (397, 411), (565, 579)) - - #----------------------------------------------------------------------------- # Prepare the data for modeling #----------------------------------------------------------------------------- -# Assign an index to tasks -ALL_TASKS = (MASONRY, CARPENTRY, PLUMBING, CEILING, ROOFING, PAINTING, WINDOWS, FACADE, GARDEN, MOVING) -for i in range(len(ALL_TASKS)): - ALL_TASKS[i].id = i - # Initialize availability calendar for workers -joe_calendar = CpoStepFunction() -jim_calendar = CpoStepFunction() -joe_calendar.set_value(0, MAX_YEARS * 365, 100) -jim_calendar.set_value(0, MAX_YEARS * 365, 100) - -# Remove week ends -for w in range(MAX_YEARS * 52): - joe_calendar.set_value(5 + (7 * w), 7 + (7 * w), 0) - jim_calendar.set_value(5 + (7 * w), 7 + (7 * w), 0) - -# Remove holidays -for b, e in JOE_HOLYDAYS: - joe_calendar.set_value(b, e, 0) -for b, e in JIM_HOLYDAYS: - jim_calendar.set_value(b, e, 0) +calendars = { w : CpoStepFunction() for w in WORKERS } +for w in WORKERS: + calendars[w].set_value(0, MAX_YEARS * 365, 100) + # Remove week ends + for i in range(MAX_YEARS * 52): + calendars[w].set_value(5 + (7 * i), 7 + (7 * i), 0) + # Remove holidays + for s,e in WORKERS[w]: + calendars[w].set_value(s, e, 0) #----------------------------------------------------------------------------- # Build the model @@ -110,11 +91,8 @@ def __init__(self, name, duration): mdl = CpoModel() # Initialize model variable sets -all_tasks = [] # Array of all tasks -joe_tasks = [] # Tasks assigned to Joe -jim_tasks = [] # Tasks assigned to Jim +worker_tasks = { w : [] for w in WORKERS} # Tasks assigned to workers (key is the worker) house_finish_times = [] # Array of house finishing times -type = dict() # Utility function def make_house(loc): @@ -123,30 +101,20 @@ def make_house(loc): ''' # Create interval variable for each task for this house - tasks = [mdl.interval_var(size=t.duration, name="H" + str(loc) + "-" + t.name) for t in ALL_TASKS] - for t in ALL_TASKS: - type[tasks[t.id]] = t.id - for t in JOE_TASKS: - tasks[t.id].set_intensity(joe_calendar) - mdl.add(mdl.forbid_start(tasks[t.id], joe_calendar)) - mdl.add(mdl.forbid_end(tasks[t.id], joe_calendar)) - for t in JIM_TASKS: - tasks[t.id].set_intensity(jim_calendar) - mdl.add(mdl.forbid_start(tasks[t.id], jim_calendar)) - mdl.add(mdl.forbid_end(tasks[t.id], jim_calendar)) + tasks = { t: interval_var(size=TASKS[t][0], intensity=calendars[TASKS[t][1]], name='H{}-{}'.format(loc,t)) for t in TASKS } + for t in TASKS: + mdl.forbid_start(tasks[t], calendars[TASKS[t][1]]) + mdl.forbid_end (tasks[t], calendars[TASKS[t][1]]) # Add precedence constraints - for p, s in PRECEDENCES: - mdl.add(mdl.end_before_start(tasks[p.id], tasks[s.id])) + mdl.add(end_before_start(tasks[p], tasks[s]) for p,s in PRECEDENCES) # Allocate tasks to workers - for t in JOE_TASKS: - joe_tasks.append(tasks[t.id]) - for t in JIM_TASKS: - jim_tasks.append(tasks[t.id]) + for t in TASKS: + worker_tasks[TASKS[t][1]].append(tasks[t]) # Update cost - house_finish_times.append(mdl.end_of(tasks[MOVING.id])) + house_finish_times.append(end_of(tasks['moving'])) # Make houses @@ -154,11 +122,10 @@ def make_house(loc): make_house(i) # Avoid each worker tasks overlapping -mdl.add(mdl.no_overlap(joe_tasks)) -mdl.add(mdl.no_overlap(jim_tasks)) +mdl.add(no_overlap(worker_tasks[w]) for w in WORKERS) # Add minimization objective -mdl.add(mdl.minimize(mdl.max(house_finish_times))) +mdl.add(minimize(max(house_finish_times))) #----------------------------------------------------------------------------- @@ -169,23 +136,23 @@ def compact(name): # Example: H3-garden -> G3 # ^ ^ loc, task = name[1:].split('-', 1) - return task[0].upper() + loc + # Returns color index and compacted name + return int(TASKS[task][2]), (task[0].upper() + loc) # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=10) +print('Solution:') +res.print_solution() # Display result -if msol and visu.is_visu_enabled(): - visu.timeline('Solution SchedCalendar') - visu.panel() - visu.pause(joe_calendar) - visu.sequence(name='Joe', - intervals=[(msol.get_var_solution(t), type[t], compact(t.name)) for t in joe_tasks]) +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution house building with calendars') visu.panel() - visu.pause(jim_calendar) - visu.sequence(name='Jim', - intervals=[(msol.get_var_solution(t), type[t], compact(t.name)) for t in jim_tasks]) + for w in WORKERS: + visu.pause(calendars[w]) + visu.sequence(name=w) + for t in worker_tasks[w]: + visu.interval(res.get_var_solution(t), *compact(t.get_name())) visu.show() diff --git a/examples/cp/visu/house_building_cumul.py b/examples/cp/visu/house_building_cumul.py index 6c15818..c0141e5 100644 --- a/examples/cp/visu/house_building_cumul.py +++ b/examples/cp/visu/house_building_cumul.py @@ -19,71 +19,55 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction, INTERVAL_MAX, INT_MAX -import docplex.cp.utils_visu as visu - +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data #----------------------------------------------------------------------------- -# House building task descriptor -class BuildingTask(object): - def __init__(self, name, duration): - self.name = name - self.duration = duration - # List of tasks to be executed for each house -MASONRY = BuildingTask('masonry', 35) -CARPENTRY = BuildingTask('carpentry', 15) -PLUMBING = BuildingTask('plumbing', 40) -CEILING = BuildingTask('ceiling', 15) -ROOFING = BuildingTask('roofing', 5) -PAINTING = BuildingTask('painting', 10) -WINDOWS = BuildingTask('windows', 5) -FACADE = BuildingTask('facade', 10) -GARDEN = BuildingTask('garden', 5) -MOVING = BuildingTask('moving', 5) +TASKS = { + 'masonry' : 35, + 'carpentry' : 15, + 'plumbing' : 40, + 'ceiling' : 15, + 'roofing' : 5, + 'painting' : 10, + 'windows' : 5, + 'facade' : 10, + 'garden' : 5, + 'moving' : 5 +} # Tasks precedence constraints (each tuple (X, Y) means X ends before start of Y) -PRECEDENCES = ( (MASONRY, CARPENTRY), - (MASONRY, PLUMBING), - (MASONRY, CEILING), - (CARPENTRY, ROOFING), - (CEILING, PAINTING), - (ROOFING, WINDOWS), - (ROOFING, FACADE), - (PLUMBING, FACADE), - (ROOFING, GARDEN), - (PLUMBING, GARDEN), - (WINDOWS, MOVING), - (FACADE, MOVING), - (GARDEN, MOVING), - (PAINTING, MOVING), - ) +PRECEDENCES = [ + ('masonry', 'carpentry'), + ('masonry', 'plumbing'), + ('masonry', 'ceiling'), + ('carpentry', 'roofing'), + ('ceiling', 'painting'), + ('roofing', 'windows'), + ('roofing', 'facade'), + ('plumbing', 'facade'), + ('roofing', 'garden'), + ('plumbing', 'garden'), + ('windows', 'moving'), + ('facade', 'moving'), + ('garden', 'moving'), + ('painting', 'moving'), +] # Number of workers NB_WORKERS = 3 # List of houses to build. Value is the minimum start date -HOUSES = (31, 0, 90, 120, 90) +HOUSES = [ 31, 0, 90, 120, 90 ] # Cash parameters NB_PAYMENTS = 5 PAYMENT_AMOUNT = 30000 PAYMENT_INTERVAL = 60 - -#----------------------------------------------------------------------------- -# Prepare the data for modeling -#----------------------------------------------------------------------------- - -# Assign an index to tasks -ALL_TASKS = (MASONRY, CARPENTRY, PLUMBING, CEILING, ROOFING, PAINTING, WINDOWS, FACADE, GARDEN, MOVING) -for i in range(len(ALL_TASKS)): - ALL_TASKS[i].id = i - - #----------------------------------------------------------------------------- # Build the model #----------------------------------------------------------------------------- @@ -91,44 +75,33 @@ def __init__(self, name, duration): # Create model mdl = CpoModel() -# Initialize model variable sets -all_tasks = [] # Array of all tasks -desc = dict() # Dictionary task interval var -> task descriptor -house = dict() # Dictionary task interval var -> id of the corresponding house -workers_usage = mdl.step_at(0, 0) # Total worker usage +# Initialize cumul function +workers_usage = 0 # Total worker usage +cash = sum(step_at(PAYMENT_INTERVAL * p, PAYMENT_AMOUNT) for p in range(NB_PAYMENTS)) -# Initialize cash function -cash = mdl.step_at(0, 0) -for p in range(NB_PAYMENTS): - cash += mdl.step_at(PAYMENT_INTERVAL * p, PAYMENT_AMOUNT) +all_tasks = [] # Array of all tasks # Utility function def make_house(loc, rd): ''' Create model elements corresponding to the building of one house loc: Identification (index) of the house to build - rd: Min start date + rd: Minimal start date ''' # Create interval variable for each task for this house - tasks = [mdl.interval_var(size=t.duration, - start=(rd, INTERVAL_MAX), - name="H{}-{}".format(loc, t.name)) for t in ALL_TASKS] - all_tasks.extend(tasks) + tasks = { t: interval_var(size=TASKS[t], name='H{}-{}'.format(loc,t)) for t in TASKS } # Add precedence constraints - for p, s in PRECEDENCES: - mdl.add(mdl.end_before_start(tasks[p.id], tasks[s.id])) - + mdl.add(end_before_start(tasks[p], tasks[s]) for p,s in PRECEDENCES) + # Update cumul functions global workers_usage global cash + workers_usage += sum(pulse(tasks[t], 1) for t in TASKS) + cash -= sum(step_at_start(tasks[t], 200 * TASKS[t]) for t in TASKS) - # Allocate tasks to workers - for t in ALL_TASKS: - desc[tasks[t.id]] = t - house[tasks[t.id]] = loc - workers_usage += mdl.pulse(tasks[t.id], 1) - cash -= mdl.step_at_start(tasks[t.id], 200 * t.duration) + # Update cost + all_tasks.extend(tasks.values()) # Make houses @@ -142,7 +115,7 @@ def make_house(loc, rd): mdl.add(cash >= 0) # Minimize overall completion date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(task) for task in all_tasks]))) +mdl.add(minimize(max(end_of(task) for task in all_tasks))) #----------------------------------------------------------------------------- @@ -153,31 +126,31 @@ def compact(name): # Example: H3-garden -> G3 # ^ ^ loc, task = name[1:].split('-', 1) - return task[0].upper() + loc + return int(loc), task[0].upper() + loc # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=10000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=10000,TimeLimit=10) +print('Solution:') +res.print_solution() # Display result -if msol and visu.is_visu_enabled(): +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): workersF = CpoStepFunction() cashF = CpoStepFunction() - for p in range(5): - cashF.add_value(60 * p, INT_MAX, 30000) + for p in range(NB_PAYMENTS): + cashF.add_value(PAYMENT_INTERVAL * p, INT_MAX, PAYMENT_AMOUNT) for task in all_tasks: - itv = msol.get_var_solution(task) + itv = res.get_var_solution(task) workersF.add_value(itv.get_start(), itv.get_end(), 1) - cashF.add_value(itv.start, INT_MAX, -200 * desc[task].duration) - + cashF.add_value(itv.start, INT_MAX, -200 * itv.get_length()) visu.timeline('Solution SchedCumul') - visu.panel(name="Schedule") + visu.panel(name='Schedule') for task in all_tasks: - visu.interval(msol.get_var_solution(task), house[task], compact(task.get_name())) - visu.panel(name="Workers") + visu.interval(res.get_var_solution(task), *compact(task.get_name())) + visu.panel(name='Workers') visu.function(segments=workersF, style='area') - visu.panel(name="Cash") + visu.panel(name='Cash') visu.function(segments=cashF, style='area', color='gold') visu.show() diff --git a/examples/cp/visu/house_building_optional.py b/examples/cp/visu/house_building_optional.py index b55df6a..c36287a 100644 --- a/examples/cp/visu/house_building_optional.py +++ b/examples/cp/visu/house_building_optional.py @@ -20,9 +20,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, INTERVAL_MIN -import docplex.cp.utils_visu as visu - +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data @@ -30,54 +28,40 @@ NB_HOUSES = 5 DEADLINE = 318 -WORKER_NAMES = ['Joe', 'Jack', 'Jim'] -NB_WORKERS = len(WORKER_NAMES) - -# House building task descriptor -class BuildingTask(object): - def __init__(self, name, duration, skills): - self.name = name - self.duration = duration # Task duration - self.skills = skills # Skills of each worker for this task +WORKERS = ['Joe', 'Jack', 'Jim'] +NB_WORKERS = len(WORKERS) # List of tasks to be executed for each house -MASONRY = BuildingTask('masonry', 35, [9, 5, 0]) -CARPENTRY = BuildingTask('carpentry', 15, [7, 0, 5]) -PLUMBING = BuildingTask('plumbing', 40, [0, 7, 0]) -CEILING = BuildingTask('ceiling', 15, [5, 8, 0]) -ROOFING = BuildingTask('roofing', 5, [6, 7, 0]) -PAINTING = BuildingTask('painting', 10, [0, 9, 6]) -WINDOWS = BuildingTask('windows', 5, [8, 0, 5]) -FACADE = BuildingTask('facade', 10, [5, 5, 0]) -GARDEN = BuildingTask('garden', 5, [5, 5, 9]) -MOVING = BuildingTask('moving', 5, [6, 0, 8]) +TASKS = { + 'masonry' : (35, [9, 5, 0], 1), + 'carpentry' : (15, [7, 0, 5], 2), + 'plumbing' : (40, [0, 7, 0], 3), + 'ceiling' : (15, [5, 8, 0], 4), + 'roofing' : ( 5, [6, 7, 0], 5), + 'painting' : (10, [0, 9, 6], 6), + 'windows' : ( 5, [8, 0, 5], 7), + 'facade' : (10, [5, 5, 0], 8), + 'garden' : ( 5, [5, 5, 9], 9), + 'moving' : ( 5, [6, 0, 8], 10) +} # Tasks precedence constraints (each tuple (X, Y) means X ends before start of Y) -PRECEDENCES = ( (MASONRY, CARPENTRY), - (MASONRY, PLUMBING), - (MASONRY, CEILING), - (CARPENTRY, ROOFING), - (CEILING, PAINTING), - (ROOFING, WINDOWS), - (ROOFING, FACADE), - (PLUMBING, FACADE), - (ROOFING, GARDEN), - (PLUMBING, GARDEN), - (WINDOWS, MOVING), - (FACADE, MOVING), - (GARDEN, MOVING), - (PAINTING, MOVING), - ) - -#----------------------------------------------------------------------------- -# Prepare the data for modeling -#----------------------------------------------------------------------------- - -# Assign an index to tasks -ALL_TASKS = (MASONRY, CARPENTRY, PLUMBING, CEILING, ROOFING, PAINTING, WINDOWS, FACADE, GARDEN, MOVING) -for i in range(len(ALL_TASKS)): - ALL_TASKS[i].id = i - +PRECEDENCES = [ + ('masonry', 'carpentry'), + ('masonry', 'plumbing'), + ('masonry', 'ceiling'), + ('carpentry', 'roofing'), + ('ceiling', 'painting'), + ('roofing', 'windows'), + ('roofing', 'facade'), + ('plumbing', 'facade'), + ('roofing', 'garden'), + ('plumbing', 'garden'), + ('windows', 'moving'), + ('facade', 'moving'), + ('garden', 'moving'), + ('painting', 'moving'), +] #----------------------------------------------------------------------------- # Build the model @@ -89,7 +73,6 @@ def __init__(self, name, duration, skills): # Initialize model variable sets total_skill = 0 # Expression computing total of skills worker_tasks = [[] for w in range(NB_WORKERS)] # Tasks (interval variables) assigned to a each worker -desc = dict() # Map retrieving task from interval variable # Utility function def make_house(loc, deadline): @@ -99,27 +82,19 @@ def make_house(loc, deadline): ''' # Create interval variable for each task for this house - tasks = [mdl.interval_var(size=t.duration, - end=(INTERVAL_MIN, deadline), - name='H' + str(loc) + '-' + t.name) for t in ALL_TASKS] + tasks = {t: interval_var(size=TASKS[t][0], end=(0, deadline), name='H{}-{}'.format(loc,t)) for t in TASKS} # Add precedence constraints - for p, s in PRECEDENCES: - mdl.add(mdl.end_before_start(tasks[p.id], tasks[s.id])) + mdl.add(end_before_start(tasks[p], tasks[s]) for p,s in PRECEDENCES) # Allocate tasks to workers global total_skill - for t in ALL_TASKS: - allocs = [] - for w in range(NB_WORKERS): - if t.skills[w] > 0: - wt = mdl.interval_var(optional=True, name="H{}-{}({})".format(loc, t.name, WORKER_NAMES[w])) - worker_tasks[w].append(wt) - allocs.append(wt) - total_skill += (t.skills[w] * mdl.presence_of(wt)) - desc[wt] = t - mdl.add(mdl.alternative(tasks[t.id], allocs)) - + allocs = { (t,w) : interval_var(optional=True, name='H{}-{}-{}'.format(loc, t, w)) for t in TASKS for w in range(NB_WORKERS) if TASKS[t][1][w] > 0 } + total_skill += sum((TASKS[t][1][w] * presence_of(allocs[t,w])) for t,w in allocs) + for t in TASKS: + mdl.add(alternative(tasks[t], [allocs[t2,w] for t2,w in allocs if t==t2])) + for t,w in allocs: + worker_tasks[w].append(allocs[t,w]) # Make houses for h in range(NB_HOUSES): @@ -127,10 +102,10 @@ def make_house(loc, deadline): # Avoid overlapping between tasks of each worker for w in range(NB_WORKERS): - mdl.add(mdl.no_overlap(worker_tasks[w])) + mdl.add(no_overlap(worker_tasks[w])) # Maximize total of skills -mdl.add(mdl.maximize(total_skill)) +mdl.add(maximize(total_skill)) #----------------------------------------------------------------------------- @@ -140,28 +115,25 @@ def make_house(loc, deadline): def compact(name): # Example: H3-garden -> G3 # ^ ^ - loc, task = name[1:].split('-', 1) - return task[0].upper() + loc + loc, task, worker = name[1:].split('-', 2) + # Returns color index and compacted name + return int(TASKS[task][2]), task[0].upper() + loc # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=10000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=10000, TimeLimit=10) + +print('Solution:') +res.print_solution() # Draw solution -if msol and visu.is_visu_enabled(): - visu.timeline('Solution SchedOptional', 0, DEADLINE) +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution house building', 0, DEADLINE) for w in range(NB_WORKERS): - visu.sequence(name=WORKER_NAMES[w]) + visu.sequence(name=WORKERS[w]) for t in worker_tasks[w]: - wt = msol.get_var_solution(t) + wt = res.get_var_solution(t) if wt.is_present(): - if desc[t].skills[w] == max(desc[t].skills): - # Green-like color when task is using the most skilled worker - color = 'lightgreen' - else: - # Red-like color when task does not use the most skilled worker - color = 'salmon' - visu.interval(wt, color, compact(wt.get_name())) + visu.interval(wt, *compact(wt.get_name())) visu.show() diff --git a/examples/cp/visu/house_building_state.py b/examples/cp/visu/house_building_state.py index 5a47eb1..d6a91d6 100644 --- a/examples/cp/visu/house_building_state.py +++ b/examples/cp/visu/house_building_state.py @@ -22,54 +22,49 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction, INTERVAL_MIN, INTERVAL_MAX -import docplex.cp.utils_visu as visu - +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data #----------------------------------------------------------------------------- -# House building task descriptor -class BuildingTask(object): - def __init__(self, name, duration): - self.name = name - self.duration = duration - # List of tasks to be executed for each house -MASONRY = BuildingTask('masonry', 35) -CARPENTRY = BuildingTask('carpentry', 15) -PLUMBING = BuildingTask('plumbing', 40) -CEILING = BuildingTask('ceiling', 15) -ROOFING = BuildingTask('roofing', 5) -PAINTING = BuildingTask('painting', 10) -WINDOWS = BuildingTask('windows', 5) -FACADE = BuildingTask('facade', 10) -GARDEN = BuildingTask('garden', 5) -MOVING = BuildingTask('moving', 5) +TASKS = { + 'masonry' : 35, + 'carpentry' : 15, + 'plumbing' : 40, + 'ceiling' : 15, + 'roofing' : 5, + 'painting' : 10, + 'windows' : 5, + 'facade' : 10, + 'garden' : 5, + 'moving' : 5 +} # Tasks precedence constraints (each tuple (X, Y) means X ends before start of Y) -PRECEDENCES = ((MASONRY, CARPENTRY), - (MASONRY, PLUMBING), - (MASONRY, CEILING), - (CARPENTRY, ROOFING), - (CEILING, PAINTING), - (ROOFING, WINDOWS), - (ROOFING, FACADE), - (PLUMBING, FACADE), - (ROOFING, GARDEN), - (PLUMBING, GARDEN), - (WINDOWS, MOVING), - (FACADE, MOVING), - (GARDEN, MOVING), - (PAINTING, MOVING), - ) +PRECEDENCES = [ + ('masonry', 'carpentry'), + ('masonry', 'plumbing'), + ('masonry', 'ceiling'), + ('carpentry', 'roofing'), + ('ceiling', 'painting'), + ('roofing', 'windows'), + ('roofing', 'facade'), + ('plumbing', 'facade'), + ('roofing', 'garden'), + ('plumbing', 'garden'), + ('windows', 'moving'), + ('facade', 'moving'), + ('garden', 'moving'), + ('painting', 'moving'), +] # List of tasks that requires the house to be clean -CLEAN_TASKS = (PLUMBING, CEILING, PAINTING) +CLEAN_TASKS = [ 'plumbing', 'ceiling', 'painting' ] # List of tasks that put the house in a dirty state -DIRTY_TASKS = (MASONRY, CARPENTRY, ROOFING, WINDOWS) +DIRTY_TASKS = [ 'masonry', 'carpentry', 'roofing', 'windows' ] # House cleaning transition time HOUSE_CLEANING_TIME = 1 @@ -80,21 +75,10 @@ def __init__(self, name, duration): # List of houses to build. Value is the minimum start date HOUSES = (31, 0, 90, 120, 90) - -#----------------------------------------------------------------------------- -# Prepare the data for modeling -#----------------------------------------------------------------------------- - -# Assign an index to tasks -ALL_TASKS = (MASONRY, CARPENTRY, PLUMBING, CEILING, ROOFING, PAINTING, WINDOWS, FACADE, GARDEN, MOVING) -for i in range(len(ALL_TASKS)): - ALL_TASKS[i].id = i - # Possible states CLEAN = 0 DIRTY = 1 - #----------------------------------------------------------------------------- # Build the model #----------------------------------------------------------------------------- @@ -104,9 +88,7 @@ def __init__(self, name, duration): # Initialize model variable sets all_tasks = [] # Array of all tasks -desc = dict() # Dictionary task interval var -> task descriptor -house = dict() # Dictionary task interval var -> id of the corresponding house -workers_usage = mdl.step_at(0, 0) # Total worker usage +workers_usage = 0 # Total worker usage all_state_functions = [] # Transition matrix for cost between house states @@ -121,30 +103,24 @@ def make_house(loc, rd): ''' # Create interval variable for each task for this house - tasks = [mdl.interval_var(size=t.duration, - start=(rd, INTERVAL_MAX), - name="H" + str(loc) + "-" + t.name) for t in ALL_TASKS] - all_tasks.extend(tasks) + tasks = { t : interval_var(size=TASKS[t], start=(rd, INTERVAL_MAX), name='H{}-{}'.format(loc,t)) for t in TASKS } + all_tasks.extend(tasks.values()) global workers_usage # Add precedence constraints - for p, s in PRECEDENCES: - mdl.add(mdl.end_before_start(tasks[p.id], tasks[s.id])) + mdl.add(end_before_start(tasks[p], tasks[s]) for p,s in PRECEDENCES) # Create house state function - house_state = mdl.state_function(TTIME, name="H" + str(loc)) + house_state = state_function(TTIME, name='H{}'.format(loc)) for t in CLEAN_TASKS: - mdl.add(mdl.always_equal(house_state, tasks[t.id], CLEAN)) + mdl.add(always_equal(house_state, tasks[t], CLEAN)) for t in DIRTY_TASKS: - mdl.add(mdl.always_equal(house_state, tasks[t.id], DIRTY)) + mdl.add(always_equal(house_state, tasks[t], DIRTY)) all_state_functions.append(house_state) # Allocate tasks to workers - for t in ALL_TASKS: - desc[tasks[t.id]] = t - house[tasks[t.id]] = loc - workers_usage += mdl.pulse(tasks[t.id], 1) + workers_usage += sum(pulse(tasks[t], 1) for t in TASKS) # Make houses @@ -152,10 +128,10 @@ def make_house(loc, rd): make_house(i, sd) # Number of workers should not be greater than the limit -mdl.add(mdl.always_in(workers_usage, (INTERVAL_MIN, INTERVAL_MAX), 0, NB_WORKERS)) +mdl.add(always_in(workers_usage, (INTERVAL_MIN, INTERVAL_MAX), 0, NB_WORKERS)) # Minimize overall completion date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(task) for task in all_tasks]))) +mdl.add(minimize(max([end_of(task) for task in all_tasks]))) #----------------------------------------------------------------------------- @@ -166,27 +142,29 @@ def compact(name): # Example: H3-garden -> G3 # ^ ^ loc, task = name[1:].split('-', 1) - return task[0].upper() + loc + return int(loc), task[0].upper() + loc # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10, FailLimit=10000) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=10000,TimeLimit=10) -if msol and visu.is_visu_enabled(): +print('Solution:') +res.print_solution() + +# Draw solution +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): workers_function = CpoStepFunction() for v in all_tasks: - itv = msol.get_var_solution(v) + itv = res.get_var_solution(v) workers_function.add_value(itv.get_start(), itv.get_end(), 1) - visu.timeline('Solution SchedState') - visu.panel(name="Schedule") + visu.panel(name='Schedule') for v in all_tasks: - visu.interval(msol.get_var_solution(v), house[v], compact(v.get_name())) - visu.panel(name="Houses state") + visu.interval(res.get_var_solution(v), *compact(v.get_name())) + visu.panel(name='Houses state') for f in all_state_functions: - visu.sequence(name=f.get_name(), segments=msol.get_var_solution(f)) - visu.panel(name="Nb of workers") + visu.sequence(name=f.get_name(), segments=res.get_var_solution(f)) + visu.panel(name='Nb of workers') visu.function(segments=workers_function, style='line') visu.show() diff --git a/examples/cp/visu/house_building_time.py b/examples/cp/visu/house_building_time.py index c164f3c..4d926df 100644 --- a/examples/cp/visu/house_building_time.py +++ b/examples/cp/visu/house_building_time.py @@ -16,8 +16,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoSegmentedFunction -import docplex.cp.utils_visu as visu +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data @@ -26,55 +25,37 @@ # Create model mdl = CpoModel() -# House building task descriptor -class BuildingTask(object): - def __init__(self, name, duration, release_date=None, due_date=None, earliness_cost=None, tardiness_cost=None): - self.name = name - self.duration = duration - self.release_date = release_date - self.due_date = due_date - self.earliness_cost = earliness_cost - self.tardiness_cost = tardiness_cost - # List of tasks to be executed for the house -MASONRY = BuildingTask('masonry', 35, release_date=25, earliness_cost=200.0) -CARPENTRY = BuildingTask('carpentry', 15, release_date=75, earliness_cost=300.0) -PLUMBING = BuildingTask('plumbing', 40) -CEILING = BuildingTask('ceiling', 15, release_date=75, earliness_cost=100.0) -ROOFING = BuildingTask('roofing', 5) -PAINTING = BuildingTask('painting', 10) -WINDOWS = BuildingTask('windows', 5) -FACADE = BuildingTask('facade', 10) -GARDEN = BuildingTask('garden', 5) -MOVING = BuildingTask('moving', 5, due_date=100, tardiness_cost=400.0) +TASKS = { + 'masonry' : (35 , 1, {'release_date':25, 'earliness_cost':200.0} ), + 'carpentry' : (15 , 2, {'release_date':75, 'earliness_cost':300.0} ), + 'plumbing' : (40 , 3, {} ), + 'ceiling' : (15 , 4, {'release_date':75, 'earliness_cost':100.0} ), + 'roofing' : ( 5 , 5, {} ), + 'painting' : (10 , 6, {} ), + 'windows' : ( 5 , 7, {} ), + 'facade' : (10 , 8, {} ), + 'garden' : ( 5 , 9, {} ), + 'moving' : ( 5 , 10, {'due_date':100, 'tardiness_cost':400.0} ) +} # Tasks precedence constraints (each tuple (X, Y) means X ends before start of Y) -PRECEDENCES = ( (MASONRY, CARPENTRY), - (MASONRY, PLUMBING), - (MASONRY, CEILING), - (CARPENTRY, ROOFING), - (CEILING, PAINTING), - (ROOFING, WINDOWS), - (ROOFING, FACADE), - (PLUMBING, FACADE), - (ROOFING, GARDEN), - (PLUMBING, GARDEN), - (WINDOWS, MOVING), - (FACADE, MOVING), - (GARDEN, MOVING), - (PAINTING, MOVING), - ) - - -#----------------------------------------------------------------------------- -# Prepare the data for modeling -#----------------------------------------------------------------------------- - -# Assign an index to tasks -ALL_TASKS = (MASONRY, CARPENTRY, PLUMBING, CEILING, ROOFING, PAINTING, WINDOWS, FACADE, GARDEN, MOVING) -for i in range(len(ALL_TASKS)): - ALL_TASKS[i].id = i - +PRECEDENCES = [ + ('masonry', 'carpentry'), + ('masonry', 'plumbing'), + ('masonry', 'ceiling'), + ('carpentry', 'roofing'), + ('ceiling', 'painting'), + ('roofing', 'windows'), + ('roofing', 'facade'), + ('plumbing', 'facade'), + ('roofing', 'garden'), + ('plumbing', 'garden'), + ('windows', 'moving'), + ('facade', 'moving'), + ('garden', 'moving'), + ('painting', 'moving'), +] #----------------------------------------------------------------------------- # Build the model @@ -84,57 +65,50 @@ def __init__(self, name, duration, release_date=None, due_date=None, earliness_c mdl = CpoModel() # Create interval variable for each building task -tasks = [mdl.interval_var(size=t.duration, name=t.name) for t in ALL_TASKS] +tasks = { t: interval_var(size=TASKS[t][0], name=t) for t in TASKS } # Add precedence constraints -for p, s in PRECEDENCES: - mdl.add(mdl.end_before_start(tasks[p.id], tasks[s.id])) +mdl.add(end_before_start(tasks[p], tasks[s]) for p,s in PRECEDENCES) # Cost function -cost = [] # List of individual costs -fearliness = dict() # Task earliness cost function (for display) -ftardiness = dict() # Task tardiness cost function (for display) - -for t in ALL_TASKS: - task = tasks[t.id] - if t.release_date is not None: - f = CpoSegmentedFunction((-t.earliness_cost, 0), [(t.release_date, 0, 0)]) - cost.append(mdl.start_eval(task, f)) - fearliness[t] = f - if t.due_date is not None: - f = CpoSegmentedFunction((0, 0), [(t.due_date, 0, t.tardiness_cost)]) - cost.append(mdl.end_eval(task, f)) - ftardiness[t] = f +fearliness = dict() # Task earliness cost function +ftardiness = dict() # Task tardiness cost function -# Minimize cost -mdl.add(mdl.minimize(mdl.sum(cost))) +for t in TASKS: + if 'release_date' in TASKS[t][2]: + fearliness[t] = CpoSegmentedFunction((-TASKS[t][2]['earliness_cost'], 0), [(TASKS[t][2]['release_date'], 0, 0)]) + if 'due_date' in TASKS[t][2]: + ftardiness[t] = CpoSegmentedFunction((0, 0), [(TASKS[t][2]['due_date'], 0, TASKS[t][2]['tardiness_cost'],)]) +# Minimize cost +mdl.add(minimize( sum( start_eval(tasks[t], fearliness[t]) for t in fearliness) + + sum( end_eval (tasks[t], ftardiness[t]) for t in ftardiness) )) #----------------------------------------------------------------------------- # Solve the model and display the result #----------------------------------------------------------------------------- -print("Solving model....") -msol = mdl.solve(TimeLimit=10) -print("Solution: ") -msol.print_solution() - -if msol and visu.is_visu_enabled(): - visu.timeline("Solution SchedTime", origin=10, horizon=120) - visu.panel("Schedule") - for t in ALL_TASKS: - visu.interval(msol.get_var_solution(tasks[t.id]), t.id, t.name) - for t in ALL_TASKS: - if t.release_date is not None: +print('Solving model...') +res = mdl.solve(TimeLimit=10) +print('Solution:') +res.print_solution() + +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution house building', origin=10, horizon=120) + visu.panel('Schedule') + for t in TASKS: + visu.interval(res.get_var_solution(tasks[t]), TASKS[t][1], t) + for t in TASKS: + itvsol = res.get_var_solution(tasks[t]) + if 'release_date' in TASKS[t][2]: visu.panel('Earliness') - itvsol = msol.get_var_solution(tasks[t.id]) cost = fearliness[t].get_value(itvsol.get_start()) - visu.function(segments=[(itvsol, cost, t.name)], color=t.id, style='interval') - visu.function(segments=fearliness[t], color=t.id) - if t.due_date is not None: + visu.function(segments=[(itvsol, cost, t)], color=TASKS[t][1], style='interval') + visu.function(segments=fearliness[t], color=TASKS[t][1]) + if 'due_date' in TASKS[t][2]: visu.panel('Tardiness') - itvsol = msol.get_var_solution(tasks[t.id]) cost = ftardiness[t].get_value(itvsol.get_end()) - visu.function(segments=[(itvsol, cost, t.name)], color=t.id, style='interval') - visu.function(segments=ftardiness[t], color=t.id) + visu.function(segments=[(itvsol, cost, t)], color=TASKS[t][1], style='interval') + visu.function(segments=ftardiness[t], color=TASKS[t][1]) visu.show() diff --git a/examples/cp/visu/job_shop_basic.py b/examples/cp/visu/job_shop_basic.py index ab47bb2..c02f8ec 100644 --- a/examples/cp/visu/job_shop_basic.py +++ b/examples/cp/visu/job_shop_basic.py @@ -18,8 +18,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os @@ -32,8 +31,8 @@ # First line contains the number of jobs, and the number of machines. # The rest of the file consists of one line per job. # Each line contains list of operations, each one given by 2 numbers: machine and duration -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/jobshop_ft06.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/jobshop_ft06.data' +with open(filename, 'r') as file: NB_JOBS, NB_MACHINES = [int(v) for v in file.readline().split()] JOBS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] @@ -57,23 +56,23 @@ mdl = CpoModel() # Create one interval variable per job operation -job_operations = [[mdl.interval_var(size=DURATION[j][m], name="O{}-{}".format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] +job_operations = [[interval_var(size=DURATION[j][m], name='O{}-{}'.format(j,m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] # Each operation must start after the end of the previous for j in range(NB_JOBS): for s in range(1, NB_MACHINES): - mdl.add(mdl.end_before_start(job_operations[j][s - 1], job_operations[j][s])) + mdl.add(end_before_start(job_operations[j][s-1], job_operations[j][s])) # Force no overlap for operations executed on a same machine machine_operations = [[] for m in range(NB_MACHINES)] for j in range(NB_JOBS): - for s in range(0, NB_MACHINES): + for s in range(NB_MACHINES): machine_operations[MACHINES[j][s]].append(job_operations[j][s]) -for lops in machine_operations: - mdl.add(mdl.no_overlap(lops)) +for mops in machine_operations: + mdl.add(no_overlap(mops)) # Minimize termination date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(job_operations[i][NB_MACHINES - 1]) for i in range(NB_JOBS)]))) +mdl.add(minimize(max(end_of(job_operations[i][NB_MACHINES-1]) for i in range(NB_JOBS)))) #----------------------------------------------------------------------------- @@ -81,21 +80,22 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=10) +print('Solution:') +res.print_solution() # Draw solution -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for job-shop " + filename) - visu.panel("Jobs") +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution for job-shop ' + filename) + visu.panel('Jobs') for i in range(NB_JOBS): visu.sequence(name='J' + str(i), - intervals=[(msol.get_var_solution(job_operations[i][j]), MACHINES[i][j], 'M' + str(MACHINES[i][j])) for j in + intervals=[(res.get_var_solution(job_operations[i][j]), MACHINES[i][j], 'M' + str(MACHINES[i][j])) for j in range(NB_MACHINES)]) - visu.panel("Machines") + visu.panel('Machines') for k in range(NB_MACHINES): visu.sequence(name='M' + str(k), - intervals=[(msol.get_var_solution(machine_operations[k][i]), k, 'J' + str(i)) for i in range(NB_JOBS)]) + intervals=[(res.get_var_solution(machine_operations[k][i]), k, 'J' + str(i)) for i in range(NB_JOBS)]) visu.show() diff --git a/examples/cp/visu/job_shop_flexible.py b/examples/cp/visu/job_shop_flexible.py index 83c045e..32cc181 100644 --- a/examples/cp/visu/job_shop_flexible.py +++ b/examples/cp/visu/job_shop_flexible.py @@ -16,8 +16,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os @@ -32,8 +31,8 @@ # First integer is the number of job steps, followed by the choices for each step. # For each step, first integer indicates the number of choices, followed # by the choices expressed with two integers: machine and duration -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/jobshopflex_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/jobshopflex_default.data' +with open(filename, 'r') as file: NB_JOBS, NB_MACHINES = [int(v) for v in file.readline().split()] list_jobs = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] @@ -68,41 +67,27 @@ mdl = CpoModel() # Following code creates: -# - creates one interval variable for each possible operation choice -# - creates one interval variable for each operation, as an alternative of all operation choices +# - creates one interval variable 'ops' for each possible operation choice +# - creates one interval variable mops' for each operation, as an alternative of all operation choices # - setup precedence constraints between operations of each job -# - fills machine_operations structure that list all operations that reside on a machine - -# Initialize working variables -job_number = {} # Job_number for each operation choice (for display) -all_operations = [] # List of all operations -machine_operations = [[] for m in range(NB_MACHINES)] # All choices per machine - -# Loop on all jobs/operations/choices -for jx, job in enumerate(JOBS): - op_vars = [] - for ox, op in enumerate(job): - choice_vars = [] - for cx, (m, d) in enumerate(op): - cv = mdl.interval_var(name="J{}_O{}_C{}_M{}".format(jx, ox, cx, m), optional=True, size=d) - job_number[cv.get_name()] = jx - choice_vars.append(cv) - machine_operations[m].append(cv) - # Create alternative - jv = mdl.interval_var(name="J{}_O{}".format(jx, ox)) - mdl.add(mdl.alternative(jv, choice_vars)) - op_vars.append(jv) - # Add precedence - if ox > 0: - mdl.add(mdl.end_before_start(op_vars[ox - 1], op_vars[ox])) - all_operations.extend(op_vars) +# - creates a no_overlap constraint an the operations of each machine + +ops = { (j,o) : interval_var(name='J{}_O{}'.format(j,o)) + for j,J in enumerate(JOBS) for o,O in enumerate(J)} +mops = { (j,o,k,m) : interval_var(name='J{}_O{}_C{}_M{}'.format(j,o,k,m), optional=True, size=d) + for j,J in enumerate(JOBS) for o,O in enumerate(J) for k, (m, d) in enumerate(O)} + +# Precedence constraints between operations of a job +mdl.add(end_before_start(ops[j,o], ops[j,o-1]) for j,o in ops if 0 0: - mdl.add(mdl.end_before_start(itvs[i][j - 1], itvs[i][j])) + mdl.add(end_before_start(itvs[i][j-1], itvs[i][j])) - sequences = [mdl.sequence_var(mach[j], name="S{}:M{}".format(k, j)) for j in range(NB_MACHINES)] - for s in sequences: - mdl.add(mdl.no_overlap(s)) - makespan = mdl.integer_var(0, INT_MAX, name='makespan' + str(k)) - mdl.add(makespan == mdl.max([mdl.end_of(itvs[i][NB_MACHINES - 1]) for i in range(NB_JOBS)])) + sequences = [sequence_var(mach[j], name='S{}:M{}'.format(k,j)) for j in range(NB_MACHINES)] + mdl.add(no_overlap(s) for s in sequences) + makespan = integer_var(0, INT_MAX, name='makespan{}'.format(k)) + mdl.add(makespan == max([end_of(itvs[i][NB_MACHINES-1]) for i in range(NB_JOBS)])) return sequences, makespan # Initialize working variables @@ -149,11 +146,11 @@ def make_scenario_submodel(k): ref_sequences = sequences else: for j in range(NB_MACHINES): - mdl.add(mdl.same_sequence(ref_sequences[j], sequences[j])) + mdl.add(same_sequence(ref_sequences[j], sequences[j])) # Minimize average makespan -expected_makespan = mdl.sum(makespans) / NB_SCENARIOS -mdl.add(mdl.minimize(expected_makespan)) +expected_makespan = sum(makespans) / NB_SCENARIOS +mdl.add(minimize(expected_makespan)) #----------------------------------------------------------------------------- @@ -161,28 +158,27 @@ def make_scenario_submodel(k): #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10, FailLimit=250000) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=250000,TimeLimit=10) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): - import docplex.cp.utils_visu as visu +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): import matplotlib.pyplot as plt - - makespan_values = [msol.get_var_solution(m).get_value() for m in makespans] + makespan_values = [res.get_var_solution(m).get_value() for m in makespans] plt.hist(makespan_values, color='skyblue') - plt.axvline(msol.get_objective_values()[0], color='navy', linestyle='dashed', linewidth=2) - plt.title("Makespan histogram") - plt.xlabel("Value") - plt.ylabel("Frequency") + plt.axvline(res.get_objective_values()[0], color='navy', linestyle='dashed', linewidth=2) + plt.title('Makespan histogram') + plt.xlabel('Value') + plt.ylabel('Frequency') - visu.timeline("Solution sequencing for stochastic job-shop " + filename) - visu.panel("Machines") + visu.timeline('Solution sequencing for stochastic job-shop ' + filename) + visu.panel('Machines') for j in range(NB_MACHINES): - visu.sequence(name='M' + str(j)) - itvs = msol.get_var_solution(ref_sequences[j]).get_value() + visu.sequence(name='M{}'.format(j)) + itvs = res.get_var_solution(ref_sequences[j]).get_value() for v in itvs: - k, i, m = v.get_name().split('-') - visu.interval(v, int(i), 'O' + i + '-' + m) + k,i,m = v.get_name().split('-') + visu.interval(v, int(i), 'O{}-{}'.format(i,m)) visu.show() diff --git a/examples/cp/visu/open_shop.py b/examples/cp/visu/open_shop.py index 2e2e766..7dfce29 100644 --- a/examples/cp/visu/open_shop.py +++ b/examples/cp/visu/open_shop.py @@ -22,8 +22,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os @@ -36,8 +35,8 @@ # First line contains the number of jobs, and the number of machines. # The rest of the file consists of one line per job that contains the list of # operations given as durations for each machines. -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/openshop_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/openshop_default.data' +with open(filename, 'r') as file: NB_JOBS, NB_MACHINES = [int(v) for v in file.readline().split()] JOB_DURATIONS = [[int(v) for v in file.readline().split()] for i in range(NB_JOBS)] @@ -50,18 +49,16 @@ mdl = CpoModel() # Create one interval variable per job operation -job_operations = [[mdl.interval_var(size=JOB_DURATIONS[j][m], name="J{}-M{}".format(j, m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] +job_operations = [[interval_var(size=JOB_DURATIONS[j][m], name='J{}-M{}'.format(j,m)) for m in range(NB_MACHINES)] for j in range(NB_JOBS)] # All operations executed on the same machine must no overlap -for i in range(NB_JOBS): - mdl.add(mdl.no_overlap([job_operations[i][j] for j in range(NB_MACHINES)])) +mdl.add(no_overlap(job_operations[i][j] for j in range(NB_MACHINES)) for i in range(NB_JOBS)) # All operations executed for the same job must no overlap -for j in range(NB_MACHINES): - mdl.add(mdl.no_overlap([job_operations[i][j] for i in range(NB_JOBS)])) +mdl.add(no_overlap(job_operations[i][j] for i in range(NB_JOBS)) for j in range(NB_MACHINES)) # Minimization completion time -mdl.add(mdl.minimize(mdl.max([mdl.end_of(job_operations[i][j]) for i in range(NB_JOBS) for j in range(NB_MACHINES)]))) +mdl.add(minimize(max(end_of(job_operations[i][j]) for i in range(NB_JOBS) for j in range(NB_MACHINES)))) #----------------------------------------------------------------------------- @@ -69,20 +66,21 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=10000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=10000,TimeLimit=10) +print('Solution: ') +res.print_solution() # Display solution -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for open-shop " + filename) - visu.panel("Jobs") +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): + visu.timeline('Solution for open-shop ' + filename) + visu.panel('Jobs') for i in range(NB_JOBS): visu.sequence(name='J' + str(i), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) - visu.panel("Machines") + intervals=[(res.get_var_solution(job_operations[i][j]), j, 'M' + str(j)) for j in range(NB_MACHINES)]) + visu.panel('Machines') for j in range(NB_MACHINES): visu.sequence(name='M' + str(j), - intervals=[(msol.get_var_solution(job_operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) + intervals=[(res.get_var_solution(job_operations[i][j]), j, 'J' + str(i)) for i in range(NB_JOBS)]) visu.show() diff --git a/examples/cp/visu/plant_location_with_solver_listener.py b/examples/cp/visu/plant_location_with_solver_listener.py index a340a1a..fdebf63 100644 --- a/examples/cp/visu/plant_location_with_solver_listener.py +++ b/examples/cp/visu/plant_location_with_solver_listener.py @@ -38,7 +38,7 @@ Log parsing is also activated to retrieve runtime information from it. """ -from docplex.cp.model import CpoModel +from docplex.cp.model import * from docplex.cp.solver.solver_listener import * from docplex.cp.config import context from docplex.cp.utils import compare_natural @@ -50,9 +50,9 @@ #----------------------------------------------------------------------------- # Read problem data from a file and convert it as a list of integers -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/plant_location.data" +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/plant_location.data' data = deque() -with open(filename, "r") as file: +with open(filename, 'r') as file: for val in file.read().split(): data.append(int(val)) @@ -80,31 +80,28 @@ mdl = CpoModel() # Create variables identifying which location serves each customer -cust = mdl.integer_var_list(nbCustomer, 0, nbLocation - 1, "CustomerLocation") +cust = integer_var_list(nbCustomer, 0, nbLocation - 1, 'CustomerLocation') # Create variables indicating which plant location is open -open = mdl.integer_var_list(nbLocation, 0, 1, "OpenLocation") +open = integer_var_list(nbLocation, 0, 1, 'OpenLocation') # Create variables indicating load of each plant -load = [mdl.integer_var(0, capacity[p], "PlantLoad_" + str(p)) for p in range(nbLocation)] +load = [integer_var(0, capacity[p], 'PlantLoad_' + str(p)) for p in range(nbLocation)] # Associate plant openness to its load -for p in range(nbLocation): - mdl.add(open[p] == (load[p] > 0)) +mdl.add(open[p] == (load[p] > 0) for p in range(nbLocation)) # Add constraints -mdl.add(mdl.pack(load, cust, demand)) +mdl.add(pack(load, cust, demand)) # Add objective -obj = mdl.scal_prod(fixedCost, open) -for c in range(nbCustomer): - obj += mdl.element(cust[c], cost[c]) -mdl.add(mdl.minimize(obj)) +obj = scal_prod(fixedCost, open) + sum(element(cust[c], cost[c]) for c in range(nbCustomer)) +mdl.add(minimize(obj)) # Add KPIs if compare_natural(context.model.version, '12.9') >= 0: - mdl.add_kpi(mdl.sum(demand) / mdl.scal_prod(open, capacity), "Average Occupancy") - mdl.add_kpi(mdl.min([load[l] / capacity[l] + (1 - open[l]) for l in range(nbLocation)]), "Min occupancy") + mdl.add_kpi(sum(demand) / scal_prod(open, capacity), 'Average Occupancy') + mdl.add_kpi(min([load[l] / capacity[l] + (1 - open[l]) for l in range(nbLocation)]), 'Min occupancy') #----------------------------------------------------------------------------- @@ -115,6 +112,6 @@ mdl.add_solver_listener(SolverProgressPanelListener(parse_log=True)) # Solve the model -print("Solve the model") -msol = mdl.solve(TimeLimit=20, LogPeriod=1000) -msol.write() +print('Solve the model') +res = mdl.solve(TimeLimit=20, LogPeriod=1000) +res.write() diff --git a/examples/cp/visu/rcpsp.py b/examples/cp/visu/rcpsp.py index c724f91..0bba201 100644 --- a/examples/cp/visu/rcpsp.py +++ b/examples/cp/visu/rcpsp.py @@ -21,8 +21,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction, INTERVAL_MIN, INTERVAL_MAX -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os #----------------------------------------------------------------------------- @@ -38,8 +37,8 @@ # - the demand on each resource (one integer per resource) # - the number of successors followed by the list of successor numbers -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/rcpsp_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/rcpsp_default.data' +with open(filename, 'r') as file: NB_TASKS, NB_RESOURCES = [int(v) for v in file.readline().split()] CAPACITIES = [int(v) for v in file.readline().split()] TASKS = [[int(v) for v in file.readline().split()] for i in range(NB_TASKS)] @@ -53,10 +52,10 @@ DURATIONS = [TASKS[t][0] for t in range(NB_TASKS)] # Extract demand of each task -DEMANDS = [TASKS[t][1:NB_RESOURCES + 1] for t in range(NB_TASKS)] +DEMANDS = [TASKS[t][1:NB_RESOURCES+1] for t in range(NB_TASKS)] # Extract successors of each task -SUCCESSORS = [TASKS[t][NB_RESOURCES + 2:] for t in range(NB_TASKS)] +SUCCESSORS = [TASKS[t][NB_RESOURCES+2:] for t in range(NB_TASKS)] #----------------------------------------------------------------------------- @@ -67,20 +66,16 @@ mdl = CpoModel() # Create task interval variables -tasks = [mdl.interval_var(name="T{}".format(i + 1), size=DURATIONS[i]) for i in range(NB_TASKS)] +tasks = [interval_var(name='T{}'.format(i+1), size=DURATIONS[i]) for i in range(NB_TASKS)] # Add precedence constraints -for t in range(NB_TASKS): - for s in SUCCESSORS[t]: - mdl.add(mdl.end_before_start(tasks[t], tasks[s - 1])) +mdl.add(end_before_start(tasks[t], tasks[s-1]) for t in range(NB_TASKS) for s in SUCCESSORS[t]) # Constrain capacity of resources -for r in range(NB_RESOURCES): - resources = [mdl.pulse(tasks[t], DEMANDS[t][r]) for t in range(NB_TASKS) if DEMANDS[t][r] > 0] - mdl.add(mdl.sum(resources) <= CAPACITIES[r]) +mdl.add(sum(pulse(tasks[t], DEMANDS[t][r]) for t in range(NB_TASKS) if DEMANDS[t][r] > 0) <= CAPACITIES[r] for r in range(NB_RESOURCES)) # Minimize end of all tasks -mdl.add(mdl.minimize(mdl.max([mdl.end_of(t) for t in tasks]))) +mdl.add(minimize(max(end_of(t) for t in tasks))) #----------------------------------------------------------------------------- @@ -88,25 +83,26 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=100000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=100000,TimeLimit=10) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): load = [CpoStepFunction() for j in range(NB_RESOURCES)] for i in range(NB_TASKS): - itv = msol.get_var_solution(tasks[i]) + itv = res.get_var_solution(tasks[i]) for j in range(NB_RESOURCES): if 0 < DEMANDS[i][j]: load[j].add_value(itv.get_start(), itv.get_end(), DEMANDS[i][j]) - visu.timeline("Solution for RCPSP " + filename) - visu.panel("Tasks") + visu.timeline('Solution for RCPSP ' + filename) + visu.panel('Tasks') for i in range(NB_TASKS): - visu.interval(msol.get_var_solution(tasks[i]), i, tasks[i].get_name()) + visu.interval(res.get_var_solution(tasks[i]), i, tasks[i].get_name()) for j in range(NB_RESOURCES): - visu.panel("R " + str(j + 1)) + visu.panel('R' + str(j+1)) visu.function(segments=[(INTERVAL_MIN, INTERVAL_MAX, CAPACITIES[j])], style='area', color='lightgrey') visu.function(segments=load[j], style='area', color=j) visu.show() diff --git a/examples/cp/visu/rcpsp_multi_mode.py b/examples/cp/visu/rcpsp_multi_mode.py index 2b2dba6..425b140 100644 --- a/examples/cp/visu/rcpsp_multi_mode.py +++ b/examples/cp/visu/rcpsp_multi_mode.py @@ -24,8 +24,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction, INTERVAL_MIN, INTERVAL_MAX -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os #----------------------------------------------------------------------------- @@ -55,8 +54,8 @@ def next_int_line(f): # - the demand for renewable resources # - the demand for non-renewable resources -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/rcpspmm_default.data" -with open(filename, "r") as file: +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/rcpspmm_default.data' +with open(filename, 'r') as file: NB_TASKS, NB_RENEWABLE, NB_NON_RENEWABLE = next_int_line(file) CAPACITIES_RENEWABLE = next_int_line(file) CAPACITIES_NON_RENEWABLE = next_int_line(file) @@ -91,7 +90,7 @@ def __init__(self, name, task, duration, dem_renewables, dem_non_renewables): # Build list of tasks tasks_data = [] for i, t in enumerate(TASKS): - task = Task("T{}".format(i), t[1]) + task = Task('T{}'.format(i), t[1]) for j in range(t[2]): task.successors.append(t[3 + j]) tasks_data.append(task) @@ -104,7 +103,7 @@ def __init__(self, name, task, duration, dem_renewables, dem_non_renewables): dur = m[2] dem_renewables = m[3 : 3 + NB_RENEWABLE] dem_non_renewables = m[3 + NB_RENEWABLE : 3 + NB_RENEWABLE + NB_NON_RENEWABLE] - mode = Mode("T{}-M{}".format(taskid, modeid), + mode = Mode('T{}-M{}'.format(taskid, modeid), tasks_data[taskid], dur, dem_renewables, dem_non_renewables) tasks_data[taskid].modes.append(mode) modes_data.append(mode) @@ -118,41 +117,31 @@ def __init__(self, name, task, duration, dem_renewables, dem_non_renewables): mdl = CpoModel() # Create one interval variable per task -tasks = {t: mdl.interval_var(name=t.name) for t in tasks_data} +tasks = { t: mdl.interval_var(name=t.name) for t in tasks_data} # Add precedence constraints -for t in tasks_data: - for s in t.successors: - mdl.add(mdl.end_before_start(tasks[t], tasks[tasks_data[s]])) +mdl.add(end_before_start(tasks[t], tasks[tasks_data[s]]) for t in tasks_data for s in t.successors) # Create one optional interval variable per mode -modes = {m: mdl.interval_var(name=m.name, optional=True, size=m.duration) for m in modes_data} +modes = { m: interval_var(name=m.name, optional=True, size=m.duration) for m in modes_data} # Add mode alternative for each task -for t in tasks_data: - mdl.add(mdl.alternative(tasks[t], [modes[m] for m in t.modes])) +mdl.add(alternative(tasks[t], [modes[m] for m in t.modes]) for t in tasks_data) -# Initialize pulse functions for renewable and non renewable resources -renewables = [mdl.pulse((0, 0), 0) for j in range(NB_RENEWABLE)] -non_renewables = [0 for j in range(NB_NON_RENEWABLE)] -for m in modes_data: - for j in range(NB_RENEWABLE): - if m.demand_renewable[j] > 0: - renewables[j] += mdl.pulse(modes[m], m.demand_renewable[j]) - for j in range(NB_NON_RENEWABLE): - if m.demand_non_renewable[j] > 0: - non_renewables[j] += m.demand_non_renewable[j] * mdl.presence_of(modes[m]) +# Initialize cumul functions for renewable and non renewable resources +renewables = [ sum(pulse(modes[m], m.demand_renewable[j]) for m in modes_data if m.demand_renewable[j] > 0) + for j in range(NB_RENEWABLE)] +non_renewables = [ sum(m.demand_non_renewable[j]*presence_of(modes[m]) for m in modes_data if m.demand_non_renewable[j] > 0 ) + for j in range(NB_NON_RENEWABLE)] # Constrain renewable resources capacity -for j in range(NB_RENEWABLE): - mdl.add(mdl.always_in(renewables[j], (INTERVAL_MIN, INTERVAL_MAX), 0, CAPACITIES_RENEWABLE[j])) +mdl.add(renewables[j] <= CAPACITIES_RENEWABLE[j] for j in range(NB_RENEWABLE)) # Constrain non-renewable resources capacity -for j in range(NB_NON_RENEWABLE): - mdl.add(non_renewables[j] <= CAPACITIES_NON_RENEWABLE[j]) +mdl.add(non_renewables[j] <= CAPACITIES_NON_RENEWABLE[j] for j in range(NB_NON_RENEWABLE)) # Minimize overall schedule end date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(tasks[t]) for t in tasks_data]))) +mdl.add(minimize(max([end_of(tasks[t]) for t in tasks_data]))) #----------------------------------------------------------------------------- @@ -160,26 +149,27 @@ def __init__(self, name, task, duration, dem_renewables, dem_non_renewables): #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=30000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=30000, TimeLimit=10) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): load = [CpoStepFunction() for j in range(NB_RENEWABLE)] for m in modes_data: - itv = msol.get_var_solution(modes[m]) + itv = res.get_var_solution(modes[m]) if itv.is_present(): for j in range(NB_RENEWABLE): if 0 < m.demand_renewable[j]: load[j].add_value(itv.get_start(), itv.get_end(), m.demand_renewable[j]) - visu.timeline("Solution for RCPSPMM " + filename) - visu.panel("Tasks") + visu.timeline('Solution for RCPSPMM ' + filename) + visu.panel('Tasks') for t in tasks_data: - visu.interval(msol.get_var_solution(tasks[t]), int(t.name[1:]), t.name) + visu.interval(res.get_var_solution(tasks[t]), int(t.name[1:]), t.name) for j in range(NB_RENEWABLE): - visu.panel("R " + str(j + 1)) + visu.panel('R ' + str(j + 1)) visu.function(segments=[(INTERVAL_MIN, INTERVAL_MAX, CAPACITIES_RENEWABLE[j])], style='area', color='lightgrey') visu.function(segments=load[j], style='area', color=j) visu.show() diff --git a/examples/cp/visu/rcpsp_multi_mode_json.py b/examples/cp/visu/rcpsp_multi_mode_json.py index 29167cf..b027096 100644 --- a/examples/cp/visu/rcpsp_multi_mode_json.py +++ b/examples/cp/visu/rcpsp_multi_mode_json.py @@ -27,8 +27,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, CpoStepFunction, INTERVAL_MIN, INTERVAL_MAX -import docplex.cp.utils_visu as visu +from docplex.cp.model import * import os import json @@ -38,7 +37,7 @@ #----------------------------------------------------------------------------- # Load input data from json file -filename = os.path.dirname(os.path.abspath(__file__)) + "/data/rcpspmm_default.json" +filename = os.path.dirname(os.path.abspath(__file__)) + '/data/rcpspmm_default.json' with open(filename, 'r') as f: jstr = f.read() JSON_DATA = json.loads(jstr) @@ -64,7 +63,7 @@ MODES = [] for t in TASKS: for i, m in enumerate(t['modes']): - m['id'] = "T{}-M{}".format(t['id'], i + 1) + m['id'] = 'T{}-M{}'.format(t['id'], i + 1) MODES.append(m) @@ -76,46 +75,31 @@ mdl = CpoModel() # Create one interval variable per task -tasks = {t['id']: mdl.interval_var(name="T{}".format(t['id'])) for t in TASKS} +tasks = {t['id']: interval_var(name='T{}'.format(t['id'])) for t in TASKS} # Add precedence constraints -for t in TASKS: - for s in t['successors']: - mdl.add(mdl.end_before_start(tasks[t['id']], tasks[s])) +mdl.add(end_before_start(tasks[t['id']], tasks[s]) for t in TASKS for s in t['successors']) -# Create one optional interval variable per task mode and add alternatives for tasks -modes = {} # Map of all modes -for t in TASKS: - tmds = [mdl.interval_var(name=m['id'], optional=True, size=m['duration']) for m in t['modes']] - mdl.add(mdl.alternative(tasks[t['id']], tmds)) - for m in tmds: - modes[m.name] = m - -# Initialize pulse functions for renewable and non renewable resources -renewables = [mdl.pulse((0, 0), 0) for j in range(NB_RENEWABLE)] -non_renewables = [0 for j in range(NB_NON_RENEWABLE)] -for m in MODES: - dren = m['demandRenewable'] - dnren = m['demandNonRenewable'] - for j in range(NB_RENEWABLE): - dem = m['demandRenewable'][j] - if dem > 0: - renewables[j] += mdl.pulse(modes[m['id']], dem) - for j in range(NB_NON_RENEWABLE): - dem = m['demandNonRenewable'][j] - if dem > 0: - non_renewables[j] += dem * mdl.presence_of(modes[m['id']]) +# Create one optional interval variable per task mode +modes = { m['id']: interval_var(name=m['id'], optional=True, size=m['duration']) for t in TASKS for m in t['modes'] } + +# Add alternative constraints for tasks +mdl.add(alternative(tasks[t['id']], [ modes[m['id']] for m in t['modes'] ]) for t in TASKS) + +# Initialize cumul functions for renewable and non renewable resources +renewables = [ sum(pulse(modes[m['id']], m['demandRenewable'][j]) for m in MODES if m['demandRenewable'][j] > 0) + for j in range(NB_RENEWABLE)] +non_renewables = [ sum(m['demandNonRenewable'][j]*presence_of(modes[m['id']]) for m in MODES if m['demandNonRenewable'][j] > 0 ) + for j in range(NB_NON_RENEWABLE)] # Constrain renewable resources capacity -for j in range(NB_RENEWABLE): - mdl.add(mdl.always_in(renewables[j], (INTERVAL_MIN, INTERVAL_MAX), 0, CAPACITIES_RENEWABLE[j])) +mdl.add(renewables[j] <= CAPACITIES_RENEWABLE[j] for j in range(NB_RENEWABLE)) # Constrain non-renewable resources capacity -for j in range(NB_NON_RENEWABLE): - mdl.add(non_renewables[j] <= CAPACITIES_NON_RENEWABLE[j]) +mdl.add(non_renewables[j] <= CAPACITIES_NON_RENEWABLE[j] for j in range(NB_NON_RENEWABLE)) # Minimize overall schedule end date -mdl.add(mdl.minimize(mdl.max([mdl.end_of(t) for t in tasks.values()]))) +mdl.add(minimize(max([end_of(t) for t in tasks.values()]))) #----------------------------------------------------------------------------- @@ -123,28 +107,29 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=30000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=30000, TimeLimit=10) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): load = [CpoStepFunction() for j in range(NB_RENEWABLE)] for m in MODES: - itv = msol.get_var_solution(modes[m['id']]) + itv = res.get_var_solution(modes[m['id']]) if itv.is_present(): for j in range(NB_RENEWABLE): dem = m['demandRenewable'][j] if dem > 0: load[j].add_value(itv.get_start(), itv.get_end(), dem) - visu.timeline("Solution for RCPSPMM " + filename) - visu.panel("Tasks") + visu.timeline('Solution for RCPSPMM ' + filename) + visu.panel('Tasks') for t in TASKS: tid = t['id'] - visu.interval(msol.get_var_solution(tasks[tid]), tid, str(tid)) + visu.interval(res.get_var_solution(tasks[tid]), tid, str(tid)) for j in range(NB_RENEWABLE): - visu.panel("R " + str(j + 1)) + visu.panel('R' + str(j + 1)) visu.function(segments=[(INTERVAL_MIN, INTERVAL_MAX, CAPACITIES_RENEWABLE[j])], style='area', color='lightgrey') visu.function(segments=load[j], style='area', color=j) visu.show() diff --git a/examples/cp/visu/setup_costs.py b/examples/cp/visu/setup_costs.py index 90c5b19..bb3137c 100644 --- a/examples/cp/visu/setup_costs.py +++ b/examples/cp/visu/setup_costs.py @@ -14,9 +14,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu - +from docplex.cp.model import * #----------------------------------------------------------------------------- # Initialize the problem data @@ -53,7 +51,6 @@ [18, 55, 34, 26, 28, 32, 40, 12, 44, 25] ] - # Task duration TASK_DURATION = [ 19, 18, 16, 11, 16, 15, 19, 18, 17, 17, @@ -84,13 +81,12 @@ mdl = CpoModel() # Build tasks for machine M1 and M2 -tasks_m1 = [mdl.interval_var(name="A{}_M1_TP{}".format(i, TASK_TYPE[i]), optional=True) for i in range(NB_TASKS)] -tasks_m2 = [mdl.interval_var(name="A{}_M2_TP{}".format(i, TASK_TYPE[i]), optional=True) for i in range(NB_TASKS)] +tasks_m1 = [interval_var(name='A{}_M1_TP{}'.format(i, TASK_TYPE[i]), optional=True) for i in range(NB_TASKS)] +tasks_m2 = [interval_var(name='A{}_M2_TP{}'.format(i, TASK_TYPE[i]), optional=True) for i in range(NB_TASKS)] # Build actual tasks as an alternative between machines -tasks = [mdl.interval_var(name="A{}_TP{}".format(i, TASK_TYPE[i]), size=TASK_DURATION[i]) for i in range(NB_TASKS)] -for i in range(NB_TASKS): - mdl.add(mdl.alternative(tasks[i], [tasks_m1[i], tasks_m2[i]])) +tasks = [interval_var(name='A{}_TP{}'.format(i, TASK_TYPE[i]), size=TASK_DURATION[i]) for i in range(NB_TASKS)] +mdl.add(alternative(tasks[i], [tasks_m1[i], tasks_m2[i]]) for i in range(NB_TASKS)) # Build a map to retrieve task id from variable name (for display purpose) task_id = dict() @@ -99,21 +95,21 @@ task_id[tasks_m2[i].get_name()] = i # Constrain tasks to no overlap on each machine -s1 = mdl.sequence_var(tasks_m1, types=TASK_TYPE, name='M1') -s2 = mdl.sequence_var(tasks_m2, types=TASK_TYPE, name='M2') -mdl.add(mdl.no_overlap(s1, SETUP_M1, 1)) -mdl.add(mdl.no_overlap(s2, SETUP_M2, 1)) +s1 = sequence_var(tasks_m1, types=TASK_TYPE, name='M1') +s2 = sequence_var(tasks_m2, types=TASK_TYPE, name='M2') +mdl.add(no_overlap(s1, SETUP_M1, 1)) +mdl.add(no_overlap(s2, SETUP_M2, 1)) -# Minimize the number of "long" setup times on machines. +# Minimize the number of 'long' setup times on machines. nbLongSetups = 0 for i in range(NB_TASKS): tpi = TASK_TYPE[i] isLongSetup1 = [1 if 30 <= SETUP_M1[tpi][j] else 0 for j in range(NB_TYPES)] + [0] isLongSetup2 = [1 if 30 <= SETUP_M2[tpi][j] else 0 for j in range(NB_TYPES)] + [0] - nbLongSetups += mdl.element(mdl.type_of_next(s1, tasks_m1[i], NB_TYPES, NB_TYPES), isLongSetup1) - nbLongSetups += mdl.element(mdl.type_of_next(s2, tasks_m2[i], NB_TYPES, NB_TYPES), isLongSetup2) + nbLongSetups += element(type_of_next(s1, tasks_m1[i], NB_TYPES, NB_TYPES), isLongSetup1) + nbLongSetups += element(type_of_next(s2, tasks_m2[i], NB_TYPES, NB_TYPES), isLongSetup2) -mdl.add(mdl.minimize(nbLongSetups)) +mdl.add(minimize(nbLongSetups)) #----------------------------------------------------------------------------- @@ -126,13 +122,15 @@ def compact(name): return task[1:] # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=10, FailLimit=1000000) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=10, FailLimit=1000000) +print('Solution: ') +res.print_solution() + +import docplex.cp.utils_visu as visu def showsequence(s, setup): - seq = msol.get_var_solution(s) + seq = res.get_var_solution(s) visu.sequence(name=s.get_name()) vs = seq.get_value() for v in vs: @@ -144,8 +142,8 @@ def showsequence(s, setup): tp2 = TASK_TYPE[task_id[vs[i + 1].get_name()]] visu.transition(end, end + setup[tp1][tp2]) -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for SchedTCost") +if res and visu.is_visu_enabled(): + visu.timeline('Solution for transition costs') showsequence(s1, SETUP_M1) showsequence(s2, SETUP_M2) visu.show() diff --git a/examples/cp/visu/setup_times.py b/examples/cp/visu/setup_times.py index e807c72..e0aa5ca 100644 --- a/examples/cp/visu/setup_times.py +++ b/examples/cp/visu/setup_times.py @@ -27,8 +27,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel, INTERVAL_MAX -import docplex.cp.utils_visu as visu +from docplex.cp.model import * #----------------------------------------------------------------------------- @@ -62,7 +61,6 @@ # Number of tasks NB_TASKS = len(TASK_TYPE) - # Task duration if executed on machine M1 TASK_DUR_M1 = [ 4, 17, 4, 7, 17, 14, 2, 14, 2, 8, 11, 14, 4, 18, 3, 2, 9, 2, 9, 17, @@ -99,13 +97,12 @@ mdl = CpoModel() # Build tasks for machine M1 and M2 -tasks_m1 = [mdl.interval_var(name="A{}_M1_TP{}".format(i, TASK_TYPE[i]), optional=True, size=TASK_DUR_M1[i]) for i in range(NB_TASKS)] -tasks_m2 = [mdl.interval_var(name="A{}_M2_TP{}".format(i, TASK_TYPE[i]), optional=True, size=TASK_DUR_M1[i]) for i in range(NB_TASKS)] +tasks_m1 = [interval_var(name='A{}_M1_TP{}'.format(i, TASK_TYPE[i]), optional=True, size=TASK_DUR_M1[i]) for i in range(NB_TASKS)] +tasks_m2 = [interval_var(name='A{}_M2_TP{}'.format(i, TASK_TYPE[i]), optional=True, size=TASK_DUR_M1[i]) for i in range(NB_TASKS)] # Build actual tasks as an alternative between machines -tasks = [mdl.interval_var(name="A{}_TP{}".format(i, TASK_TYPE[i])) for i in range(NB_TASKS)] -for i in range(NB_TASKS): - mdl.add(mdl.alternative(tasks[i], [tasks_m1[i], tasks_m2[i]])) +tasks = [interval_var(name='A{}_TP{}'.format(i, TASK_TYPE[i])) for i in range(NB_TASKS)] +mdl.add(alternative(tasks[i], [tasks_m1[i], tasks_m2[i]]) for i in range(NB_TASKS)) # Build a map to retrieve task id from variable name (for display purpose) task_id = dict() @@ -114,13 +111,13 @@ task_id[tasks_m2[i].get_name()] = i # Constrain tasks to no overlap on each machine -s1 = mdl.sequence_var(tasks_m1, types=TASK_TYPE, name='M1') -s2 = mdl.sequence_var(tasks_m2, types=TASK_TYPE, name='M2') -mdl.add(mdl.no_overlap(s1, SETUP_M1, 1)) -mdl.add(mdl.no_overlap(s2, SETUP_M2, 1)) +s1 = sequence_var(tasks_m1, types=TASK_TYPE, name='M1') +s2 = sequence_var(tasks_m2, types=TASK_TYPE, name='M2') +mdl.add(no_overlap(s1, SETUP_M1, 1)) +mdl.add(no_overlap(s2, SETUP_M2, 1)) # Minimize the makespan -mdl.add(mdl.minimize(mdl.max([mdl.end_of(tasks[i]) for i in range(NB_TASKS)]))) +mdl.add(minimize(max([end_of(tasks[i]) for i in range(NB_TASKS)]))) #----------------------------------------------------------------------------- @@ -132,8 +129,10 @@ def compact(name): task, foo = name.split('_', 1) return task[1:] +import docplex.cp.utils_visu as visu + def showsequence(s, setup): - seq = msol.get_var_solution(s) + seq = res.get_var_solution(s) visu.sequence(name=s.get_name()) vs = seq.get_value() for v in vs: @@ -146,13 +145,13 @@ def showsequence(s, setup): visu.transition(end, end + setup[tp1][tp2]) # Solve model -print("Solving model....") -msol = mdl.solve(FailLimit=100000, TimeLimit=10) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(FailLimit=100000, TimeLimit=10) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): - visu.timeline("Solution for SchedSetup") +if res and visu.is_visu_enabled(): + visu.timeline('Solution for SchedSetup') showsequence(s1, SETUP_M1) showsequence(s2, SETUP_M2) visu.show() diff --git a/examples/cp/visu/squaring_square.py b/examples/cp/visu/squaring_square.py index 6b22c63..717017f 100644 --- a/examples/cp/visu/squaring_square.py +++ b/examples/cp/visu/squaring_square.py @@ -17,8 +17,7 @@ Please refer to documentation for appropriate setup of solving configuration. """ -from docplex.cp.model import CpoModel -import docplex.cp.utils_visu as visu +from docplex.cp.model import * #----------------------------------------------------------------------------- @@ -40,24 +39,24 @@ mdl = CpoModel() # Create array of variables for subsquares -vx = [mdl.interval_var(size=SIZE_SUBSQUARE[i], name="X" + str(i), end=(0, SIZE_SQUARE)) for i in range(NB_SUBSQUARE)] -vy = [mdl.interval_var(size=SIZE_SUBSQUARE[i], name="Y" + str(i), end=(0, SIZE_SQUARE)) for i in range(NB_SUBSQUARE)] +vx = [interval_var(size=SIZE_SUBSQUARE[i], name='X{}'.format(i), end=(0, SIZE_SQUARE)) for i in range(NB_SUBSQUARE)] +vy = [interval_var(size=SIZE_SUBSQUARE[i], name='Y{}'.format(i), end=(0, SIZE_SQUARE)) for i in range(NB_SUBSQUARE)] # Create dependencies between variables for i in range(len(SIZE_SUBSQUARE)): for j in range(i): - mdl.add( (mdl.end_of(vx[i]) <= mdl.start_of(vx[j])) | (mdl.end_of(vx[j]) <= mdl.start_of(vx[i])) - | (mdl.end_of(vy[i]) <= mdl.start_of(vy[j])) | (mdl.end_of(vy[j]) <= mdl.start_of(vy[i]))) + mdl.add( (end_of(vx[i]) <= start_of(vx[j])) | (end_of(vx[j]) <= start_of(vx[i])) + | (end_of(vy[i]) <= start_of(vy[j])) | (end_of(vy[j]) <= start_of(vy[i]))) # To speed-up the search, create cumulative expressions on each dimension -rx = mdl.sum([mdl.pulse(vx[i], SIZE_SUBSQUARE[i]) for i in range(NB_SUBSQUARE)]) -mdl.add(mdl.always_in(rx, (0, SIZE_SQUARE), SIZE_SQUARE, SIZE_SQUARE)) +rx = sum([pulse(vx[i], SIZE_SUBSQUARE[i]) for i in range(NB_SUBSQUARE)]) +mdl.add(always_in(rx, (0, SIZE_SQUARE), SIZE_SQUARE, SIZE_SQUARE)) -ry = mdl.sum([mdl.pulse(vy[i], SIZE_SUBSQUARE[i]) for i in range(NB_SUBSQUARE)]) -mdl.add(mdl.always_in(ry, (0, SIZE_SQUARE), SIZE_SQUARE, SIZE_SQUARE)) +ry = sum([pulse(vy[i], SIZE_SUBSQUARE[i]) for i in range(NB_SUBSQUARE)]) +mdl.add(always_in(ry, (0, SIZE_SQUARE), SIZE_SQUARE, SIZE_SQUARE)) # Define search phases, also to speed-up the search -mdl.set_search_phases([mdl.search_phase(vx), mdl.search_phase(vy)]) +mdl.set_search_phases([search_phase(vx), search_phase(vy)]) #----------------------------------------------------------------------------- @@ -65,23 +64,24 @@ #----------------------------------------------------------------------------- # Solve model -print("Solving model....") -msol = mdl.solve(TimeLimit=20, LogPeriod=50000) -print("Solution: ") -msol.print_solution() +print('Solving model...') +res = mdl.solve(TimeLimit=20, LogPeriod=50000) +print('Solution: ') +res.print_solution() -if msol and visu.is_visu_enabled(): +import docplex.cp.utils_visu as visu +if res and visu.is_visu_enabled(): import matplotlib.pyplot as plt import matplotlib.cm as cm from matplotlib.patches import Polygon # Plot external square - print("Plotting squares....") + print('Plotting squares...') fig, ax = plt.subplots() plt.plot((0, 0), (0, SIZE_SQUARE), (SIZE_SQUARE, SIZE_SQUARE), (SIZE_SQUARE, 0)) for i in range(len(SIZE_SUBSQUARE)): # Display square i - sx, sy = msol.get_var_solution(vx[i]), msol.get_var_solution(vy[i]) + sx, sy = res.get_var_solution(vx[i]), res.get_var_solution(vy[i]) (sx1, sx2, sy1, sy2) = (sx.get_start(), sx.get_end(), sy.get_start(), sy.get_end()) poly = Polygon([(sx1, sy1), (sx1, sy2), (sx2, sy2), (sx2, sy1)], fc=cm.Set2(float(i) / len(SIZE_SUBSQUARE))) ax.add_patch(poly) diff --git a/examples/mp/jupyter/boxes.ipynb b/examples/mp/jupyter/boxes.ipynb index d79b09c..92353d5 100644 --- a/examples/mp/jupyter/boxes.ipynb +++ b/examples/mp/jupyter/boxes.ipynb @@ -216,7 +216,7 @@ "source": [ "from docplex.mp.model import Model\n", "\n", - "mdl = Model(\"boxes\")" + "mdl = Model(name=\"boxes\")" ] }, { @@ -225,7 +225,9 @@ "source": [ "#### Define the decision variables\n", "\n", - "* For each box $i$ ($i$ in $1..N$) and object $j$ ($j$ in $1..N$), we define a binary variable $X_{i,j}$ equal to $1$ if and only if object $j$ is stored in box $i$." + "* For each box $i$ ($i$ in $1..N$) and object $j$ ($j$ in $1..N$), we define a binary variable $X_{i,j}$ equal to $1$ if and only if object $j$ is stored in box $i$.\n", + "\n", + "Note that the $name$ parameter is actually a function, this function takes a key pair $ij$ and coins a new name for each corresponding variables. The $name$ parameter also acceptsa string prefix, in which case, Docplex will generate names by concatenating the prefix with the string representation of keys." ] }, { @@ -235,7 +237,7 @@ "outputs": [], "source": [ "# decision variables is a 2d-matrix\n", - "x = mdl.binary_var_matrix(box_range, obj_range, lambda ij: \"x_%d_%d\" %(ij[0], ij[1]))" + "x = mdl.binary_var_matrix(box_range, obj_range, name=lambda ij: \"x_%d_%d\" %(ij[0], ij[1]))" ] }, { @@ -258,8 +260,7 @@ " for i in box_range)\n", " \n", "# one box for each object\n", - "mdl.add_constraints(mdl.sum(x[i,j] for i in box_range) == 1\n", - " for j in obj_range)\n", + "mdl.add_constraints(mdl.sum(x[i,j] for i in box_range) == 1 for j in obj_range)\n", "\n", "mdl.print_information()" ] @@ -280,7 +281,7 @@ "outputs": [], "source": [ "# minimize total displacement\n", - "mdl.minimize( mdl.sum(distances[i,j] * x[i,j] for i in box_range for j in obj_range) )" + "mdl.minimize( mdl.dotf(x, lambda ij: distances[ij]))" ] }, { @@ -300,7 +301,7 @@ "source": [ "mdl.print_information()\n", "\n", - "assert mdl.solve(), \"!!! Solve of the model fails\"" + "assert mdl.solve(log_output=True), \"!!! Solve of the model fails\"" ] }, { @@ -347,7 +348,7 @@ "metadata": {}, "outputs": [], "source": [ - "mdl.add_constraint(x[1,2] == 0)" + "mdl.add_(x[1,2] == 0)" ] }, { @@ -381,8 +382,8 @@ "metadata": {}, "outputs": [], "source": [ - "ok2 = mdl.solve()\n", - "assert ok2, \"solve failed\"\n", + "s2 = mdl.solve()\n", + "assert s2, \"solve failed\"\n", "mdl.report()\n", "d2 = mdl.objective_value\n", "sol2 = make_solution_vector(x)\n", @@ -417,8 +418,7 @@ "outputs": [], "source": [ "# forall k in 2..N-1 then we can use the sum on the right hand side\n", - "mdl.add_constraints(x[k,6] <= x[k-1,5] + x[k+1,5]\n", - " for k in range(2,N))\n", + "mdl.add_constraints(x[k,6] <= x[k-1,5] + x[k+1,5] for k in range(2,N))\n", " \n", "# if 6 is in box 1 then 5 must be in 2\n", "mdl.add_constraint(x[1,6] <= x[2,5])\n", @@ -427,8 +427,8 @@ "mdl.add_constraint(x[N,6] <= x[N-1,5])\n", "\n", "# we solve again\n", - "ok3 = mdl.solve()\n", - "assert ok3, \"solve failed\"\n", + "s3 = mdl.solve()\n", + "assert s3, \"solve failed\"\n", "mdl.report()\n", "d3 = mdl.objective_value\n", "\n", @@ -455,23 +455,29 @@ "metadata": {}, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n", - "from pylab import rcParams\n", - "%matplotlib inline\n", - "rcParams['figure.figsize'] = 12, 6\n", - "\n", - "def display_solution(sol):\n", - " obj_boxes = make_obj_box_dir(sol)\n", - " xs = []\n", - " ys = []\n", - " for o in obj_range:\n", - " b = obj_boxes[o]\n", - " box_x = box_coords[b][0]\n", - " box_y = box_coords[b][1]\n", - " obj_x = obj_coords[o][0]\n", - " obj_y = obj_coords[o][1]\n", - " plt.text(obj_x, obj_y, str(o), bbox=dict(facecolor='red', alpha=0.5))\n", - " plt.plot([obj_x, box_x], [obj_y, box_y])\n" + "try:\n", + " import matplotlib.pyplot as plt\n", + " from pylab import rcParams\n", + " %matplotlib inline\n", + " rcParams['figure.figsize'] = 12, 6\n", + " \n", + " def display_solution(sol):\n", + " obj_boxes = make_obj_box_dir(sol)\n", + " xs = []\n", + " ys = []\n", + " for o in obj_range:\n", + " b = obj_boxes[o]\n", + " box_x = box_coords[b][0]\n", + " box_y = box_coords[b][1]\n", + " obj_x = obj_coords[o][0]\n", + " obj_y = obj_coords[o][1]\n", + " plt.text(obj_x, obj_y, str(o), bbox=dict(facecolor='red', alpha=0.5))\n", + " plt.plot([obj_x, box_x], [obj_y, box_y])\n", + "\n", + "except ImportError:\n", + " print(\"matplotlib not found, nothing will be displayed\")\n", + " plt = None\n", + " def display_solution(sol): pass\n" ] }, { @@ -581,7 +587,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Copyright © 2017-2019 IBM. IPLA licensed Sample Materials." + "Copyright © 2017-2021 IBM. IPLA licensed Sample Materials." ] }, { diff --git a/examples/mp/jupyter/green_truck.ipynb b/examples/mp/jupyter/green_truck.ipynb index e88ee83..7ea6139 100644 --- a/examples/mp/jupyter/green_truck.ipynb +++ b/examples/mp/jupyter/green_truck.ipynb @@ -124,9 +124,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "try:\n", @@ -148,9 +146,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from collections import namedtuple" @@ -189,9 +185,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "def read_json_tuples(name, my_namedtuple):\n", @@ -335,9 +329,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# Make sure the data is consistent: latest arrive time >= earliest departure time\n", @@ -368,9 +360,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from math import ceil, floor\n", @@ -416,9 +406,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from docplex.mp.model import Model\n", @@ -511,9 +499,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "for tr in triples:\n", @@ -629,9 +615,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "#solution object model\n", @@ -687,9 +671,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "from IPython.display import display" diff --git a/examples/mp/jupyter/incremental_modeling.ipynb b/examples/mp/jupyter/incremental_modeling.ipynb index e048a35..1152469 100644 --- a/examples/mp/jupyter/incremental_modeling.ipynb +++ b/examples/mp/jupyter/incremental_modeling.ipynb @@ -168,9 +168,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# first import the Model class from docplex.mp\n", @@ -193,9 +191,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# by default, all variables in Docplex have a lower bound of 0 and infinite upper bound\n", @@ -206,9 +202,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "m.maximize(12 * desk + 20 * cell)\n", @@ -343,9 +337,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "hybrid = m.integer_var(name='hybrid')" @@ -367,8 +359,7 @@ "metadata": {}, "outputs": [], "source": [ - "m.add_constraint(hybrid >= 350)\n", - ";" + "m.add_constraint(hybrid >= 350);" ] }, { @@ -391,8 +382,8 @@ "metadata": {}, "outputs": [], "source": [ - "m.get_objective_expr().add_term(hybrid, 10)\n", - ";" + "m.objective_expr.add_term(hybrid, 10)\n", + "print(f\"objective={m.objective_expr}\")" ] }, { @@ -426,8 +417,7 @@ "outputs": [], "source": [ "m.get_constraint_by_name(\"assembly_limit\").lhs.add_term(hybrid, 0.2)\n", - "ct_painting.lhs.add_term(hybrid, 0.2)\n", - ";" + "ct_painting.lhs.add_term(hybrid, 0.2);" ] }, { @@ -493,9 +483,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "# constraint: polishing time limit\n", @@ -515,6 +503,43 @@ " print(\"model can't solve\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "A very brutal solution to make the model feasible again is to remove the new constrains, using `Model.remove_constraint`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.remove_constraint(ct_polishing)\n", + "msol1 = m.solve()\n", + "m.print_information()\n", + "if msol1:\n", + " print(\"Model without polishing is again feasible\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's add it again and try more subtle techniques to remedy infeasibility" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m.add(ct_polishing)\n", + "m.print_information()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -645,7 +670,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.6" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/infeasible.ipynb b/examples/mp/jupyter/infeasible.ipynb index c552782..f91ce45 100644 --- a/examples/mp/jupyter/infeasible.ipynb +++ b/examples/mp/jupyter/infeasible.ipynb @@ -70,10 +70,10 @@ "output_type": "stream", "text": [ "* system is: Windows 64bit\n", - "* Python version 3.7.4, located at: c:\\python\\anaconda531\\envs\\531_37\\python.exe\n", - "* docplex is present, version is (2, 11, 0)\n", - "* CPLEX library is present, version is 12.10.0.0, located at: C:\\OPTIM\\cplex_distrib\\cplex1210R0\\python\\3.7\\x64_win64\n", - "* pandas is present, version is 0.25.1\n" + "* Python version 3.7.8, located at: c:\\local\\python373\\python.exe\n", + "* docplex is present, version is 2.20.0\n", + "* CPLEX library is present, version is 20.1.0.0, located at: C:\\Program Files\\IBM\\ILOG\\CPLEX_Studio201\\cplex\\python\\3.7\\x64_win64\n", + "* pandas is present, version is 1.2.0\n" ] } ], @@ -113,6 +113,7 @@ " - number of constraints: 4\n", " - linear=4\n", " - parameters: defaults\n", + " - objective: none\n", " - problem type is: LP\n" ] } @@ -150,7 +151,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Version identifier: 12.10.0.0 | 2019-09-19 | b4d7cc16e3\n", + "Version identifier: 20.1.0.0 | 2020-11-10 | 9bedb6d68\n", "CPXPARAM_Read_DataCheck 1\n", "Constraints 'y_gt_x' and 'z_gt_y' are inconsistent.\n", "Presolve time = 0.00 sec. (0.00 ticks)\n", @@ -396,6 +397,7 @@ " - number of constraints: 5\n", " - linear=5\n", " - parameters: defaults\n", + " - objective: minimize\n", " - problem type is: LP\n" ] } @@ -473,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 12, "metadata": {}, "outputs": [ { diff --git a/examples/mp/jupyter/load_balancing.ipynb b/examples/mp/jupyter/load_balancing.ipynb index 1a95fe2..28c5a1b 100644 --- a/examples/mp/jupyter/load_balancing.ipynb +++ b/examples/mp/jupyter/load_balancing.ipynb @@ -96,7 +96,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -119,7 +119,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -128,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -139,7 +139,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -151,7 +151,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -163,7 +163,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -193,7 +193,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -224,7 +224,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -242,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -259,7 +259,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -281,9 +281,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: load_balancing\n", + " - number of variables: 582\n", + " - binary=581, integer=1, continuous=0\n", + " - number of constraints: 7\n", + " - linear=7\n", + " - parameters: defaults\n", + " - objective: none\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "mdl.add_constraints(\n", " mdl.sum(assign_user_to_server_vars[u, s] * u.running for u in users) <= max_processes_per_server\n", @@ -293,7 +308,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "metadata": {}, "outputs": [], "source": [ @@ -306,9 +321,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: load_balancing\n", + " - number of variables: 582\n", + " - binary=581, integer=1, continuous=0\n", + " - number of constraints: 663\n", + " - linear=663\n", + " - parameters: defaults\n", + " - objective: none\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "# sum of assignment vars for (u, all s in servers) == 1\n", "for u in users:\n", @@ -319,9 +349,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: load_balancing\n", + " - number of variables: 582\n", + " - binary=581, integer=1, continuous=0\n", + " - number of constraints: 670\n", + " - linear=670\n", + " - parameters: defaults\n", + " - objective: none\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "number_of_active_servers = mdl.sum((active_var_by_server[svr] for svr in servers))\n", "mdl.add_kpi(number_of_active_servers, \"Number of active servers\")\n", @@ -352,9 +397,24 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: load_balancing\n", + " - number of variables: 582\n", + " - binary=581, integer=1, continuous=0\n", + " - number of constraints: 670\n", + " - linear=670\n", + " - parameters: defaults\n", + " - objective: minimize\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ "# Set objective function\n", "mdl.minimize(number_of_active_servers)\n", @@ -375,9 +435,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* model load_balancing solved with objective = 82.000\n", + "* KPI: Number of active servers = 2.000\n", + "* KPI: Total number of migrations = 52.000\n", + "* KPI: Max sleeping workload = 82.000\n" + ] + } + ], "source": [ "# build an ordered sequence of goals\n", "ordered_kpi_keywords = [\"servers\", \"migrations\", \"sleeping\"]\n", @@ -398,9 +469,103 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Active Servers: ['server003', 'server004']\n", + "*** User assignment ***\n", + "user001 uses server004, migration: yes\n", + "user002 uses server004, migration: yes\n", + "user003 uses server003, migration: yes\n", + "user004 uses server004, migration: yes\n", + "user005 uses server004, migration: yes\n", + "user006 uses server004, migration: yes\n", + "user007 uses server003, migration: yes\n", + "user008 uses server003, migration: yes\n", + "user009 uses server004, migration: yes\n", + "user010 uses server003, migration: yes\n", + "user011 uses server003, migration: yes\n", + "user012 uses server003, migration: yes\n", + "user013 uses server004, migration: yes\n", + "user014 uses server003, migration: yes\n", + "user015 uses server004, migration: yes\n", + "user016 uses server004, migration: yes\n", + "user017 uses server003, migration: yes\n", + "user018 uses server003, migration: yes\n", + "user019 uses server003, migration: yes\n", + "user020 uses server003, migration: yes\n", + "user021 uses server004, migration: yes\n", + "user022 uses server004, migration: yes\n", + "user023 uses server004, migration: yes\n", + "user024 uses server004, migration: yes\n", + "user025 uses server003, migration: no\n", + "user026 uses server003, migration: no\n", + "user027 uses server003, migration: no\n", + "user028 uses server003, migration: no\n", + "user029 uses server003, migration: no\n", + "user030 uses server003, migration: no\n", + "user031 uses server003, migration: no\n", + "user032 uses server003, migration: no\n", + "user033 uses server003, migration: no\n", + "user034 uses server003, migration: no\n", + "user035 uses server003, migration: no\n", + "user036 uses server003, migration: no\n", + "user037 uses server003, migration: no\n", + "user038 uses server003, migration: no\n", + "user039 uses server003, migration: no\n", + "user040 uses server003, migration: no\n", + "user041 uses server004, migration: no\n", + "user042 uses server004, migration: no\n", + "user043 uses server004, migration: no\n", + "user044 uses server004, migration: no\n", + "user045 uses server004, migration: no\n", + "user046 uses server004, migration: no\n", + "user047 uses server004, migration: no\n", + "user048 uses server004, migration: no\n", + "user049 uses server004, migration: no\n", + "user050 uses server004, migration: no\n", + "user051 uses server004, migration: no\n", + "user052 uses server004, migration: no\n", + "user053 uses server004, migration: no\n", + "user054 uses server004, migration: no\n", + "user055 uses server004, migration: yes\n", + "user056 uses server003, migration: yes\n", + "user057 uses server003, migration: yes\n", + "user058 uses server004, migration: yes\n", + "user059 uses server003, migration: yes\n", + "user060 uses server003, migration: yes\n", + "user061 uses server003, migration: yes\n", + "user062 uses server004, migration: yes\n", + "user063 uses server003, migration: yes\n", + "user064 uses server004, migration: yes\n", + "user065 uses server004, migration: yes\n", + "user066 uses server003, migration: yes\n", + "user067 uses server004, migration: yes\n", + "user068 uses server003, migration: yes\n", + "user069 uses server004, migration: yes\n", + "user070 uses server003, migration: yes\n", + "user071 uses server004, migration: yes\n", + "user072 uses server004, migration: yes\n", + "user073 uses server003, migration: yes\n", + "user074 uses server004, migration: yes\n", + "user075 uses server003, migration: yes\n", + "user076 uses server004, migration: yes\n", + "user077 uses server004, migration: yes\n", + "user078 uses server004, migration: yes\n", + "user079 uses server003, migration: yes\n", + "user080 uses server004, migration: yes\n", + "user081 uses server004, migration: yes\n", + "user082 uses server004, migration: yes\n", + "*** Servers sleeping processes ***\n", + "Server: server003 #sleeping=82.0\n", + "Server: server004 #sleeping=82.0\n" + ] + } + ], "source": [ "active_servers = sorted([s for s in servers if active_var_by_server[s].solution_value == 1])\n", "\n", diff --git a/examples/mp/jupyter/marketing_campaign.ipynb b/examples/mp/jupyter/marketing_campaign.ipynb index 80cbd94..47d8f4e 100644 --- a/examples/mp/jupyter/marketing_campaign.ipynb +++ b/examples/mp/jupyter/marketing_campaign.ipynb @@ -97,9 +97,70 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
namecostfactor
0gift20.00.20
1newsletter15.00.05
2seminar23.00.30
\n", + "
" + ], + "text/plain": [ + " name cost factor\n", + "0 gift 20.0 0.20\n", + "1 newsletter 15.0 0.05\n", + "2 seminar 23.0 0.30" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "import pandas as pd\n", + "from pandas import DataFrame, Series\n", "\n", "names = {\n", " 139987 : \"Guadalupe J. Martinez\", 140030 : \"Michelle M. Lopez\", 140089 : \"Terry L. Ridgley\", \n", @@ -122,12 +183,13 @@ " (139667, \"Pension\", 0.13221, \"Mortgage\", 0.10675), (139696, \"Savings\", 0.16188, \"Pension\", 0.13221), (139752, \"Pension\", 0.13221, \"Mortgage\", 0.10675), \n", " (139832, \"Savings\", 0.95678, \"Pension\", 0.83426), (139859, \"Savings\", 0.95678, \"Pension\", 0.75925), (139881, \"Pension\", 0.13221, \"Mortgage\", 0.10675)]\n", "\n", - "products = [\"Car loan\", \"Savings\", \"Mortgage\", \"Pension\"]\n", - "productValue = [100, 200, 300, 400]\n", - "budgetShare = [0.6, 0.1, 0.2, 0.1]\n", + "products = [\"Savings\", \"Mortgage\", \"Pension\"]\n", + "product_value = [200, 300, 400]\n", + "budget_share = [0.2, 0.5, 0.3]\n", "\n", - "availableBudget = 500\n", - "channels = pd.DataFrame(data=[(\"gift\", 20.0, 0.20), (\"newsletter\", 15.0, 0.05), (\"seminar\", 23.0, 0.30)], columns=[\"name\", \"cost\", \"factor\"])" + "available_budget = 500\n", + "channels = DataFrame(data=[(\"gift\", 20.0, 0.20), (\"newsletter\", 15.0, 0.05), (\"seminar\", 23.0, 0.30)], columns=[\"name\", \"cost\", \"factor\"])\n", + "channels.head(5)" ] }, { @@ -143,11 +205,8 @@ "metadata": {}, "outputs": [], "source": [ - "try: # Python 2\n", - " offers = pd.DataFrame(data=data, index=xrange(0, len(data)), columns=[\"customerid\", \"Product1\", \"Confidence1\", \"Product2\", \"Confidence2\"])\n", - "except: # Python 3\n", - " offers = pd.DataFrame(data=data, index=range(0, len(data)), columns=[\"customerid\", \"Product1\", \"Confidence1\", \"Product2\", \"Confidence2\"])\n", - "offers.insert(0,'name',pd.Series(names[i[0]] for i in data))" + "offers = DataFrame(data=data, index=range(0, len(data)), columns=[\"customerid\", \"Product1\", \"Confidence1\", \"Product2\", \"Confidence2\"])\n", + "offers.insert(0,'name', Series(names[i[0]] for i in data))" ] }, { @@ -238,7 +297,7 @@ " \n", " \n", " \n", - " 17\n", + " 17\n", " Cassio Lombardo\n", " Pension\n", " 0.13221\n", @@ -246,7 +305,7 @@ " 0.10675\n", " \n", " \n", - " 7\n", + " 7\n", " Christian Austerlitz\n", " Pension\n", " 0.13221\n", @@ -254,7 +313,7 @@ " 0.10675\n", " \n", " \n", - " 24\n", + " 24\n", " Earl B. Wood\n", " Savings\n", " 0.95678\n", @@ -262,7 +321,7 @@ " 0.83426\n", " \n", " \n", - " 19\n", + " 19\n", " Eldar Muravyov\n", " Pension\n", " 0.13221\n", @@ -270,7 +329,7 @@ " 0.10675\n", " \n", " \n", - " 6\n", + " 6\n", " Fabien Mailhot\n", " Pension\n", " 0.13221\n", @@ -278,7 +337,7 @@ " 0.10675\n", " \n", " \n", - " 26\n", + " 26\n", " Franca Palermo\n", " Pension\n", " 0.13221\n", @@ -286,7 +345,7 @@ " 0.10675\n", " \n", " \n", - " 25\n", + " 25\n", " Gabrielly Sousa Martins\n", " Savings\n", " 0.95678\n", @@ -294,7 +353,7 @@ " 0.75925\n", " \n", " \n", - " 13\n", + " 13\n", " George Blomqvist\n", " Savings\n", " 0.16428\n", @@ -302,7 +361,7 @@ " 0.13221\n", " \n", " \n", - " 0\n", + " 0\n", " Guadalupe J. Martinez\n", " Pension\n", " 0.13221\n", @@ -310,7 +369,7 @@ " 0.10675\n", " \n", " \n", - " 21\n", + " 21\n", " Jameel Abdul-Ghani Gerges\n", " Pension\n", " 0.13221\n", @@ -318,7 +377,7 @@ " 0.10675\n", " \n", " \n", - " 10\n", + " 10\n", " Lee Tsou\n", " Pension\n", " 0.13221\n", @@ -326,7 +385,7 @@ " 0.10675\n", " \n", " \n", - " 23\n", + " 23\n", " Matheus Azevedo Melo\n", " Pension\n", " 0.13221\n", @@ -334,7 +393,7 @@ " 0.10675\n", " \n", " \n", - " 1\n", + " 1\n", " Michelle M. Lopez\n", " Savings\n", " 0.95678\n", @@ -342,7 +401,7 @@ " 0.84446\n", " \n", " \n", - " 3\n", + " 3\n", " Miranda B. Roush\n", " Pension\n", " 0.13221\n", @@ -350,7 +409,7 @@ " 0.10675\n", " \n", " \n", - " 12\n", + " 12\n", " Miroslav Škaroupka\n", " Savings\n", " 0.95676\n", @@ -358,7 +417,7 @@ " 0.82269\n", " \n", " \n", - " 5\n", + " 5\n", " Roland Guérette\n", " Pension\n", " 0.13221\n", @@ -366,7 +425,7 @@ " 0.10675\n", " \n", " \n", - " 11\n", + " 11\n", " Sanaa' Hikmah Hakimi\n", " Pension\n", " 0.13221\n", @@ -374,7 +433,7 @@ " 0.10675\n", " \n", " \n", - " 4\n", + " 4\n", " Sandra J. Wynkoop\n", " Pension\n", " 0.80506\n", @@ -382,7 +441,7 @@ " 0.28391\n", " \n", " \n", - " 20\n", + " 20\n", " Shu T'an\n", " Savings\n", " 0.95675\n", @@ -390,7 +449,7 @@ " 0.27248\n", " \n", " \n", - " 8\n", + " 8\n", " Steffen Meister\n", " Pension\n", " 0.13221\n", @@ -398,7 +457,7 @@ " 0.10675\n", " \n", " \n", - " 2\n", + " 2\n", " Terry L. Ridgley\n", " Savings\n", " 0.95678\n", @@ -406,7 +465,7 @@ " 0.80233\n", " \n", " \n", - " 18\n", + " 18\n", " Trinity Zelaya Miramontes\n", " Savings\n", " 0.28934\n", @@ -414,7 +473,7 @@ " 0.13221\n", " \n", " \n", - " 16\n", + " 16\n", " Vlad Alekseeva\n", " Pension\n", " 0.13221\n", @@ -422,7 +481,7 @@ " 0.10675\n", " \n", " \n", - " 14\n", + " 14\n", " Will Henderson\n", " Savings\n", " 0.95678\n", @@ -430,7 +489,7 @@ " 0.86779\n", " \n", " \n", - " 9\n", + " 9\n", " Wolfgang Sanger\n", " Pension\n", " 0.13221\n", @@ -438,7 +497,7 @@ " 0.10675\n", " \n", " \n", - " 15\n", + " 15\n", " Yuina Ohira\n", " Pension\n", " 0.13225\n", @@ -446,7 +505,7 @@ " 0.10675\n", " \n", " \n", - " 22\n", + " 22\n", " Zeeb Longoria Marrero\n", " Savings\n", " 0.16188\n", @@ -569,7 +628,8 @@ "source": [ "from docplex.mp.model import Model\n", "\n", - "mdl = Model(name=\"marketing_campaign\")" + "mdl = Model(name=\"marketing_campaign\")\n", + "mdl.round_solution = True # make sure integer vars are automatically rounded" ] }, { @@ -577,29 +637,54 @@ "metadata": {}, "source": [ "#### Define the decision variables\n", - "- The integer decision variables `channelVars`, represent whether or not a customer will be made an offer for a particular product via a particular channel.\n", - "- The integer decision variable `totaloffers` represents the total number of offers made.\n", - "- The continuous variable `budgetSpent` represents the total cost of the offers made." + "- The integer decision variables `channel_vars`, represent whether or not a customer will be made an offer for a particular product via a particular channel.\n", + "- The integer decision variable `total_offers` represents the total number of offers made.\n", + "- The continuous variable `budget_pent` represents the total cost of the offers made." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Model: marketing_campaign\n", + " - number of variables: 248\n", + " - binary=243, integer=1, continuous=4\n", + " - number of constraints: 0\n", + " - linear=0\n", + " - parameters: defaults\n", + " - objective: none\n", + " - problem type is: MILP\n" + ] + } + ], "source": [ - "try: # Python 2\n", - " offersR = xrange(0, len(offers))\n", - " productsR = xrange(0, len(products))\n", - " channelsR = xrange(0, len(channels))\n", - "except: # Python 3\n", - " offersR = range(0, len(offers))\n", - " productsR = range(0, len(products))\n", - " channelsR = range(0, len(channels))\n", + "offersR = range(0, len(offers))\n", + "productsR = range(0, len(products))\n", + "channelsR = range(0, len(channels))\n", + "\n", + "# names of channels\n", + "channel_names = channels['name'].tolist()\n", + "\n", + "# this function is used to coin names for channel variables\n", + "def name_chan_var(opch):\n", + " offer, p, ch = opch\n", + " return f\"ch_{offer}_{p}_{channel_names[ch]}\"\n", + "\n", + "channel_vars = mdl.binary_var_cube(offersR, productsR, channelsR, name=name_chan_var)\n", + "total_offers = mdl.integer_var(name=\"total_offers\")\n", + "budget_spent = mdl.continuous_var(name=\"spent\")\n", + "\n", + "def name_prod_budget(p):\n", + " return f\"product_budget_{products[p]}\"\n", + "\n", + "budget_per_product = mdl.continuous_var_list(productsR, name=name_prod_budget)\n", "\n", - "channelVars = mdl.binary_var_cube(offersR, productsR, channelsR)\n", - "totaloffers = mdl.integer_var(lb=0)\n", - "budgetSpent = mdl.continuous_var()" + "mdl.print_information()" ] }, { @@ -622,10 +707,10 @@ "output_type": "stream", "text": [ "Model: marketing_campaign\n", - " - number of variables: 326\n", - " - binary=324, integer=1, continuous=1\n", - " - number of constraints: 34\n", - " - linear=34\n", + " - number of variables: 248\n", + " - binary=243, integer=1, continuous=4\n", + " - number of constraints: 36\n", + " - linear=36\n", " - parameters: defaults\n", " - objective: none\n", " - problem type is: MILP\n" @@ -634,29 +719,28 @@ ], "source": [ "# Only 1 product is offered to each customer \n", - "mdl.add_constraints( mdl.sum(channelVars[o,p,c] for p in productsR for c in channelsR) <=1\n", + "mdl.add( mdl.sum(channel_vars[o,p,c] for p in productsR for c in channelsR) <= 1\n", " for o in offersR)\n", "\n", - "mdl.add_constraint( totaloffers == mdl.sum(channelVars[o,p,c] \n", - " for o in offersR \n", - " for p in productsR \n", - " for c in channelsR) )\n", + "# total offers is simply sum of channel vars\n", + "mdl.add( total_offers == mdl.sum(channel_vars))\n", "\n", - "mdl.add_constraint( budgetSpent == mdl.sum(channelVars[o,p,c]*channels.at[c, \"cost\"] \n", - " for o in offersR \n", - " for p in productsR \n", - " for c in channelsR) )\n", + "# define per product budgets\n", + "for p in productsR:\n", + " mdl.add(budget_per_product[p] == mdl.sum(channel_vars[o, p, c] * channels.at[c, \"cost\"]\n", + " for o in offersR\n", + " for c in channelsR))\n", + " \n", + "mdl.add( budget_spent == mdl.sum(budget_per_product))\n", "\n", - "# Balance the offers among products \n", + "# Balance the offers among products\n", + "assert sum(budget_share) == 1 # shares equal 1\n", "for p in productsR:\n", - " mdl.add_constraint( mdl.sum(channelVars[o,p,c] for o in offersR for c in channelsR) \n", - " <= budgetShare[p] * totaloffers )\n", + " mdl.add( mdl.sum(channel_vars[o,p,c] for o in offersR for c in channelsR) \n", + " <= budget_share[p] * total_offers )\n", " \n", "# Do not exceed the budget\n", - "mdl.add_constraint( mdl.sum(channelVars[o,p,c]*channels.at[c, \"cost\"] \n", - " for o in offersR \n", - " for p in productsR \n", - " for c in channelsR) <= availableBudget ) \n", + "mdl.add_constraint( budget_spent <= available_budget ) \n", "\n", "mdl.print_information()" ] @@ -676,26 +760,27 @@ "metadata": {}, "outputs": [], "source": [ - "mdl.maximize(\n", - " mdl.sum( channelVars[idx,p,idx2] * c.factor * productValue[p]* o.Confidence1 \n", - " for p in productsR \n", + "expected_p1 = mdl.sum( channel_vars[idx,p,idx2] * c.factor * product_value[p]* o.Confidence1 \n", + " for p in productsR\n", " for idx,o in offers[offers['Product1'] == products[p]].iterrows() \n", " for idx2, c in channels.iterrows())\n", - " +\n", - " mdl.sum( channelVars[idx,p,idx2] * c.factor * productValue[p]* o.Confidence2 \n", - " for p in productsR \n", + "\n", + "expected_p2 = mdl.sum( channel_vars[idx,p,idx2] * c.factor * product_value[p]* o.Confidence2 \n", + " for p in productsR\n", " for idx,o in offers[offers['Product2'] == products[p]].iterrows() \n", " for idx2, c in channels.iterrows())\n", - " )" + "\n", + "\n", + "mdl.maximize(expected_p1 + expected_p2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Solve the model\n", + "#### Define KPIs\n", "\n", - "If you're using a Community Edition of CPLEX runtimes, depending on the size of the problem, the solve stage may fail and will need a paying subscription or product installation." + "KPIs are numbers, computed to give insights, not necessarily used in the optimization process." ] }, { @@ -704,10 +789,110 @@ "metadata": {}, "outputs": [], "source": [ - "s = mdl.solve()\n", + "for p in productsR:\n", + " mdl.add_kpi(budget_per_product[p], budget_per_product[p].name)\n", + " \n", + "mdl.add_kpi(expected_p1, \"Expected P1 return\");\n", + "mdl.add_kpi(expected_p2, \"Expected P2 return\");" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Solve the model\n", + "\n", + "If you're using a Community Edition of CPLEX runtimes, depending on the size of the problem, the solve stage may fail and will need a paying subscription or product installation." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Version identifier: 12.10.0.0 | 2019-09-19 | b4d7cc16e3\n", + "CPXPARAM_Read_DataCheck 1\n", + "Found incumbent of value 0.000000 after 0.00 sec. (0.01 ticks)\n", + "Tried aggregator 2 times.\n", + "MIP Presolve eliminated 1 rows and 55 columns.\n", + "MIP Presolve modified 3 coefficients.\n", + "Aggregator did 3 substitutions.\n", + "Reduced MIP has 32 rows, 190 columns, and 760 nonzeros.\n", + "Reduced MIP has 189 binaries, 1 generals, 0 SOSs, and 0 indicators.\n", + "Presolve time = 0.00 sec. (1.36 ticks)\n", + "Probing time = 0.00 sec. (0.40 ticks)\n", + "Tried aggregator 1 time.\n", + "Detecting symmetries...\n", + "Reduced MIP has 32 rows, 190 columns, and 760 nonzeros.\n", + "Reduced MIP has 189 binaries, 1 generals, 0 SOSs, and 0 indicators.\n", + "Presolve time = 0.00 sec. (0.59 ticks)\n", + "Probing time = 0.00 sec. (0.40 ticks)\n", + "Clique table members: 27.\n", + "MIP emphasis: balance optimality and feasibility.\n", + "MIP search method: dynamic search.\n", + "Parallel mode: deterministic, using up to 12 threads.\n", + "Root relaxation solution time = 0.00 sec. (0.31 ticks)\n", + "\n", + " Nodes Cuts/\n", + " Node Left Objective IInf Best Integer Best Bound ItCnt Gap\n", + "\n", + "* 0+ 0 0.0000 2946.6993 --- \n", + " 0 0 862.3006 5 0.0000 862.3006 30 --- \n", + " 0 0 844.9441 9 0.0000 Cuts: 7 57 --- \n", + " 0 0 844.9441 11 0.0000 Cuts: 6 65 --- \n", + "* 0 0 integral 0 844.4226 Cuts: 3 71 0.00%\n", + " 0 0 cutoff 844.4226 71 --- \n", + "Elapsed time = 0.13 sec. (10.87 ticks, tree = 0.01 MB, solutions = 2)\n", + "\n", + "Cover cuts applied: 1\n", + "Mixed integer rounding cuts applied: 2\n", + "Zero-half cuts applied: 3\n", + "Lift and project cuts applied: 1\n", + "Gomory fractional cuts applied: 1\n", + "\n", + "Root node processing (before b&c):\n", + " Real time = 0.13 sec. (10.88 ticks)\n", + "Parallel b&c, 12 threads:\n", + " Real time = 0.00 sec. (0.00 ticks)\n", + " Sync time (average) = 0.00 sec.\n", + " Wait time (average) = 0.00 sec.\n", + " ------------\n", + "Total (root+branch&cut) = 0.13 sec. (10.88 ticks)\n" + ] + } + ], + "source": [ + "s = mdl.solve(log_output=True)\n", "assert s, \"No Solution !!!\"" ] }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "* model marketing_campaign solved with objective = 844.423\n", + "* KPI: product_budget_Savings = 92.000\n", + "* KPI: product_budget_Mortgage = 230.000\n", + "* KPI: product_budget_Pension = 138.000\n", + "* KPI: Expected P1 return = 190.942\n", + "* KPI: Expected P2 return = 653.480\n" + ] + } + ], + "source": [ + "# print objectives and kpis\n", + "mdl.report()" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -719,14 +904,14 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Marketing plan has 20 offers costing 364.0\n" + "Marketing plan has 20 offers costing 460.0\n" ] }, { @@ -757,151 +942,151 @@ " \n", " \n", " \n", - " 0\n", - " newsletter\n", - " Car loan\n", - " Fabien Mailhot\n", - " \n", - " \n", - " 1\n", - " newsletter\n", - " Car loan\n", - " Christian Austerlitz\n", - " \n", - " \n", - " 2\n", - " newsletter\n", - " Car loan\n", - " Lee Tsou\n", - " \n", - " \n", - " 3\n", - " newsletter\n", - " Car loan\n", - " Sanaa' Hikmah Hakimi\n", - " \n", - " \n", - " 4\n", - " newsletter\n", - " Car loan\n", + " 0\n", + " seminar\n", + " Savings\n", " George Blomqvist\n", " \n", " \n", - " 5\n", - " newsletter\n", - " Car loan\n", - " Yuina Ohira\n", + " 1\n", + " seminar\n", + " Savings\n", + " Trinity Zelaya Miramontes\n", " \n", " \n", - " 6\n", - " newsletter\n", - " Car loan\n", - " Vlad Alekseeva\n", + " 2\n", + " seminar\n", + " Savings\n", + " Shu T'an\n", " \n", " \n", - " 7\n", - " newsletter\n", - " Car loan\n", - " Cassio Lombardo\n", + " 3\n", + " seminar\n", + " Savings\n", + " Zeeb Longoria Marrero\n", " \n", " \n", - " 8\n", - " newsletter\n", - " Car loan\n", - " Trinity Zelaya Miramontes\n", + " 4\n", + " seminar\n", + " Mortgage\n", + " Guadalupe J. Martinez\n", " \n", " \n", - " 9\n", - " newsletter\n", - " Car loan\n", - " Eldar Muravyov\n", + " 5\n", + " seminar\n", + " Mortgage\n", + " Miranda B. Roush\n", " \n", " \n", - " 10\n", - " newsletter\n", - " Car loan\n", - " Jameel Abdul-Ghani Gerges\n", + " 6\n", + " seminar\n", + " Mortgage\n", + " Roland Guérette\n", " \n", " \n", - " 11\n", - " newsletter\n", - " Car loan\n", - " Zeeb Longoria Marrero\n", + " 7\n", + " seminar\n", + " Mortgage\n", + " Fabien Mailhot\n", " \n", " \n", - " 12\n", + " 8\n", " seminar\n", - " Savings\n", - " Terry L. Ridgley\n", + " Mortgage\n", + " Lee Tsou\n", " \n", " \n", - " 13\n", + " 9\n", " seminar\n", - " Savings\n", - " Gabrielly Sousa Martins\n", + " Mortgage\n", + " Miroslav Škaroupka\n", " \n", " \n", - " 14\n", + " 10\n", " seminar\n", " Mortgage\n", - " Miranda B. Roush\n", + " Yuina Ohira\n", " \n", " \n", - " 15\n", + " 11\n", " seminar\n", " Mortgage\n", - " Miroslav Škaroupka\n", + " Vlad Alekseeva\n", " \n", " \n", - " 16\n", + " 12\n", " seminar\n", " Mortgage\n", " Matheus Azevedo Melo\n", " \n", " \n", - " 17\n", + " 13\n", " seminar\n", " Mortgage\n", " Franca Palermo\n", " \n", " \n", - " 18\n", + " 14\n", " seminar\n", " Pension\n", " Michelle M. Lopez\n", " \n", " \n", - " 19\n", + " 15\n", + " seminar\n", + " Pension\n", + " Terry L. Ridgley\n", + " \n", + " \n", + " 16\n", + " seminar\n", + " Pension\n", + " Sandra J. Wynkoop\n", + " \n", + " \n", + " 17\n", " seminar\n", " Pension\n", " Will Henderson\n", " \n", + " \n", + " 18\n", + " seminar\n", + " Pension\n", + " Earl B. Wood\n", + " \n", + " \n", + " 19\n", + " seminar\n", + " Pension\n", + " Gabrielly Sousa Martins\n", + " \n", " \n", "\n", "" ], "text/plain": [ - " channel product customer\n", - "0 newsletter Car loan Fabien Mailhot\n", - "1 newsletter Car loan Christian Austerlitz\n", - "2 newsletter Car loan Lee Tsou\n", - "3 newsletter Car loan Sanaa' Hikmah Hakimi\n", - "4 newsletter Car loan George Blomqvist\n", - "5 newsletter Car loan Yuina Ohira\n", - "6 newsletter Car loan Vlad Alekseeva\n", - "7 newsletter Car loan Cassio Lombardo\n", - "8 newsletter Car loan Trinity Zelaya Miramontes\n", - "9 newsletter Car loan Eldar Muravyov\n", - "10 newsletter Car loan Jameel Abdul-Ghani Gerges\n", - "11 newsletter Car loan Zeeb Longoria Marrero\n", - "12 seminar Savings Terry L. Ridgley\n", - "13 seminar Savings Gabrielly Sousa Martins\n", - "14 seminar Mortgage Miranda B. Roush\n", - "15 seminar Mortgage Miroslav Škaroupka\n", - "16 seminar Mortgage Matheus Azevedo Melo\n", - "17 seminar Mortgage Franca Palermo\n", - "18 seminar Pension Michelle M. Lopez\n", - "19 seminar Pension Will Henderson" + " channel product customer\n", + "0 seminar Savings George Blomqvist\n", + "1 seminar Savings Trinity Zelaya Miramontes\n", + "2 seminar Savings Shu T'an\n", + "3 seminar Savings Zeeb Longoria Marrero\n", + "4 seminar Mortgage Guadalupe J. Martinez\n", + "5 seminar Mortgage Miranda B. Roush\n", + "6 seminar Mortgage Roland Guérette\n", + "7 seminar Mortgage Fabien Mailhot\n", + "8 seminar Mortgage Lee Tsou\n", + "9 seminar Mortgage Miroslav Škaroupka\n", + "10 seminar Mortgage Yuina Ohira\n", + "11 seminar Mortgage Vlad Alekseeva\n", + "12 seminar Mortgage Matheus Azevedo Melo\n", + "13 seminar Mortgage Franca Palermo\n", + "14 seminar Pension Michelle M. Lopez\n", + "15 seminar Pension Terry L. Ridgley\n", + "16 seminar Pension Sandra J. Wynkoop\n", + "17 seminar Pension Will Henderson\n", + "18 seminar Pension Earl B. Wood\n", + "19 seminar Pension Gabrielly Sousa Martins" ] }, "metadata": {}, @@ -911,14 +1096,14 @@ "source": [ "report = [(channels.at[c, \"name\"], products[p], names[offers.at[o, \"customerid\"]]) \n", " for c in channelsR \n", - " for p in productsR \n", - " for o in offersR if channelVars[o,p,c].solution_value==1]\n", + " for p in productsR\n", + " for o in offersR if abs(channel_vars[o,p,c].solution_value-1) <= 1e-6]\n", "\n", - "assert len(report) == totaloffers.solution_value\n", + "assert len(report) == round(total_offers.solution_value)\n", "\n", - "print(\"Marketing plan has {0} offers costing {1}\".format(totaloffers.solution_value, budgetSpent.solution_value))\n", + "print(\"Marketing plan has {0} offers costing {1}\".format(total_offers.solution_value, budget_spent.solution_value))\n", "\n", - "report_bd = pd.DataFrame(report, columns=['channel', 'product', 'customer'])\n", + "report_bd = DataFrame(report, columns=['channel', 'product', 'customer'])\n", "display(report_bd)" ] }, @@ -932,7 +1117,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -962,59 +1147,131 @@ " \n", " \n", " \n", - " 12\n", + " 0\n", " Savings\n", - " Terry L. Ridgley\n", + " George Blomqvist\n", " \n", " \n", - " 13\n", + " 1\n", " Savings\n", - " Gabrielly Sousa Martins\n", + " Trinity Zelaya Miramontes\n", " \n", " \n", - " 14\n", + " 2\n", + " Savings\n", + " Shu T'an\n", + " \n", + " \n", + " 3\n", + " Savings\n", + " Zeeb Longoria Marrero\n", + " \n", + " \n", + " 4\n", + " Mortgage\n", + " Guadalupe J. Martinez\n", + " \n", + " \n", + " 5\n", " Mortgage\n", " Miranda B. Roush\n", " \n", " \n", - " 15\n", + " 6\n", + " Mortgage\n", + " Roland Guérette\n", + " \n", + " \n", + " 7\n", + " Mortgage\n", + " Fabien Mailhot\n", + " \n", + " \n", + " 8\n", + " Mortgage\n", + " Lee Tsou\n", + " \n", + " \n", + " 9\n", " Mortgage\n", " Miroslav Škaroupka\n", " \n", " \n", - " 16\n", + " 10\n", + " Mortgage\n", + " Yuina Ohira\n", + " \n", + " \n", + " 11\n", + " Mortgage\n", + " Vlad Alekseeva\n", + " \n", + " \n", + " 12\n", " Mortgage\n", " Matheus Azevedo Melo\n", " \n", " \n", - " 17\n", + " 13\n", " Mortgage\n", " Franca Palermo\n", " \n", " \n", - " 18\n", + " 14\n", " Pension\n", " Michelle M. Lopez\n", " \n", " \n", - " 19\n", + " 15\n", + " Pension\n", + " Terry L. Ridgley\n", + " \n", + " \n", + " 16\n", + " Pension\n", + " Sandra J. Wynkoop\n", + " \n", + " \n", + " 17\n", " Pension\n", " Will Henderson\n", " \n", + " \n", + " 18\n", + " Pension\n", + " Earl B. Wood\n", + " \n", + " \n", + " 19\n", + " Pension\n", + " Gabrielly Sousa Martins\n", + " \n", " \n", "\n", "" ], "text/plain": [ - " product customer\n", - "12 Savings Terry L. Ridgley\n", - "13 Savings Gabrielly Sousa Martins\n", - "14 Mortgage Miranda B. Roush\n", - "15 Mortgage Miroslav Škaroupka\n", - "16 Mortgage Matheus Azevedo Melo\n", - "17 Mortgage Franca Palermo\n", - "18 Pension Michelle M. Lopez\n", - "19 Pension Will Henderson" + " product customer\n", + "0 Savings George Blomqvist\n", + "1 Savings Trinity Zelaya Miramontes\n", + "2 Savings Shu T'an\n", + "3 Savings Zeeb Longoria Marrero\n", + "4 Mortgage Guadalupe J. Martinez\n", + "5 Mortgage Miranda B. Roush\n", + "6 Mortgage Roland Guérette\n", + "7 Mortgage Fabien Mailhot\n", + "8 Mortgage Lee Tsou\n", + "9 Mortgage Miroslav Škaroupka\n", + "10 Mortgage Yuina Ohira\n", + "11 Mortgage Vlad Alekseeva\n", + "12 Mortgage Matheus Azevedo Melo\n", + "13 Mortgage Franca Palermo\n", + "14 Pension Michelle M. Lopez\n", + "15 Pension Terry L. Ridgley\n", + "16 Pension Sandra J. Wynkoop\n", + "17 Pension Will Henderson\n", + "18 Pension Earl B. Wood\n", + "19 Pension Gabrielly Sousa Martins" ] }, "metadata": {}, @@ -1068,9 +1325,9 @@ "celltoolbar": "Dashboard", "gist_id": "6011986", "kernelspec": { - "display_name": "Python 3", + "display_name": "docplex37", "language": "python", - "name": "python3" + "name": "docplex37" }, "language_info": { "codemirror_mode": { @@ -1082,7 +1339,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.8" + "version": "3.7.5" } }, "nbformat": 4, diff --git a/examples/mp/jupyter/sktrans/transformers.ipynb b/examples/mp/jupyter/sktrans/transformers.ipynb index 6b49485..14a6007 100644 --- a/examples/mp/jupyter/sktrans/transformers.ipynb +++ b/examples/mp/jupyter/sktrans/transformers.ipynb @@ -170,7 +170,7 @@ }, "outputs": [], "source": [ - "mat_fn = np.matrix([FOOD_NUTRIENTS[f][1:] for f in range(nb_foods)])\n", + "mat_fn = np.array([FOOD_NUTRIENTS[f][1:] for f in range(nb_foods)])\n", "print('The food-nutrient matrix has shape: {0}'.format(mat_fn.shape))" ] }, @@ -215,7 +215,7 @@ "outputs": [], "source": [ "# step 1. add two lines for nutrient mins, maxs\n", - "nf2 = np.append(mat_fn, np.matrix([nutrient_mins, nutrient_maxs]), axis=0)\n", + "nf2 = np.append(mat_fn, np.array([nutrient_mins, nutrient_maxs]), axis=0)\n", "mat_nf = nf2.transpose()" ] },