Skip to content

Commit

Permalink
2.12.182
Browse files Browse the repository at this point in the history
vlkong committed Dec 19, 2019
1 parent 53cb00a commit 120aad5
Showing 2 changed files with 926 additions and 1 deletion.
925 changes: 925 additions & 0 deletions examples/mp/jupyter/infeasible.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,925 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"render": true
},
"source": [
"# Handling infeasible models with Docplex\n",
"\n",
"This tutorial includes everything you need to set up Decision Optimization engines, build a mathematical programming model, then use the progress listeners to monitor progress, capture intermediate solutions and stop the solve on your own criteria.\n",
"\n",
"\n",
"When you finish this tutorial, you'll have a foundational knowledge of _Prescriptive Analytics_.\n",
"\n",
">This notebook is part of **[Prescriptive Analytics for Python](http://ibmdecisionoptimization.github.io/docplex-doc/)**\n",
">\n",
">It requires either an [installation of CPLEX Optimizers](http://ibmdecisionoptimization.github.io/docplex-doc/getting_started.html) or it can be run on [IBM Watson Studio Cloud](https://www.ibm.com/cloud/watson-studio/>) (Sign up for a [free IBM Cloud account](https://dataplatform.cloud.ibm.com/registration/stepone?context=wdp&apps=all>)\n",
"and you can start using Watson Studio Cloud right away).\n",
"\n",
"\n",
"Table of contents:\n",
"\n",
"* [Use decision optimization](#Use-decision-optimization)\n",
" * [Step 1: Set up a basic infeasible model](#Step-1:-Set-up-the-prescriptive-model)\n",
" * [Step 2: Monitoring CPLEX progress](#Step-2:-Monitoring-CPLEX-progress)\n",
" * [Step 3: Aborting the search with a custom progress listener](#Step-3:-Aborting-the-search-with-a-custom-progress-listener)\n",
" * [Variant: using matplotlib to plot a chart of gap vs. time](#Variant:-using-matplotlib-to-plot-a-chart-of-gap-vs.-time)\n",
"* [Summary](#Summary)\n",
"****\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"render": true
},
"source": [
"## How Decision Optimization can help\n",
"\n",
"* 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. \n",
"\n",
"* 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. \n",
"\n",
"* 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. \n",
"<br/>\n",
"\n",
"<u>With prescriptive analytics, you can:</u> \n",
"\n",
"* Automate the complex decisions and trade-offs to better manage your limited resources.\n",
"* Take advantage of a future opportunity or mitigate a future risk.\n",
"* Proactively update recommendations based on changing events.\n",
"* Meet operational goals, increase customer loyalty, prevent threats and fraud, and optimize business processes.\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"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"
]
}
],
"source": [
"from docplex.mp.environment import Environment\n",
"Environment().print_information()"
]
},
{
"cell_type": "markdown",
"metadata": {
"render": true
},
"source": [
"## Example 1: handling a cyclic infeasible model. "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You start with a very simple infeasible model: you have three variables, each of which is greater than the previous one, in a cyclic fashion. Of course this leads to an infeasible model"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: cyclic3\n",
" - number of variables: 3\n",
" - binary=0, integer=0, continuous=3\n",
" - number of constraints: 4\n",
" - linear=4\n",
" - parameters: defaults\n",
" - problem type is: LP\n"
]
}
],
"source": [
"from docplex.mp.model import Model\n",
"\n",
"def build_infeasible_cyclic_model3():\n",
" m = Model(name='cyclic3')\n",
" x,y,z = m.continuous_var_list(keys=['x', 'y', 'z'], name=str)\n",
" m.add( y >= x+1, name=\"y_gt_x\")\n",
" m.add( z >= y+1, name=\"z_gt_y\") \n",
" m.add( x >= z+1, name=\"x_gt_z\")\n",
" # add another constraint, should noever appear in conflicts\n",
" m.add(x + y + z <= 33)\n",
" return m\n",
"\n",
"cycle3 = build_infeasible_cyclic_model3()\n",
"cycle3.print_information()\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, the model is infeasible."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Version identifier: 12.10.0.0 | 2019-09-19 | b4d7cc16e3\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",
"the model is infeasible\n"
]
}
],
"source": [
"s = cycle3.solve(log_output=True)\n",
"assert s is None\n",
"print(\"the model is infeasible\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using the conflict refiner on the infeasible cyclic model"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"First, you can use the Conflict refiner on this model. The conflict refiner computes a minimal cluster of constraints, which causes the infeasibility.\n",
"\n",
"Using the conflict refiner requires the following steps:\n",
"\n",
" - instantiate a `ConflictRefiner` instance\n",
" - call `refine_conflict' on the model.\n",
" \n",
"The output is an object of type `ConflictRefinerResults` which holds all information about the minimal conflict.\n",
"Displaying this result object lists all modeling objects which belong to the minimal conflict.\n",
" "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"conflicts: 3\n",
" - linear constraints: 3\n"
]
}
],
"source": [
"from docplex.mp.conflict_refiner import ConflictRefiner\n",
"\n",
"cr = ConflictRefiner()\n",
"crr = cr.refine_conflict(cycle3, display=True)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using the constraint relaxer on the infeasible cyclic model\n",
"\n",
"Another way to handle infeasibilities is to use the _relaxer_ (class `docplex.mp.relaxer.Relaxer`). The relaxer tries to find a _minimal_ feasible relaxation of the model, by relaxing certain constraints. \n",
"For example, a constraint `x == 1` can be relaxed with a slack of 1 to accept a value of 2 for x.\n",
"\n",
"the relaxer tries to minimize the total value of slack in finding a feasible relaxation."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 3\n",
" - relaxed: y_gt_x, with relaxation: -1.0\n",
" - relaxed: z_gt_y, with relaxation: -1.0\n",
" - relaxed: x_gt_z, with relaxation: -1.0\n",
"* total absolute relaxation: 3.0\n",
"solution for: cyclic3\n"
]
}
],
"source": [
"from docplex.mp.relaxer import Relaxer\n",
"\n",
"rx = Relaxer()\n",
"rs = rx.relax(cycle3)\n",
"rx.print_information()\n",
"rs.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The relaxer has relaxed one constraint ( x>= z+1) by 3, and found a solution wiht x=0, x=1, z=2, breaking the cyclic chain of constraints.\n",
"\n",
"Unlike the conflict refiner, the relaxer provides a _relaxed_ solution to the initial model, with minimal slack. But there's more: the relaxer can also search for the best business objective, once it has found the minimal slack.\n",
"\n",
"To illustrate, add an objective to your model, try to minimize z, and see what happens:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 2\n",
" - relaxed: z_gt_y, with relaxation: -2.0\n",
" - relaxed: x_gt_z, with relaxation: -1.0\n",
"* total absolute relaxation: 3.0\n",
"solution for: cyclic3\n",
"z: 0.000\n",
"y = 1.000\n"
]
}
],
"source": [
"# retrieve the z variable using Model.get_var_by_name()\n",
"z = cycle3.get_var_by_name('z')\n",
"assert z\n",
"\n",
"cycle3.minimize(z)\n",
"rs = rx.relax(cycle3)\n",
"rx.print_information()\n",
"rs.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Th relaxed solution has changed, finding a minimum objective of 0 for z, and the relaxations have also changed: now two constraints are relaxed, but the total absolute slack remains unchanged, equal to 3.\n",
"\n",
"To summarize, the relaxer finds a relaxed solution in two steps:\n",
"\n",
" - first it finds the minimal amount of slack that is necessary to find a feasible solution\n",
" - second, with this minimal slack value it searches for the best objective value, if any."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this case, the minimal conflict contains the three cyclic constraints, but not the fourth (x+y+z <= 30)."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 2: Handling an infeasible production problem\n",
"\n",
"The model aims at minimizing the production cost for a number of products\n",
"while satisfying customer demand. \n",
"Production is constrained by the company's resources.\n",
"\n",
"The model first declares the products and the resources.\n",
"The data consists of the description of the products (the demand, the inside\n",
"and outside costs, and the resource consumption) and the capacity of the\n",
"various resources.\n",
"\n",
"The variables for this problem are the quantities produced for each products.\n",
"\n",
"Of course, this model is naive, un-realistic and not robust, as it fails as soon as demand is not satisfied. But this is excatly why it is well adapted to show you how to repair infeasible models.\n",
"\n",
"First define the data:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# costs are stored in a dict from product names to cost\n",
"COSTS = {\"kluski\": 0.6, \"capellini\": 0.8, \"fettucine\": 0.3}\n",
"\n",
"# demands are stored in a dict from product names to demands\n",
"DEMANDS = {\"kluski\": 10, \"capellini\": 20, \"fettucine\": 30}\n",
"\n",
"# resources are stored as a dict of resource name to resource capacity\n",
"RESOURCES = {\"flour\": 40, \"eggs\": 60}\n",
"\n",
"CONSUMPTIONS = {(\"kluski\", \"flour\"): 0.5,\n",
" (\"kluski\", \"eggs\"): 0.2,\n",
" (\"capellini\", \"flour\"): 0.4,\n",
" (\"capellini\", \"eggs\"): 0.4,\n",
" (\"fettucine\", \"flour\"): 0.3,\n",
" (\"fettucine\", \"eggs\"): 0.6}"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"from docplex.mp.model import Model\n",
"import six\n",
"\n",
"def build_production_problem(costs, resources, consumptions, demands, **kwargs):\n",
" products = [p for p, _ in six.iteritems(costs)]\n",
"\n",
" mdl = Model(name='pasta_production', **kwargs)\n",
" # --- decision variables ---\n",
" mdl.q_vars = mdl.continuous_var_dict(products, name=\"q\")\n",
"\n",
" # --- constraints ---\n",
" # demand satisfaction\n",
" mdl.add_constraints((mdl.q_vars[p] >= demands[p], 'ct_demand_%s' % p) for p in products)\n",
"\n",
" # --- resource capacity ---\n",
" mdl.add_constraints((mdl.sum(mdl.q_vars[p] * consumptions[p, res] for p in products) <= cap,\n",
" 'ct_res_%s' % res) for res, cap in six.iteritems(resources))\n",
"\n",
" # --- objective ---\n",
" mdl.minimize(mdl.dotf(mdl.q_vars, lambda p: costs[p]))\n",
" return mdl\n"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Model: pasta_production\n",
" - number of variables: 3\n",
" - binary=0, integer=0, continuous=3\n",
" - number of constraints: 5\n",
" - linear=5\n",
" - parameters: defaults\n",
" - problem type is: LP\n"
]
}
],
"source": [
"pasta1 = build_production_problem(COSTS, RESOURCES, CONSUMPTIONS, DEMANDS)\n",
"pasta1.print_information()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This default model is feasible, solve the model"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"solution for: pasta_production\n",
"objective: 31.000\n",
"q_kluski = 10.000\n",
"q_capellini = 20.000\n",
"q_fettucine = 30.000\n"
]
}
],
"source": [
"s1 = pasta1.solve()\n",
"s1.display()"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"!! Pasta production with double demand is impossible\n"
]
}
],
"source": [
"demands2 = {p: 2*d for p, d in six.iteritems(DEMANDS)}\n",
"pasta2 = build_production_problem(COSTS, RESOURCES, CONSUMPTIONS, demands2)\n",
"s2 = pasta2.solve()\n",
"if s2 is None:\n",
" print(\"!! Pasta production with double demand is impossible\")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now double the demand."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using the conflict refiner on the production problem\n",
"\n",
"Start by running the conflict refiner on the second production model."
]
},
{
"cell_type": "code",
"execution_count": 23,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"conflicts: 4\n",
" - linear constraints: 4\n",
"conflict(s): 4\n",
" - status: Member, LinearConstraint: ct_demand_kluski: q_kluski >= 20\n",
" - status: Member, LinearConstraint: ct_demand_capellini: q_capellini >= 40\n",
" - status: Member, LinearConstraint: ct_demand_fettucine: q_fettucine >= 60\n",
" - status: Member, LinearConstraint: ct_res_flour: 0.500q_kluski+0.400q_capel..\n"
]
}
],
"source": [
"from docplex.mp.conflict_refiner import ConflictRefiner\n",
"\n",
"crr = ConflictRefiner().refine_conflict(pasta2, display=True)\n",
"crr.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Not surprisingly, you can see that the conflict involves all three demands but also the flour resource constraint, but we have no idea which quantity demands cannot be satisfied."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Using the default relaxer on the production problem\n",
"\n",
"The purpose of the relaxer is to allow relaxation of some constraints by a minimal amount to provide both a _relaxed solution_ and also a measure of how the constraints were infeasible."
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 1\n",
" - relaxed: ct_res_flour, with relaxation: 4.0\n",
"* total absolute relaxation: 4.0\n",
"solution for: pasta_production\n",
"objective: 62.000\n",
"q_kluski = 20.000\n",
"q_capellini = 40.000\n",
"q_fettucine = 60.000\n"
]
}
],
"source": [
"from docplex.mp.relaxer import Relaxer\n",
"# create an instance of relaxer\n",
"rx = Relaxer()\n",
"rs = rx.relax(pasta2)\n",
"rx.print_information()\n",
"rs.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The relaxer managed to satisfy all demands by _relaxing_ the flour constraint by an amount of 4. What does this mean?\n",
"To explain, first remember what this flour constraint was all about. You can use the `Model.get_constraint_by_name` method to retrieve a constraint from its name."
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"ct_res_flour: 0.500q_kluski+0.400q_capellini+0.300q_fettucine <= 40\n"
]
}
],
"source": [
"# get back the constraint from its name\n",
"ctf = pasta2.get_constraint_by_name(\"ct_res_flour\")\n",
"assert ctf is not None\n",
"print(str(ctf))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now you can see what the _left hand side_ evaluates in the relaxed solution"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"44.0"
]
},
"execution_count": 15,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"ctf.lhs.solution_value"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This explains the relaxation of 4 for the flour resource constraint"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Managing constraint priorities \n",
"\n",
"It might well happen that the relaxation found by the relaxer does not make sense in real world. For example, in our production example, resource constraints can be impossible to relax, but demands could be.\n",
"\n",
"This is where priorities enter the game. By setting priorities, users can control how the relaxer chooses constraints to relax. In the following code, you can set a HIGH priority to resource constraints (you could even make them mandatory) and a LOW priority to demand constraints."
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 1\n",
" - relaxed: ct_demand_kluski, with relaxation: -8.0\n",
"* total absolute relaxation: 8.0\n",
"solution for: pasta_production\n",
"objective: 57.200\n",
"q_kluski = 12.000\n",
"q_capellini = 40.000\n",
"q_fettucine = 60.000\n"
]
}
],
"source": [
"from docplex.mp.basic import Priority\n",
"\n",
"for ctr in pasta2.find_matching_linear_constraints(\"ct_res\"):\n",
" ctr.priority = Priority.HIGH\n",
"for ctd in pasta2.find_matching_linear_constraints(\"ct_dem\"):\n",
" ctd.priority = Priority.LOW\n",
" \n",
"rx2 = Relaxer()\n",
"rs2 = rx2.relax(pasta2)\n",
"rx2.print_information()\n",
"rs2.display()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In this new relaxed solution, all resource constraints are satisfied, but one demand is not: kluski demand has an unfilled quantity of 8.\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Setting constraint priorities explicitly is the most basic way to control relaxation, but there are others. A _function_ can be used: the relaxer will call the function for each constraint to determine its priority. Possible values are:\n",
"\n",
" - relaxable priorities: VERY_LOW, LOW, MEDIUM, HIGH, VERY_HIGH\n",
" - non-relaxable priority: MANDATORY\n",
" \n",
"Constraints with higher priority are less likely to be relaxed than constraints with lower priorities. Still, relaxation of a high-priority constraint cannot be ruled out, if it is the only way to provide a relaxed solution.\n",
"\n",
"### Managing priorities with functions\n",
"\n",
"In this section, you can see how to use a function to compute the priority of a constraint. The function must take a constraint and return a priority (an enumerated type, see `docplex.mp.basic.Priority`\n",
"\n",
"First, you reset all priorities to None (the default)."
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"# reset all priorities\n",
"for c in pasta2.iter_constraints():\n",
" c.priority = None "
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 1\n",
" - relaxed: ct_demand_kluski, with relaxation: -8.0\n",
"* total absolute relaxation: 8.0\n"
]
}
],
"source": [
"# define the constraint -> priority function\n",
"def map_priority(ct):\n",
" ctname = ct.name\n",
" if not ctname:\n",
" return Priority.MANDATORY\n",
" elif \"ct_res\" in ctname:\n",
" return Priority.HIGH\n",
" elif \"ct_dem\" in ctname:\n",
" return Priority.LOW\n",
" else:\n",
" # will not be relaxed\n",
" return Priority.MANDATORY\n",
" \n",
"# create a new instance of Relaxer with this function.\n",
"rx3 = Relaxer(prioritizer=map_priority)\n",
"\n",
"# use it to relax pasta2 model\n",
"rx3.relax(pasta2)\n",
"\n",
"# display relaxation.\n",
"rx3.print_information()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, you get the same result as with the explicit priorities: an unsatisfied demand of 8 for kluski.\n",
"\n",
"Note that relaxer can also accept a _dictionary_ of constraints to priorities.\n",
"\n",
"### The default relaxer revisited\n",
"\n",
"Now that you know about setting priorities, you can understand the default behavior of the `relaxer` class: for each constraint, you use either its explicit priority (if set) or the default`MEDIUM` priority. \n",
"\n",
"If no priority has been set, all constraints are considered equally relaxable.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 3: Handling variable bounds in an infeasible model\n",
"\n",
"### Variable bounds in conflict refiner\n",
"\n",
"The conflict refiner takes into account variable bounds. This is illustrated with a very simple model, with three integer variables with lower bound 1, the sum of which should be less than 2."
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"conflicts: 5\n",
" - linear constraint: 1\n",
" - lower bounds: 4\n"
]
}
],
"source": [
"m4 = Model(name='m4')\n",
"ijs = m4.integer_var_list(keys=[\"i\", \"j\", \"k\", \"l\"], name =str, lb = 1)\n",
"m4.add(m4.sum(ijs) <= 2)\n",
"\n",
"s4 = m4.solve()\n",
"assert s4 is None\n",
"ConflictRefiner().refine_conflict(m4, display=True);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The resulting conflict contains the sum constraint _and_ the three lower bounds."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Variable bounds in relaxer\n",
"\n",
"The relaxer only relaxes _constraints_ , so in this case it relaxes the sum constraint."
]
},
{
"cell_type": "code",
"execution_count": 20,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 1\n",
" - relaxed: i+j+k+l <= 2, with relaxation: 2.0\n",
"* total absolute relaxation: 2.0\n"
]
}
],
"source": [
"r4 = Relaxer()\n",
"r4.relax(m4)\n",
"r4.print_information()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Changing variable lower bounds to constraints allows the relaxer to take them into account.\n",
"You can set a *LOW* priority to lower bounds, so we expect the default relaxer to relax them before the sum constraint, which will be considered with the default MEDIUM priority."
]
},
{
"cell_type": "code",
"execution_count": 21,
"metadata": {},
"outputs": [],
"source": [
"for v in m4.iter_variables():\n",
" v.lb = 0\n",
" clb = m4.add(v >= 1, \"{0}_ge_1\".format(v.name))\n",
" clb.priority = Priority.LOW"
]
},
{
"cell_type": "code",
"execution_count": 22,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"* number of relaxations: 2\n",
" - relaxed: k_ge_1, with relaxation: -1.0\n",
" - relaxed: l_ge_1, with relaxation: -1.0\n",
"* total absolute relaxation: 2.0\n"
]
}
],
"source": [
"r4 = Relaxer()\n",
"r4.relax(m4)\n",
"r4.print_information()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"As expected, the relaxer has relaxed two lower bound constraints, but not the sum constraint."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"You have learned how to use both the conflict refiner and the relaxer, and the differences between them\n",
"\n",
"- The conflict refiner lists constraints which are participating in the infeasibility. Constraints not mentioned in the conflict are not a problem.\n",
"- The conflict refiner considers both constraints and variable bounds.\n",
"- The conflict refiner does not provide any relaxed solution, nor any quantitative information.\n",
"\n",
"In constrast, the relaxer provides a relaxed solution, and indicates which constraints are relaxed, and with what quantities. It does not consider variables bounds. It requires a mapping from constraints to Priority objects, which can take many forms: a function, a dictionary,...\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"render": true
},
"source": [
"#### References\n",
"* [Decision Optimization CPLEX Modeling for Python documentation](http://ibmdecisionoptimization.github.io/docplex-doc/)\n",
"* Need help with DOcplex or to report a bug? Please go [here](https://stackoverflow.com/questions/tagged/docplex)\n",
"* Contact us at dofeedback@wwpdl.vnet.ibm.com\"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Copyright &copy; 2017-2019 IBM. Sample Materials."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"gist_id": "6011986",
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.4"
}
},
"nbformat": 4,
"nbformat_minor": 1
}
2 changes: 1 addition & 1 deletion examples/mp/modeling/diet.py
Original file line number Diff line number Diff line change
@@ -67,7 +67,7 @@ def build_diet_model(name='diet', **kwargs):
nutrients = [Nutrient(*row) for row in NUTRIENTS]

food_nutrients = {(fn[0], nutrients[n].name):
fn[1 + n] for fn in FOOD_NUTRIENTS for n in range(len(NUTRIENTS))}
fn[1 + n] for fn in FOOD_NUTRIENTS for n in range(len(NUTRIENTS))}

# Model
mdl = Model(name=name, **kwargs)

0 comments on commit 120aad5

Please sign in to comment.