Decision Optimization Sensitivity Analysis

AlainChabrier
Analytics Vidhya
Published in
5 min readOct 22, 2019

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.

Example of generated demand

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.

Example of resource data

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:

Example of solution

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.

Displaying all outcomes from the notebook.

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.

Model builder visualizations.

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

--

--

AlainChabrier
Analytics Vidhya

Former Decision Optimization Senior Technical Staff Member at IBM Opinions are my own and I do not work for any company anymore.