This tutorial includes everything you need to set up IBM Decision Optimization CPLEX Modeling for Python (DOcplex), build a Mathematical Programming model, and get its solution by solving the model on the cloud with IBM ILOG CPLEX Optimizer.
When you finish this tutorial, you'll have a foundational knowledge of Prescriptive Analytics.
This notebook is part of Prescriptive Analytics for Python
It requires either an installation of CPLEX Optimizers or it can be run on IBM Watson Studio Cloud (Sign up for a free IBM Cloud account and you can start using Watson Studio Cloud right away).
Table of contents:
Prescriptive analytics (decision optimization) technology recommends actions that are based on desired outcomes. It takes into account specific scenarios, resources, and knowledge of past and current events. With this insight, your organization can make better decisions and have greater control of business outcomes.
Prescriptive analytics is the next step on the path to insight-based actions. It creates value through synergy with predictive analytics, which analyzes data to predict future outcomes.
Prescriptive analytics takes that insight to the next level by suggesting the optimal way to handle that future situation. Organizations that can act fast in dynamic conditions and make superior decisions in uncertain environments gain a strong competitive advantage.
With prescriptive analytics, you can:
In [ ]:
import sys
try:
import docplex.mp
except:
raise Exception('Please install docplex. See https://pypi.org/project/docplex/')
Note that the more global package docplex contains another subpackage docplex.cp that is dedicated to Constraint Programming, another branch of optimization.
data" : [ [ 1, "13BFA4C7-78CE-4D83-B53D-B57C60B701CF", 1, 1441918880, "885709", 1441918880, "885709", null, "Albany Park", "M, W: 10AM-6PM; TU, TH: 12PM-8PM; F, SA: 9AM-5PM; SU: Closed", "Yes", "Yes ", "3401 W. Foster Avenue", "CHICAGO", "IL", "60625", "(773) 539-5450", [ "http://www.chipublib.org/locations/1/", null ], [ null, "41.975456", "-87.71409", null, false ] ]
This code snippet represents library "3401 W. Foster Avenue" located at 41.975456, -87.71409Disclaimer: This site provides applications using data that has been modified for use from its original source, www.cityofchicago.org, the official website of the City of Chicago. The City of Chicago makes no claims as to the content, accuracy, timeliness, or completeness of any of the data provided at this site. The data provided at this site is subject to change at any time. It is understood that the data provided at this site is being used at one’s own risk.
In [ ]:
# Store longitude, latitude and street crossing name of each public library location.
class XPoint(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return "P(%g_%g)" % (self.x, self.y)
class NamedPoint(XPoint):
def __init__(self, name, x, y):
XPoint.__init__(self, x, y)
self.name = name
def __str__(self):
return self.name
To easily compute distance between 2 points, we use the Python package geopy
In [ ]:
try:
import geopy.distance
except:
if hasattr(sys, 'real_prefix'):
#we are in a virtual env.
!pip install geopy
else:
!pip install --user geopy
In [ ]:
# Simple distance computation between 2 locations.
from geopy.distance import great_circle
def get_distance(p1, p2):
return great_circle((p1.y, p1.x), (p2.y, p2.x)).miles
In [ ]:
def build_libraries_from_url(url):
import requests
import json
from six import iteritems
r = requests.get(url)
myjson = json.loads(r.text, parse_constant='utf-8')
# find columns for name and location
columns = myjson['meta']['view']['columns']
name_col = -1
location_col = -1
for (i, col) in enumerate(columns):
if col['name'].strip().lower() == 'name':
name_col = i
if col['name'].strip().lower() == 'location':
location_col = i
if (name_col == -1 or location_col == -1):
raise RuntimeError("Could not find name and location columns in data. Maybe format of %s changed?" % url)
# get library list
data = myjson['data']
libraries = []
k = 1
for location in data:
uname = location[name_col]
try:
latitude = float(location[location_col][1])
longitude = float(location[location_col][2])
except TypeError:
latitude = longitude = None
try:
name = str(uname)
except:
name = "???"
name = "P_%s_%d" % (name, k)
if latitude and longitude:
cp = NamedPoint(name, longitude, latitude)
libraries.append(cp)
k += 1
return libraries
In [ ]:
libraries = build_libraries_from_url('https://data.cityofchicago.org/api/views/x8fc-8rcq/rows.json?accessType=DOWNLOAD')
In [ ]:
print("There are %d public libraries in Chicago" % (len(libraries)))
In [ ]:
nb_shops = 5
print("We would like to open %d coffee shops" % nb_shops)
We will use the folium library to display a map with markers.
In [ ]:
try:
import folium
except:
if hasattr(sys, 'real_prefix'):
#we are in a virtual env.
!pip install folium
else:
!pip install --user folium
In [ ]:
import folium
map_osm = folium.Map(location=[41.878, -87.629], zoom_start=11)
for library in libraries:
lt = library.y
lg = library.x
folium.Marker([lt, lg]).add_to(map_osm)
map_osm
After running the above code, the data is displayed but it is impossible to determine where to ideally open the coffee shops by just looking at the map.
Let's set up DOcplex to write and solve an optimization model that will help us determine where to locate the coffee shops in an optimal way.
In [ ]:
from docplex.mp.environment import Environment
env = Environment()
env.print_information()
In [ ]:
from docplex.mp.model import Model
mdl = Model("coffee shops")
In [ ]:
BIGNUM = 999999999
# Ensure unique points
libraries = set(libraries)
# For simplicity, let's consider that coffee shops candidate locations are the same as libraries locations.
# That is: any library location can also be selected as a coffee shop.
coffeeshop_locations = libraries
# Decision vars
# Binary vars indicating which coffee shop locations will be actually selected
coffeeshop_vars = mdl.binary_var_dict(coffeeshop_locations, name="is_coffeeshop")
#
# Binary vars representing the "assigned" libraries for each coffee shop
link_vars = mdl.binary_var_matrix(coffeeshop_locations, libraries, "link")
In [ ]:
for c_loc in coffeeshop_locations:
for b in libraries:
if get_distance(c_loc, b) >= BIGNUM:
mdl.add_constraint(link_vars[c_loc, b] == 0, "ct_forbid_{0!s}_{1!s}".format(c_loc, b))
Second constraint: each library must be linked to a coffee shop that is open.
In [ ]:
mdl.add_constraints(link_vars[c_loc, b] <= coffeeshop_vars[c_loc]
for b in libraries
for c_loc in coffeeshop_locations)
mdl.print_information()
Third constraint: each library is linked to exactly one coffee shop.
In [ ]:
mdl.add_constraints(mdl.sum(link_vars[c_loc, b] for c_loc in coffeeshop_locations) == 1
for b in libraries)
mdl.print_information()
Fourth constraint: there is a fixed number of coffee shops to open.
In [ ]:
# Total nb of open coffee shops
mdl.add_constraint(mdl.sum(coffeeshop_vars[c_loc] for c_loc in coffeeshop_locations) == nb_shops)
# Print model information
mdl.print_information()
In [ ]:
# Minimize total distance from points to hubs
total_distance = mdl.sum(link_vars[c_loc, b] * get_distance(c_loc, b) for c_loc in coffeeshop_locations for b in libraries)
mdl.minimize(total_distance)
In [ ]:
print("# coffee shops locations = %d" % len(coffeeshop_locations))
print("# coffee shops = %d" % nb_shops)
assert mdl.solve(), "!!! Solve of the model fails"
In [ ]:
total_distance = mdl.objective_value
open_coffeeshops = [c_loc for c_loc in coffeeshop_locations if coffeeshop_vars[c_loc].solution_value == 1]
not_coffeeshops = [c_loc for c_loc in coffeeshop_locations if c_loc not in open_coffeeshops]
edges = [(c_loc, b) for b in libraries for c_loc in coffeeshop_locations if int(link_vars[c_loc, b]) == 1]
print("Total distance = %g" % total_distance)
print("# coffee shops = {0}".format(len(open_coffeeshops)))
for c in open_coffeeshops:
print("new coffee shop: {0!s}".format(c))
In [ ]:
import folium
map_osm = folium.Map(location=[41.878, -87.629], zoom_start=11)
for coffeeshop in open_coffeeshops:
lt = coffeeshop.y
lg = coffeeshop.x
folium.Marker([lt, lg], icon=folium.Icon(color='red',icon='info-sign')).add_to(map_osm)
for b in libraries:
if b not in open_coffeeshops:
lt = b.y
lg = b.x
folium.Marker([lt, lg]).add_to(map_osm)
for (c, b) in edges:
coordinates = [[c.y, c.x], [b.y, b.x]]
map_osm.add_child(folium.PolyLine(coordinates, color='#FF0000', weight=5))
map_osm
Copyright © 2017-2019 IBM. IPLA licensed Sample Materials.
In [ ]: