Decision Optimization Sensitivity Analysis
When you develop a new Decision Optimization model, you want to understand how new input data impact the solution. With Decision Optimization for Watson Studio, you can easily generate and solve many scenarios and see the impact.
The Staff Planning example
Let’s use a Staff Planning example. It is included as one of our examples and you can create a new model builder from this zipped file. The problem consists of deciding how much fixed and temporary employees you should hire to fulfill some demand at your shop or restaurant.
Random data is used to represent the demand for two days with some peaks at lunch and dinner.
The different resources are modeled as two simple types, each one with a different working duration and cost per day, with a minimum and maximum availability.
Then the optimization model is a simple covering model.
Decision variables include the number of working resources of each type, and the number of resource of each type starting to work and working at each period.
# start[r,t] is number of resource r starting to work at period t
start = mdl.integer_var_matrix(keys1=resources, keys2=periods, name="start")# work[r,t] is number of resource r working at period t
work = mdl.integer_var_matrix(keys1=resources, keys2=periods, name="work")# nr[r] is number of resource r working in total
nr = mdl.integer_var_dict(keys=resources, name="nr")# nr[r,d] is number of resource r working on day d
nrd = mdl.integer_var_matrix(keys1=resources, keys2=days, name="nrd")
Constraints are including the link between these variables, minimum and maximum availability and the coverage of the demand.
# available per day
for r in resources:
min_avail = int(df_resources[df_resources.id == r].min_avail)
max_avail = int(df_resources[df_resources.id == r].max_avail)
for d in range(N_DAYS):
mdl.add( mdl.sum(start[r,t] for t in range(d*N_PERIODS_PER_DAY, (d+1)*N_PERIODS_PER_DAY)) == nrd[r,d])
mdl.add( nrd[r,d] <= nr[r] )
mdl.add( min_avail <= nr[r] )
mdl.add( nr[r] <= max_avail )
# working
for r in resources:
duration = int(df_resources[df_resources.id == r].duration)*4
for t in periods:
mdl.add( mdl.sum(start[r,t2] for t2 in range(max(t-duration,0), t)) == work[r,t])
# work vs demand
for t in periods:
demand = int(df_demands[df_demands.period == t].demand)
mdl.add( mdl.sum( work[r,t] for r in resources) >= demand)
Finally, some KPIs and objectives are added.
total_cost = mdl.sum( int(df_resources[df_resources.id == r].cost)*nr[r] for r in resources)
n_fix_used = nr['fix']
n_temp_used = nr['temp']mdl.add_kpi(total_cost , "Total Cost")
mdl.add_kpi(n_fix_used , "Nb Fix Used")
mdl.add_kpi(n_temp_used , "Nb Temp Used")mdl.minimize(total_cost)
Solving this model, you get the number of fixed and temporary resources to hire to optimize your costs, and the starting times for each of them, as represented in the following chart:
All these charts are easily configured using the visualizations available in the model builder.
Sensitivity analysis
The next question is: how solutions are sensitive to the input data? Is the total objective or the split between fixed and temporary resources changing a lot from some demand distribution to another? This sensitivity analysis can be done in quite some different ways depending on how random data is generated (e.g. we could use historical data and weather predictions…) but also depending on what part of the solution should be considered as immutable during the analysis (e.g. we could fix the number of fixed employees).
All these methods will basically consists in (1) generating new input data scenarios, then (2) running the optimization model with optionally some of the solution frozen.
Let’s take the simplest case, where you want to run the model N times, with each time a new randomly generated demand., and then solve the optimization model without any frozen decisions.
You can do this very easily from a python notebooks where you can script how the scenarios are generated and solved.
You will use the dd_scenario
python package, which needs a project_context
and an apikey
to access, modify and solve scenarios. See this documentation for more details on how to get these.
First, you need to install the dd_scenario
package from pip.
pip install --user dd-scenario
Then import and create a new Client
using the model builder and scenario names.
from dd_scenario import *
# In order to use the solve() function, you must provide an API key when creating the client
client = Client(pc=pc, apikey=apikey)
decision = client.get_model_builder(name="StaffPlanning")
scenario = decision.get_scenario(name="Scenario 1")
Finally, copy and solve the scenario as many time as needed with the changes you need (here, new random demand is generated using the random_demand
function).
The code also creates a different my_kpis
output table and put all them aside.
all_kpis = pd.DataFrame()
for i in range(1, N_SCENARIOS+1):
sc_name = "Copy %02d" % (i)
print(sc_name) copy = decision.get_scenario(name=sc_name)
if (copy != None):
print(" Deleting old...")
decision.delete_container(copy) print(" Copying from original scenario...")
copy = scenario.copy(sc_name) print(" Generating new demand...")
df_demands = random_demand(200)
copy.add_table_data("demands", df_demands, category='input') print(" Solving...")
copy.solve() print(" Grabbing solution kpis...")
kpis = copy.get_table_data('kpis')
kpis['scenario'] = sc_name
mk = [[ kpis.iloc[0]['VALUE'], "%02d" % (kpis.iloc[1]['VALUE']), sc_name, "%02d" % (kpis.iloc[2]['VALUE'])]]
my_kpis = pd.DataFrame(data=mk, columns=['cost','fix','scenario','temp'])
copy.add_table_data('my_kpis', data=my_kpis, category='output')
all_kpis = all_kpis.append(kpis)
print("Done!")
Visualize sensitivity analysis results
You can visualize results in the notebook using Brunel for example.
See the complete notebook code here.
But you can also work with visualizations in the model builder and compare multiple scenarios. For example, you can create a 2D or 3D view of how the total cost varies according to the number of required fixed and temporary employees.
Try it!
You can try all this right now using the ongoing beta of the model builder on Watson Studio.
email: alain.chabrier@ibm.com
linkedin: https://www.linkedin.com/in/alain-chabrier-5430656/
twitter: https://twitter.com/AlainChabrier